env: correctly handle env_load_prio
[platform/kernel/u-boot.git] / env / env.c
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Copyright (C) 2017 Google, Inc
4  * Written by Simon Glass <sjg@chromium.org>
5  */
6
7 #include <common.h>
8 #include <env.h>
9 #include <env_internal.h>
10 #include <log.h>
11 #include <linux/bitops.h>
12 #include <linux/bug.h>
13
14 DECLARE_GLOBAL_DATA_PTR;
15
16 #if defined(CONFIG_NEEDS_MANUAL_RELOC)
17 void env_fix_drivers(void)
18 {
19         struct env_driver *drv;
20         const int n_ents = ll_entry_count(struct env_driver, env_driver);
21         struct env_driver *entry;
22
23         drv = ll_entry_start(struct env_driver, env_driver);
24         for (entry = drv; entry != drv + n_ents; entry++) {
25                 if (entry->name)
26                         entry->name += gd->reloc_off;
27                 if (entry->load)
28                         entry->load += gd->reloc_off;
29                 if (entry->save)
30                         entry->save += gd->reloc_off;
31                 if (entry->erase)
32                         entry->erase += gd->reloc_off;
33                 if (entry->init)
34                         entry->init += gd->reloc_off;
35         }
36 }
37 #endif
38
39 static struct env_driver *_env_driver_lookup(enum env_location loc)
40 {
41         struct env_driver *drv;
42         const int n_ents = ll_entry_count(struct env_driver, env_driver);
43         struct env_driver *entry;
44
45         drv = ll_entry_start(struct env_driver, env_driver);
46         for (entry = drv; entry != drv + n_ents; entry++) {
47                 if (loc == entry->location)
48                         return entry;
49         }
50
51         /* Not found */
52         return NULL;
53 }
54
55 static enum env_location env_locations[] = {
56 #ifdef CONFIG_ENV_IS_IN_EEPROM
57         ENVL_EEPROM,
58 #endif
59 #ifdef CONFIG_ENV_IS_IN_EXT4
60         ENVL_EXT4,
61 #endif
62 #ifdef CONFIG_ENV_IS_IN_FAT
63         ENVL_FAT,
64 #endif
65 #ifdef CONFIG_ENV_IS_IN_FLASH
66         ENVL_FLASH,
67 #endif
68 #ifdef CONFIG_ENV_IS_IN_MMC
69         ENVL_MMC,
70 #endif
71 #ifdef CONFIG_ENV_IS_IN_NAND
72         ENVL_NAND,
73 #endif
74 #ifdef CONFIG_ENV_IS_IN_NVRAM
75         ENVL_NVRAM,
76 #endif
77 #ifdef CONFIG_ENV_IS_IN_REMOTE
78         ENVL_REMOTE,
79 #endif
80 #ifdef CONFIG_ENV_IS_IN_SATA
81         ENVL_ESATA,
82 #endif
83 #ifdef CONFIG_ENV_IS_IN_SPI_FLASH
84         ENVL_SPI_FLASH,
85 #endif
86 #ifdef CONFIG_ENV_IS_IN_UBI
87         ENVL_UBI,
88 #endif
89 #ifdef CONFIG_ENV_IS_NOWHERE
90         ENVL_NOWHERE,
91 #endif
92 };
93
94 static bool env_has_inited(enum env_location location)
95 {
96         return gd->env_has_init & BIT(location);
97 }
98
99 static void env_set_inited(enum env_location location)
100 {
101         /*
102          * We're using a 32-bits bitmask stored in gd (env_has_init)
103          * using the above enum value as the bit index. We need to
104          * make sure that we're not overflowing it.
105          */
106         BUILD_BUG_ON(ENVL_COUNT > BITS_PER_LONG);
107
108         gd->env_has_init |= BIT(location);
109 }
110
111 /**
112  * env_get_location() - Returns the best env location for a board
113  * @op: operations performed on the environment
114  * @prio: priority between the multiple environments, 0 being the
115  *        highest priority
116  *
117  * This will return the preferred environment for the given priority.
118  * This is overridable by boards if they need to.
119  *
120  * All implementations are free to use the operation, the priority and
121  * any other data relevant to their choice, but must take into account
122  * the fact that the lowest prority (0) is the most important location
123  * in the system. The following locations should be returned by order
124  * of descending priorities, from the highest to the lowest priority.
125  *
126  * Returns:
127  * an enum env_location value on success, a negative error code otherwise
128  */
129 __weak enum env_location env_get_location(enum env_operation op, int prio)
130 {
131         if (prio >= ARRAY_SIZE(env_locations))
132                 return ENVL_UNKNOWN;
133
134         return env_locations[prio];
135 }
136
137
138 /**
139  * env_driver_lookup() - Finds the most suited environment location
140  * @op: operations performed on the environment
141  * @prio: priority between the multiple environments, 0 being the
142  *        highest priority
143  *
144  * This will try to find the available environment with the highest
145  * priority in the system.
146  *
147  * Returns:
148  * NULL on error, a pointer to a struct env_driver otherwise
149  */
150 static struct env_driver *env_driver_lookup(enum env_operation op, int prio)
151 {
152         enum env_location loc = env_get_location(op, prio);
153         struct env_driver *drv;
154
155         if (loc == ENVL_UNKNOWN)
156                 return NULL;
157
158         drv = _env_driver_lookup(loc);
159         if (!drv) {
160                 debug("%s: No environment driver for location %d\n", __func__,
161                       loc);
162                 return NULL;
163         }
164
165         return drv;
166 }
167
168 __weak int env_get_char_spec(int index)
169 {
170         return *(uchar *)(gd->env_addr + index);
171 }
172
173 int env_get_char(int index)
174 {
175         if (gd->env_valid == ENV_INVALID)
176                 return default_environment[index];
177         else
178                 return env_get_char_spec(index);
179 }
180
181 int env_load(void)
182 {
183         struct env_driver *drv;
184         int best_prio = -1;
185         int prio;
186
187         for (prio = 0; (drv = env_driver_lookup(ENVOP_LOAD, prio)); prio++) {
188                 int ret;
189
190                 if (!drv->load)
191                         continue;
192
193                 if (!env_has_inited(drv->location))
194                         continue;
195
196                 printf("Loading Environment from %s... ", drv->name);
197                 /*
198                  * In error case, the error message must be printed during
199                  * drv->load() in some underlying API, and it must be exactly
200                  * one message.
201                  */
202                 ret = drv->load();
203                 if (!ret) {
204                         printf("OK\n");
205                         gd->env_load_prio = prio;
206
207                         return 0;
208                 } else if (ret == -ENOMSG) {
209                         /* Handle "bad CRC" case */
210                         if (best_prio == -1)
211                                 best_prio = prio;
212                 } else {
213                         debug("Failed (%d)\n", ret);
214                 }
215         }
216
217         /*
218          * In case of invalid environment, we set the 'default' env location
219          * to the best choice, i.e.:
220          *   1. Environment location with bad CRC, if such location was found
221          *   2. Otherwise use the location with highest priority
222          *
223          * This way, next calls to env_save() will restore the environment
224          * at the right place.
225          */
226         if (best_prio >= 0)
227                 debug("Selecting environment with bad CRC\n");
228         else
229                 best_prio = 0;
230
231         gd->env_load_prio = best_prio;
232
233         return -ENODEV;
234 }
235
236 int env_save(void)
237 {
238         struct env_driver *drv;
239
240         drv = env_driver_lookup(ENVOP_SAVE, gd->env_load_prio);
241         if (drv) {
242                 int ret;
243
244                 printf("Saving Environment to %s... ", drv->name);
245                 if (!drv->save) {
246                         printf("not possible\n");
247                         return -ENODEV;
248                 }
249
250                 if (!env_has_inited(drv->location)) {
251                         printf("not initialized\n");
252                         return -ENODEV;
253                 }
254
255                 ret = drv->save();
256                 if (ret)
257                         printf("Failed (%d)\n", ret);
258                 else
259                         printf("OK\n");
260
261                 if (!ret)
262                         return 0;
263         }
264
265         return -ENODEV;
266 }
267
268 int env_erase(void)
269 {
270         struct env_driver *drv;
271
272         drv = env_driver_lookup(ENVOP_ERASE, gd->env_load_prio);
273         if (drv) {
274                 int ret;
275
276                 if (!drv->erase)
277                         return -ENODEV;
278
279                 if (!env_has_inited(drv->location))
280                         return -ENODEV;
281
282                 printf("Erasing Environment on %s... ", drv->name);
283                 ret = drv->erase();
284                 if (ret)
285                         printf("Failed (%d)\n", ret);
286                 else
287                         printf("OK\n");
288
289                 if (!ret)
290                         return 0;
291         }
292
293         return -ENODEV;
294 }
295
296 int env_init(void)
297 {
298         struct env_driver *drv;
299         int ret = -ENOENT;
300         int prio;
301
302         for (prio = 0; (drv = env_driver_lookup(ENVOP_INIT, prio)); prio++) {
303                 if (!drv->init || !(ret = drv->init()))
304                         env_set_inited(drv->location);
305
306                 debug("%s: Environment %s init done (ret=%d)\n", __func__,
307                       drv->name, ret);
308         }
309
310         if (!prio)
311                 return -ENODEV;
312
313         if (ret == -ENOENT) {
314                 gd->env_addr = (ulong)&default_environment[0];
315                 gd->env_valid = ENV_VALID;
316
317                 return 0;
318         }
319
320         return ret;
321 }