nand.c 9.6 KB
Newer Older
W
wdenk 已提交
1
/*
2 3 4
 * (C) Copyright 2000-2010
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
5 6 7
 * (C) Copyright 2008
 * Stuart Wood, Lab X Technologies <stuart.wood@labxtechnologies.com>
 *
W
wdenk 已提交
8 9 10 11 12
 * (C) Copyright 2004
 * Jian Zhang, Texas Instruments, jzhang@ti.com.
 *
 * (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH <www.elinos.com>
 * Andreas Heppel <aheppel@sysgo.de>
13
 *
14
 * SPDX-License-Identifier:	GPL-2.0+
W
wdenk 已提交
15 16 17 18 19 20
 */

#include <common.h>
#include <command.h>
#include <environment.h>
#include <linux/stddef.h>
21
#include <malloc.h>
22
#include <memalign.h>
23
#include <nand.h>
24 25
#include <search.h>
#include <errno.h>
W
wdenk 已提交
26

27 28
#if defined(CONFIG_CMD_SAVEENV) && defined(CONFIG_CMD_NAND) && \
		!defined(CONFIG_SPL_BUILD)
W
wdenk 已提交
29
#define CMD_SAVEENV
30
#elif defined(CONFIG_ENV_OFFSET_REDUND)
31
#error CONFIG_ENV_OFFSET_REDUND must have CONFIG_CMD_SAVEENV & CONFIG_CMD_NAND
W
wdenk 已提交
32 33
#endif

34 35
#if defined(CONFIG_ENV_SIZE_REDUND) &&	\
	(CONFIG_ENV_SIZE_REDUND != CONFIG_ENV_SIZE)
36
#error CONFIG_ENV_SIZE_REDUND should be the same as CONFIG_ENV_SIZE
W
wdenk 已提交
37 38
#endif

39 40
#ifndef CONFIG_ENV_RANGE
#define CONFIG_ENV_RANGE	CONFIG_ENV_SIZE
41 42
#endif

43
#if defined(ENV_IS_EMBEDDED)
44
env_t *env_ptr = &environment;
45 46
#elif defined(CONFIG_NAND_ENV_DST)
env_t *env_ptr = (env_t *)CONFIG_NAND_ENV_DST;
W
wdenk 已提交
47
#else /* ! ENV_IS_EMBEDDED */
48
env_t *env_ptr;
W
wdenk 已提交
49 50
#endif /* ENV_IS_EMBEDDED */

51
DECLARE_GLOBAL_DATA_PTR;
W
wdenk 已提交
52

53 54 55 56 57 58
/*
 * This is called before nand_init() so we can't read NAND to
 * validate env data.
 *
 * Mark it OK for now. env_relocate() in env_common.c will call our
 * relocate function which does the real validation.
59 60
 *
 * When using a NAND boot image (like sequoia_nand), the environment
61 62 63
 * can be embedded or attached to the U-Boot image in NAND flash.
 * This way the SPL loads not only the U-Boot image from NAND but
 * also the environment.
W
wdenk 已提交
64
 */
