You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1102 lines
33 KiB
1102 lines
33 KiB
/*
|
|
* Copyright (c) Hisilicon Technologies Co., Ltd. 2020-2020. All rights reserved.
|
|
* Description: nand module driver
|
|
* Author: Hisilicon
|
|
* Create: 2020-10-15
|
|
*/
|
|
|
|
#define LARGEFILE64_SOURCE
|
|
#include "nand_raw.h"
|
|
|
|
#include <fcntl.h>
|
|
#include <sys/ioctl.h>
|
|
#include <unistd.h>
|
|
#include <limits.h>
|
|
|
|
#include "soc_log.h"
|
|
#include "uapi_flash.h"
|
|
#include "nand.h"
|
|
#include "flash_ext.h"
|
|
#include "securec.h"
|
|
|
|
#define NAND_PAGE_BUF_SIZE 16384
|
|
#define NAND_OOB_BUF_SIZE 1200
|
|
|
|
static struct nand_raw_ctrl *g_nand_raw_ctrl = NULL;
|
|
static td_s32 check_skip_badblock(struct mtd_partition *ptn, td_s32 *blockindex, td_s32 blocksize);
|
|
|
|
typedef struct {
|
|
td_u64 start;
|
|
td_u64 datalen;
|
|
td_u64 ooblen;
|
|
td_u8 *data_ptr;
|
|
td_u8 *oob_ptr;
|
|
} write_req_s;
|
|
|
|
static td_s32 nand_raw_probe(struct mtd_partition *ptn, td_char *buf, td_s32 buf_len, td_s32 *readonly)
|
|
{
|
|
td_s32 dev = -1;
|
|
td_char tmp_buf[PATH_MAX] = {0};
|
|
|
|
if (buf_len <= 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (realpath(buf, tmp_buf) == NULL) {
|
|
soc_print("Failed to realpath: %s.\n", buf);
|
|
return -1;
|
|
}
|
|
|
|
dev = open(tmp_buf, O_RDWR);
|
|
if (dev == -1) {
|
|
dev = open(tmp_buf, O_RDONLY);
|
|
if (dev == -1) {
|
|
ptn->perm = ACCESS_NONE;
|
|
return -1;
|
|
}
|
|
ptn->perm = ACCESS_RD;
|
|
*readonly = 1;
|
|
} else {
|
|
ptn->perm = ACCESS_RDWR;
|
|
}
|
|
return dev;
|
|
}
|
|
|
|
static td_s32 nand_raw_get_mtd_info(td_s32 dev, const struct mtd_info_user *mtdinfo)
|
|
{
|
|
if (ioctl(dev, MEMGETINFO, mtdinfo) != 0) {
|
|
soc_print("Can't get mtd information.\n");
|
|
return TD_FAILURE;
|
|
}
|
|
|
|
if ((mtdinfo->type != MTD_NANDFLASH) && (mtdinfo->type != MTD_MLCNANDFLASH)) {
|
|
return TD_FAILURE;
|
|
}
|
|
|
|
return TD_SUCCESS;
|
|
}
|
|
|
|
static uapi_flash_partinfo *nand_raw_get_part_info(td_s32 part)
|
|
{
|
|
td_char devname[32]; /* 32 - buffer size */
|
|
uapi_flash_partinfo *part_info = NULL;
|
|
|
|
memset_s(devname, sizeof(devname), 0, sizeof(devname));
|
|
if (snprintf_s(devname, sizeof(devname), sizeof(devname) - 1, "mtd%d", part) == -1) {
|
|
soc_print("Failed to snprintf_s.\n");
|
|
return NULL;
|
|
}
|
|
|
|
part_info = get_flash_partition_info(UAPI_FLASH_TYPE_NAND_0, devname, (td_u32)strlen(devname));
|
|
if (part_info == NULL) {
|
|
dbg_out("Can't get \"%s\" partition information.\n", devname);
|
|
return NULL;
|
|
}
|
|
return part_info;
|
|
}
|
|
|
|
static td_s32 nand_raw_init_part(td_s32 part, struct mtd_partition *ptn, struct mtd_info_user *mtdinfo)
|
|
{
|
|
td_s32 dev;
|
|
td_s32 readonly = ACCESS_NONE;
|
|
td_char buf[PATH_MAX];
|
|
uapi_flash_partinfo *part_info = NULL;
|
|
|
|
ptn->fd = INVALID_FD;
|
|
memset_s(buf, PATH_MAX, 0, PATH_MAX);
|
|
if (snprintf_s(buf, PATH_MAX, PATH_MAX - 1, DEV_MTDBASE"%d", part) == -1) {
|
|
soc_print("Failed to snprintf_s.\n");
|
|
return -1;
|
|
}
|
|
|
|
dev = nand_raw_probe(ptn, buf, sizeof(buf), &readonly);
|
|
if (dev < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (nand_raw_get_mtd_info(dev, mtdinfo) != TD_SUCCESS) {
|
|
close(dev);
|
|
return -1;
|
|
}
|
|
|
|
part_info = nand_raw_get_part_info(part);
|
|
if (part_info == NULL) {
|
|
close(dev);
|
|
return -1;
|
|
}
|
|
|
|
if (strncpy_s(ptn->mtddev, sizeof(ptn->mtddev), buf, strlen(buf)) != 0) {
|
|
close(dev);
|
|
return -1;
|
|
}
|
|
if (strncpy_s(ptn->partname, sizeof(ptn->partname), part_info->part_name, strlen(part_info->part_name)) != 0) {
|
|
close(dev);
|
|
return -1;
|
|
}
|
|
|
|
ptn->mtddev[sizeof(ptn->mtddev) - 1] = '\0';
|
|
ptn->partname[sizeof(ptn->partname) - 1] = '\0';
|
|
ptn->fd = dev;
|
|
ptn->readonly = readonly;
|
|
ptn->start = part_info->start_addr;
|
|
ptn->end = part_info->start_addr + mtdinfo->size - 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static td_void nand_raw_init_info(td_s16 max_partition)
|
|
{
|
|
td_s32 ix;
|
|
struct mtd_info_user mtdinfo;
|
|
struct mtd_partition *ptn = g_nand_raw_ctrl->partition;
|
|
|
|
for (ix = 0; ix < max_partition; ix++) {
|
|
if (nand_raw_init_part(ix, ptn, &mtdinfo) < 0) {
|
|
continue;
|
|
}
|
|
|
|
g_nand_raw_ctrl->num_partition++;
|
|
if (g_nand_raw_ctrl->num_partition == 1) {
|
|
g_nand_raw_ctrl->pagesize = mtdinfo.writesize;
|
|
g_nand_raw_ctrl->blocksize = mtdinfo.erasesize;
|
|
g_nand_raw_ctrl->pagemask = (mtdinfo.writesize - 1);
|
|
g_nand_raw_ctrl->blockmask = (mtdinfo.erasesize - 1);
|
|
g_nand_raw_ctrl->oobsize = mtdinfo.oobsize;
|
|
g_nand_raw_ctrl->oobused = NFC610_OOBSIZE_FOR_YAFFS;
|
|
g_nand_raw_ctrl->pageshift = (td_u32)offshift(mtdinfo.writesize);
|
|
g_nand_raw_ctrl->blockshift = (td_u32)offshift(mtdinfo.erasesize);
|
|
}
|
|
g_nand_raw_ctrl->size += mtdinfo.size;
|
|
ptn++;
|
|
}
|
|
}
|
|
|
|
td_s32 nand_raw_init(td_void)
|
|
{
|
|
td_s32 max_partition;
|
|
td_u32 nandraw_ctl_len;
|
|
|
|
if (g_nand_raw_ctrl != NULL) {
|
|
return 0;
|
|
}
|
|
|
|
max_partition = get_max_partition();
|
|
if (max_partition < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (++max_partition >= MAX_PARTS) {
|
|
soc_print("partition maybe more than %d, please increase MAX_PARTS.\n", MAX_PARTS);
|
|
}
|
|
if (flash_partition_info_init() != 0) {
|
|
soc_print("Initial partition information failure.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (((sizeof(struct nand_raw_ctrl) + max_partition * sizeof(struct mtd_partition)) > MAX_U32) ||
|
|
(max_partition * sizeof(struct mtd_partition) > MAX_U32)) {
|
|
return -1;
|
|
}
|
|
|
|
nandraw_ctl_len = (td_u32)(sizeof(struct nand_raw_ctrl) + max_partition * sizeof(struct mtd_partition));
|
|
if (nandraw_ctl_len == 0) {
|
|
return -1;
|
|
}
|
|
|
|
g_nand_raw_ctrl = (struct nand_raw_ctrl *)malloc(nandraw_ctl_len);
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Not enough memory.\n");
|
|
return -1;
|
|
}
|
|
memset_s(g_nand_raw_ctrl, nandraw_ctl_len, 0, nandraw_ctl_len);
|
|
|
|
g_nand_raw_ctrl->num_partition = 0;
|
|
g_nand_raw_ctrl->size = 0;
|
|
|
|
nand_raw_init_info((td_s16)max_partition);
|
|
|
|
if (g_nand_raw_ctrl->num_partition == 0) {
|
|
dbg_out("Nand partition numbers is 0!\n");
|
|
free(g_nand_raw_ctrl);
|
|
g_nand_raw_ctrl = NULL;
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
td_void nand_raw_get_info(td_u64 *totalsize, td_u32 *pagesize,
|
|
td_u32 *blocksize, td_u32 *oobsize, td_u32 *blockshift)
|
|
{
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Nandraw Control is not initialized!\n");
|
|
return;
|
|
}
|
|
|
|
if (totalsize == NULL || pagesize == NULL || blocksize == NULL \
|
|
|| oobsize == NULL || blockshift == NULL) {
|
|
return;
|
|
}
|
|
|
|
*totalsize = g_nand_raw_ctrl->size;
|
|
*pagesize = g_nand_raw_ctrl->pagesize;
|
|
*blocksize = g_nand_raw_ctrl->blocksize;
|
|
*oobsize = g_nand_raw_ctrl->oobused;
|
|
*blockshift = g_nand_raw_ctrl->blockshift;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. if open SPI/NOR FLASH, return 0
|
|
* 2. if dev_name cannot match g_nand_raw_ctrl, return error_valid;
|
|
*/
|
|
td_u64 nand_raw_get_start_addr(const td_char *name, td_ulong blocksize, td_s32 *value_valid)
|
|
{
|
|
struct mtd_partition *ptn = NULL;
|
|
td_s32 max_partition;
|
|
td_s32 ix;
|
|
|
|
TD_UNUSED(blocksize);
|
|
|
|
if (value_valid == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
if ((name == NULL) || (*name == '\0')) {
|
|
*value_valid = 0;
|
|
return 0;
|
|
}
|
|
|
|
ptn = g_nand_raw_ctrl->partition;
|
|
|
|
max_partition = g_nand_raw_ctrl->num_partition;
|
|
|
|
/* as the partition start with 0, if we have x partition,the max_partition will be x-1 ,
|
|
so in following code,we use " ix <= max_partition" instead of " ix < max_partition"
|
|
which be used in old SDK version. as well as , the later "if (max_partition < ix)" ,we use
|
|
"<" instead of "=" which be used in old SDK version */
|
|
for (ix = 0; ix < max_partition; ix++) {
|
|
if (strncmp(ptn->mtddev, name,
|
|
strlen(ptn->mtddev) > strlen(name) ? strlen(ptn->mtddev) : strlen(name)) == 0) {
|
|
break;
|
|
}
|
|
|
|
/* match partition name */
|
|
if (strncmp(ptn->partname, name, strlen(ptn->partname)) == 0) {
|
|
break;
|
|
}
|
|
|
|
ptn++;
|
|
}
|
|
|
|
if (max_partition <= ix) {
|
|
*value_valid = 0;
|
|
return 0;
|
|
}
|
|
/* lint -e661 */
|
|
*value_valid = 1;
|
|
/* lint +e661 */
|
|
return ptn->start;
|
|
}
|
|
|
|
static td_s32 nand_raw_read_check(td_u64 offset, const td_u8 *buffer, td_ulong length)
|
|
{
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (buffer == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if ((offset >= g_nand_raw_ctrl->size) || (length == 0)) {
|
|
return -1;
|
|
}
|
|
|
|
if (((td_ulong)offset & g_nand_raw_ctrl->pagemask) != 0) {
|
|
soc_print("startaddr should be align with pagesize(0x%X)\n", g_nand_raw_ctrl->pagesize);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 nand_raw_read_block(struct mtd_partition *ptn,
|
|
td_u64 *offset, td_u8 **buffer, td_ulong *length, td_s32 read_oob)
|
|
{
|
|
td_s32 total_read = 0;
|
|
|
|
do {
|
|
/* read one page one by one */
|
|
td_ulong num_read = (*length >= g_nand_raw_ctrl->pagesize ? g_nand_raw_ctrl->pagesize : *length);
|
|
if (lseek64(ptn->fd, (off64_t)(*offset - ptn->start), SEEK_SET) != -1 &&
|
|
read(ptn->fd, *buffer, (size_t)num_read) != (ssize_t)num_read) {
|
|
soc_print("read \"%s\" fail. error(%d)\n", ptn->mtddev, errno);
|
|
return -1;
|
|
}
|
|
|
|
*buffer += num_read;
|
|
*length -= num_read;
|
|
total_read += (td_s32)num_read;
|
|
|
|
if (read_oob == 0) {
|
|
} else if (*length >= g_nand_raw_ctrl->oobused) {
|
|
struct mtd_oob_buf oob;
|
|
oob.start = (uint32_t)(*offset - ptn->start);
|
|
oob.length = g_nand_raw_ctrl->oobused;
|
|
oob.ptr = (td_u8 *)*buffer;
|
|
|
|
if (ioctl(ptn->fd, MEMREADOOB, &oob) != 0) {
|
|
soc_print("read oob \"%s\" fail. error(%d)\n", ptn->mtddev, errno);
|
|
return -1;
|
|
}
|
|
|
|
*buffer += g_nand_raw_ctrl->oobused;
|
|
*length -= g_nand_raw_ctrl->oobused;
|
|
total_read += (td_s32)g_nand_raw_ctrl->oobused;
|
|
} else { /* read_oob && *length < g_nand_raw_ctrl->oobused */
|
|
*length = 0; /* read end when length less than oobsize. */
|
|
}
|
|
*offset += (td_ulong)num_read;
|
|
} while ((*length != 0) && ((*offset & g_nand_raw_ctrl->blockmask) != 0));
|
|
return total_read;
|
|
}
|
|
|
|
static td_s32 nand_raw_permit(const struct mtd_partition *ptn,
|
|
td_u64 offset, td_ulong length, uapi_flash_access_perm perm)
|
|
{
|
|
if (ptn == NULL) {
|
|
soc_print("ptn is null\n");
|
|
return 0;
|
|
}
|
|
|
|
return ((ptn->start <= offset) && (offset < ptn->end) && (length != 0) &&
|
|
(((unsigned int)ptn->perm & (unsigned int)perm) != 0) && (ptn->fd != INVALID_FD));
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. startaddr should be alignment with pagesize
|
|
* 2. startaddr maybe change when meet bad block
|
|
*/
|
|
td_s32 nand_raw_read(flash_rw_info_s *rw_info, td_u8 *buffer, td_ulong length, td_s32 skip_badblock)
|
|
{
|
|
td_s32 ix, rel;
|
|
td_s32 totalread = 0;
|
|
td_u64 offset;
|
|
|
|
if (rw_info == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
offset = rw_info->start_addr;
|
|
|
|
if (nand_raw_read_check(offset, buffer, length) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition && (length != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
/* lint -save -e655 */
|
|
while (nand_raw_permit(ptn, offset, length, ACCESS_RD) != 0) {
|
|
if (offset - rw_info->open_addr >= rw_info->limit_len) {
|
|
soc_print("bad block cause read end(beyond limit_len =%#llx)!\n", rw_info->limit_len);
|
|
return totalread;
|
|
}
|
|
dbg_out(">ptn->fd=%d, len=%#lx, *saddr=%#llx, offset=%#llx\n", ptn->fd, length,
|
|
rw_info->start_addr, offset);
|
|
|
|
td_s32 blockindex = (td_s32)((offset - ptn->start) / g_nand_raw_ctrl->blocksize);
|
|
rel = check_skip_badblock(ptn, &blockindex, (td_s32)g_nand_raw_ctrl->blocksize);
|
|
if (skip_badblock == 0) {
|
|
} else if (rel < 0) {
|
|
return rel;
|
|
} else if (rel > 0) { /* bad block, move to start address of the block */
|
|
offset += (td_ulong)((td_u32)rel << g_nand_raw_ctrl->blockshift);
|
|
continue;
|
|
}
|
|
|
|
/* read all pages in one block */
|
|
rel = nand_raw_read_block(ptn, &offset, &buffer, &length, rw_info->with_oob);
|
|
if (rel < 0) {
|
|
return -1;
|
|
}
|
|
totalread += rel;
|
|
}
|
|
}
|
|
rw_info->start_addr = offset;
|
|
|
|
return totalread;
|
|
}
|
|
|
|
static td_s32 nand_raw_erase_check(td_u64 offset, td_u64 length)
|
|
{
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if ((offset >= g_nand_raw_ctrl->size) || (length == 0)) {
|
|
return -1;
|
|
}
|
|
|
|
if ((((td_ulong)offset & g_nand_raw_ctrl->blockmask) != 0) ||
|
|
(((td_ulong)length & g_nand_raw_ctrl->blockmask) != 0)) {
|
|
soc_print("offset or length should be alignment with blocksize(0x%X)\n",
|
|
(td_u32)g_nand_raw_ctrl->blocksize);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 nand_raw_erase_block(struct mtd_partition *ptn, td_u64 *offset, td_u64 *length)
|
|
{
|
|
struct erase_info_user64 eraseinfo;
|
|
eraseinfo.start = (td_u64)(*offset - ptn->start);
|
|
eraseinfo.length = (td_u64)(g_nand_raw_ctrl->blocksize);
|
|
|
|
/* the block will be marked bad when erase error, so don't deal with */
|
|
if (ioctl(ptn->fd, MEMERASE64, &eraseinfo) != 0) {
|
|
soc_print("Erase 0x%llx failed!\n", *offset);
|
|
if (nand_mark_badblock(*offset, (td_u64)g_nand_raw_ctrl->blocksize) != 0) {
|
|
soc_print("\nMTD block_markbad at 0x%08llx failed\n", *offset);
|
|
return TD_FAILURE;
|
|
}
|
|
}
|
|
*length -= g_nand_raw_ctrl->blocksize;
|
|
*offset += (td_ulong)(1 << g_nand_raw_ctrl->blockshift);
|
|
|
|
return (td_s32)g_nand_raw_ctrl->blocksize;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. offset should be alignment with blocksize
|
|
* 2. if there is a bad block, length should subtract.
|
|
*/
|
|
td_s64 nand_raw_erase(td_s32 fd, td_u64 startaddr, td_u64 length, td_u64 openaddr, td_u64 limit_len)
|
|
{
|
|
td_s32 ix, rel, blockindex;
|
|
td_s64 totalerase = 0;
|
|
td_u64 offset = startaddr;
|
|
td_u64 temp_lenght = length;
|
|
|
|
TD_UNUSED(fd);
|
|
|
|
if (nand_raw_erase_check(offset, temp_lenght) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if ((offset + temp_lenght) > g_nand_raw_ctrl->size) {
|
|
temp_lenght = g_nand_raw_ctrl->size - offset;
|
|
}
|
|
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition && (temp_lenght != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
while (nand_raw_permit(ptn, offset, (td_ulong)temp_lenght, ACCESS_WR) != 0) {
|
|
dbg_out(">ptn->fd=%d, len=%#llx, *startaddr=%#llx, offset=%#llx\n", ptn->fd, temp_lenght,
|
|
startaddr, offset);
|
|
|
|
if (ptn->readonly != 0) {
|
|
soc_print("erase a read only partition \"%s\".\n", ptn->mtddev);
|
|
return -1;
|
|
}
|
|
|
|
if (offset - openaddr >= limit_len) {
|
|
soc_print("1bad block cause erase end(beyond limit_len =%#llx)!\n", limit_len);
|
|
return totalerase;
|
|
}
|
|
|
|
blockindex = (td_s32)((offset - ptn->start) >> g_nand_raw_ctrl->blockshift);
|
|
rel = check_skip_badblock(ptn, &blockindex, (td_s32)g_nand_raw_ctrl->blocksize);
|
|
if (rel < 0) {
|
|
return TD_FAILURE;
|
|
} else if (rel > 0) { /* skip bad block */
|
|
offset += (td_ulong)((td_u32)rel << g_nand_raw_ctrl->blockshift);
|
|
continue;
|
|
}
|
|
|
|
/* no bad block */
|
|
rel = nand_raw_erase_block(ptn, &offset, &temp_lenght);
|
|
if (rel < 0) {
|
|
return TD_FAILURE;
|
|
}
|
|
totalerase += rel;
|
|
}
|
|
}
|
|
return totalerase;
|
|
}
|
|
|
|
static td_s32 nand_raw_force_erase_check(td_u64 offset)
|
|
{
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (((td_ulong)offset & g_nand_raw_ctrl->blockmask) != 0) {
|
|
soc_print("offset should be alignment with blocksize(0x%X)\n", (td_u32)g_nand_raw_ctrl->blocksize);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. offset should be alignment with blocksize
|
|
*/
|
|
td_s32 nand_raw_force_erase(td_u64 offset)
|
|
{
|
|
td_s32 ix, rel;
|
|
td_u64 temp_offset = offset;
|
|
|
|
if (nand_raw_force_erase_check(temp_offset) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (temp_offset >= g_nand_raw_ctrl->size) {
|
|
return 0;
|
|
}
|
|
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition; ix++) {
|
|
struct mtd_partition *ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
/* length is equal to 1 indicates true */
|
|
if (nand_raw_permit(ptn, temp_offset, 1, ACCESS_WR) != 0) {
|
|
if (ptn->readonly != 0) {
|
|
soc_print("erase a read only partition \"%s\".\n", ptn->mtddev);
|
|
return -1;
|
|
}
|
|
dbg_out("dev: \"%s\", from: 0x%llX\n", ptn->mtddev, (temp_offset - ptn->start));
|
|
|
|
temp_offset = temp_offset - ptn->start;
|
|
|
|
rel = ioctl(ptn->fd, MEMFORCEERASEBLOCK, &temp_offset);
|
|
if (rel != 0) {
|
|
soc_print("Force Erase 0x%llx failed!\n", temp_offset);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 nand_raw_write_check(td_u64 offset, const td_u8 *buffer, td_ulong length, td_s32 write_oob)
|
|
{
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (((td_ulong)offset & g_nand_raw_ctrl->pagemask) != 0) {
|
|
soc_print("Startaddr should be alignment with pagesize(0x%X)\n", g_nand_raw_ctrl->pagesize);
|
|
return -1;
|
|
}
|
|
|
|
/* if write_oob is used, align with (oobsize + pagesize) */
|
|
if ((write_oob != 0) && ((length % (g_nand_raw_ctrl->pagesize + g_nand_raw_ctrl->oobused)) != 0)) {
|
|
soc_print("Length should be alignment with pagesize + oobsize when write oob.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (g_nand_raw_ctrl->pagesize > NAND_PAGE_BUF_SIZE) {
|
|
soc_print("%s: BUG program need enough databuf.\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
|
|
if ((buffer == NULL) || (offset >= g_nand_raw_ctrl->size) || (length == 0)) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 nand_raw_write_malloc(td_u8 **databuf, td_u8 **oobbuf, td_s32 write_oob)
|
|
{
|
|
*databuf = malloc(NAND_PAGE_BUF_SIZE);
|
|
if (!*databuf) {
|
|
return -1;
|
|
}
|
|
memset_s(*databuf, NAND_PAGE_BUF_SIZE, 0, NAND_PAGE_BUF_SIZE);
|
|
|
|
*oobbuf = malloc(NAND_OOB_BUF_SIZE);
|
|
if (*oobbuf == NULL) {
|
|
return -1;
|
|
}
|
|
memset_s(*oobbuf, NAND_OOB_BUF_SIZE, 0, NAND_OOB_BUF_SIZE);
|
|
|
|
if (write_oob == 0) {
|
|
if (g_nand_raw_ctrl->oobused > NAND_OOB_BUF_SIZE) {
|
|
soc_print("%s: BUG program need enough oobbuf.\n", __FUNCTION__);
|
|
return -1;
|
|
}
|
|
memset_s(*oobbuf, NAND_OOB_BUF_SIZE, 0xFF, NAND_OOB_BUF_SIZE);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 nand_raw_write_data(td_u8 **data_ptr, td_u8 **buffer, td_ulong *length, td_u8 *databuf)
|
|
{
|
|
td_ulong num_data = (*length >= g_nand_raw_ctrl->pagesize ? g_nand_raw_ctrl->pagesize : *length);
|
|
/* less than one pagesize */
|
|
if (num_data < g_nand_raw_ctrl->pagesize) {
|
|
memset_s(databuf, NAND_PAGE_BUF_SIZE, 0xFF, NAND_PAGE_BUF_SIZE);
|
|
if (memcpy_s(databuf, NAND_PAGE_BUF_SIZE, *buffer, num_data) != 0) {
|
|
return -1;
|
|
}
|
|
*data_ptr = databuf;
|
|
} else {
|
|
*data_ptr = *buffer;
|
|
}
|
|
*buffer += num_data;
|
|
*length -= num_data;
|
|
return (td_s32)num_data;
|
|
}
|
|
|
|
static td_s32 nand_raw_write_oob(td_u8 **oob_ptr,
|
|
td_u8 **buffer, td_ulong *length, td_u8 *oobbuf, td_s32 write_oob)
|
|
{
|
|
td_s32 num_oob = 0;
|
|
|
|
if (write_oob != 0) { /* if write_oob */
|
|
if (*length < g_nand_raw_ctrl->oobused) {
|
|
soc_print("%s(%d): buf not align error!\n", __FILE__, __LINE__);
|
|
return -1;
|
|
}
|
|
*oob_ptr = *buffer;
|
|
|
|
*buffer += g_nand_raw_ctrl->oobused;
|
|
*length -= g_nand_raw_ctrl->oobused;
|
|
num_oob = (td_s32)g_nand_raw_ctrl->oobused;
|
|
} else {
|
|
*oob_ptr = oobbuf;
|
|
}
|
|
return num_oob;
|
|
}
|
|
|
|
|
|
static td_s32 nand_raw_write_ioctl(struct mtd_partition *ptn, write_req_s *req_buf)
|
|
{
|
|
struct mtd_write_req write_req_buf;
|
|
|
|
/* avoid mark bad block unexpected. */
|
|
if ((*(req_buf->oob_ptr + 1) << 8) + *req_buf->oob_ptr != 0xFFFF) { /* 8 - left shift bits */
|
|
soc_print("Please check input data, it maybe mark bad block. value:0x%04X\n",
|
|
(*(req_buf->oob_ptr + 1) << 8) + *req_buf->oob_ptr); /* 8 - left shift bits */
|
|
return -1;
|
|
}
|
|
|
|
/* should reerase and write if write error when upgrade. */
|
|
memset_s(&write_req_buf, sizeof(write_req_buf), 0xff, sizeof(write_req_buf));
|
|
write_req_buf.start = req_buf->start;
|
|
write_req_buf.usr_data = (td_u64)(uintptr_t)req_buf->data_ptr;
|
|
write_req_buf.len = req_buf->datalen;
|
|
write_req_buf.usr_oob = (td_u64)(uintptr_t)req_buf->oob_ptr;
|
|
write_req_buf.ooblen = req_buf->ooblen;
|
|
write_req_buf.mode = MTD_OPS_PLACE_OOB;
|
|
|
|
if (ioctl(ptn->fd, MEMWRITE, &write_req_buf) != 0) {
|
|
soc_print("ioctl(%s MEMWRITE) fail. error(%d)\n", ptn->mtddev, errno);
|
|
/* union return value to TD_FAILURE. */
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 nand_raw_write_block(struct mtd_partition *ptn,
|
|
td_u64 *offset, td_u8 **buffer, td_ulong *length, td_s32 write_oob)
|
|
{
|
|
td_s32 totalwrite = 0;
|
|
td_u8 *databuf = NULL;
|
|
td_u8 *oobbuf = NULL;
|
|
|
|
if (nand_raw_write_malloc(&databuf, &oobbuf, write_oob) == 0) {
|
|
do {
|
|
/* for ioctl cmd args */
|
|
write_req_s req_buf;
|
|
td_s32 num_data, num_oob;
|
|
|
|
memset_s(&req_buf, sizeof(req_buf), 0, sizeof(req_buf));
|
|
num_data = nand_raw_write_data(&req_buf.data_ptr, buffer, length, databuf);
|
|
if (num_data < 0) {
|
|
totalwrite = -1;
|
|
break;
|
|
}
|
|
|
|
num_oob = nand_raw_write_oob(&req_buf.oob_ptr, buffer, length, oobbuf, write_oob);
|
|
if (num_oob < 0) {
|
|
totalwrite = -1;
|
|
break;
|
|
}
|
|
|
|
totalwrite += num_data + num_oob;
|
|
|
|
req_buf.start = *offset - ptn->start;
|
|
req_buf.ooblen = g_nand_raw_ctrl->oobused;
|
|
req_buf.datalen = g_nand_raw_ctrl->pagesize;
|
|
|
|
if (nand_raw_write_ioctl(ptn, &req_buf) < 0) {
|
|
totalwrite = -1;
|
|
break;
|
|
}
|
|
|
|
*offset += (unsigned long)num_data;
|
|
} while ((*length != 0) && (((*offset & g_nand_raw_ctrl->blockmask)) != 0));
|
|
} else {
|
|
totalwrite = -1;
|
|
}
|
|
|
|
if (databuf != NULL) {
|
|
free(databuf);
|
|
}
|
|
|
|
if (oobbuf != NULL) {
|
|
free(oobbuf);
|
|
}
|
|
|
|
return totalwrite;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. startaddr should be alignment with pagesize
|
|
*/
|
|
td_s32 nand_raw_write(flash_rw_info_s *rw_info, td_u8 *buffer, td_ulong length)
|
|
{
|
|
td_s32 ix, blockindex, rel;
|
|
td_s32 totalwrite = 0;
|
|
td_u64 offset;
|
|
|
|
if (rw_info == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
offset = rw_info->start_addr;
|
|
|
|
if (nand_raw_write_check(offset, buffer, length, rw_info->with_oob) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition && (length != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
while (nand_raw_permit(ptn, offset, length, ACCESS_WR) != 0) {
|
|
if (ptn->readonly != 0) {
|
|
soc_print("Write a read only partition \"%s\".\n", ptn->mtddev);
|
|
return -1;
|
|
}
|
|
|
|
if (offset - rw_info->open_addr >= rw_info->limit_len) {
|
|
soc_print("bad block cause write end(beyond limit_len =%#llx)!\n", rw_info->limit_len);
|
|
return UAPI_FLASH_END_DUETO_BADBLOCK;
|
|
}
|
|
|
|
/* must skip bad block when write */
|
|
blockindex = (td_s32)((offset - ptn->start) >> g_nand_raw_ctrl->blockshift);
|
|
rel = check_skip_badblock(ptn, &blockindex, (td_s32)g_nand_raw_ctrl->blocksize);
|
|
if (rel < 0) {
|
|
return rel;
|
|
} else if (rel > 0) {
|
|
/* move to start address of the block */
|
|
offset += (td_ulong)((td_u32)rel << g_nand_raw_ctrl->blockshift);
|
|
continue;
|
|
} /* rel == 0, no bad block */
|
|
|
|
dbg_out(">ptn->fd=%d, len=%#lx, *saddr=%#llx, offset=%#llx\n", ptn->fd, length,
|
|
rw_info->start_addr, offset);
|
|
|
|
rel = nand_raw_write_block(ptn, &offset, &buffer, &length, rw_info->with_oob);
|
|
if (rel < 0) {
|
|
return -1;
|
|
}
|
|
totalwrite += rel;
|
|
}
|
|
}
|
|
|
|
rw_info->start_addr = offset;
|
|
|
|
return totalwrite;
|
|
}
|
|
|
|
static td_s32 nand_badblock_check(td_u64 offset, td_u64 length)
|
|
{
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (offset >= g_nand_raw_ctrl->size || (length == 0)) {
|
|
return 0;
|
|
}
|
|
|
|
if ((((td_ulong)offset & g_nand_raw_ctrl->blockmask) != 0) ||
|
|
(((td_ulong)length & g_nand_raw_ctrl->blockmask) != 0)) {
|
|
soc_print("offset or length should be alignment with blocksize(0x%X)\n",
|
|
(td_u32)g_nand_raw_ctrl->blocksize);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static td_s32 nand_badblock_permit(const struct mtd_partition *ptn, td_u64 offset, td_u64 length)
|
|
{
|
|
if (ptn == NULL) {
|
|
soc_print("ptn is null\n");
|
|
return 0;
|
|
}
|
|
|
|
return ((ptn->start <= offset) && (offset < ptn->end) && (length != 0) && (ptn->fd != INVALID_FD));
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. offset and length should be alignment with blocksize
|
|
*/
|
|
td_s32 nand_mark_badblock(td_u64 offset, td_u64 length)
|
|
{
|
|
td_s32 ix;
|
|
td_u64 blockoffset;
|
|
td_u64 temp_offset = offset;
|
|
td_u64 temp_length = length;
|
|
|
|
if (nand_badblock_check(temp_offset, temp_length) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if (temp_offset + temp_length > g_nand_raw_ctrl->size) {
|
|
temp_length = g_nand_raw_ctrl->size - temp_offset;
|
|
}
|
|
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition && (temp_length != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
while (nand_badblock_permit(ptn, temp_offset, temp_length) != 0) {
|
|
if (ptn->readonly != 0) {
|
|
soc_print("mark bad block error, a read only partition \"%s\".\n", ptn->mtddev);
|
|
return -1;
|
|
}
|
|
|
|
dbg_out("dev: \"%s\", from: 0x%llX, length: 0x%llX\n", ptn->mtddev, (temp_offset - ptn->start),
|
|
temp_length);
|
|
|
|
blockoffset = temp_offset - ptn->start;
|
|
|
|
if (ioctl(ptn->fd, MEMSETBADBLOCK, &blockoffset) != 0) {
|
|
soc_print("Mark bad block 0x%llX failed!\n", temp_offset);
|
|
}
|
|
|
|
temp_offset += (td_ulong)(1UL << g_nand_raw_ctrl->blockshift);
|
|
temp_length -= (td_ulong)(1UL << g_nand_raw_ctrl->blockshift);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* warning:
|
|
* 1. offset and length should be alignment with blocksize
|
|
*/
|
|
td_s32 nand_show_badblock(td_u64 offset, td_u64 length)
|
|
{
|
|
td_s32 ix;
|
|
td_s32 badblock;
|
|
td_u64 blockoffset;
|
|
td_u64 temp_offset = offset;
|
|
td_u64 temp_length = length;
|
|
|
|
if (nand_badblock_check(temp_offset, temp_length) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
if ((temp_offset + temp_length) > g_nand_raw_ctrl->size) {
|
|
temp_length = g_nand_raw_ctrl->size - temp_offset;
|
|
}
|
|
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition && (temp_length != 0); ix++) {
|
|
struct mtd_partition *ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
while (nand_badblock_permit(ptn, temp_offset, temp_length) != 0) {
|
|
dbg_out("dev: \"%s\", from: 0x%llX, length: 0x%llX\n", ptn->mtddev, (temp_offset - ptn->start),
|
|
temp_length);
|
|
|
|
blockoffset = temp_offset - ptn->start;
|
|
|
|
badblock = ioctl(ptn->fd, MEMGETBADBLOCK, &blockoffset);
|
|
if (badblock < 0) {
|
|
soc_print("Get nand badblock fail. error(%d)\n", errno);
|
|
return -1;
|
|
} else if (badblock == 1) {
|
|
soc_print("Bad block at addr: 0x%lX of \"%s\", absolute addr: 0x%llX\n",
|
|
(td_ulong)blockoffset, ptn->mtddev, (td_u64)temp_offset);
|
|
}
|
|
|
|
temp_offset += (td_ulong)(1UL << g_nand_raw_ctrl->blockshift);
|
|
temp_length -= (td_ulong)(1UL << g_nand_raw_ctrl->blockshift);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
td_s32 nand_raw_info(struct mtd_info_user *mtdinfo)
|
|
{
|
|
td_s32 rel;
|
|
|
|
if (mtdinfo == NULL) {
|
|
soc_print("mtdinfo is null.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
if (g_nand_raw_ctrl->partition[0].fd == INVALID_FD) {
|
|
return -1;
|
|
}
|
|
|
|
rel = ioctl(g_nand_raw_ctrl->partition[0].fd, MEMGETINFO, mtdinfo);
|
|
if (rel != 0) {
|
|
soc_print("ioctl \"%s\" fail. error(%d)\n", g_nand_raw_ctrl->partition[0].mtddev, errno);
|
|
return rel;
|
|
}
|
|
|
|
if (g_nand_raw_ctrl->size > 0xFFFFFFFF) {
|
|
soc_print("nand flash size out of range of an td_ulong.\n");
|
|
}
|
|
|
|
mtdinfo->size = (uint32_t)g_nand_raw_ctrl->size;
|
|
|
|
return 0;
|
|
}
|
|
|
|
td_s32 nand_raw_dump_partition(td_void)
|
|
{
|
|
td_s32 ix;
|
|
struct mtd_partition *ptn __attribute__((unused)) = NULL;
|
|
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
soc_print("Please initialize before use this function.\n");
|
|
return -1;
|
|
}
|
|
|
|
soc_print("-------------------------\n");
|
|
soc_print("mtd device start length mode\n");
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition; ix++) {
|
|
ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
soc_print("%-12s ", ptn->mtddev);
|
|
soc_print("%5s ", int_to_size(ptn->start));
|
|
soc_print("%6s ", int_to_size(ptn->end + 1 - ptn->start));
|
|
soc_print("%2s ", ptn->readonly != 0 ? "r" : "rw");
|
|
soc_print("\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
td_s32 nand_raw_destroy(td_void)
|
|
{
|
|
td_s32 ix;
|
|
|
|
if (g_nand_raw_ctrl == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
for (ix = 0; ix < g_nand_raw_ctrl->num_partition; ix++) {
|
|
if (g_nand_raw_ctrl->partition[ix].fd != INVALID_FD) {
|
|
close(g_nand_raw_ctrl->partition[ix].fd);
|
|
}
|
|
}
|
|
|
|
free(g_nand_raw_ctrl);
|
|
g_nand_raw_ctrl = NULL;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* > 0 skip bad block num.
|
|
* = 0 no bad block.
|
|
* < 0 error.
|
|
*/
|
|
static td_s32 check_skip_badblock(struct mtd_partition *ptn, td_s32 *blockindex, td_s32 blocksize)
|
|
{
|
|
td_s32 rel = 0;
|
|
td_s32 badblock;
|
|
td_u64 size = (ptn->end - ptn->start) + 1;
|
|
loff_t offset = ((loff_t)(*blockindex) * (loff_t)blocksize);
|
|
|
|
if (ptn->fd == INVALID_FD) {
|
|
return -1;
|
|
}
|
|
|
|
do {
|
|
badblock = ioctl(ptn->fd, MEMGETBADBLOCK, &offset);
|
|
if (badblock < 0) {
|
|
soc_print("Get nand badblock fail. error(%d)\n", errno);
|
|
return -1;
|
|
}
|
|
if (badblock == 1) {
|
|
soc_print("Skip bad block at addr: 0x%llX of \"%s\", absolute addr: 0x%llX\n",
|
|
(td_u64)offset, ptn->mtddev, ((td_u64)offset + ptn->start));
|
|
(*blockindex)++;
|
|
rel++;
|
|
}
|
|
offset = (loff_t)(*blockindex) * (loff_t)blocksize;
|
|
} while (badblock == 1 && offset < (loff_t)size);
|
|
|
|
return rel;
|
|
}
|
|
|
|
td_s32 nand_raw_get_physical_index(td_u64 startaddr, td_s32 *blockindex, td_s32 blocksize)
|
|
{
|
|
struct mtd_partition *ptn = NULL;
|
|
td_s32 badblock;
|
|
td_s32 ix;
|
|
td_s32 i = 0;
|
|
|
|
loff_t offset;
|
|
td_u64 offset_addr = startaddr;
|
|
td_s32 logcial_index;
|
|
td_s32 physical_index = 0;
|
|
|
|
if (blockindex == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
logcial_index = *blockindex;
|
|
|
|
for (ix = 0; i < logcial_index && ix < g_nand_raw_ctrl->num_partition; ix++) {
|
|
ptn = &g_nand_raw_ctrl->partition[ix];
|
|
|
|
if (ptn->end + 1 <= startaddr) {
|
|
continue;
|
|
}
|
|
|
|
if (ptn->fd == INVALID_FD) {
|
|
return -1;
|
|
}
|
|
|
|
while ((i < logcial_index) && (offset_addr < ptn->end)) {
|
|
offset = (loff_t)(offset_addr - ptn->start);
|
|
|
|
badblock = ioctl(ptn->fd, MEMGETBADBLOCK, &offset);
|
|
if (badblock < 0) {
|
|
soc_print("Get nand badblock fail. error(%d)\n", errno);
|
|
return -1;
|
|
} else if (badblock == 0) {
|
|
i++;
|
|
}
|
|
physical_index++;
|
|
offset_addr += (td_ulong)blocksize;
|
|
}
|
|
}
|
|
|
|
dbg_out("logcial_addr=%d, physical_addr=%d\n", logcial_index, physical_index);
|
|
*blockindex = physical_index;
|
|
return 0;
|
|
}
|