u-boot/common/cmd_mig.c

1020 lines
30 KiB
C

#include <common.h>
#ifndef CONFIG_TPL
#include <watchdog.h>
#include <command.h>
#include <image.h>
#include <malloc.h>
#include <environment.h>
#include <linux/ctype.h>
#include <asm/errno.h>
#include <nand.h>
#include <../board/netmodule/netbox/settings.h>
/**
* U-Boot memory-layout: migration memory (malloc or area unpack linux kernel)
*
* 0x00000000 reserved (not fo use)
* 0x00001000 linux kernel & root file system
* 0x01000000 tftp download uboot
* 0x02700000 dtb (device tree blob)
* 0x02800000 uimage (kernel image)
* 0x03C00000 uboot code
* .......... uboot heap
* .......... uboot stack (backward)
* 0x04000000 64MB
**/
/* #define MIGRATION_MEMORY_MALLOC */
#ifdef MIGRATION_MEMORY_MALLOC
/* 1MB (malloc): maximum of available memory in uboot */
#define MIGRATION_MEMORY_SIZE 0x00100000
#else /* MIGRATION_MEMORY_MALLOC */
/* 8MB (small): area unpack linux kernel */
/*
#define MIGRATION_MEMORY_ADDR 0x00800000
#define MIGRATION_MEMORY_SIZE 0x00800000
*/
/* 16MB (medium): area tftp download uboot */
/*
#define MIGRATION_MEMORY_ADDR 0x01000000
#define MIGRATION_MEMORY_SIZE 0x01000000
*/
/* 32MB (large): area unpack linux kernel and tftp download uboot and dtb */
#define MIGRATION_MEMORY_ADDR 0x00800000
#define MIGRATION_MEMORY_SIZE 0x02000000
#endif /* MIGRATION_MEMORY_MALLOC */
/**
* E2P-layout: migration state at the end of the partition table
* offset size descriptor
* 0x0000 0x0400 Variable Board descriptor
* 0x0400 0x0200 Variable Licensing data
* 0x0600 0x0200 Variable Partition table
* > 0x07F8 0x0008 Migration-Status
* 0x0800 0x0800 U-Boot environment
* 0x1000 0x1000 Reserved (spare)
*/
#define E3P_BD___SIZE 0x0400
#define E3P_LIC__SIZE 0x0200
#define E3P_PART_SIZE 0x0200
#define E3P_ENV__SIZE 0x0800
#define E3P_RES__SIZE 0x1000
#define E3P_BD___OFFSET 0x0000
#define E3P_LIC__OFFSET (E3P_BD___OFFSET+E3P_BD___SIZE)
#define E3P_PART_OFFSET (E3P_LIC__OFFSET+E3P_LIC__SIZE)
#define E3P_ENV__OFFSET (E3P_PART_OFFSET+E3P_PART_SIZE)
#define E3P_RES__OFFSET (E3P_ENV__OFFSET+E3P_ENV__SIZE)
#define E3P_SIZE (E3P_RES__OFFSET+E3P_RES__SIZE)
#define E3P_MIG_ADDR 0x50
#define E3P_MIG_STATE (E3P_PART_OFFSET+E3P_PART_SIZE-E3P_MIG_SIZE)
#define E3P_MIG_SIZE 8
#define E3P_MIG_MIN 3
/**
* Migration state
* initialize
* - migrate bbt
* - migrate uboot
* for all blocks with 1-bit ECC
* - backup to inactive partition
* - restore using 4-bit ECC
* finished
*/
#define MIG_STATE_BBT 0x00
#define MIG_STATE_UBOOT 0x01
#define MIG_STATE_BACKUP 0x02
#define MIG_STATE_RESTORE 0x03
#define MIG_STATE_BACKWARD 0x04
#define MIG_STATE_ERROR 0x0E
#define MIG_STATE_DONE 0xFF
#define MIG_STATE_S(t, n) ((0x0F & (t)) | ((0x0F & ((unsigned char)(n))) << 4))
#define MIG_STATE_T(s) (0x0F & ((s) >> 0))
#define MIG_STATE_N(s) (0x0F & ((s) >> 4))
#define MIG_STATE_BACKUP_RESTORE_NUM_STEPS infoMigration.num
#define YAFFS_OOB_SIZE (16+12)
#define ALL_LEDS ((1 << LED_COUNT) - 1)
typedef struct _MigrationLocation
{
int offset;
int size;
} MigrationLocation;
typedef struct _MigrationInfo
{
MigrationLocation backup;
MigrationLocation bbt;
MigrationLocation uboot;
MigrationLocation table[32];
int num;
unsigned char *buffer;
int size;
} MigrationInfo;
static MigrationInfo infoMigration;
static char PROGRESS_CHARS[5] = "-\\|/";
extern void eeprom_init (void);
extern int eeprom_read (unsigned dev_addr, unsigned offset, uchar *buffer, unsigned cnt);
extern int eeprom_write (unsigned dev_addr, unsigned offset, uchar *buffer, unsigned cnt);
extern int nand_migrate_bbt(int forward_migration);
extern int load_uboot(int do_relocate, int* flash_address, int* flash_num_blocks, int* ram_address, int* entry_point);
extern int do_setled (int color, unsigned int mask);
static int get_mig_state(int verbose)
{
int i, n;
int state = -1;
unsigned char val[E3P_MIG_SIZE];
unsigned char ctr[E3P_MIG_SIZE];
memset(val, 0, sizeof(val));
memset(ctr, 0, sizeof(ctr));
eeprom_init ();
if (eeprom_read (E3P_MIG_ADDR, E3P_MIG_STATE, (uchar *)val, E3P_MIG_SIZE) != 0) {
if (verbose) printf("read migration state failed\n");
return -1;
}
for (i = 0;i < E3P_MIG_SIZE;i++) {
for (n = 0;n < E3P_MIG_SIZE;n++) {
if (val[i] == val[n]) {
ctr[i]++;
}
}
}
for (i = 0;i < E3P_MIG_SIZE;i++) {
if (ctr[i] >= E3P_MIG_MIN) {
state = val[i];
break;
}
}
if (verbose) {
if (state >= 0) {
printf("read migration state 0x%.2X (", state);
if ((state & ~MIG_STATE_BACKWARD) == MIG_STATE_BBT) {
printf("%sBBT", ((state & MIG_STATE_BACKWARD) != 0) ? "!" : "");
} else
if ((state & ~MIG_STATE_BACKWARD) == MIG_STATE_UBOOT) {
printf("%sUBOOT", ((state & MIG_STATE_BACKWARD) != 0) ? "!" : "");
} else
if (MIG_STATE_T(state & ~MIG_STATE_BACKWARD) == MIG_STATE_BACKUP) {
printf("%sBACKUP[%d]", ((state & MIG_STATE_BACKWARD) != 0) ? "!" : "", MIG_STATE_N(state));
} else
if (MIG_STATE_T(state & ~MIG_STATE_BACKWARD) == MIG_STATE_RESTORE) {
printf("%sRESTORE[%d]", ((state & MIG_STATE_BACKWARD) != 0) ? "!" : "", MIG_STATE_N(state));
} else
if (MIG_STATE_T(state) == MIG_STATE_ERROR) {
printf("ERROR[%d]", MIG_STATE_N(state));
} else
if (state == MIG_STATE_DONE) {
printf("DONE");
} else {
printf("?");
}
printf(")\n");
} else {
printf("read migration state invalid\n");
}
if (ctr[0] != E3P_MIG_SIZE) {
printf(" ");
for (i = 0;i < E3P_MIG_SIZE;i++) {
printf(" %.2X", val[i]);
}
printf("\n");
}
}
return state;
}
static int set_mig_state(unsigned char state, int verbose)
{
int i = 0;
eeprom_init ();
for (i = 0; i < E3P_MIG_SIZE; i++) {
if (eeprom_write (E3P_MIG_ADDR, E3P_MIG_STATE + i, (uchar *)&state, 1) != 0) {
if (verbose) printf("write migration state[%d] 0x%.2X failed\n", i, state);
return -1;
}
}
if (verbose) printf("write migration state 0x%.2X successful\n", state);
return 0;
}
static int check_kernel_header (long addr, int verbose)
{
image_header_t *hdr;
int support_ecc_bch = -1;
hdr = (image_header_t *)addr;
if (!hdr) {
printf("header: invalid address\n");
return -1;
}
if (!image_check_magic (hdr)) {
printf("header: invalid magic\n");
return -1;
}
if (!image_check_hcrc (hdr)) {
printf("header: invalid header crc\n");
return -1;
}
if (!image_check_dcrc (hdr)) {
printf("header: invalid data crc\n");
return -1;
}
if (!image_check_target_arch (hdr)) {
printf("header: invalid arch\n");
return -1;
}
if (!image_check_type (hdr, IH_TYPE_KERNEL)) {
printf("header: invalid type\n");
return -1;
}
if (!image_check_os (hdr, IH_OS_LINUX)) {
printf("header: invalid os\n");
return -1;
}
if (image_get_comp (hdr) != IH_COMP_GZIP) {
printf("header: invalid comp\n");
return -1;
}
if (verbose) printf("header:\n");
if (strstr(image_get_name (hdr), "[BCH]") != NULL) {
if (verbose) printf("- mode: kernel 4-bit ECC [BCH] support\n");
support_ecc_bch = 1;
} else {
if (verbose) printf("- mode: kernel only 1-bit ECC: support\n");
support_ecc_bch = 0;
}
if (verbose) printf("- name: <%s>\n", image_get_name (hdr));
if (verbose) printf("- size: <%d>\n", image_get_data_size (hdr));
if (verbose) printf("- data: <0x%.8X>\n", (unsigned int)image_get_data (hdr));
return support_ecc_bch;
}
static int tpl_exists(int verbose) {
int rc = -1;
int i = 0;
int res = -1;
int old_ecc_mode = 0;
ulong offs = 0x00000000;
int tpl = 0x00001000;
size_t size = 0x00020000;
unsigned char *addr = NULL;
nand_info_t *nand = &nand_info[0];
addr = (unsigned char *)malloc(size);
if (addr != NULL) {
old_ecc_mode = nand_get_bch_ecc_mode();
nand_set_bch_ecc_mode(0);
res = nand_read_skip_bad(nand, offs, &size, (u_char *)addr);
nand_set_bch_ecc_mode(old_ecc_mode);
if (res == 0) {
for (i = tpl; i < size; i++) {
if (addr[i] != 0xFF) {
rc = 0;
break;
}
}
if (rc == 0) {
if (verbose) printf("tlp found not empty data 0x%.2X at 0x%.8X\n", addr[i], i);
} else {
if (verbose) printf("tlp not found\n");
}
} else {
if (verbose) printf("tlp read block failed\n");
}
} else {
if (verbose) printf("tlp alloc block failed\n");
}
if (addr != NULL) {
free(addr);
addr = NULL;
}
return rc;
}
static int get_mig_info(int verbose)
{
const BD_PartitionEntry64* pEntry = NULL;
int rc = 0;
int offset = 0;
int size = 0;
int i = 0;
/* init */
memset(&infoMigration, 0, sizeof(infoMigration));
/* backup (inactive root partition) */
pEntry = settings_getPartitionEntryByName("root0");
if ((pEntry->flags & BD_Partition_Flags_Active) == BD_Partition_Flags_Active) {
pEntry = settings_getPartitionEntryByName("root1");
if ((pEntry->flags & BD_Partition_Flags_Active) == BD_Partition_Flags_Active) {
pEntry = NULL;
}
}
if (pEntry != NULL) {
infoMigration.backup.offset=pEntry->offset;
infoMigration.backup.size=pEntry->size;
} else {
rc = -1;
}
/* bbt */
pEntry = settings_getPartitionEntryByName("bbt");
if (pEntry != NULL) {
infoMigration.bbt.offset=pEntry->offset;
infoMigration.bbt.size=pEntry->size;
} else {
rc = -1;
}
/* uboot */
pEntry = settings_getPartitionEntryByName("boot");
if (pEntry != NULL) {
if ((pEntry->offset == 0) && (pEntry->size > 0x00020000)) {
infoMigration.uboot.offset=pEntry->offset + 0x00020000;
infoMigration.uboot.size=pEntry->size - 0x00020000;
} else {
rc = -1;
}
} else {
rc = -1;
}
/* migration table (active root and data partition) */
pEntry = settings_getPartitionEntryByName("root0");
if ((pEntry->flags & BD_Partition_Flags_Active) != BD_Partition_Flags_Active) {
pEntry = settings_getPartitionEntryByName("root1");
if ((pEntry->flags & BD_Partition_Flags_Active) != BD_Partition_Flags_Active) {
pEntry = NULL;
}
}
if (pEntry != NULL) {
offset = pEntry->offset;
while (offset < (pEntry->offset + pEntry->size)) {
size = infoMigration.backup.size / 2;
if ((offset + size) > (pEntry->offset + pEntry->size)) {
size = (pEntry->offset + pEntry->size) - offset;
}
infoMigration.table[infoMigration.num].offset = offset;
infoMigration.table[infoMigration.num].size = size;
infoMigration.num++;
offset += size;
}
} else {
rc = -1;
}
pEntry = settings_getPartitionEntryByName("data");
if (pEntry != NULL) {
offset = pEntry->offset;
while (offset < (pEntry->offset + pEntry->size)) {
size = infoMigration.backup.size / 2;
if ((offset + size) > (pEntry->offset + pEntry->size)) {
size = (pEntry->offset + pEntry->size) - offset;
}
infoMigration.table[infoMigration.num].offset = offset;
infoMigration.table[infoMigration.num].size = size;
infoMigration.num++;
offset += size;
}
} else {
rc = -1;
}
if (verbose) {
printf("info:\n");
printf("- backup 0x%.8X 0x%.8X\n", infoMigration.backup.offset, infoMigration.backup.size);
printf("- bbt 0x%.8X 0x%.8X\n", infoMigration.bbt.offset, infoMigration.bbt.size);
printf("- uboot 0x%.8X 0x%.8X\n", infoMigration.uboot.offset, infoMigration.uboot.size);
for (i = 0; i < infoMigration.num; i++) {
printf("- mig[%d] 0x%.8X 0x%.8X\n", i, infoMigration.table[i].offset, infoMigration.table[i].size);
}
}
return rc;
}
static int size_nand(unsigned int addr, int end) {
nand_info_t *nand = &nand_info[0];
int len = 0;
int size_block_oob = (nand->erasesize / nand->writesize) * (nand->writesize + nand->oobsize);
while (addr < end) {
if (!nand_block_isbad (nand, (ulong)addr)) {
len += size_block_oob;
}
addr += nand->erasesize;
}
return len;
}
static int read_nand(unsigned int *addr, unsigned int end, unsigned char *ptr, int size, int bch) {
int old_ecc_mode = 0;
struct mtd_oob_ops ops;
nand_info_t *nand = &nand_info[0];
int rc = 0;
int len = 0;
int n = 0;
int i = 0;
int size_block_oob = (nand->erasesize / nand->writesize) * (nand->writesize + nand->oobsize);
printf(" R 0x%.8X(0x%.8X) ", (*addr), end - (*addr));
old_ecc_mode = nand_get_bch_ecc_mode();
if (old_ecc_mode != bch) { nand_set_bch_ecc_mode(bch); }
while (((*addr) < end) && ((len + size_block_oob) <= size)) {
WATCHDOG_RESET();
if (!nand_block_isbad (nand, (ulong)(*addr))) {
for (n = 0; n < (nand->erasesize / nand->writesize); n++) {
memset(&ops, 0, sizeof(ops));
memset(ptr, 0xFF, nand->writesize + nand->oobsize);
ops.datbuf = (u_char *)(ptr+0);
ops.oobbuf = (u_char *)(ptr+nand->writesize);
ops.len = nand->writesize;
ops.ooblen = YAFFS_OOB_SIZE;
ops.ooboffs = 0;
ops.mode = MTD_OOB_AUTO;
rc = nand->read_oob(nand, (*addr), &ops);
if (rc && rc != -EUCLEAN) {
printf("nand read fatal error %d at 0x%.8X\n", rc, (*addr));
}
ptr += nand->writesize + nand->oobsize;
len += nand->writesize + nand->oobsize;
(*addr) += nand->writesize;
}
} else {
printf("\b 0x%.8X ", (*addr));
(*addr) += nand->erasesize;
}
if (!(i & 0x7)) {
do_setled(0, ALL_LEDS); /* All LEDs off */
do_setled(1, (1 << ((i >> 3) % LED_COUNT))); /* One LED green */
}
printf("\b%c", PROGRESS_CHARS[(i++) % 4]);
}
if (old_ecc_mode != bch) { nand_set_bch_ecc_mode(old_ecc_mode); }
printf("\b > 0x%.8X(0x%.8X)\n", (*addr), len);
return len;
}
static int write_nand(unsigned int *addr, unsigned int end, unsigned char *ptr, int size, int bch) {
int old_ecc_mode = 0;
struct mtd_oob_ops ops;
nand_info_t *nand = &nand_info[0];
int rc = 0;
int len = 0;
int n = 0;
int i = 0;
int size_block_oob = (nand->erasesize / nand->writesize) * (nand->writesize + nand->oobsize);
printf(" W 0x%.8X(0x%.8X) ", (*addr), end - (*addr));
old_ecc_mode = nand_get_bch_ecc_mode();
if (old_ecc_mode != bch) { nand_set_bch_ecc_mode(bch); }
while (((*addr) < end) && ((len + size_block_oob) <= size)) {
WATCHDOG_RESET();
if (!nand_block_isbad (nand, (ulong)(*addr))) {
for (n = 0; n < (nand->erasesize / nand->writesize); n++) {
memset(&ops, 0, sizeof(ops));
ops.datbuf = (u_char *)(ptr+0);
ops.oobbuf = (u_char *)(ptr+nand->writesize);
ops.len = nand->writesize;
ops.ooblen = nand->oobsize;
ops.ooboffs = 0;
ops.mode = MTD_OOB_AUTO;
rc = nand->write_oob(nand, (*addr), &ops);
if (rc && rc != -EUCLEAN) {
printf("nand write fatal error %d at 0x%.8X\n", rc, (*addr));
}
ptr += nand->writesize + nand->oobsize;
len += nand->writesize + nand->oobsize;
(*addr) += nand->writesize;
}
} else {
printf("\b 0x%.8X ", (*addr));
(*addr) += nand->erasesize;
}
if (!(i & 0x7)) {
do_setled(0, ALL_LEDS); /* All LEDs off */
do_setled(1, (1 << ((i >> 3) % LED_COUNT))); /* One LED green */
}
printf("\b%c", PROGRESS_CHARS[(i++) % 4]);
}
if (old_ecc_mode != bch) { nand_set_bch_ecc_mode(old_ecc_mode); }
printf("\b > 0x%.8X(0x%.8X)\n", (*addr), len);
return len;
}
static int erase_nand(unsigned int addr, int size) {
nand_erase_options_t opts;
nand_info_t *nand = &nand_info[0];
int rc = 0;
int len = 0;
int i = 0;
printf(" E 0x%.8X(0x%.8X) ", addr, size);
while (len < size) {
WATCHDOG_RESET();
if (!nand_block_isbad (nand, (ulong)addr)) {
memset(&opts, 0, sizeof(opts));
opts.offset = addr;
opts.length = nand->erasesize;
opts.jffs2 = 0;
opts.quiet = 1;
rc = nand_erase_opts(nand, &opts);
if (rc != 0) {
printf("nand erase fatal error %d at 0x%.8X\n", rc, addr);
}
} else {
printf("\b 0x%.8X ", addr);
}
addr += nand->erasesize;
len += nand->erasesize;
if (!(i & 0x7)) {
do_setled(0, ALL_LEDS); /* All LEDs off */
do_setled(1, (1 << ((i >> 3) % LED_COUNT))); /* One LED green */
}
printf("\b%c", PROGRESS_CHARS[(i++) % 4]);
}
printf("\b > 0x%.8X(0x%.8X)\n", addr, len);
return len;
}
static int migrate_setup(void)
{
#ifdef MIGRATION_MEMORY_MALLOC
infoMigration.size = 0x00100000; /* 1MB */
infoMigration.buffer = (unsigned char *)malloc(infoMigration.size);
if (infoMigration.buffer == NULL) {
infoMigration.size = 0;
return -1;
}
#else /* MIGRATION_MEMORY_MALLOC */
infoMigration.size = MIGRATION_MEMORY_SIZE;
infoMigration.buffer = (unsigned char *)MIGRATION_MEMORY_ADDR;
#endif /* MIGRATION_MEMORY_MALLOC */
/*memset(infoMigration.buffer, 0, infoMigration.size);*/
return 0;
}
static void migrate_cleanup(void)
{
#ifdef MIGRATION_MEMORY_MALLOC
if (infoMigration.buffer != NULL) {
free(infoMigration.buffer);
}
#endif /* MIGRATION_MEMORY_MALLOC */
infoMigration.buffer = NULL;
infoMigration.size = 0;
}
static int migrate_init(void)
{
if (get_mig_info(0) < 0) {
return -1;
}
if (migrate_setup() < 0) {
return -1;
}
do_setled(1, ALL_LEDS);
return 0;
}
static void migrate_error(int rc)
{
migrate_cleanup();
do_setled(2, ALL_LEDS);
}
static void migrate_done(void)
{
migrate_cleanup();
do_setled(0, ALL_LEDS);
}
static int migrate_bbt(int forward_migration)
{
printf("BBT\n");
nand_migrate_bbt(forward_migration);
printf("DONE\n");
return 0;
}
static int migrate_uboot(int forward_migration)
{
nand_info_t *nand = &nand_info[0];
int old_ecc_mode = nand_get_bch_ecc_mode();
int flash_address;
int flash_num_blocks;
int ram_address;
int res = -1;
int target_address;
int bad_blocks_before_current_uboot;
int bad_blocks_within_current_uboot;
int i;
size_t write_len;
struct erase_info einfo;
printf("UBOOT\n");
nand_set_bch_ecc_mode(forward_migration ? 0 : 1);
res = load_uboot(0, &flash_address, &flash_num_blocks, &ram_address, NULL);
if (res) {
printf(KERN_INFO "Could not find U-Boot to migrate. Skipping U-Boot migration!\n");
res = 0;
goto abort;
}
printf(KERN_INFO "Found U-Boot at 0x%x. Length is %d blocks.\n", flash_address, flash_num_blocks);
nand_set_bch_ecc_mode(forward_migration);
/* Erase everything in u-boot area except currently active u-boot */
i = 0;
do {
int addr = CONFIG_SYS_NAND_U_BOOT_OFFS + i*CONFIG_SYS_NAND_BLOCK_SIZE;
WATCHDOG_RESET();
if ((addr>=flash_address) && (addr<(flash_address + ((flash_num_blocks + bad_blocks_within_current_uboot) * CONFIG_SYS_NAND_BLOCK_SIZE))))
{
/* Do not erase old u-boot for now */
i++;
continue;
}
if (!nand_block_isbad(nand, addr)) {
memset(&einfo, 0, sizeof(einfo));
einfo.mtd = nand;
einfo.addr = (unsigned long)addr;
einfo.len = CONFIG_SYS_NAND_BLOCK_SIZE;
res = nand_erase_nand(nand, &einfo, 1);
if (res < 0) {
printf(KERN_ERR "Erasing U-boot area failed at 0x%x\n", addr);
}
}
i++;
} while (i*CONFIG_SYS_NAND_BLOCK_SIZE<CONFIG_SYS_NAND_U_BOOT_PARTITION_SIZE);
/* Calculate number of bad blocks before current u-boot */
bad_blocks_before_current_uboot = 0;
i = 0;
do {
if (nand_block_isbad(nand, CONFIG_SYS_NAND_U_BOOT_OFFS + i*CONFIG_SYS_NAND_BLOCK_SIZE)) {
bad_blocks_before_current_uboot++;
}
i++;
} while (CONFIG_SYS_NAND_U_BOOT_OFFS + i*CONFIG_SYS_NAND_BLOCK_SIZE < flash_address);
/* Calculate number of bad blocks within current u-boot */
bad_blocks_within_current_uboot = 0;
i = 0;
do {
if (nand_block_isbad(nand, flash_address + (i+bad_blocks_within_current_uboot)*CONFIG_SYS_NAND_BLOCK_SIZE)) {
bad_blocks_within_current_uboot++;
} else {
i++;
}
if (((flash_address-CONFIG_SYS_NAND_U_BOOT_OFFS)+(i+bad_blocks_within_current_uboot)*CONFIG_SYS_NAND_BLOCK_SIZE)>=CONFIG_SYS_NAND_U_BOOT_PARTITION_SIZE) {
printf(KERN_ERR "Invalid U-Boot image.\n");
goto abort;
}
} while (i<flash_num_blocks);
printf(KERN_INFO "Number of bad blocks before current U-Boot: %d\n", bad_blocks_before_current_uboot);
printf(KERN_INFO "Number of bad blocks within current U-Boot: %d\n", bad_blocks_within_current_uboot);
/* Find some space to copy the u-boot to */
if (flash_address > (CONFIG_SYS_NAND_U_BOOT_OFFS +
((flash_num_blocks + bad_blocks_before_current_uboot + 1 /* Add one in case an additional block gets bad during write */) * CONFIG_SYS_NAND_BLOCK_SIZE))) {
target_address = CONFIG_SYS_NAND_U_BOOT_OFFS;
} else {
int bad_blocks_after_current_uboot;
target_address = flash_address + ((flash_num_blocks + bad_blocks_within_current_uboot) * CONFIG_SYS_NAND_BLOCK_SIZE);
/* Check, if there are enough good blocks after current U-boot */
bad_blocks_after_current_uboot = 0;
i = 0;
do {
if (nand_block_isbad(nand, target_address + (i+bad_blocks_after_current_uboot)*CONFIG_SYS_NAND_BLOCK_SIZE)) {
bad_blocks_after_current_uboot++;
} else {
i++;
}
if (((target_address-CONFIG_SYS_NAND_U_BOOT_OFFS)+(i+bad_blocks_after_current_uboot)*CONFIG_SYS_NAND_BLOCK_SIZE)>=CONFIG_SYS_NAND_U_BOOT_PARTITION_SIZE) {
printf(KERN_ERR "No space to migrate U-Boot.\n");
goto abort;
}
} while (i<flash_num_blocks);
printf(KERN_INFO "Number of bad blocks within destination area for U-Boot migration: %d\n", bad_blocks_after_current_uboot);
}
printf(KERN_INFO "Migrating U-Boot to flash address 0x%x.\n", target_address);
/* Write new U-boot */
write_len = flash_num_blocks * CONFIG_SYS_NAND_BLOCK_SIZE;
WATCHDOG_RESET();
printf("Writing U-Boot from 0x%x (ram), length:%x\n", ram_address, write_len);
res = nand_write_skip_bad(nand, target_address, &write_len, (u_char*)ram_address);
if (res) {
printf(KERN_ERR "Error while writing new U-Boot.\n");
/* Fail in this case to prevent erasure of old working u-boot */
goto abort;
}
/* Erase old U-Boot */
i = 0;
do {
int addr = flash_address + i*CONFIG_SYS_NAND_BLOCK_SIZE;
WATCHDOG_RESET();
if (!nand_block_isbad(nand, addr)) {
memset(&einfo, 0, sizeof(einfo));
einfo.mtd = nand;
einfo.addr = (unsigned long)addr;
einfo.len = CONFIG_SYS_NAND_BLOCK_SIZE;
res = nand_erase_nand(nand, &einfo, 1);
if (res < 0) {
printf(KERN_ERR "Erasing U-boot area failed at 0x%x\n", addr);
}
}
i++;
} while (i<flash_num_blocks + bad_blocks_within_current_uboot);
res = 0;
abort:
nand_set_bch_ecc_mode(old_ecc_mode);
printf("DONE\n");
return res;
}
static int migrate_backup(int n, int forward_migration)
{
unsigned int taddr;
unsigned int baddr;
unsigned int tend;
unsigned int bend;
int rlen;
int wlen;
int size;
printf("BACKUP[%d]\n", n);
if ((n < 0) || ((n + 1) > MIG_STATE_BACKUP_RESTORE_NUM_STEPS)) {
printf("ABORTED\n");
return -1;
}
if (erase_nand(infoMigration.backup.offset, infoMigration.backup.size) < 0) {
printf("ABORTED\n");
return -1;
}
taddr = infoMigration.table[n].offset;
baddr = infoMigration.backup.offset;
tend = taddr + infoMigration.table[n].size;
bend = baddr + infoMigration.backup.size;
while (taddr < tend) {
size = size_nand(taddr, tend);
if (size == 0) break;
if (size > infoMigration.size) size = infoMigration.size;
rlen = read_nand(&taddr, tend, infoMigration.buffer, size, forward_migration ? 0 : 1);
if (rlen < 0) {
printf("ABORTED\n");
return -1;
}
wlen = write_nand(&baddr, bend, infoMigration.buffer, rlen, 1);
if (wlen < 0) {
printf("ABORTED\n");
return -1;
}
if (wlen != rlen) {
printf("ABORTED\n");
return -1;
}
}
printf("DONE\n");
return 0;
}
static int migrate_restore(int n, int forward_migration)
{
unsigned int taddr;
unsigned int baddr;
unsigned int tend;
unsigned int bend;
int rlen;
int wlen;
int size;
printf("RESTORE[%d]\n", n);
if ((n < 0) || ((n + 1) > MIG_STATE_BACKUP_RESTORE_NUM_STEPS)) {
printf("ABORTED\n");
return -1;
}
if (erase_nand(infoMigration.table[n].offset, infoMigration.table[n].size) < 0) {
printf("ABORTED\n");
return -1;
}
taddr = infoMigration.table[n].offset;
baddr = infoMigration.backup.offset;
tend = taddr + infoMigration.table[n].size;
bend = baddr + infoMigration.backup.size;
while (taddr < tend) {
size = size_nand(taddr, tend);
if (size == 0) break;
if (size > infoMigration.size) size = infoMigration.size;
rlen = read_nand(&baddr, bend, infoMigration.buffer, size, 1);
if (rlen < 0) {
printf("ABORTED\n");
return -1;
}
wlen = write_nand(&taddr, tend, infoMigration.buffer, rlen, forward_migration ? 1 : 0);
if (wlen < 0) {
printf("ABORTED\n");
return -1;
}
if (wlen != rlen) {
printf("ABORTED\n");
return -1;
}
}
if ((n + 1) < MIG_STATE_BACKUP_RESTORE_NUM_STEPS) {
printf("DONE (NEXT)\n");
return 1;
}
if (erase_nand(infoMigration.backup.offset, infoMigration.backup.size) < 0) {
printf("ABORTED\n");
return -1;
}
printf("DONE\n");
return 0;
}
static int migration(long addr, int forward_migration) {
int i, rc;
int state, next;
state = get_mig_state(0);
if (state < 0) {
return -1;
}
if (state == MIG_STATE_DONE) {
if (tpl_exists(0) < 0) {
return -1;
}
if (addr == 0) {
return -1;
}
if (forward_migration == 1) {
if (check_kernel_header(addr, 0) != 1) {
return -1;
}
} else if (forward_migration == 0) {
if (check_kernel_header(addr, 0) != 0) {
return -1;
}
} else {
return -1;
}
next = MIG_STATE_BBT;
if (forward_migration == 0) {
next |= MIG_STATE_BACKWARD;
}
} else {
next = state;
}
if (migrate_init() < 0) {
return -1;
}
for (i = 0;i < 0x100;i++) {
rc = 0;
state = next;
/*printf("migration set state 0x%.2X\n", state);*/
WATCHDOG_RESET();
set_mig_state(state, 0);
if ((state & ~MIG_STATE_BACKWARD) == MIG_STATE_BBT) {
rc = migrate_bbt(((state & MIG_STATE_BACKWARD) != 0) ? 0 : 1);
next = (state & MIG_STATE_BACKWARD) | MIG_STATE_UBOOT;
} else
if ((state & ~MIG_STATE_BACKWARD) == MIG_STATE_UBOOT) {
rc = migrate_uboot(((state & MIG_STATE_BACKWARD) != 0) ? 0 : 1);
next = (state & MIG_STATE_BACKWARD) | MIG_STATE_S(MIG_STATE_BACKUP, 0);
} else
if (MIG_STATE_T(state & ~MIG_STATE_BACKWARD) == MIG_STATE_BACKUP) {
rc = migrate_backup(MIG_STATE_N(state), ((state & MIG_STATE_BACKWARD) != 0) ? 0 : 1);
next = (state & MIG_STATE_BACKWARD) | MIG_STATE_S(MIG_STATE_RESTORE, MIG_STATE_N(state) + 0);
} else
if (MIG_STATE_T(state & ~MIG_STATE_BACKWARD) == MIG_STATE_RESTORE) {
rc = migrate_restore(MIG_STATE_N(state), ((state & MIG_STATE_BACKWARD) != 0) ? 0 : 1);
if (rc > 0) {
next = (state & MIG_STATE_BACKWARD) | MIG_STATE_S(MIG_STATE_BACKUP, MIG_STATE_N(state) + 1);
rc = 0;
} else {
next = MIG_STATE_DONE;
}
} else
if (state == MIG_STATE_DONE) {
migrate_done();
return 0;
} else {
/*printf("migration invalid state 0x%.2X\n", state);*/
migrate_error(rc);
return -1;
}
if (rc < 0) {
break;
}
}
set_mig_state(MIG_STATE_S(MIG_STATE_ERROR, 0), 0);
migrate_error(rc);
return -1;
}
/*******************************************************************/
/* mig - migration */
/*******************************************************************
*
* state:
* - bbt
* - uboot
* - backup rootfs and data partition
* - restore rootfs and data partition
*
*******************************************************************/
int do_mig (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
const char *cmd = NULL;
int forward_migration = 1;
if (argc > 1) {
cmd = argv[1];
}
if (cmd == NULL) {
printf("missing argument command\n\n");
} else if (strcmp(cmd, "update") == 0) {
if (argc > 2) {
if (strcmp("-r", argv[2])==0) {
forward_migration = 0;
argv++;
argc--;
}
}
if (argc > 2) {
return (migration(simple_strtoul(argv[2], NULL, 16), forward_migration) < 0) ? -1 : 0;
} else {
return (migration(0, -1) < 0) ? -1 : 0;
}
} else if (strcmp(cmd, "state") == 0) {
if (argc > 2) {
puts("Warning: "
"Use this command only for testing purposes "
"if you\n"
" "
"are sure of what you are doing!\n"
"\nReally change migration state? <y/N>\n");
if (getc() == 'y' && getc() == '\r') {
return (set_mig_state(simple_strtoul(argv[2], NULL, 16), 1) < 0) ? -1 : 0;
} else {
puts("aborted\n");
}
} else {
return (get_mig_state(1) < 0) ? -1 : 0;
}
} else if (strcmp(cmd, "info") == 0) {
return (get_mig_info(1) < 0) ? -1 : 0;
} else if (strcmp(cmd, "hdr") == 0) {
if (argc > 2) {
return (check_kernel_header(simple_strtoul(argv[2], NULL, 16), 1) != 1) ? -1 : 0;
} else {
printf("missing argument 'addr' address of loaded image header\n\n");
}
} else if (strcmp(cmd, "tpl") == 0) {
return (tpl_exists(1) < 0) ? -1 : 0;
} else {
printf("invalid argument command '%s'\n\n", cmd);
}
cmd_usage(cmdtp);
return -1;
}
U_BOOT_CMD(
mig, CONFIG_SYS_MAXARGS, 1, do_mig,
"MIG sub-system",
"update [-r] [addr]: start migration or continue at last state\n"
" - if state not equals finished (0xFF) continue migration\n"
" - check header of kernel image at memory address 'addr'\n"
" and check tpl exists at nand flash offset 0x0000'1000\n"
" before starting migration\n"
" -r: Migrate backwards (4-bit ECC to 1-bit ECC)\n"
"mig state [val]: get/set migration state\n"
"mig info: migration information\n"
"mig hdr addr: check kernel header\n"
"mig tpl: check tpl exists at nand flash offset 0x0000'1000"
);
#endif /* CONFIG_TPL */