nand.c 9.1 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
#if defined(CONFIG_CMD_SAVEENV) && defined(CONFIG_CMD_NAND)
W
wdenk 已提交
28
#define CMD_SAVEENV
29
#elif defined(CONFIG_ENV_OFFSET_REDUND)
30
#error CONFIG_ENV_OFFSET_REDUND must have CONFIG_CMD_SAVEENV & CONFIG_CMD_NAND
W
wdenk 已提交
31 32
#endif

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

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

42
char *env_name_spec = "NAND";
W
wdenk 已提交
43

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

52
DECLARE_GLOBAL_DATA_PTR;
W
wdenk 已提交
53

54 55 56 57 58 59
/*
 * 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.
60 61
 *
 * When using a NAND boot image (like sequoia_nand), the environment
62 63 64
 * 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 已提交
65 66 67
 */
int env_init(void)
{
68
#if defined(ENV_IS_EMBEDDED) || defined(CONFIG_NAND_ENV_DST)
69
	int crc1_ok = 0, crc2_ok = 0;
70 71 72 73
	env_t *tmp_env1;

#ifdef CONFIG_ENV_OFFSET_REDUND
	env_t *tmp_env2;
74

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

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

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

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

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

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

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

#ifdef CMD_SAVEENV
124 125 126 127
/*
 * 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.
 */
128
static int writeenv(size_t offset, u_char *buf)
129
{
130
	size_t end = offset + CONFIG_ENV_RANGE;
131
	size_t amount_saved = 0;
132
	size_t blocksize, len;
133
	struct mtd_info *mtd;
134 135
	u_char *char_ptr;

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

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

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

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

	return 0;
}
160

161 162 163 164
struct env_location {
	const char *name;
	const nand_erase_options_t erase_opts;
};
165

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

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

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

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

184 185
	return ret;
}
186

187 188
int saveenv(void)
{
189
	int	ret = 0;
190
	ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
	int	env_idx = 0;
	static const struct env_location location[] = {
		{
			.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 已提交
210

211

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

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

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

223 224 225 226 227 228
	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 */
		gd->env_valid = gd->env_valid == 2 ? 1 : 2;
		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 = oob_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
void env_relocate_spec(void)
319 320
{
#if !defined(ENV_IS_EMBEDDED)
321
	int read1_fail = 0, read2_fail = 0;
322
	env_t *tmp_env1, *tmp_env2;
323

324 325
	tmp_env1 = (env_t *)malloc(CONFIG_ENV_SIZE);
	tmp_env2 = (env_t *)malloc(CONFIG_ENV_SIZE);
326
	if (tmp_env1 == NULL || tmp_env2 == NULL) {
327
		puts("Can't allocate buffers for environment\n");
328
		set_default_env("!malloc() failed");
329
		goto done;
330 331
	}

332 333
	read1_fail = readenv(CONFIG_ENV_OFFSET, (u_char *) tmp_env1);
	read2_fail = readenv(CONFIG_ENV_OFFSET_REDUND, (u_char *) tmp_env2);
334

335 336 337 338 339
	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");
340

341 342
	if (read1_fail && read2_fail) {
		set_default_env("!bad env area");
343
		goto done;
344
	} else if (!read1_fail && read2_fail) {
345
		gd->env_valid = 1;
346 347
		env_import((char *)tmp_env1, 1);
	} else if (read1_fail && !read2_fail) {
348
		gd->env_valid = 2;
349
		env_import((char *)tmp_env2, 1);
350
	} else {
351
		env_import_redund((char *)tmp_env1, (char *)tmp_env2);
352 353
	}

354
done:
355 356
	free(tmp_env1);
	free(tmp_env2);
357 358 359

#endif /* ! ENV_IS_EMBEDDED */
}
360
#else /* ! CONFIG_ENV_OFFSET_REDUND */
361
/*
362 363 364
 * 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.
365
 */
366
void env_relocate_spec(void)
W
wdenk 已提交
367 368
{
#if !defined(ENV_IS_EMBEDDED)
W
Wolfgang Denk 已提交
369
	int ret;
370
	ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
W
wdenk 已提交
371

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

386
	ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
387 388 389 390
	if (ret) {
		set_default_env("!readenv() failed");
		return;
	}
W
wdenk 已提交
391

392
	env_import(buf, 1);
W
wdenk 已提交
393 394
#endif /* ! ENV_IS_EMBEDDED */
}
395
#endif /* CONFIG_ENV_OFFSET_REDUND */