65
static int env_nand_init(void)
W
wdenk 已提交
66
{
67
#if defined(ENV_IS_EMBEDDED) || defined(CONFIG_NAND_ENV_DST)
68
	int crc1_ok = 0, crc2_ok = 0;
69 70 71 72
	env_t *tmp_env1;

#ifdef CONFIG_ENV_OFFSET_REDUND
	env_t *tmp_env2;
73

74
	tmp_env2 = (env_t *)((ulong)env_ptr + CONFIG_ENV_SIZE);
75
	crc2_ok = crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc;
76 77
#endif
	tmp_env1 = env_ptr;
78
	crc1_ok = crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc;
79

80
	if (!crc1_ok && !crc2_ok) {
81 82
		gd->env_addr	= 0;
		gd->env_valid	= 0;
83 84 85

		return 0;
	} else if (crc1_ok && !crc2_ok) {
86
		gd->env_valid = ENV_VALID;
87 88 89
	}
#ifdef CONFIG_ENV_OFFSET_REDUND
	else if (!crc1_ok && crc2_ok) {
90
		gd->env_valid = ENV_REDUND;
91
	} else {
92
		/* both ok - check serial */
93
		if (tmp_env1->flags == 255 && tmp_env2->flags == 0)
94
			gd->env_valid = ENV_REDUND;
95
		else if (tmp_env2->flags == 255 && tmp_env1->flags == 0)
96
			gd->env_valid = ENV_VALID;
97
		else if (tmp_env1->flags > tmp_env2->flags)
98
			gd->env_valid = ENV_VALID;
99
		else if (tmp_env2->flags > tmp_env1->flags)
100
			gd->env_valid = ENV_REDUND;
101
		else /* flags are equal - almost impossible */
102
			gd->env_valid = ENV_VALID;
103 104
	}

105
	if (gd->env_valid == ENV_REDUND)
106 107 108
		env_ptr = tmp_env2;
	else
#endif
109
	if (gd->env_valid == ENV_VALID)
110
		env_ptr = tmp_env1;
111 112 113 114

	gd->env_addr = (ulong)env_ptr->data;

#else /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
115
	gd->env_addr	= (ulong)&default_environment[0];
116
	gd->env_valid	= ENV_VALID;
117
#endif /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
W
wdenk 已提交
118

119
	return 0;
W
wdenk 已提交
120 121 122
}

#ifdef CMD_SAVEENV
123 124 125 126
/*
 * The legacy NAND code saved the environment in the first NAND device i.e.,
 * nand_dev_desc + 0. This is also the behaviour using the new NAND code.
 */
127
static int writeenv(size_t offset, u_char *buf)
128
{
129
	size_t end = offset + CONFIG_ENV_RANGE;
130
	size_t amount_saved = 0;
131
	size_t blocksize, len;
132
	struct mtd_info *mtd;
133 134
	u_char *char_ptr;

135 136 137 138 139
	mtd = get_nand_dev_by_index(0);
	if (!mtd)
		return 1;

	blocksize = mtd->erasesize;
140
	len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
141

142
	while (amount_saved < CONFIG_ENV_SIZE && offset < end) {
143
		if (nand_block_isbad(mtd, offset)) {
144 145 146
			offset += blocksize;
		} else {
			char_ptr = &buf[amount_saved];
147
			if (nand_write(mtd, offset, &len, char_ptr))
148
				return 1;
149

150
			offset += blocksize;
151
			amount_saved += len;
152 153
		}
	}
154
	if (amount_saved != CONFIG_ENV_SIZE)
155 156 157 158
		return 1;

	return 0;
}
159

160
struct nand_env_location {
161 162 163
	const char *name;
	const nand_erase_options_t erase_opts;
};
164

165
static int erase_and_write_env(const struct nand_env_location *location,
166
		u_char *env_new)
W
wdenk 已提交
167
{
168
	struct mtd_info *mtd;
169
	int ret = 0;
170

171 172
	mtd = get_nand_dev_by_index(0);
	if (!mtd)
173 174
		return 1;

175
	printf("Erasing %s...\n", location->name);
176
	if (nand_erase_opts(mtd, &location->erase_opts))
177
		return 1;
178

179 180 181
	printf("Writing to %s... ", location->name);
	ret = writeenv(location->erase_opts.offset, env_new);
	puts(ret ? "FAILED!\n" : "OK\n");
182

183 184
	return ret;
}
185

