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