1020 lines
30 KiB
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 */
|