186
static int env_nand_save(void)
187
{
188
	int	ret = 0;
189
	ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
190
	int	env_idx = 0;
191
	static const struct nand_env_location location[] = {
192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
		{
			.name = "NAND",
			.erase_opts = {
				.length = CONFIG_ENV_RANGE,
				.offset = CONFIG_ENV_OFFSET,
			},
		},
#ifdef CONFIG_ENV_OFFSET_REDUND
		{
			.name = "redundant NAND",
			.erase_opts = {
				.length = CONFIG_ENV_RANGE,
				.offset = CONFIG_ENV_OFFSET_REDUND,
			},
		},
#endif
	};
W
Wolfgang Denk 已提交
209

210

211
	if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
212
		return 1;
213

M
Marek Vasut 已提交
214 215 216 217
	ret = env_export(env_new);
	if (ret)
		return ret;

218
#ifdef CONFIG_ENV_OFFSET_REDUND
219
	env_idx = (gd->env_valid == ENV_VALID);
220
#endif
W
wdenk 已提交
221

222 223 224 225
	ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
#ifdef CONFIG_ENV_OFFSET_REDUND
	if (!ret) {
		/* preset other copy for next write */
226 227
		gd->env_valid = gd->env_valid == ENV_REDUND ? ENV_VALID :
				ENV_REDUND;
228
		return ret;
229
	}
W
wdenk 已提交
230

231 232 233 234 235 236 237
	env_idx = (env_idx + 1) & 1;
	ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
	if (!ret)
		printf("Warning: primary env write failed,"
				" redundancy is lost!\n");
#endif

238
	return ret;
W
wdenk 已提交
239 240 241
}
#endif /* CMD_SAVEENV */

242 243 244 245 246 247
#if defined(CONFIG_SPL_BUILD)
static int readenv(size_t offset, u_char *buf)
{
	return nand_spl_load_image(offset, CONFIG_ENV_SIZE, buf);
}
#else
248
static int readenv(size_t offset, u_char *buf)
249
{
250
	size_t end = offset + CONFIG_ENV_RANGE;
251
	size_t amount_loaded = 0;
252
	size_t blocksize, len;
253
	struct mtd_info *mtd;
254 255
	u_char *char_ptr;

256 257
	mtd = get_nand_dev_by_index(0);
	if (!mtd)
258
		return 1;
259

260
	blocksize = mtd->erasesize;
261
	len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
262

263
	while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
264
		if (nand_block_isbad(mtd, offset)) {
265 266 267
			offset += blocksize;
		} else {
			char_ptr = &buf[amount_loaded];
268
			if (nand_read_skip_bad(mtd, offset,
269
					       &len, NULL,
270
					       mtd->size, char_ptr))
271
				return 1;
272

273
			offset += blocksize;
274
			amount_loaded += len;
275 276
		}
	}
277

278
	if (amount_loaded != CONFIG_ENV_SIZE)
279 280 281 282
		return 1;

	return 0;
}
283
#endif /* #if defined(CONFIG_SPL_BUILD) */
284

285
#ifdef CONFIG_ENV_OFFSET_OOB
286
int get_nand_env_oob(struct mtd_info *mtd, unsigned long *result)
287 288
{
	struct mtd_oob_ops ops;
289
	uint32_t oob_buf[ENV_OFFSET_SIZE / sizeof(uint32_t)];
290 291
	int ret;

292 293 294 295 296
	ops.datbuf	= NULL;
	ops.mode	= MTD_OOB_AUTO;
	ops.ooboffs	= 0;
	ops.ooblen	= ENV_OFFSET_SIZE;
	ops.oobbuf	= (void *)oob_buf;
297

298
	ret = mtd->read_oob(mtd, ENV_OFFSET_SIZE, &ops);
299 300 301 302
	if (ret) {
		printf("error reading OOB block 0\n");
		return ret;
	}
303

304
	if (oob_buf[0] == ENV_OOB_MARKER) {
305
		*result = ovoid ob_buf[1] * mtd->erasesize;
306 307
	} else if (oob_buf[0] == ENV_OOB_MARKER_OLD) {
		*result = oob_buf[1];
308
	} else {
309 310
		printf("No dynamic environment marker in OOB block 0\n");
		return -ENOENT;
311
	}
312 313

	return 0;
314 315 316
}
#endif

