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_valid   = ENV_INVALID;
 111#endif /* ENV_IS_EMBEDDED || CONFIG_NAND_ENV_DST */
 112
 113        return 0;
 114}
 115
 116#ifdef CMD_SAVEENV
 117/*
 118 * The legacy NAND code saved the environment in the first NAND device i.e.,
 119 * nand_dev_desc + 0. This is also the behaviour using the new NAND code.
 120 */
 121static int writeenv(size_t offset, u_char *buf)
 122{
 123        size_t end = offset + CONFIG_ENV_RANGE;
 124        size_t amount_saved = 0;
 125        size_t blocksize, len;
 126        struct mtd_info *mtd;
 127        u_char *char_ptr;
 128
 129        mtd = get_nand_dev_by_index(0);
 130        if (!mtd)
 131                return 1;
 132
 133        blocksize = mtd->erasesize;
 134        len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
 135
 136        while (amount_saved < CONFIG_ENV_SIZE && offset < end) {
 137                if (nand_block_isbad(mtd, offset)) {
 138                        offset += blocksize;
 139                } else {
 140                        char_ptr = &buf[amount_saved];
 141                        if (nand_write(mtd, offset, &len, char_ptr))
 142                                return 1;
 143
 144                        offset += blocksize;
 145                        amount_saved += len;
 146                }
 147        }
 148        if (amount_saved != CONFIG_ENV_SIZE)
 149                return 1;
 150
 151        return 0;
 152}
 153
 154struct nand_env_location {
 155        const char *name;
 156        const nand_erase_options_t erase_opts;
 157};
 158
 159static int erase_and_write_env(const struct nand_env_location *location,
 160                u_char *env_new)
 161{
 162        struct mtd_info *mtd;
 163        int ret = 0;
 164
 165        mtd = get_nand_dev_by_index(0);
 166        if (!mtd)
 167                return 1;
 168
 169        printf("Erasing %s...\n", location->name);
 170        if (nand_erase_opts(mtd, &location->erase_opts))
 171                return 1;
 172
 173        printf("Writing to %s... ", location->name);
 174        ret = writeenv(location->erase_opts.offset, env_new);
 175        puts(ret ? "FAILED!\n" : "OK\n");
 176
 177        return ret;
 178}
 179
 180static int env_nand_save(void)
 181{
 182        int     ret = 0;
 183        ALLOC_CACHE_ALIGN_BUFFER(env_t, env_new, 1);
 184        int     env_idx = 0;
 185        static const struct nand_env_location location[] = {
 186                {
 187                        .name = "NAND",
 188                        .erase_opts = {
 189                                .length = CONFIG_ENV_RANGE,
 190                                .offset = CONFIG_ENV_OFFSET,
 191                        },
 192                },
 193#ifdef CONFIG_ENV_OFFSET_REDUND
 194                {
 195                        .name = "redundant NAND",
 196                        .erase_opts = {
 197                                .length = CONFIG_ENV_RANGE,
 198                                .offset = CONFIG_ENV_OFFSET_REDUND,
 199                        },
 200                },
 201#endif
 202        };
 203
 204
 205        if (CONFIG_ENV_RANGE < CONFIG_ENV_SIZE)
 206                return 1;
 207
 208        ret = env_export(env_new);
 209        if (ret)
 210                return ret;
 211
 212#ifdef CONFIG_ENV_OFFSET_REDUND
 213        env_idx = (gd->env_valid == ENV_VALID);
 214#endif
 215
 216        ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
 217#ifdef CONFIG_ENV_OFFSET_REDUND
 218        if (!ret) {
 219                /* preset other copy for next write */
 220                gd->env_valid = gd->env_valid == ENV_REDUND ? ENV_VALID :
 221                                ENV_REDUND;
 222                return ret;
 223        }
 224
 225        env_idx = (env_idx + 1) & 1;
 226        ret = erase_and_write_env(&location[env_idx], (u_char *)env_new);
 227        if (!ret)
 228                printf("Warning: primary env write failed,"
 229                                " redundancy is lost!\n");
 230#endif
 231
 232        return ret;
 233}
 234#endif /* CMD_SAVEENV */
 235
 236#if defined(CONFIG_SPL_BUILD)
 237static int readenv(size_t offset, u_char *buf)
 238{
 239        return nand_spl_load_image(offset, CONFIG_ENV_SIZE, buf);
 240}
 241#else
 242static int readenv(size_t offset, u_char *buf)
 243{
 244        size_t end = offset + CONFIG_ENV_RANGE;
 245        size_t amount_loaded = 0;
 246        size_t blocksize, len;
 247        struct mtd_info *mtd;
 248        u_char *char_ptr;
 249
 250        mtd = get_nand_dev_by_index(0);
 251        if (!mtd)
 252                return 1;
 253
 254        blocksize = mtd->erasesize;
 255        len = min(blocksize, (size_t)CONFIG_ENV_SIZE);
 256
 257        while (amount_loaded < CONFIG_ENV_SIZE && offset < end) {
 258                if (nand_block_isbad(mtd, offset)) {
 259                        offset += blocksize;
 260                } else {
 261                        char_ptr = &buf[amount_loaded];
 262                        if (nand_read_skip_bad(mtd, offset,
 263                                               &len, NULL,
 264                                               mtd->size, char_ptr))
 265                                return 1;
 266
 267                        offset += blocksize;
 268                        amount_loaded += len;
 269                }
 270        }
 271
 272        if (amount_loaded != CONFIG_ENV_SIZE)
 273                return 1;
 274
 275        return 0;
 276}
 277#endif /* #if defined(CONFIG_SPL_BUILD) */
 278
 279#ifdef CONFIG_ENV_OFFSET_OOB
 280int get_nand_env_oob(struct mtd_info *mtd, unsigned long *result)
 281{
 282        struct mtd_oob_ops ops;
 283        uint32_t oob_buf[ENV_OFFSET_SIZE / sizeof(uint32_t)];
 284        int ret;
 285
 286        ops.datbuf      = NULL;
 287        ops.mode        = MTD_OOB_AUTO;
 288        ops.ooboffs     = 0;
 289        ops.ooblen      = ENV_OFFSET_SIZE;
 290        ops.oobbuf      = (void *)oob_buf;
 291
 292        ret = mtd->read_oob(mtd, ENV_OFFSET_SIZE, &ops);
 293        if (ret) {
 294                printf("error reading OOB block 0\n");
 295                return ret;
 296        }
 297
 298        if (oob_buf[0] == ENV_OOB_MARKER) {
 299                *result = ovoid ob_buf[1] * mtd->erasesize;
 300        } else if (oob_buf[0] == ENV_OOB_MARKER_OLD) {
 301                *result = oob_buf[1];
 302        } else {
 303                printf("No dynamic environment marker in OOB block 0\n");
 304                return -ENOENT;
 305        }
 306
 307        return 0;
 308}
 309#endif
 310
 311#ifdef CONFIG_ENV_OFFSET_REDUND
 312static int env_nand_load(void)
 313{
 314#if defined(ENV_IS_EMBEDDED)
 315        return 0;
 316#else
 317        int read1_fail, read2_fail;
 318        env_t *tmp_env1, *tmp_env2;
 319        int ret = 0;
 320
 321        tmp_env1 = (env_t *)malloc(CONFIG_ENV_SIZE);
 322        tmp_env2 = (env_t *)malloc(CONFIG_ENV_SIZE);
 323        if (tmp_env1 == NULL || tmp_env2 == NULL) {
 324                puts("Can't allocate buffers for environment\n");
 325                env_set_default("malloc() failed", 0);
 326                ret = -EIO;
 327                goto done;
 328        }
 329
 330        read1_fail = readenv(CONFIG_ENV_OFFSET, (u_char *) tmp_env1);
 331        read2_fail = readenv(CONFIG_ENV_OFFSET_REDUND, (u_char *) tmp_env2);
 332
 333        ret = env_import_redund((char *)tmp_env1, read1_fail, (char *)tmp_env2,
 334                                read2_fail, H_EXTERNAL);
 335
 336done:
 337        free(tmp_env1);
 338        free(tmp_env2);
 339
 340        return ret;
 341#endif /* ! ENV_IS_EMBEDDED */
 342}
 343#else /* ! CONFIG_ENV_OFFSET_REDUND */
 344/*
 345 * The legacy NAND code saved the environment in the first NAND
 346 * device i.e., nand_dev_desc + 0. This is also the behaviour using
 347 * the new NAND code.
 348 */
 349static int env_nand_load(void)
 350{
 351#if !defined(ENV_IS_EMBEDDED)
 352        int ret;
 353        ALLOC_CACHE_ALIGN_BUFFER(char, buf, CONFIG_ENV_SIZE);
 354
 355#if defined(CONFIG_ENV_OFFSET_OOB)
 356        struct mtd_info *mtd  = get_nand_dev_by_index(0);
 357        /*
 358         * If unable to read environment offset from NAND OOB then fall through
 359         * to the normal environment reading code below
 360         */
 361        if (mtd && !get_nand_env_oob(mtd, &nand_env_oob_offset)) {
 362                printf("Found Environment offset in OOB..\n");
 363        } else {
 364                env_set_default("no env offset in OOB", 0);
 365                return;
 366        }
 367#endif
 368
 369        ret = readenv(CONFIG_ENV_OFFSET, (u_char *)buf);
 370        if (ret) {
 371                env_set_default("readenv() failed", 0);
 372                return -EIO;
 373        }
 374
 375        return env_import(buf, 1, H_EXTERNAL);
 376#endif /* ! ENV_IS_EMBEDDED */
 377
 378        return 0;
 379}
 380#endif /* CONFIG_ENV_OFFSET_REDUND */
 381
 382U_BOOT_ENV_LOCATION(nand) = {
 383        .location       = ENVL_NAND,
 384        ENV_NAME("NAND")
 385        .load           = env_nand_load,
 386#if defined(CMD_SAVEENV)
 387        .save           = env_save_ptr(env_nand_save),
 388#endif
 389        .init           = env_nand_init,
 390};
 391