/* * 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 #include #include #include #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; }