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 <asm/global_data.h>
12#include <linux/bitops.h>
13#include <linux/bug.h>
14
15DECLARE_GLOBAL_DATA_PTR;
16
17static struct env_driver *_env_driver_lookup(enum env_location loc)
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 (loc == entry->location)
26			return entry;
27	}
28
29	/* Not found */
30	return NULL;
31}
32
33static enum env_location env_locations[] = {
34#ifdef CONFIG_ENV_IS_IN_EEPROM
35	ENVL_EEPROM,
36#endif
37#ifdef CONFIG_ENV_IS_IN_EXT4
38	ENVL_EXT4,
39#endif
40#ifdef CONFIG_ENV_IS_IN_FAT
41	ENVL_FAT,
42#endif
43#ifdef CONFIG_ENV_IS_IN_FLASH
44	ENVL_FLASH,
45#endif
46#ifdef CONFIG_ENV_IS_IN_MMC
47	ENVL_MMC,
48#endif
49#ifdef CONFIG_ENV_IS_IN_NAND
50	ENVL_NAND,
51#endif
52#ifdef CONFIG_ENV_IS_IN_NVRAM
53	ENVL_NVRAM,
54#endif
55#ifdef CONFIG_ENV_IS_IN_REMOTE
56	ENVL_REMOTE,
57#endif
58#ifdef CONFIG_ENV_IS_IN_SPI_FLASH
59	ENVL_SPI_FLASH,
60#endif
61#ifdef CONFIG_ENV_IS_IN_UBI
62	ENVL_UBI,
63#endif
64#ifdef CONFIG_ENV_IS_NOWHERE
65	ENVL_NOWHERE,
66#endif
67};
68
69static bool env_has_inited(enum env_location location)
70{
71	return gd->env_has_init & BIT(location);
72}
73
74static void env_set_inited(enum env_location location)
75{
76	/*
77	 * We're using a 32-bits bitmask stored in gd (env_has_init)
78	 * using the above enum value as the bit index. We need to
79	 * make sure that we're not overflowing it.
80	 */
81	BUILD_BUG_ON(ENVL_COUNT > BITS_PER_LONG);
82
83	gd->env_has_init |= BIT(location);
84}
85
86/**
87 * arch_env_get_location() - Returns the best env location for an arch
88 * @op: operations performed on the environment
89 * @prio: priority between the multiple environments, 0 being the
90 *        highest priority
91 *
92 * This will return the preferred environment for the given priority.
93 * This is overridable by architectures if they need to and has lower
94 * priority than board side env_get_location() override.
95 *
96 * All implementations are free to use the operation, the priority and
97 * any other data relevant to their choice, but must take into account
98 * the fact that the lowest prority (0) is the most important location
99 * in the system. The following locations should be returned by order
100 * of descending priorities, from the highest to the lowest priority.
101 *
102 * Returns:
103 * an enum env_location value on success, a negative error code otherwise
104 */
105__weak enum env_location arch_env_get_location(enum env_operation op, int prio)
106{
107	if (prio >= ARRAY_SIZE(env_locations))
108		return ENVL_UNKNOWN;
109
110	return env_locations[prio];
111}
112
113/**
114 * env_get_location() - Returns the best env location for a board
115 * @op: operations performed on the environment
116 * @prio: priority between the multiple environments, 0 being the
117 *        highest priority
118 *
119 * This will return the preferred environment for the given priority.
120 * This is overridable by boards if they need to.
121 *
122 * All implementations are free to use the operation, the priority and
123 * any other data relevant to their choice, but must take into account
124 * the fact that the lowest prority (0) is the most important location
125 * in the system. The following locations should be returned by order
126 * of descending priorities, from the highest to the lowest priority.
127 *
128 * Returns:
129 * an enum env_location value on success, a negative error code otherwise
130 */
131__weak enum env_location env_get_location(enum env_operation op, int prio)
132{
133	return arch_env_get_location(op, prio);
134}
135
136/**
137 * env_driver_lookup() - Finds the most suited environment location
138 * @op: operations performed on the environment
139 * @prio: priority between the multiple environments, 0 being the
140 *        highest priority
141 *
142 * This will try to find the available environment with the highest
143 * priority in the system.
144 *
145 * Returns:
146 * NULL on error, a pointer to a struct env_driver otherwise
147 */
148static struct env_driver *env_driver_lookup(enum env_operation op, int prio)
149{
150	enum env_location loc = env_get_location(op, prio);
151	struct env_driver *drv;
152
153	if (loc == ENVL_UNKNOWN)
154		return NULL;
155
156	drv = _env_driver_lookup(loc);
157	if (!drv) {
158		debug("%s: No environment driver for location %d\n", __func__,
159		      loc);
160		return NULL;
161	}
162
163	return drv;
164}
165
166int env_load(void)
167{
168	struct env_driver *drv;
169	int best_prio = -1;
170	int prio;
171
172	if (CONFIG_IS_ENABLED(ENV_WRITEABLE_LIST)) {
173		/*
174		 * When using a list of writeable variables, the baseline comes
175		 * from the built-in default env. So load this first.
176		 */
177		env_set_default(NULL, 0);
178	}
179
180	for (prio = 0; (drv = env_driver_lookup(ENVOP_LOAD, prio)); prio++) {
181		int ret;
182
183		if (!env_has_inited(drv->location))
184			continue;
185
186		printf("Loading Environment from %s... ", drv->name);
187		/*
188		 * In error case, the error message must be printed during
189		 * drv->load() in some underlying API, and it must be exactly
190		 * one message.
191		 */
192		ret = drv->load();
193		if (!ret) {
194			printf("OK\n");
195			gd->env_load_prio = prio;
196
197			return 0;
198		} else if (ret == -ENOMSG) {
199			/* Handle "bad CRC" case */
200			if (best_prio == -1)
201				best_prio = prio;
202		} else {
203			debug("Failed (%d)\n", ret);
204		}
205	}
206
207	/*
208	 * In case of invalid environment, we set the 'default' env location
209	 * to the best choice, i.e.:
210	 *   1. Environment location with bad CRC, if such location was found
211	 *   2. Otherwise use the location with highest priority
212	 *
213	 * This way, next calls to env_save() will restore the environment
214	 * at the right place.
215	 */
216	if (best_prio >= 0)
217		debug("Selecting environment with bad CRC\n");
218	else
219		best_prio = 0;
220
221	gd->env_load_prio = best_prio;
222
223	return -ENODEV;
224}
225
226int env_reload(void)
227{
228	struct env_driver *drv;
229
230	drv = env_driver_lookup(ENVOP_LOAD, gd->env_load_prio);
231	if (drv) {
232		int ret;
233
234		printf("Loading Environment from %s... ", drv->name);
235
236		if (!env_has_inited(drv->location)) {
237			printf("not initialized\n");
238			return -ENODEV;
239		}
240
241		ret = drv->load();
242		if (ret)
243			printf("Failed (%d)\n", ret);
244		else
245			printf("OK\n");
246
247		if (!ret)
248			return 0;
249	}
250
251	return -ENODEV;
252}
253
254int env_save(void)
255{
256	struct env_driver *drv;
257
258	drv = env_driver_lookup(ENVOP_SAVE, gd->env_load_prio);
259	if (drv) {
260		int ret;
261
262		printf("Saving Environment to %s... ", drv->name);
263		if (!drv->save) {
264			printf("not possible\n");
265			return -ENODEV;
266		}
267
268		if (!env_has_inited(drv->location)) {
269			printf("not initialized\n");
270			return -ENODEV;
271		}
272
273		ret = drv->save();
274		if (ret)
275			printf("Failed (%d)\n", ret);
276		else
277			printf("OK\n");
278
279		if (!ret)
280			return 0;
281	}
282
283	return -ENODEV;
284}
285
286int env_erase(void)
287{
288	struct env_driver *drv;
289
290	drv = env_driver_lookup(ENVOP_ERASE, gd->env_load_prio);
291	if (drv) {
292		int ret;
293
294		if (!drv->erase) {
295			printf("not possible\n");
296			return -ENODEV;
297		}
298
299		if (!env_has_inited(drv->location)) {
300			printf("not initialized\n");
301			return -ENODEV;
302		}
303
304		printf("Erasing Environment on %s... ", drv->name);
305		ret = drv->erase();
306		if (ret)
307			printf("Failed (%d)\n", ret);
308		else
309			printf("OK\n");
310
311		if (!ret)
312			return 0;
313	}
314
315	return -ENODEV;
316}
317
318int env_init(void)
319{
320	struct env_driver *drv;
321	int ret = -ENOENT;
322	int prio;
323
324	for (prio = 0; (drv = env_driver_lookup(ENVOP_INIT, prio)); prio++) {
325		if (!drv->init || !(ret = drv->init()))
326			env_set_inited(drv->location);
327		if (ret == -ENOENT)
328			env_set_inited(drv->location);
329
330		debug("%s: Environment %s init done (ret=%d)\n", __func__,
331		      drv->name, ret);
332
333		if (gd->env_valid == ENV_INVALID)
334			ret = -ENOENT;
335	}
336
337	if (!prio)
338		return -ENODEV;
339
340	if (ret == -ENOENT) {
341		gd->env_addr = (ulong)&default_environment[0];
342		gd->env_valid = ENV_VALID;
343
344		return 0;
345	}
346
347	return ret;
348}
349
350int env_select(const char *name)
351{
352	struct env_driver *drv;
353	const int n_ents = ll_entry_count(struct env_driver, env_driver);
354	struct env_driver *entry;
355	int prio;
356	bool found = false;
357
358	printf("Select Environment on %s: ", name);
359
360	/* search ENV driver by name */
361	drv = ll_entry_start(struct env_driver, env_driver);
362	for (entry = drv; entry != drv + n_ents; entry++) {
363		if (!strcmp(entry->name, name)) {
364			found = true;
365			break;
366		}
367	}
368
369	if (!found) {
370		printf("driver not found\n");
371		return -ENODEV;
372	}
373
374	/* search priority by driver */
375	for (prio = 0; (drv = env_driver_lookup(ENVOP_INIT, prio)); prio++) {
376		if (entry->location == env_get_location(ENVOP_LOAD, prio)) {
377			/* when priority change, reset the ENV flags */
378			if (gd->env_load_prio != prio) {
379				gd->env_load_prio = prio;
380				gd->env_valid = ENV_INVALID;
381				gd->flags &= ~GD_FLG_ENV_DEFAULT;
382			}
383			printf("OK\n");
384			return 0;
385		}
386	}
387	printf("priority not found\n");
388
389	return -ENODEV;
390}
391