1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  *  Author: Christian Marangi <ansuelsmth@gmail.com>
4  */
5 #include <env_internal.h>
6 #include <errno.h>
7 #include <malloc.h>
8 #include <mtd.h>
9 #include <asm/cache.h>
10 #include <asm/global_data.h>
11 #include <linux/mtd/mtd.h>
12 #include <u-boot/crc.h>
13 
14 DECLARE_GLOBAL_DATA_PTR;
15 
setup_mtd_device(struct mtd_info ** mtd_env)16 static int setup_mtd_device(struct mtd_info **mtd_env)
17 {
18 	struct mtd_info *mtd;
19 
20 	mtd_probe_devices();
21 
22 	mtd = get_mtd_device_nm(CONFIG_ENV_MTD_DEV);
23 	if (IS_ERR_OR_NULL(mtd)) {
24 		env_set_default("get_mtd_device_nm() failed", 0);
25 		return mtd ? PTR_ERR(mtd) : -EINVAL;
26 	}
27 
28 	*mtd_env = mtd;
29 
30 	return 0;
31 }
32 
env_mtd_save(void)33 static int env_mtd_save(void)
34 {
35 	char *saved_buf = NULL, *write_buf, *tmp;
36 	struct erase_info ei = { };
37 	struct mtd_info *mtd_env;
38 	u32 sect_size, sect_num;
39 	size_t ret_len = 0;
40 	u32 write_size;
41 	env_t env_new;
42 	int remaining;
43 	u32 offset;
44 	int ret;
45 
46 	ret = setup_mtd_device(&mtd_env);
47 	if (ret)
48 		return ret;
49 
50 	sect_size = mtd_env->erasesize;
51 
52 	/* Is the sector larger than the env (i.e. embedded) */
53 	if (sect_size > CONFIG_ENV_SIZE) {
54 		saved_buf = malloc(sect_size);
55 		if (!saved_buf) {
56 			ret = -ENOMEM;
57 			goto done;
58 		}
59 
60 		offset = CONFIG_ENV_OFFSET;
61 		remaining = sect_size;
62 		tmp = saved_buf;
63 
64 		while (remaining) {
65 			/* Skip the block if it is bad */
66 			if (!(offset % sect_size) &&
67 			    mtd_block_isbad(mtd_env, offset)) {
68 				offset += sect_size;
69 				continue;
70 			}
71 
72 			ret = mtd_read(mtd_env, offset, mtd_env->writesize,
73 				       &ret_len, tmp);
74 			if (ret)
75 				goto done;
76 
77 			tmp += ret_len;
78 			offset += ret_len;
79 			remaining -= ret_len;
80 		}
81 	}
82 
83 	ret = env_export(&env_new);
84 	if (ret)
85 		goto done;
86 
87 	sect_num = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size);
88 
89 	ei.mtd = mtd_env;
90 	ei.addr = CONFIG_ENV_OFFSET;
91 	ei.len = sect_num * sect_size;
92 
93 	puts("Erasing MTD...");
94 	ret = mtd_erase(mtd_env, &ei);
95 	if (ret)
96 		goto done;
97 
98 	if (sect_size > CONFIG_ENV_SIZE) {
99 		memcpy(saved_buf, &env_new, CONFIG_ENV_SIZE);
100 		write_size = sect_size;
101 		write_buf = saved_buf;
102 	} else {
103 		write_size = sect_num * sect_size;
104 		write_buf = (char *)&env_new;
105 	}
106 
107 	offset = CONFIG_ENV_OFFSET;
108 	remaining = write_size;
109 	tmp = write_buf;
110 
111 	puts("Writing to MTD...");
112 	while (remaining) {
113 		/* Skip the block if it is bad */
114 		if (!(offset % sect_size) &&
115 		    mtd_block_isbad(mtd_env, offset)) {
116 			offset += sect_size;
117 			continue;
118 		}
119 
120 		ret = mtd_write(mtd_env, offset, mtd_env->writesize,
121 				&ret_len, tmp);
122 		if (ret)
123 			goto done;
124 
125 		offset += mtd_env->writesize;
126 		remaining -= ret_len;
127 		tmp += ret_len;
128 	}
129 
130 	ret = 0;
131 	puts("done\n");
132 
133 done:
134 	put_mtd_device(mtd_env);
135 
136 	if (saved_buf)
137 		free(saved_buf);
138 
139 	return ret;
140 }
141 
env_mtd_load(void)142 static int env_mtd_load(void)
143 {
144 	struct mtd_info *mtd_env;
145 	char *buf, *tmp;
146 	size_t ret_len;
147 	int remaining;
148 	u32 sect_size;
149 	u32 offset;
150 	int ret;
151 
152 	buf = (char *)memalign(ARCH_DMA_MINALIGN, CONFIG_ENV_SIZE);
153 	if (!buf) {
154 		env_set_default("memalign() failed", 0);
155 		return -EIO;
156 	}
157 
158 	ret = setup_mtd_device(&mtd_env);
159 	if (ret)
160 		goto out;
161 
162 	sect_size = mtd_env->erasesize;
163 
164 	offset = CONFIG_ENV_OFFSET;
165 	remaining = CONFIG_ENV_SIZE;
166 	tmp = buf;
167 
168 	while (remaining) {
169 		/* Skip the block if it is bad */
170 		if (!(offset % sect_size) &&
171 		    mtd_block_isbad(mtd_env, offset)) {
172 			offset += sect_size;
173 			continue;
174 		}
175 
176 		ret = mtd_read(mtd_env, offset, mtd_env->writesize,
177 			       &ret_len, tmp);
178 		if (ret) {
179 			env_set_default("mtd_read() failed", 1);
180 			goto out;
181 		}
182 
183 		tmp += ret_len;
184 		offset += ret_len;
185 		remaining -= ret_len;
186 	}
187 
188 	ret = env_import(buf, 1, H_EXTERNAL);
189 	if (!ret)
190 		gd->env_valid = ENV_VALID;
191 
192 out:
193 	put_mtd_device(mtd_env);
194 
195 	free(buf);
196 
197 	return ret;
198 }
199 
env_mtd_erase(void)200 static int env_mtd_erase(void)
201 {
202 	struct mtd_info *mtd_env;
203 	u32 sect_size, sect_num;
204 	char *saved_buf = NULL, *tmp;
205 	struct erase_info ei;
206 	size_t ret_len;
207 	int remaining;
208 	u32 offset;
209 	int ret;
210 
211 	ret = setup_mtd_device(&mtd_env);
212 	if (ret)
213 		return ret;
214 
215 	sect_size = mtd_env->erasesize;
216 
217 	/* Is the sector larger than the env (i.e. embedded) */
218 	if (sect_size > CONFIG_ENV_SIZE) {
219 		saved_buf = malloc(sect_size);
220 		if (!saved_buf) {
221 			ret = -ENOMEM;
222 			goto done;
223 		}
224 
225 		offset = CONFIG_ENV_OFFSET;
226 		remaining = sect_size;
227 		tmp = saved_buf;
228 
229 		while (remaining) {
230 			/* Skip the block if it is bad */
231 			if (!(offset % sect_size) &&
232 			    mtd_block_isbad(mtd_env, offset)) {
233 				offset += sect_size;
234 				continue;
235 			}
236 
237 			ret = mtd_read(mtd_env, offset, mtd_env->writesize,
238 				       &ret_len, tmp);
239 			if (ret)
240 				goto done;
241 
242 			tmp += ret_len;
243 			offset += ret_len;
244 			remaining -= ret_len;
245 		}
246 	}
247 
248 	sect_num = DIV_ROUND_UP(CONFIG_ENV_SIZE, sect_size);
249 
250 	ei.mtd = mtd_env;
251 	ei.addr = CONFIG_ENV_OFFSET;
252 	ei.len = sect_num * sect_size;
253 
254 	ret = mtd_erase(mtd_env, &ei);
255 	if (ret)
256 		goto done;
257 
258 	if (sect_size > CONFIG_ENV_SIZE) {
259 		memset(saved_buf, 0, CONFIG_ENV_SIZE);
260 
261 		offset = CONFIG_ENV_OFFSET;
262 		remaining = sect_size;
263 		tmp = saved_buf;
264 
265 		while (remaining) {
266 			/* Skip the block if it is bad */
267 			if (!(offset % sect_size) &&
268 			    mtd_block_isbad(mtd_env, offset)) {
269 				offset += sect_size;
270 				continue;
271 			}
272 
273 			ret = mtd_write(mtd_env, offset, mtd_env->writesize,
274 					&ret_len, tmp);
275 			if (ret)
276 				goto done;
277 
278 			offset += mtd_env->writesize;
279 			remaining -= ret_len;
280 			tmp += ret_len;
281 		}
282 	}
283 
284 	ret = 0;
285 
286 done:
287 	put_mtd_device(mtd_env);
288 
289 	if (saved_buf)
290 		free(saved_buf);
291 
292 	return ret;
293 }
294 
env_mtd_get_env_addr(void)295 __weak void *env_mtd_get_env_addr(void)
296 {
297 	return (void *)CONFIG_ENV_ADDR;
298 }
299 
300 /*
301  * Check if Environment on CONFIG_ENV_ADDR is valid.
302  */
env_mtd_init_addr(void)303 static int env_mtd_init_addr(void)
304 {
305 	env_t *env_ptr = (env_t *)env_mtd_get_env_addr();
306 
307 	if (!env_ptr)
308 		return -ENOENT;
309 
310 	if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
311 		gd->env_addr = (ulong)&env_ptr->data;
312 		gd->env_valid = ENV_VALID;
313 	} else {
314 		gd->env_valid = ENV_INVALID;
315 	}
316 
317 	return 0;
318 }
319 
env_mtd_init(void)320 static int env_mtd_init(void)
321 {
322 	int ret;
323 
324 	ret = env_mtd_init_addr();
325 	if (ret != -ENOENT)
326 		return ret;
327 
328 	/*
329 	 * return here -ENOENT, so env_init()
330 	 * can set the init bit and later if no
331 	 * other Environment storage is defined
332 	 * can set the default environment
333 	 */
334 	return -ENOENT;
335 }
336 
337 U_BOOT_ENV_LOCATION(mtd) = {
338 	.location	= ENVL_MTD,
339 	ENV_NAME("MTD")
340 	.load		= env_mtd_load,
341 	.save		= ENV_SAVE_PTR(env_mtd_save),
342 	.erase		= ENV_ERASE_PTR(env_mtd_erase),
343 	.init		= env_mtd_init,
344 };
345