317
#ifdef CONFIG_ENV_OFFSET_REDUND
318
static int env_nand_load(void)
319
{
320 321 322
#if defined(ENV_IS_EMBEDDED)
	return 0;
#else
323
	int read1_fail = 0, read2_fail = 0;
324
	env_t *tmp_env1, *tmp_env2;
325
	int ret = 0;
326

327 328
	tmp_env1 = (env_t *)malloc(CONFIG_ENV_SIZE);
	tmp_env2 = (env_t *)malloc(CONFIG_ENV_SIZE);
329
	if (tmp_env1 == NULL || tmp_env2 == NULL) {
330
		puts("Can't allocate buffers for environment\n");
331
		set_default_env("!malloc() failed");
332
		ret = -EIO;
333
		goto done;
334 335
	}

336 337
	read1_fail = readenv(CONFIG_ENV_OFFSET, (u_char *) tmp_env1);
	read2_fail = readenv(CONFIG_ENV_OFFSET_REDUND, (u_char *) tmp_env2);
338

339 340 341 342 343
	if (read1_fail && read2_fail)
		puts("*** Error - No Valid Environment Area found\n");
	else if (read1_fail || read2_fail)
		puts("*** Warning - some problems detected "
		     "reading environment; recovered successfully\n");
344

345 346
	if (read1_fail && read2_fail) {
		set_default_env("!bad env area");
347
		goto done;
348
	} else if (!read1_fail && read2_fail) {
349
		gd->env_valid = ENV_VALID;
350 351
		env_import((char *)tmp_env1, 1);
	} else if (read1_fail && !read2_fail) {
352
		gd->env_valid = ENV_REDUND;
353
		env_import((char *)tmp_env2, 1);
354
	} else {
355
		env_import_redund((char *)tmp_env1, (char *)tmp_env2);
356 357
	}

358
done:
359 360
	free(tmp_env1);
	free(tmp_env2);
361

362
	return ret;
363 364
#endif /* ! ENV_IS_EMBEDDED */
}
365
#else /* ! CONFIG_ENV_OFFSET_REDUND */
366
/*
367 368 369
 * The legacy NAND code saved the environment in the first NAND
 * device i.e., nand_dev_desc + 0. This is also the behaviour using
 * the new NAND code.
370
 */
371
static int env_nand_load(void)
W
wdenk 已提交
372 373
{
#if !defined(ENV_IS_EMBEDDED)
W
Wolfgang Denk 已提交
374
	int ret;
375
	ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
W
wdenk 已提交
376

377
#if defined(CONFIG_ENV_OFFSET_OOB)
378
	struct mtd_info *mtd  = get_nand_dev_by_index(0);
379 380
	/*
	 * If unable to read environment offset from NAND OOB then fall through
381 382
	 * to the normal environment reading code below
	 */
383
	if (mtd && !get_nand_env_oob(mtd, &nand_env_oob_offset)) {
384
		printf("Found Environment offset in OOB..\n");
385 386 387 388
	} else {
		set_default_env("!no env offset in OOB");
		return;
	}
389 390
#endif

391
	ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
392 393
	if (ret) {
		set_default_env("!readenv() failed");
394
		return -EIO;
395
	}
W
wdenk 已提交
396

397
	env_import(buf, 1);
W
wdenk 已提交
398
#endif /* ! ENV_IS_EMBEDDED */
399 400

	return 0;
W
wdenk 已提交
401
}
402
#endif /* CONFIG_ENV_OFFSET_REDUND */
403 404 405

U_BOOT_ENV_LOCATION(nand) = {
	.location	= ENVL_NAND,
S
Simon Glass 已提交
406
	ENV_NAME("NAND")
407
	.load		= env_nand_load,
408
#if defined(CMD_SAVEENV)
409
	.save		= env_save_ptr(env_nand_save),
410
#endif
411
	.init		= env_nand_init,
412
};