uboot/env/nand.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0+
   2/*
   3 * (C) Copyright 2000-2010
   4 * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
   5 *
   6 * (C) Copyright 2008
   7 * Stuart Wood, Lab X Technologies <stuart.wood@labxtechnologies.com>
   8 *
   9 * (C) Copyright 2004
  10 * Jian Zhang, Texas Instruments, jzhang@ti.com.
  11 *
  12 * (C) Copyright 2001 Sysgo Real-Time Solutions, GmbH <www.elinos.com>
  13 * Andreas Heppel <aheppel@sysgo.de>
  14 */
  15
  16#include <common.h>
  17#include <command.h>
  18#include <env.h>
  19#include <env_internal.h>
  20#include <asm/global_data.h>
  21#include <linux/stddef.h>
  22#include <malloc.h>
  23#include <memalign.h>
  24#include <nand.h>
  25#include <search.h>
  26#include <errno.h>
  27#include <u-boot/crc.h>
  28
  29#if defined(CONFIG_CMD_SAVEENV) && defined(CONFIG_CMD_NAND) && \
  30                !defined(CONFIG_SPL_BUILD)
  31#define CMD_SAVEENV
  32#elif defined(CONFIG_ENV_OFFSET_REDUND) && !defined(CONFIG_SPL_BUILD)
  33#error CONFIG_ENV_OFFSET_REDUND must have CONFIG_CMD_SAVEENV & CONFIG_CMD_NAND
  34#endif
  35
  36#ifndef CONFIG_ENV_RANGE
  37#define CONFIG_ENV_RANGE        CONFIG_ENV_SIZE
  38#endif
  39
  40#if defined(ENV_IS_EMBEDDED)
  41static env_t *env_ptr = &environment;
  42#elif defined(CONFIG_NAND_ENV_DST)
  43static env_t *env_ptr = (env_t *)CONFIG_NAND_ENV_DST;
  44#endif /* ENV_IS_EMBEDDED */
  45
  46DECLARE_GLOBAL_DATA_PTR;
  47
  48/*
  49 * This is called before nand_init() so we can't read NAND to
  50 * validate env data.
  51 *
  52 * Mark it OK for now. env_relocate() in env_common.c will call our
  53 * relocate function which does the real validation.
  54 *
  55 * When using a NAND boot image (like sequoia_nand), the environment
  56 * can be embedded or attached to the U-Boot image in NAND flash.
  57 * This way the SPL loads not only the U-Boot image from NAND but
  58 * also the environment.
  59 */
  60static int env_nand_init(void)
  61{
  62#if defined(ENV_IS_EMBEDDED) || defined(CONFIG_NAND_ENV_DST)
  63        int crc1_ok = 0, crc2_ok = 0;
  64        env_t *tmp_env1;
  65
  66#ifdef CONFIG_ENV_OFFSET_REDUND
  67        env_t *tmp_env2;
  68
  69        tmp_env2 = (env_t *)((ulong)env_ptr + CONFIG_ENV_SIZE);
  70        crc2_ok = crc32(0, tmp_env2->data, ENV_SIZE) == tmp_env2->crc;
  71#endif
  72        tmp_env1 = env_ptr;
  73        crc1_ok = crc32(0, tmp_env1->data, ENV_SIZE) == tmp_env1->crc;
  74
  75        if (!crc1_ok && !crc2_ok) {
  76                gd->env_addr    = 0;
  77                gd->env_valid   = ENV_INVALID;
  78
  79                return 0;
  80        } else if (crc1_ok && !crc2_ok) {
  81                gd->env_valid = ENV_VALID;
  82        }
  83#ifdef CONFIG_ENV_OFFSET_REDUND
  84        else if (!crc1_ok && crc2_ok) {
  85                gd->env_valid = ENV_REDUND;
  86        } else {
  87                /* both ok - check serial */
  88                if (tmp_env1->flags == 255 && tmp_env2->flags == 0)
  89                        gd->env_valid = ENV_REDUND;
  90                else if (tmp_env2->flags == 255 && tmp_env1->flags == 0)
  91                        gd->env_valid = ENV_VALID;
  92                else if (tmp_env1->flags > tmp_env2->flags)
  93                        gd->env_valid = ENV_VALID;
  94                else if (tmp_env2->flags > tmp_env1->flags)
  95                        gd->env_valid = ENV_REDUND;
  96                else /* flags are equal - almost impossible */
  97                        gd->env_valid = ENV_VALID;
  98        }
  99
 100        if (gd->env_valid == ENV_REDUND)
 101                env_ptr = tmp_env2;
 102        else
 103#endif
 104        if (gd->env_valid == ENV_VALID)
 105                env_ptr = tmp_env1;
 106
 107        gd->env_addr = (ulong)env_ptr->data;
 108
 109#else /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
 110        gd->env_addr    = (ulong)&default_environment[0];
 111        gd->env_valid   = ENV_VALID;
 112#endif /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
 113
 114        return 0;
 115}
 116
 117#ifdef CMD_SAVEENV
 118/*
 119 * The legacy NAND code saved the environment in the first NAND device i.e.,
 120 * nand_dev_desc + 0. This is also the behaviour using the new NAND code.
 121 */
 122static int writeenv(size_t offset, u_char *buf)
 123{
 124        size_t end = offset + CONFIG_ENV_RANGE;
 125        size_t amount_saved = 0;
 126        size_t blocksize, len;
 127        struct mtd_info *mtd;
 128        u_char *char_ptr;
 129
 130        mtd = get_nand_dev_by_index(0);
 131        if (!mtd)
 132                return 1;
 133
 134        blocksize = mtd->erasesize;
 135        len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
 136
 137        while (amount_saved < CONFIG_ENV_SIZE && offset < end) {
 138                if (nand_block_isbad(mtd, offset)) {
 139                        offset += blocksize;
 140                } else {
 141                        char_ptr = &buf[amount_saved];
 142                        if (nand_write(mtd, offset, &len, char_ptr))
 143                                return 1;
 144
 145                        offset += blocksize;
 146                        amount_saved += len;
 147                }
 148        }
 149        if (amount_saved != CONFIG_ENV_SIZE)
 150                return 1;
 151
 152        return 0;
 153}
 154
 155struct nand_env_location {
 156        const char *name;
 157        const nand_erase_options_t erase_opts;
 158};
 159
 160static int erase_and_write_env(const struct nand_env_location *location,
 161                u_char *env_new)
 162{
 163        struct mtd_info *mtd;
 164        int ret = 0;
 165
 166        mtd = get_nand_dev_by_index(0);
 167        if (!mtd)
 168                return 1;
 169
 170        printf("Erasing %s...\n", location->name);
 171        if (nand_erase_opts(mtd, &location->erase_opts))
 172                return 1;
 173
 174        printf("Writing to %s... ", location->name);
 175        ret = writeenv(location->erase_opts.offset, env_new);
 176        puts(ret ? "FAILED!\n" : "OK\n");
 177
 178        return ret;
 179}
 180
 181static int env_nand_save(void)
 182{
 183        int     ret = 0;
 184        ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
 185        int     env_idx = 0;
 186        static const struct nand_env_location location[] = {
 187                {
 188                        .name = "NAND",
 189                        .erase_opts = {
 190                                .length = CONFIG_ENV_RANGE,
 191                                .offset = CONFIG_ENV_OFFSET,
 192                        },
 193                },
 194#ifdef CONFIG_ENV_OFFSET_REDUND
 195                {
 196                        .name = "redundant NAND",
 197                        .erase_opts = {
 198                                .length = CONFIG_ENV_RANGE,
 199                                .offset = CONFIG_ENV_OFFSET_REDUND,
 200                        },
 201                },
 202#endif
 203        };
 204
 205
 206        if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
 207                return 1;
 208
 209        ret = env_export(env_new);
 210        if (ret)
 211                return ret;
 212
 213#ifdef CONFIG_ENV_OFFSET_REDUND
 214        env_idx = (gd->env_valid == ENV_VALID);
 215#endif
 216
 217        ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
 218#ifdef CONFIG_ENV_OFFSET_REDUND
 219        if (!ret) {
 220                /* preset other copy for next write */
 221                gd->env_valid = gd->env_valid == ENV_REDUND ? ENV_VALID :
 222                                ENV_REDUND;
 223                return ret;
 224        }
 225
 226        env_idx = (env_idx + 1) & 1;
 227        ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
 228        if (!ret)
 229                printf("Warning: primary env write failed,"
 230                                " redundancy is lost!\n");
 231#endif
 232
 233        return ret;
 234}
 235#endif /* CMD_SAVEENV */
 236
 237#if defined(CONFIG_SPL_BUILD)
 238static int readenv(size_t offset, u_char *buf)
 239{
 240        return nand_spl_load_image(offset, CONFIG_ENV_SIZE, buf);
 241}
 242#else
 243static int readenv(size_t offset, u_char *buf)
 244{
 245        size_t end = offset + CONFIG_ENV_RANGE;
 246        size_t amount_loaded = 0;
 247        size_t blocksize, len;
 248        struct mtd_info *mtd;
 249        u_char *char_ptr;
 250
 251        mtd = get_nand_dev_by_index(0);
 252        if (!mtd)
 253                return 1;
 254
 255        blocksize = mtd->erasesize;
 256        len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
 257
 258        while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
 259                if (nand_block_isbad(mtd, offset)) {
 260                        offset += blocksize;
 261                } else {
 262                        char_ptr = &buf[amount_loaded];
 263                        if (nand_read_skip_bad(mtd, offset,
 264                                               &len, NULL,
 265                                               mtd->size, char_ptr))
 266                                return 1;
 267
 268                        offset += blocksize;
 269                        amount_loaded += len;
 270                }
 271        }
 272
 273        if (amount_loaded != CONFIG_ENV_SIZE)
 274                return 1;
 275
 276        return 0;
 277}
 278#endif /* #if defined(CONFIG_SPL_BUILD) */
 279
 280#ifdef CONFIG_ENV_OFFSET_OOB
 281int get_nand_env_oob(struct mtd_info *mtd, unsigned long *result)
 282{
 283        struct mtd_oob_ops ops;
 284        uint32_t oob_buf[ENV_OFFSET_SIZE / sizeof(uint32_t)];
 285        int ret;
 286
 287        ops.datbuf      = NULL;
 288        ops.mode        = MTD_OOB_AUTO;
 289        ops.ooboffs     = 0;
 290        ops.ooblen      = ENV_OFFSET_SIZE;
 291        ops.oobbuf      = (void *)oob_buf;
 292
 293        ret = mtd->read_oob(mtd, ENV_OFFSET_SIZE, &ops);
 294        if (ret) {
 295                printf("error reading OOB block 0\n");
 296                return ret;
 297        }
 298
 299        if (oob_buf[0] == ENV_OOB_MARKER) {
 300                *result = ovoid ob_buf[1] * mtd->erasesize;
 301        } else if (oob_buf[0] == ENV_OOB_MARKER_OLD) {
 302                *result = oob_buf[1];
 303        } else {
 304                printf("No dynamic environment marker in OOB block 0\n");
 305                return -ENOENT;
 306        }
 307
 308        return 0;
 309}
 310#endif
 311
 312#ifdef CONFIG_ENV_OFFSET_REDUND
 313static int env_nand_load(void)
 314{
 315#if defined(ENV_IS_EMBEDDED)
 316        return 0;
 317#else
 318        int read1_fail, read2_fail;
 319        env_t *tmp_env1, *tmp_env2;
 320        int ret = 0;
 321
 322        tmp_env1 = (env_t *)malloc(CONFIG_ENV_SIZE);
 323        tmp_env2 = (env_t *)malloc(CONFIG_ENV_SIZE);
 324        if (tmp_env1 == NULL || tmp_env2 == NULL) {
 325                puts("Can't allocate buffers for environment\n");
 326                env_set_default("malloc() failed", 0);
 327                ret = -EIO;
 328                goto done;
 329        }
 330
 331        read1_fail = readenv(CONFIG_ENV_OFFSET, (u_char *) tmp_env1);
 332        read2_fail = readenv(CONFIG_ENV_OFFSET_REDUND, (u_char *) tmp_env2);
 333
 334        ret = env_import_redund((char *)tmp_env1, read1_fail, (char *)tmp_env2,
 335                                read2_fail, H_EXTERNAL);
 336
 337done:
 338        free(tmp_env1);
 339        free(tmp_env2);
 340
 341        return ret;
 342#endif /* ! ENV_IS_EMBEDDED */
 343}
 344#else /* ! CONFIG_ENV_OFFSET_REDUND */
 345/*
 346 * The legacy NAND code saved the environment in the first NAND
 347 * device i.e., nand_dev_desc + 0. This is also the behaviour using
 348 * the new NAND code.
 349 */
 350static int env_nand_load(void)
 351{
 352#if !defined(ENV_IS_EMBEDDED)
 353        int ret;
 354        ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
 355
 356#if defined(CONFIG_ENV_OFFSET_OOB)
 357        struct mtd_info *mtd  = get_nand_dev_by_index(0);
 358        /*
 359         * If unable to read environment offset from NAND OOB then fall through
 360         * to the normal environment reading code below
 361         */
 362        if (mtd && !get_nand_env_oob(mtd, &nand_env_oob_offset)) {
 363                printf("Found Environment offset in OOB..\n");
 364        } else {
 365                env_set_default("no env offset in OOB", 0);
 366                return;
 367        }
 368#endif
 369
 370        ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
 371        if (ret) {
 372                env_set_default("readenv() failed", 0);
 373                return -EIO;
 374        }
 375
 376        return env_import(buf, 1, H_EXTERNAL);
 377#endif /* ! ENV_IS_EMBEDDED */
 378
 379        return 0;
 380}
 381#endif /* CONFIG_ENV_OFFSET_REDUND */
 382
 383U_BOOT_ENV_LOCATION(nand) = {
 384        .location       = ENVL_NAND,
 385        ENV_NAME("NAND")
 386        .load           = env_nand_load,
 387#if defined(CMD_SAVEENV)
 388        .save           = env_save_ptr(env_nand_save),
 389#endif
 390        .init           = env_nand_init,
 391};
 392