1. add cvitek folders to u-boot-2021.10 2. add cv183x/cv182x part 3. add cv181x/cv180x part Change-Id: I6dc2e5ff509dbab16bd60bfb3fd61852da5e01f6
982 lines
30 KiB
C
982 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
#include <stdlib.h>
|
|
#include <common.h>
|
|
#include <config.h>
|
|
#include <command.h>
|
|
#include <fs.h>
|
|
#include <part.h>
|
|
#include <vsprintf.h>
|
|
#include <u-boot/md5.h>
|
|
#include <image-sparse.h>
|
|
#include <div64.h>
|
|
#include <linux/math64.h>
|
|
#include <linux/log2.h>
|
|
#include <u-boot/crc.h>
|
|
#include <time.h>
|
|
#include <rand.h>
|
|
#include <env.h>
|
|
|
|
#define cvi_assert(expr) do {\
|
|
if (!(expr)) {\
|
|
printf("%s:%d[%s]\n", __FILE__, __LINE__, __func__);\
|
|
while \
|
|
(1);\
|
|
} \
|
|
} while (0)
|
|
|
|
#include <cvsnfc.h>
|
|
|
|
//#define CVI_SD_UPDATE_DEBUG
|
|
|
|
#ifdef CVI_SD_UPDATE_DEBUG
|
|
#define DEBUG_WRITE 1
|
|
#define DEBUG_READ 1
|
|
#define DEBUG_ERASE 1
|
|
#define DEBUG_CMD_FLOW 1
|
|
#define DEBUG_DUMP_FIP 1
|
|
#else
|
|
#define DEBUG_WRITE 0
|
|
#define DEBUG_READ 0
|
|
#define DEBUG_ERASE 0
|
|
#define DEBUG_CMD_FLOW 0
|
|
#define DEBUG_DUMP_FIP 1
|
|
#endif
|
|
|
|
#define FORCE_ROM_USE_2K_PAGE
|
|
#define PLAT_BM_FIP_MAX_SIZE 0xA0000 // 640KB, Fixed, don't change unless you know it
|
|
#define PLAT_BM_FIP_MAX_CHECK_SIZE 0xA0000 // 640KB, Fixed, don't change unless you know it
|
|
|
|
//------------------------------------------------------------------------------
|
|
// data type definitions: typedef, struct or class
|
|
//------------------------------------------------------------------------------
|
|
|
|
//------------------------------------------------------------------------------
|
|
// constant definitions:
|
|
//------------------------------------------------------------------------------
|
|
#define FIP_IMAGE_HEAD "FIPH" /* FIP Image Header 1 */
|
|
#define FIP_IMAGE_BODY "FIPB" /* FIP Image body */
|
|
|
|
#define FIP_MAGIC_NUMBER "CVBL01\n\0"
|
|
|
|
#define SPI_NAND_VERSION (0x1823a001)
|
|
|
|
/* system vector related definitions */
|
|
#define SPI_NAND_BASE_DATA_BACKUP_COPY (2)
|
|
|
|
#define MAX_BLOCK_CNT (20)
|
|
/* system vector relocated definitions end here */
|
|
|
|
#define SPI_NAND_FIP_RSVD_BLOCK_COUNT MAX_BLOCK_CNT // Reserved blocks for FIP.
|
|
|
|
#define BACKUP_FIP_START_POSITION 9
|
|
|
|
#define BIT(nr) (1UL << (nr))
|
|
|
|
#define PTR_INC(base, offset) (void *)((uint8_t *)(base) + (offset))
|
|
#define GET_PG_IDX_IN_BLK(x, y) ((x) % (y))
|
|
#define GET_BLK_IDX(x, y) ((x) / (y))
|
|
|
|
#define FIP_STOP_RECOVERY 2
|
|
#define FIP_CHECKNUM_ERROR 1
|
|
#define FIP_HEADER_NO_ISSUE 0
|
|
#define FIP_PAGE_ERROR 1
|
|
#define FIP_PAGE_NO_ISSUE 0
|
|
#define FIP_NO_AVAILABLE_BLOCK 1
|
|
|
|
struct _spi_nand_info_t {
|
|
uint32_t version;
|
|
uint32_t id;
|
|
uint32_t page_size;
|
|
|
|
uint32_t spare_size;
|
|
uint32_t block_size;
|
|
uint32_t pages_per_block;
|
|
|
|
uint32_t fip_block_cnt;
|
|
uint8_t pages_per_block_shift;
|
|
uint8_t badblock_pos;
|
|
uint8_t dummy_data1[2];
|
|
uint32_t flags;
|
|
uint8_t ecc_en_feature_offset;
|
|
uint8_t ecc_en_mask;
|
|
uint8_t ecc_status_offset;
|
|
uint8_t ecc_status_mask;
|
|
uint8_t ecc_status_shift;
|
|
uint8_t ecc_status_uncorr_val;
|
|
uint8_t dummy_data2[2];
|
|
uint32_t erase_count; // erase count for sys base block
|
|
uint8_t sck_l;
|
|
uint8_t sck_h;
|
|
uint16_t max_freq;
|
|
uint32_t sample_param;
|
|
uint8_t xtal_switch;
|
|
uint8_t dummy_data3[71];
|
|
};
|
|
|
|
#define FIP_BK_TAG_SIZE 4
|
|
/* the header should start with FIH1, FIB1+sequence number */
|
|
struct block_header_t {
|
|
uint8_t tag[FIP_BK_TAG_SIZE];
|
|
uint32_t bk_cnt_or_seq; /* for first block, it is block count. Otherwise, it is sequence number */
|
|
uint32_t checknum; /* the check number to make sure all fip header and body are consistent */
|
|
uint32_t dummy;
|
|
};
|
|
|
|
struct _fip_param1_t {
|
|
uint64_t magic1;
|
|
uint32_t magic2;
|
|
uint32_t param_cksum;
|
|
struct _spi_nand_info_t nand_info;
|
|
};
|
|
|
|
struct _fip_info_t {
|
|
uint8_t bk_position[SPI_NAND_FIP_RSVD_BLOCK_COUNT / 2];
|
|
uint32_t checknum[SPI_NAND_FIP_RSVD_BLOCK_COUNT / 2];
|
|
uint8_t total_count;
|
|
uint16_t current_bk_mask;
|
|
};
|
|
|
|
struct _spi_nand_info_t *g_spi_nand_info;
|
|
struct _fip_param1_t *g_fip_param;
|
|
struct _spi_nand_info_t spinand_info;
|
|
static char spi_nand_defect_bk_list[(MAX_BLOCK_CNT / 8) + 1];
|
|
static char spi_nand_bk_usage_list[(MAX_BLOCK_CNT / 8) + 1];
|
|
|
|
static uint8_t spi_nand_g_param_init = 0xff;
|
|
uint8_t *pg_buf;
|
|
uint32_t g_virgin_start;
|
|
|
|
//int spi_nand_flush_vec(void);
|
|
//void spi_nand_dump_vec(void);
|
|
|
|
static inline u32 DESC_CONV(char *x)
|
|
{
|
|
return ((((((x[0] << 8) | x[1]) << 8) | x[2]) << 8) | x[3]);
|
|
}
|
|
|
|
static inline u32 CHECK_MASK_BIT(void *_mask, u32 bit)
|
|
{
|
|
u32 w = bit / 8;
|
|
u32 off = bit % 8;
|
|
|
|
return ((u8 *)_mask)[w] & (1 << off);
|
|
}
|
|
|
|
static inline void SET_MASK_BIT(void *_mask, u32 bit)
|
|
{
|
|
u32 byte = bit / 8;
|
|
u32 offset = bit % 8;
|
|
((u8 *)_mask)[byte] |= (1 << offset);
|
|
}
|
|
|
|
static void get_spi_nand_info(void)
|
|
{
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
|
|
spinand_info.version = SPI_NAND_VERSION;
|
|
spinand_info.id = host->nand_chip_info->id[0]
|
|
| host->nand_chip_info->id[1] << 8
|
|
| host->nand_chip_info->id[2] << 16;
|
|
spinand_info.page_size = host->nand_chip_info->pagesize;
|
|
|
|
spinand_info.spare_size = host->nand_chip_info->oobsize;
|
|
spinand_info.block_size = host->nand_chip_info->erasesize;
|
|
|
|
spinand_info.pages_per_block = (host->nand_chip_info->erasesize /
|
|
host->nand_chip_info->pagesize);
|
|
|
|
spinand_info.badblock_pos = host->nand_chip_info->badblock_pos;
|
|
|
|
spinand_info.fip_block_cnt = SPI_NAND_FIP_RSVD_BLOCK_COUNT;
|
|
|
|
spinand_info.pages_per_block_shift = ilog2(spinand_info.pages_per_block);
|
|
|
|
spinand_info.flags = host->nand_chip_info->flags;
|
|
|
|
if (DEBUG_WRITE) {
|
|
printf("NAND_DEBUG: %s NAND id=0x%x, page size=%d, page per block=%d, block_size=%d, fip_block_cnt=%d\n",
|
|
__func__,
|
|
spinand_info.id,
|
|
spinand_info.page_size,
|
|
spinand_info.pages_per_block,
|
|
spinand_info.block_size,
|
|
spinand_info.fip_block_cnt);
|
|
}
|
|
|
|
// TODO: get ecc info form nand_chip_info
|
|
spinand_info.ecc_en_feature_offset = host->nand_chip_info->ecc_en_feature_offset;
|
|
spinand_info.ecc_en_mask = host->nand_chip_info->ecc_en_mask;
|
|
spinand_info.ecc_status_offset = host->nand_chip_info->ecc_status_offset;
|
|
spinand_info.ecc_status_mask = host->nand_chip_info->ecc_status_mask;
|
|
spinand_info.ecc_status_shift = host->nand_chip_info->ecc_status_shift;
|
|
spinand_info.ecc_status_uncorr_val = host->nand_chip_info->ecc_status_uncorr_val;
|
|
spinand_info.sck_l = host->nand_chip_info->sck_l;
|
|
spinand_info.sck_h = host->nand_chip_info->sck_h;
|
|
spinand_info.max_freq = host->nand_chip_info->max_freq;
|
|
spinand_info.sample_param = host->nand_chip_info->sample_param;
|
|
spinand_info.xtal_switch = host->nand_chip_info->xtal_switch;
|
|
|
|
if (DEBUG_WRITE) {
|
|
printf("NAND_DEBUG: %s, ecc_en_feature_offset = 0x%x, ecc_status_offset = 0x%x\n", __func__,
|
|
spinand_info.ecc_en_feature_offset, spinand_info.ecc_status_offset);
|
|
|
|
printf("NAND_DEBUG: %s, sck_l = 0x%x, sck_h = 0x%x, max_freq = %d, sample_param=0x%x\n", __func__,
|
|
spinand_info.sck_l, spinand_info.sck_h, spinand_info.max_freq, spinand_info.sample_param);
|
|
|
|
printf("NAND_DEBUG: %s, xtal_switch = %d\n", __func__, spinand_info.xtal_switch);
|
|
}
|
|
}
|
|
|
|
static void spi_nand_global_param_init(void)
|
|
{
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
|
|
if (spi_nand_g_param_init == 1) /* global parameters is initialized */
|
|
return;
|
|
|
|
g_fip_param = (struct _fip_param1_t *)malloc(sizeof(struct _fip_param1_t));
|
|
memset(g_fip_param, 0, sizeof(struct _fip_param1_t));
|
|
|
|
g_spi_nand_info = &g_fip_param->nand_info;
|
|
|
|
get_spi_nand_info();
|
|
|
|
pg_buf = (uint8_t *)malloc(info->page_size + info->spare_size);
|
|
|
|
spi_nand_g_param_init = 1;
|
|
|
|
}
|
|
|
|
/*
|
|
* Erase all block and scan defect block
|
|
*/
|
|
static int spi_nand_scan_defect(void)
|
|
{
|
|
int status;
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
|
|
u32 ttl_pg_sz = info->page_size + info->spare_size;
|
|
|
|
printf("Scan and erase first %d blocks\n", info->fip_block_cnt);
|
|
// printf("%s info =%p, page_size %d, block size=%d, spare_size=%d\n", __func__, info,
|
|
// info->page_size, info->block_size, info->spare_size);
|
|
|
|
memset(spi_nand_defect_bk_list, 0, (MAX_BLOCK_CNT / 8) + 1);
|
|
memset(spi_nand_bk_usage_list, 0, (MAX_BLOCK_CNT / 8) + 1);
|
|
|
|
for (u32 blk_id = 0; blk_id < info->fip_block_cnt; blk_id++) {
|
|
uint32_t pg = blk_id << info->pages_per_block_shift;
|
|
|
|
host->addr_value[1] = pg;
|
|
cvsnfc_send_cmd_erase(host);
|
|
|
|
memset(pg_buf, 0xff, ttl_pg_sz);
|
|
|
|
status = cvsnfc_read_page_raw(host->mtd, host->chip, pg_buf, 0, pg);
|
|
|
|
if (status) {
|
|
printf("read status %d, ", status);
|
|
cvi_assert(0);
|
|
}
|
|
|
|
u8 *mark = (u8 *)pg_buf;
|
|
/* Should we check spare data at 800H of this page instead of byte 0 */
|
|
//printf("NAND_DEBUG: block[%d] spare byte[0] at 0x800 = %x\n", blk_id, *(mark + info->page_size));
|
|
|
|
if ((*mark != 0xff) || (*(mark + info->page_size) != 0xff)) {
|
|
printf("\n\nFound bad block %d, ", blk_id);
|
|
//printf("bad ");
|
|
//printf(" mark : 0x%x\n\n", *mark);
|
|
//bbt_dump_buf("data:", pg_buf, 16);
|
|
SET_MASK_BIT(spi_nand_defect_bk_list, blk_id);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int spi_nand_blk_allocator(int fip_idx)
|
|
{
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
int blk_idx;
|
|
u8 start_blk = 0;
|
|
|
|
if (fip_idx == 1) {
|
|
start_blk = BACKUP_FIP_START_POSITION;
|
|
|
|
for (blk_idx = start_blk; blk_idx < info->fip_block_cnt; blk_idx++)
|
|
if ((CHECK_MASK_BIT(spi_nand_defect_bk_list, blk_idx) == 0) &&
|
|
(CHECK_MASK_BIT(spi_nand_bk_usage_list, blk_idx) == 0))
|
|
break;
|
|
if (blk_idx >= info->fip_block_cnt) {
|
|
printf("no available block can be allocated for fip[1]\n");
|
|
return -FIP_NO_AVAILABLE_BLOCK;
|
|
}
|
|
} else {
|
|
for (blk_idx = start_blk; blk_idx < BACKUP_FIP_START_POSITION; blk_idx++)
|
|
if ((CHECK_MASK_BIT(spi_nand_defect_bk_list, blk_idx) == 0) &&
|
|
(CHECK_MASK_BIT(spi_nand_bk_usage_list, blk_idx) == 0))
|
|
break;
|
|
if (blk_idx >= BACKUP_FIP_START_POSITION) {
|
|
printf("no available block can be allocated for fip[0]\n");
|
|
return -FIP_NO_AVAILABLE_BLOCK;
|
|
}
|
|
}
|
|
|
|
SET_MASK_BIT(spi_nand_bk_usage_list, blk_idx);
|
|
|
|
return blk_idx;
|
|
}
|
|
|
|
int check_fip_checknum(struct _fip_info_t *fip_info)
|
|
{
|
|
int i;
|
|
|
|
if (fip_info->total_count != 0xff) {
|
|
for (i = 1; i < fip_info->total_count; i++) {
|
|
if (fip_info->checknum[0] == fip_info->checknum[i])
|
|
continue; /* checknum is consistent with fip header */
|
|
else
|
|
return 1; /* checknum is not consistent, it means fip is corrupted */
|
|
}
|
|
return 0; /* all checknums are consistence */
|
|
}
|
|
|
|
return 1; /* no valid total_count, it means fip is corrupted */
|
|
}
|
|
|
|
int fip_reallocate_and_recovery(struct _fip_info_t *fip, int fip_idx, int bk_idx)
|
|
{
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
int status;
|
|
void *temp_buf;
|
|
uint8_t bk_shift;
|
|
int bk_page_id;
|
|
int target_page_id;
|
|
int blk_id;
|
|
|
|
bk_shift = info->pages_per_block_shift;
|
|
|
|
temp_buf = (uint8_t *)malloc(info->page_size);
|
|
|
|
/* erase original block first */
|
|
host->addr_value[1] = (fip + fip_idx)->bk_position[bk_idx] << bk_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
|
|
retry:
|
|
blk_id = spi_nand_blk_allocator(fip_idx);
|
|
|
|
if (blk_id < 0)
|
|
return -1;
|
|
|
|
(fip + fip_idx)->bk_position[bk_idx] = blk_id;
|
|
|
|
for (int pg_idx = 0; pg_idx < info->pages_per_block; pg_idx++) {
|
|
if (fip_idx == 0) {
|
|
bk_page_id = ((fip + 1)->bk_position[bk_idx] << info->pages_per_block_shift) + pg_idx;
|
|
target_page_id = (fip->bk_position[bk_idx] << info->pages_per_block_shift) + pg_idx;
|
|
} else {
|
|
bk_page_id = (fip->bk_position[bk_idx] << info->pages_per_block_shift) + pg_idx;
|
|
target_page_id = ((fip + 1)->bk_position[bk_idx] << info->pages_per_block_shift) + pg_idx;
|
|
}
|
|
|
|
status = cvsnfc_read_page(host->mtd, host->chip, (void *)pg_buf,
|
|
info->page_size, bk_page_id);
|
|
|
|
if (status < 0) {
|
|
printf("%s, ECC UNCORR detect on backup bk %d page %d, stop recovery\n", __func__,
|
|
(fip + fip_idx)->bk_position[bk_idx], pg_idx);
|
|
goto retry;
|
|
}
|
|
|
|
cvsnfc_write_page(host->mtd, host->chip, (void *)pg_buf, 0,
|
|
target_page_id);
|
|
|
|
status = cvsnfc_read_page(host->mtd, host->chip, (void *)temp_buf, info->page_size,
|
|
target_page_id);
|
|
|
|
if (status < 0) {
|
|
printf("%s, ECC UNCORR detect on bk %d page %d after recovery\n", __func__,
|
|
(fip + fip_idx)->bk_position[bk_idx], pg_idx);
|
|
}
|
|
|
|
if (memcmp(temp_buf, pg_buf, info->page_size)) {
|
|
printf("%s, fip 0 read back compare error at bk %d!\n", __func__,
|
|
(fip + fip_idx)->bk_position[bk_idx]);
|
|
}
|
|
|
|
}
|
|
|
|
free(temp_buf);
|
|
return 0;
|
|
}
|
|
|
|
int fip_block_recover(struct _fip_info_t *fip, int bk_idx, int backup_fip, int target_fip)
|
|
{
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
int status;
|
|
void *temp_buf;
|
|
int backup_page_id;
|
|
int target_page_id;
|
|
|
|
temp_buf = (uint8_t *)malloc(info->page_size);
|
|
for (int pg_idx = 0; pg_idx < info->pages_per_block; pg_idx++) {
|
|
|
|
backup_page_id = ((fip + backup_fip)->bk_position[bk_idx] << info->pages_per_block_shift) + pg_idx;
|
|
target_page_id = ((fip + target_fip)->bk_position[bk_idx] << info->pages_per_block_shift) + pg_idx;
|
|
|
|
status = cvsnfc_read_page(host->mtd, host->chip, (void *)pg_buf,
|
|
info->page_size, backup_page_id);
|
|
|
|
if (status < 0) {
|
|
printf("%s, ECC UNCORR detect on backup bk %d page %d, stop recovery\n",
|
|
__func__, (fip + backup_fip)->bk_position[bk_idx], pg_idx);
|
|
goto recover_fail;
|
|
}
|
|
|
|
cvsnfc_write_page(host->mtd, host->chip, (void *)pg_buf, 0,
|
|
target_page_id);
|
|
|
|
status = cvsnfc_read_page(host->mtd, host->chip, (void *)temp_buf, info->page_size,
|
|
target_page_id);
|
|
|
|
if (status < 0) {
|
|
printf("%s, ECC UNCORR detect on bk %d page %d after recovery\n",
|
|
__func__, (fip + target_fip)->bk_position[bk_idx], pg_idx);
|
|
if (fip_reallocate_and_recovery(fip, target_fip, bk_idx) < 0)
|
|
goto recover_fail;
|
|
}
|
|
|
|
if (memcmp(temp_buf, pg_buf, info->page_size)) {
|
|
printf("%s, fip 0 read back compare error at bk %d!\n", __func__,
|
|
fip->bk_position[bk_idx]);
|
|
if (fip_reallocate_and_recovery(fip, target_fip, bk_idx) < 0)
|
|
goto recover_fail;
|
|
}
|
|
}
|
|
|
|
free(temp_buf);
|
|
return FIP_PAGE_NO_ISSUE;
|
|
|
|
recover_fail:
|
|
free(temp_buf);
|
|
return FIP_STOP_RECOVERY;
|
|
}
|
|
|
|
int fip_check_checknum_and_recovery(struct _fip_info_t *fip)
|
|
{
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
int status;
|
|
int bk_id;
|
|
|
|
if (check_fip_checknum(fip) && !check_fip_checknum(fip + 1)) {
|
|
/* checknums within fip_info[0] are not consistnece, but fip_info[1] is OK. recovery fip0 from fip1 */
|
|
if (fip->current_bk_mask != (fip + 1)->current_bk_mask) {
|
|
for (int i = 0; i < (fip + 1)->total_count; i++) {
|
|
if (!((fip->current_bk_mask >> i) & 0x1)) {
|
|
printf("fip[0] bk idx %d is corrupted, recover from fip[1]\n", i);
|
|
|
|
bk_id = spi_nand_blk_allocator(0);
|
|
fip->bk_position[i] = bk_id;
|
|
host->addr_value[1] = fip->bk_position[i] << info->pages_per_block_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
status = fip_block_recover(fip, i, 1, 0);
|
|
if (status)
|
|
return status;
|
|
}
|
|
}
|
|
|
|
fip->total_count = (fip + 1)->total_count;
|
|
fip->current_bk_mask = (fip + 1)->current_bk_mask;
|
|
} else {
|
|
printf("Checknums of fip[0] are not consistence\n");
|
|
|
|
for (int i = 0; i < fip->total_count; i++) {
|
|
/* Erase original blocks first */
|
|
host->addr_value[1] = fip->bk_position[i] << info->pages_per_block_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
|
|
/* Read pages from backup fip then write to target pages */
|
|
|
|
status = fip_block_recover(fip, i, 1, 0);
|
|
if (status)
|
|
return status;
|
|
}
|
|
}
|
|
} else if (!check_fip_checknum(fip) && check_fip_checknum(fip + 1)) {
|
|
/* checknums within fip_info[1] are not consistnece, but fip_info[0] is OK. recovery fip1 from fip0 */
|
|
if (fip->current_bk_mask != (fip + 1)->current_bk_mask) {
|
|
for (int i = 0; i < fip->total_count; i++) {
|
|
if (!(((fip + 1)->current_bk_mask >> i) & 0x1)) {
|
|
printf("fip[1] bk idx %d is corrupted, recover from fip[0]\n", i);
|
|
|
|
bk_id = spi_nand_blk_allocator(1);
|
|
(fip + 1)->bk_position[i] = bk_id;
|
|
host->addr_value[1] = (fip + 1)->bk_position[i] << info->pages_per_block_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
status = fip_block_recover(fip, i, 0, 1);
|
|
if (status)
|
|
return status;
|
|
}
|
|
}
|
|
(fip + 1)->total_count = fip->total_count;
|
|
(fip + 1)->current_bk_mask = fip->current_bk_mask;
|
|
} else {
|
|
printf("Checknums of fip[1] are not consistence\n");
|
|
|
|
for (int i = 0; i < (fip + 1)->total_count; i++) {
|
|
/* Erase original blocks first */
|
|
host->addr_value[1] = (fip + 1)->bk_position[i] << info->pages_per_block_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
|
|
/* Read pages from backup fip then write to target pages */
|
|
status = fip_block_recover(fip, i, 0, 1);
|
|
if (status)
|
|
return status;
|
|
}
|
|
}
|
|
} else if (check_fip_checknum(fip) && check_fip_checknum(fip + 1)) {
|
|
printf("Both fip[0] and fip[1] are corrupted, please do re-download again!!\n");
|
|
return FIP_STOP_RECOVERY;
|
|
} else if (fip->checknum[0] != (fip + 1)->checknum[0]) {
|
|
/* checknums of fip0 and fip1 are not the same, it means fip download is not complete*/
|
|
/* recovery fip0 from fip1 */
|
|
printf("Checknum of fip[0] is not the same with fip[1]\n");
|
|
for (int i = 0; i < (fip + 1)->total_count; i++) {
|
|
|
|
/* Erase original blocks first */
|
|
host->addr_value[1] = fip->bk_position[i] << info->pages_per_block_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
|
|
/* Read pages from backup fip then write to target pages */
|
|
status = fip_block_recover(fip, i, 1, 0);
|
|
if (status)
|
|
return status;
|
|
}
|
|
|
|
fip->total_count = (fip + 1)->total_count;
|
|
fip->current_bk_mask = (fip + 1)->current_bk_mask;
|
|
} else {
|
|
printf("All Header check PASS\n");
|
|
return FIP_HEADER_NO_ISSUE;
|
|
}
|
|
|
|
return FIP_CHECKNUM_ERROR;
|
|
}
|
|
|
|
int fip_check_page_and_recovery(struct _fip_info_t *fip)
|
|
{
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
int status;
|
|
uint8_t erase_and_recover = 0;
|
|
uint8_t fip_idx = 0;
|
|
uint8_t bk_shift;
|
|
|
|
bk_shift = info->pages_per_block_shift;
|
|
|
|
for (fip_idx = 0; fip_idx < SPI_NAND_BASE_DATA_BACKUP_COPY; fip_idx++) {
|
|
|
|
for (int i = 0; i < fip->total_count; i++) {
|
|
for (int pg_idx = 0; pg_idx < info->pages_per_block; pg_idx++) {
|
|
int page_id;
|
|
|
|
page_id = ((fip + fip_idx)->bk_position[i] << bk_shift) + pg_idx;
|
|
status = cvsnfc_read_page(host->mtd, host->chip, (void *)pg_buf,
|
|
info->page_size, page_id);
|
|
|
|
if (status < 0) {
|
|
printf("ECC UNCORR detect on bk %d page %d\n",
|
|
(fip + fip_idx)->bk_position[i], pg_idx);
|
|
erase_and_recover = 1;
|
|
break; /* need to erase a whole block and then re-write again */
|
|
}
|
|
}
|
|
|
|
if (erase_and_recover == 1) {
|
|
printf("Recover bk %d due to page error\n", (fip + fip_idx)->bk_position[i]);
|
|
host->addr_value[1] = (fip + fip_idx)->bk_position[i] << bk_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
if (fip_idx == 0)
|
|
status = fip_block_recover(fip, i, 1, 0);
|
|
else
|
|
status = fip_block_recover(fip, i, 0, 1);
|
|
|
|
if (status)
|
|
return status;
|
|
|
|
erase_and_recover = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("All Page check PASS\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function is entry point of normal bootup of u-boot
|
|
* check each block status and recovery it if ECC ERROR occur
|
|
*/
|
|
int check_and_update_fip_bin(void)
|
|
{
|
|
struct _spi_nand_info_t *info;
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
u32 blk_id = 0;
|
|
struct _fip_info_t fip_info[SPI_NAND_BASE_DATA_BACKUP_COPY];
|
|
struct block_header_t *bk_header;
|
|
u32 pg_sz, bk_sz;
|
|
int status;
|
|
u8 fip_header_count = 0;
|
|
u32 boot_src;
|
|
|
|
spi_nand_global_param_init();
|
|
|
|
info = &spinand_info;
|
|
|
|
pg_sz = info->page_size;
|
|
bk_sz = info->block_size;
|
|
|
|
memset(pg_buf, 0, sizeof(pg_sz));
|
|
memset(&fip_info, 0xff, sizeof(struct _fip_info_t) * SPI_NAND_BASE_DATA_BACKUP_COPY);
|
|
for (int i = 0; i < SPI_NAND_BASE_DATA_BACKUP_COPY; i++) {
|
|
fip_info[i].current_bk_mask = 0x0;
|
|
}
|
|
memset(spi_nand_defect_bk_list, 0, (MAX_BLOCK_CNT / 8) + 1);
|
|
memset(spi_nand_bk_usage_list, 0, (MAX_BLOCK_CNT / 8) + 1);
|
|
|
|
for (blk_id = 0; blk_id < SPI_NAND_FIP_RSVD_BLOCK_COUNT; blk_id++) {
|
|
|
|
status = cvsnfc_read_page(host->mtd, host->chip, (void *)pg_buf, pg_sz,
|
|
(blk_id << info->pages_per_block_shift) + 0);
|
|
/* Read first page of each block */
|
|
|
|
if (status < 0) {
|
|
printf("%s, find ECC UNCORR on bk %d\n", __func__, blk_id);
|
|
SET_MASK_BIT(spi_nand_defect_bk_list, blk_id);
|
|
continue;
|
|
}
|
|
|
|
bk_header = (struct block_header_t *)pg_buf;
|
|
|
|
if (!memcmp(bk_header->tag, FIP_IMAGE_HEAD, FIP_BK_TAG_SIZE)) {
|
|
fip_header_count++;
|
|
if (fip_header_count == 1 && blk_id >= BACKUP_FIP_START_POSITION)
|
|
printf("WARNING!! First fip header position exceed backup start position\n");
|
|
|
|
if (fip_header_count == 1)
|
|
spi_nand_adjust_max_freq(host, (u32 *)bk_header->tag, &bk_header->checknum);
|
|
|
|
if (blk_id < BACKUP_FIP_START_POSITION) {
|
|
if (fip_info[0].bk_position[0] == 0xff) {
|
|
fip_info[0].bk_position[0] = blk_id;
|
|
fip_info[0].checknum[0] = bk_header->checknum;
|
|
fip_info[0].total_count = bk_header->bk_cnt_or_seq;
|
|
fip_info[0].current_bk_mask |= 0x1 << 0;
|
|
SET_MASK_BIT(spi_nand_bk_usage_list, blk_id);
|
|
continue;
|
|
} else
|
|
printf("fip info[0] header is not initialized before\n");
|
|
} else {
|
|
if (fip_info[1].bk_position[0] == 0xff) {
|
|
fip_info[1].bk_position[0] = blk_id;
|
|
fip_info[1].checknum[0] = bk_header->checknum;
|
|
fip_info[1].total_count = bk_header->bk_cnt_or_seq;
|
|
fip_info[1].current_bk_mask |= 0x1 << 0;
|
|
SET_MASK_BIT(spi_nand_bk_usage_list, blk_id);
|
|
continue;
|
|
} else
|
|
printf("fip info[1] header is not initialized before\n");
|
|
}
|
|
if (fip_header_count > SPI_NAND_BASE_DATA_BACKUP_COPY)
|
|
printf("%s, WARNING!! find unexpected fip_header at bk %d\n", __func__, blk_id);
|
|
} else if (!memcmp(bk_header->tag, FIP_IMAGE_BODY, FIP_BK_TAG_SIZE)) {
|
|
if (blk_id < BACKUP_FIP_START_POSITION) {
|
|
if (fip_info[0].bk_position[bk_header->bk_cnt_or_seq] == 0xff) {
|
|
fip_info[0].bk_position[bk_header->bk_cnt_or_seq] = blk_id;
|
|
fip_info[0].checknum[bk_header->bk_cnt_or_seq] = bk_header->checknum;
|
|
fip_info[0].current_bk_mask |= 0x1 << bk_header->bk_cnt_or_seq;
|
|
SET_MASK_BIT(spi_nand_bk_usage_list, blk_id);
|
|
continue;
|
|
} else
|
|
printf("fip info[0] body %d is not initialized before\n",
|
|
bk_header->bk_cnt_or_seq);
|
|
} else {
|
|
if (fip_info[1].bk_position[bk_header->bk_cnt_or_seq] == 0xff) {
|
|
fip_info[1].bk_position[bk_header->bk_cnt_or_seq] = blk_id;
|
|
fip_info[1].checknum[bk_header->bk_cnt_or_seq] = bk_header->checknum;
|
|
fip_info[1].current_bk_mask |= 0x1 << bk_header->bk_cnt_or_seq;
|
|
SET_MASK_BIT(spi_nand_bk_usage_list, blk_id);
|
|
continue;
|
|
} else
|
|
printf("fip info[1] body %d is not initialized before\n",
|
|
bk_header->bk_cnt_or_seq);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (DEBUG_DUMP_FIP) {
|
|
for (int i = 0; i < SPI_NAND_BASE_DATA_BACKUP_COPY; i++) {
|
|
printf("fip_info[%d] total count=%d\n", i, fip_info[i].total_count);
|
|
|
|
for (int j = 0; j < (SPI_NAND_FIP_RSVD_BLOCK_COUNT / 2); j++)
|
|
printf("fip_info[%d].bk_position[%d]= 0x%x, fip_info[%d].checknum[%d] = 0x%x\n",
|
|
i, j, fip_info[i].bk_position[j], i, j, fip_info[i].checknum[j]);
|
|
printf("fip_info[%d].current mask=0x%x\n", i, fip_info[i].current_bk_mask);
|
|
}
|
|
|
|
printf("defect list = %02x%02x%02x\n", spi_nand_defect_bk_list[2],
|
|
spi_nand_defect_bk_list[1], spi_nand_defect_bk_list[0]);
|
|
printf("usage list = %02x%02x%02x\n", spi_nand_bk_usage_list[2],
|
|
spi_nand_bk_usage_list[1], spi_nand_bk_usage_list[0]);
|
|
}
|
|
|
|
boot_src = readl((unsigned int *)BOOT_SOURCE_FLAG_ADDR);
|
|
|
|
if (boot_src == MAGIC_NUM_SD_DL || boot_src == MAGIC_NUM_USB_DL)
|
|
return 0; /* No need to check and update spinand */
|
|
|
|
if (fip_header_count <= SPI_NAND_BASE_DATA_BACKUP_COPY) {
|
|
if (!fip_check_checknum_and_recovery(fip_info)) {
|
|
/* check page ecc and do revoery if necessary */
|
|
fip_check_page_and_recovery(fip_info);
|
|
}
|
|
// } else if (fip_header_count == 1) {
|
|
// printf("Only detect 1 fip header\n");
|
|
} else if (fip_header_count > SPI_NAND_BASE_DATA_BACKUP_COPY) {
|
|
printf("WARNING!! find unexpected fip_header\n");
|
|
} else {
|
|
printf("WARNING!! cannot find fip_header\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static uint32_t spi_nand_crc16_ccitt_with_tag(unsigned char *buf, int len)
|
|
{
|
|
uint32_t crc = 0;
|
|
|
|
crc = crc16_ccitt(0, buf, len);
|
|
crc |= 0xCAFE0000;
|
|
|
|
return crc;
|
|
}
|
|
|
|
static void spi_nand_check_size_error(u32 target_len, u32 curr_offset, u32 pg_size)
|
|
{
|
|
if ((target_len - curr_offset) >= pg_size)
|
|
printf("## WARNING 1 ## data size %d to be wrote is wrong!!\n", (target_len - curr_offset));
|
|
}
|
|
|
|
static int spi_nand_flush_fip_bin(void *buf)
|
|
{
|
|
int status = 0;
|
|
void *buffer = (void *)buf;
|
|
void *src_buf_addr = 0;
|
|
struct cvsnfc_host *host = cvsnfc_get_host();
|
|
struct _spi_nand_info_t *info = &spinand_info;
|
|
u32 pg_sz, pg_per_blk, bk_sz;
|
|
u32 total_len, ttl_block_cnt_to_write, ttl_pg_cnt_to_write;
|
|
struct block_header_t bk_header;
|
|
u32 bk_overhead; /* used to calculate total block header size in page 0 of each block */
|
|
u32 blk_idx = 0;
|
|
u32 blk_id = 0;
|
|
u32 src_len;
|
|
uint8_t *temp_buf;
|
|
unsigned int checknum;
|
|
|
|
srand(get_timer(0));
|
|
checknum = rand();
|
|
|
|
printf("Generated checknum=0x%x\n", checknum);
|
|
|
|
pg_sz = info->page_size;
|
|
bk_sz = info->block_size;
|
|
src_len = env_get_ulong("filesize", 16, 0);
|
|
|
|
printf("fip size=%d bytes\n", src_len);
|
|
|
|
ttl_block_cnt_to_write = src_len / bk_sz;
|
|
if ((src_len % bk_sz) != 0)
|
|
ttl_block_cnt_to_write += 1;
|
|
|
|
bk_overhead = sizeof(struct block_header_t);
|
|
memset(&bk_header, 0, sizeof(struct block_header_t));
|
|
|
|
total_len = (src_len + (ttl_block_cnt_to_write * bk_overhead));
|
|
|
|
ttl_pg_cnt_to_write = total_len / pg_sz;
|
|
if (total_len % pg_sz != 0)
|
|
ttl_pg_cnt_to_write += 1; /* add 1 page to write remaining data */
|
|
|
|
ttl_block_cnt_to_write = total_len / bk_sz; /* re-calculate new block count */
|
|
if (total_len % bk_sz != 0)
|
|
ttl_block_cnt_to_write += 1;
|
|
|
|
if (DEBUG_WRITE)
|
|
printf("Write totol_len=%d, bk_overhead=%d, ttl_page_cnt_to_write=%d, ttl_block_cnt_to_write=%d\n",
|
|
total_len, bk_overhead, ttl_pg_cnt_to_write, ttl_block_cnt_to_write);
|
|
|
|
pg_per_blk = info->pages_per_block;
|
|
|
|
temp_buf = (uint8_t *)malloc(info->page_size);
|
|
|
|
for (u32 i = 0; i < SPI_NAND_BASE_DATA_BACKUP_COPY; i++) {
|
|
u32 offset_in_buf = 0;
|
|
uint8_t wrote_bk_cnt = 0;
|
|
|
|
printf("write %d copy of fip\n", i + 1);
|
|
|
|
for (u32 pg_idx_in_buf = 0; pg_idx_in_buf < ttl_pg_cnt_to_write; pg_idx_in_buf++) {
|
|
u32 pg_idx_in_blk;
|
|
|
|
uint8_t block_damage = 0;
|
|
|
|
pg_idx_in_blk = GET_PG_IDX_IN_BLK(pg_idx_in_buf, pg_per_blk);
|
|
blk_idx = GET_BLK_IDX(pg_idx_in_buf, pg_per_blk);
|
|
|
|
src_buf_addr = PTR_INC(buffer, offset_in_buf);
|
|
|
|
if (pg_idx_in_blk == 0) {
|
|
if (wrote_bk_cnt == 0) { /* Fill FIP image first block header */
|
|
struct _fip_param1_t *temp_fip_param;
|
|
uint32_t crc = 0;
|
|
int param_crc_size;
|
|
|
|
memcpy(bk_header.tag, FIP_IMAGE_HEAD, 4);
|
|
bk_header.bk_cnt_or_seq = (uint32_t)ttl_block_cnt_to_write;
|
|
bk_header.checknum = checknum;
|
|
memcpy(temp_buf, &bk_header, bk_overhead);
|
|
temp_fip_param = src_buf_addr;
|
|
|
|
memcpy(&temp_fip_param->nand_info, info, sizeof(struct _spi_nand_info_t));
|
|
param_crc_size = 0x800 - offsetof(struct _fip_param1_t, nand_info);
|
|
|
|
crc = spi_nand_crc16_ccitt_with_tag((unsigned char *)&temp_fip_param->nand_info
|
|
, param_crc_size);
|
|
//printf("%s, get crc=0x%08x, param_crc_size=%d\n", __func__,
|
|
// crc, param_crc_size);
|
|
temp_fip_param->param_cksum = crc;
|
|
memcpy((temp_buf + bk_overhead), src_buf_addr, (pg_sz - bk_overhead));
|
|
|
|
} else { /* Fill remaining FIP image body */
|
|
memcpy(bk_header.tag, FIP_IMAGE_BODY, 4);
|
|
bk_header.bk_cnt_or_seq = (uint32_t)wrote_bk_cnt;
|
|
bk_header.checknum = checknum;
|
|
memcpy(temp_buf, &bk_header, bk_overhead);
|
|
if (pg_idx_in_buf == (ttl_pg_cnt_to_write - 1)) { /* last page */
|
|
spi_nand_check_size_error(src_len, offset_in_buf, pg_sz);
|
|
memcpy((temp_buf + bk_overhead), src_buf_addr,
|
|
(src_len - offset_in_buf));
|
|
} else
|
|
memcpy((temp_buf + bk_overhead), src_buf_addr, (pg_sz - bk_overhead));
|
|
}
|
|
wrote_bk_cnt++;
|
|
offset_in_buf = offset_in_buf + (pg_sz - bk_overhead); /* Insert fip header in page 0 */
|
|
} else {
|
|
if (pg_idx_in_buf == (ttl_pg_cnt_to_write - 1)) { /* last page */
|
|
spi_nand_check_size_error(src_len, offset_in_buf, pg_sz);
|
|
memcpy(temp_buf, src_buf_addr, (src_len - offset_in_buf));
|
|
} else
|
|
memcpy(temp_buf, src_buf_addr, pg_sz);
|
|
|
|
offset_in_buf = offset_in_buf + pg_sz;
|
|
}
|
|
|
|
if (DEBUG_WRITE)
|
|
printf("flush fip.bin with next offset = 0x%x\n",
|
|
offset_in_buf);
|
|
retry:
|
|
if (pg_idx_in_blk == 0 || block_damage == 1) {
|
|
/* pg_idx_in_blk == 0 means need a new block */
|
|
/* damage == 1 means need to find next block*/
|
|
blk_id = spi_nand_blk_allocator(i);
|
|
|
|
if (DEBUG_WRITE)
|
|
printf("NAND_DEBUG: %s, allocate blk_id=%d for page %d (%x)\n",
|
|
__func__, blk_id, pg_idx_in_buf, pg_idx_in_buf);
|
|
|
|
if (blk_id == -1) {
|
|
cvi_assert(0);
|
|
return -1;
|
|
}
|
|
|
|
block_damage = 0;
|
|
}
|
|
|
|
/* should we supplyment remain data of last page if remaining data is less then 1 page ?*/
|
|
cvsnfc_write_page(host->mtd, host->chip, (void *)temp_buf, 0,
|
|
(blk_id << info->pages_per_block_shift) + pg_idx_in_blk);
|
|
/* read page again to check data consistent */
|
|
memset(pg_buf, 0, sizeof(pg_sz));
|
|
status = cvsnfc_read_page(host->mtd, host->chip, (void *)pg_buf, pg_sz,
|
|
(blk_id << info->pages_per_block_shift) + pg_idx_in_blk);
|
|
|
|
if (status < 0) {
|
|
printf("##WARNING## %s update failed at block %d page %d, please check...\n",
|
|
__func__, blk_id, pg_idx_in_blk);
|
|
//cvi_assert(0);
|
|
block_damage = 1;
|
|
host->addr_value[1] = blk_id << info->pages_per_block_shift;
|
|
cvsnfc_send_cmd_erase(host);
|
|
SET_MASK_BIT(spi_nand_defect_bk_list, blk_id);
|
|
goto retry;
|
|
|
|
} else if (status > 0) {
|
|
printf("NAND_DEBUG: Read ecc corr page idx %d, blk_id %d\n", pg_idx_in_blk, blk_id);
|
|
}
|
|
|
|
if (memcmp(temp_buf, pg_buf, pg_sz)) {
|
|
printf("fip read back compare error!\n");
|
|
bbt_dump_buf((void *)"temp_buf", temp_buf, pg_sz >> 4);
|
|
bbt_dump_buf((void *)"pg_buf", pg_buf, pg_sz >> 4);
|
|
cvi_assert(0);
|
|
}
|
|
}
|
|
}
|
|
|
|
free(temp_buf);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int spi_nand_fip_download(void *buf)
|
|
{
|
|
int status;
|
|
|
|
/* always erase fip partition first and then download re-assemble fip */
|
|
|
|
status = spi_nand_scan_defect();
|
|
if (status) {
|
|
printf("scan factory error\n");
|
|
return -1;
|
|
}
|
|
|
|
status = spi_nand_flush_fip_bin(buf);
|
|
if (status)
|
|
printf("flush fip error\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function is entry point of u-boot download process
|
|
*/
|
|
int do_cvi_update_spinand(uint32_t component, void *addr)
|
|
{
|
|
printf("%s with version 0x%x\n", __func__, SPI_NAND_VERSION);
|
|
|
|
if (DEBUG_WRITE)
|
|
bbt_dump_buf("source fip", addr, 0x40);
|
|
|
|
spi_nand_fip_download(addr);
|
|
|
|
return 0;
|
|
}
|
|
|