nand.c 9.07 KB
Newer Older
1
// SPDX-License-Identifier: GPL-2.0+
2
/*
3 4 5
 * (C) Copyright 2000-2010
 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
 *
6 7 8
 * (C) Copyright 2008
 * Stuart Wood, Lab X Technologies <stuart.wood@labxtechnologies.com>
 *
9 10 11 12 13 14 15 16 17 18 19
 * (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>
 */

#include <common.h>
#include <command.h>
#include <environment.h>
#include <linux/stddef.h>
20
#include <malloc.h>
21
#include <memalign.h>
22
#include <nand.h>
23 24
#include <search.h>
#include <errno.h>
25

26 27
#if defined(CONFIG_CMD_SAVEENV) && defined(CONFIG_CMD_NAND) && \
		!defined(CONFIG_SPL_BUILD)
28
#define CMD_SAVEENV
29
#elif defined(CONFIG_ENV_OFFSET_REDUND) && !defined(CONFIG_SPL_BUILD)
30
#error CONFIG_ENV_OFFSET_REDUND must have CONFIG_CMD_SAVEENV & CONFIG_CMD_NAND
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
36 37
#endif

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

42
#if defined(ENV_IS_EMBEDDED)
43
static env_t *env_ptr = &environment;
44
#elif defined(CONFIG_NAND_ENV_DST)
45
static env_t *env_ptr = (env_t *)CONFIG_NAND_ENV_DST;
46 47
#endif /* ENV_IS_EMBEDDED */

48
DECLARE_GLOBAL_DATA_PTR;
49

50 51 52 53 54 55
/*
 * 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.
56 57
 *
 * When using a NAND boot image (like sequoia_nand), the environment
58 59 60
 * 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.
61
 */
62
static int env_nand_init(void)
63
{
64
#if defined(ENV_IS_EMBEDDED) || defined(CONFIG_NAND_ENV_DST)
65
	int crc1_ok = 0, crc2_ok = 0;
66 67 68 69
	env_t *tmp_env1;

#ifdef CONFIG_ENV_OFFSET_REDUND
	env_t *tmp_env2;
70

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

77
	if (!crc1_ok && !crc2_ok) {
78
		gd->env_addr	= 0;
79
		gd->env_valid	= ENV_INVALID;
80 81 82

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

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

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

#else /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
112
	gd->env_addr	= (ulong)&default_environment[0];
113
	gd->env_valid	= ENV_VALID;
114
#endif /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
115

116
	return 0;
117 118 119
}

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

132 133 134 135 136
	mtd = get_nand_dev_by_index(0);
	if (!mtd)
		return 1;

	blocksize = mtd->erasesize;
137
	len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
138

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

147
			offset += blocksize;
148
			amount_saved += len;
149 150
		}
	}
151
	if (amount_saved != CONFIG_ENV_SIZE)
152 153 154 155
		return 1;

	return 0;
}
156

157
struct nand_env_location {
158 159 160
	const char *name;
	const nand_erase_options_t erase_opts;
};
161

162
static int erase_and_write_env(const struct nand_env_location *location,
163
		u_char *env_new)
164
{
165
	struct mtd_info *mtd;
166
	int ret = 0;
167

168 169
	mtd = get_nand_dev_by_index(0);
	if (!mtd)
170 171
		return 1;

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

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

180 181
	return ret;
}
182

183
static int env_nand_save(void)
184
{
185
	int	ret = 0;
186
	ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
187
	int	env_idx = 0;
188
	static const struct nand_env_location location[] = {
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
		{
			.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
	};
Wolfgang Denk's avatar
Wolfgang Denk committed
206

207

208
	if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
209
		return 1;
210

211 212 213 214
	ret = env_export(env_new);
	if (ret)
		return ret;

215
#ifdef CONFIG_ENV_OFFSET_REDUND
216
	env_idx = (gd->env_valid == ENV_VALID);
217
#endif
218

219 220 221 222
	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 */
223 224
		gd->env_valid = gd->env_valid == ENV_REDUND ? ENV_VALID :
				ENV_REDUND;
225
		return ret;
226
	}
227

228 229 230 231 232 233 234
	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

235
	return ret;
236 237 238
}
#endif /* CMD_SAVEENV */

239 240 241 242 243 244
#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
245
static int readenv(size_t offset, u_char *buf)
246
{
247
	size_t end = offset + CONFIG_ENV_RANGE;
248
	size_t amount_loaded = 0;
249
	size_t blocksize, len;
250
	struct mtd_info *mtd;
251 252
	u_char *char_ptr;

253 254
	mtd = get_nand_dev_by_index(0);
	if (!mtd)
255
		return 1;
256

257
	blocksize = mtd->erasesize;
258
	len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
259

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

270
			offset += blocksize;
271
			amount_loaded += len;
272 273
		}
	}
274

275
	if (amount_loaded != CONFIG_ENV_SIZE)
276 277 278 279
		return 1;

	return 0;
}
280
#endif /* #if defined(CONFIG_SPL_BUILD) */
281

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

289 290 291 292 293
	ops.datbuf	= NULL;
	ops.mode	= MTD_OOB_AUTO;
	ops.ooboffs	= 0;
	ops.ooblen	= ENV_OFFSET_SIZE;
	ops.oobbuf	= (void *)oob_buf;
294

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

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

	return 0;
311 312 313
}
#endif

314
#ifdef CONFIG_ENV_OFFSET_REDUND
315
static int env_nand_load(void)
316
{
317 318 319
#if defined(ENV_IS_EMBEDDED)
	return 0;
#else
320
	int read1_fail, read2_fail;
321
	env_t *tmp_env1, *tmp_env2;
322
	int ret = 0;
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", 0);
329
		ret = -EIO;
330
		goto done;
331 332
	}

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

336 337
	ret = env_import_redund((char *)tmp_env1, read1_fail, (char *)tmp_env2,
				read2_fail);
338

339
done:
340 341
	free(tmp_env1);
	free(tmp_env2);
342

343
	return ret;
344 345
#endif /* ! ENV_IS_EMBEDDED */
}
346
#else /* ! CONFIG_ENV_OFFSET_REDUND */
347
/*
348 349 350
 * 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.
351
 */
352
static int env_nand_load(void)
353 354
{
#if !defined(ENV_IS_EMBEDDED)
355
	int ret;
356
	ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
357

358
#if defined(CONFIG_ENV_OFFSET_OOB)
359
	struct mtd_info *mtd  = get_nand_dev_by_index(0);
360 361
	/*
	 * If unable to read environment offset from NAND OOB then fall through
362 363
	 * to the normal environment reading code below
	 */
364
	if (mtd && !get_nand_env_oob(mtd, &nand_env_oob_offset)) {
365
		printf("Found Environment offset in OOB..\n");
366
	} else {
367
		set_default_env("no env offset in OOB", 0);
368 369
		return;
	}
370 371
#endif

372
	ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
373
	if (ret) {
374
		set_default_env("readenv() failed", 0);
375
		return -EIO;
376
	}
377

378
	return env_import(buf, 1);
379
#endif /* ! ENV_IS_EMBEDDED */
380 381

	return 0;
382
}
383
#endif /* CONFIG_ENV_OFFSET_REDUND */
384 385 386

U_BOOT_ENV_LOCATION(nand) = {
	.location	= ENVL_NAND,
387
	ENV_NAME("NAND")
388
	.load		= env_nand_load,
389
#if defined(CMD_SAVEENV)
390
	.save		= env_save_ptr(env_nand_save),
391
#endif
392
	.init		= env_nand_init,
393
};