/* * Copyright (c) Hisilicon Technologies Co., Ltd. 2020-2020. All rights reserved. * Description: spi module driver * Author: Hisilicon * Create: 2020-10-15 */ #include "spi_raw.h" #include #include #include #include #include "soc_log.h" #include "uapi_flash.h" #include "nand.h" #include "td_type.h" #include "flash_ext.h" #include "securec.h" static struct nand_raw_ctrl *g_spiraw_ctl = NULL; static td_s32 spi_raw_prope(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 spi_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_SPIFLASH) { return TD_FAILURE; } return TD_SUCCESS; } static uapi_flash_partinfo *spi_raw_get_part_info(td_s32 part) { uapi_flash_partinfo *part_info = NULL; td_char devname[MAX_DEV_NAME_LEN]; /* 32 - buffer size */ 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_NOR_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 spi_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; if (snprintf_s(buf, PATH_MAX, PATH_MAX - 1, DEV_MTDBASE"%d", part) == -1) { soc_print("Failed to snprintf_s.\n"); return -1; } dev = spi_raw_prope(ptn, buf, sizeof(buf), &readonly); if (dev < 0) { return -1; } if (spi_raw_get_mtd_info(dev, mtdinfo) != TD_SUCCESS) { close(dev); return -1; } part_info = spi_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; } ptn->mtddev[sizeof(ptn->mtddev) - 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 spi_raw_init_info(td_s16 max_partition) { td_s32 ix; struct mtd_info_user mtdinfo; struct mtd_partition *ptn = g_spiraw_ctl->partition; for (ix = 0; ix < max_partition; ix++) { if (spi_raw_init_part(ix, ptn, &mtdinfo) < 0) { continue; } g_spiraw_ctl->num_partition++; if (g_spiraw_ctl->num_partition == 1) { g_spiraw_ctl->pagesize = mtdinfo.writesize; g_spiraw_ctl->blocksize = mtdinfo.erasesize; g_spiraw_ctl->pagemask = (mtdinfo.writesize - 1); g_spiraw_ctl->blockmask = (mtdinfo.erasesize - 1); g_spiraw_ctl->oobsize = mtdinfo.oobsize; g_spiraw_ctl->pageshift = (td_u32)offshift(mtdinfo.writesize); g_spiraw_ctl->blockshift = (td_u32)offshift(mtdinfo.erasesize); } g_spiraw_ctl->size += mtdinfo.size; ptn++; } } td_s32 spi_raw_init(td_void) { td_s32 max_partition; td_u32 spiraw_ctl_len; if (g_spiraw_ctl != 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; } spiraw_ctl_len = (td_u32)(sizeof(struct nand_raw_ctrl) + max_partition * sizeof(struct mtd_partition)); if (spiraw_ctl_len == 0) { return -1; } g_spiraw_ctl = (struct nand_raw_ctrl *)malloc(spiraw_ctl_len); if (g_spiraw_ctl == NULL) { soc_print("Not enough memory.\n"); return -ENOMEM; } memset_s(g_spiraw_ctl, spiraw_ctl_len, 0, spiraw_ctl_len); g_spiraw_ctl->num_partition = 0; g_spiraw_ctl->size = 0; spi_raw_init_info((td_s16)max_partition); if (g_spiraw_ctl->num_partition == 0) { dbg_out("Spi partition numbers is 0!\n"); free(g_spiraw_ctl); g_spiraw_ctl = NULL; return -1; } return 0; } td_void spi_raw_get_info(td_u64 *totalsize, td_u32 *pagesize, td_u32 *blocksize, td_u32 *oobsize, td_u32 *blockshift) { if (totalsize == NULL || pagesize == NULL || blocksize == NULL || \ oobsize == NULL || blockshift == NULL) { return; } *totalsize = g_spiraw_ctl->size; *pagesize = g_spiraw_ctl->pagesize; *blocksize = g_spiraw_ctl->blocksize; *oobsize = g_spiraw_ctl->oobsize; *blockshift = g_spiraw_ctl->blockshift; } /* * warning: * 1. if open SPI/NOR FLASH, return 0 * 2. if dev_name cannot match g_spiraw_ctl, return error_valid; */ td_u64 spi_raw_get_start_addr(const td_char *dev_name, td_ulong blocksize, td_s32 *value_valid) { struct mtd_partition *ptn = NULL; td_s32 max_partition; td_s32 ix; if (value_valid == NULL) { return 0; } if (dev_name == NULL) { *value_valid = 0; return 0; } ptn = g_spiraw_ctl->partition; max_partition = get_max_partition(); if (max_partition == 0) { soc_print("Can't find mtd device at /dev/mtdx.\n"); return 0; } if (g_spiraw_ctl->blocksize != blocksize) { *value_valid = 1; return 0; } for (ix = 0; ix <= max_partition; ix++) { if (strncmp(ptn->mtddev, dev_name, strlen(ptn->mtddev) > strlen(dev_name) ? strlen(ptn->mtddev) : strlen(dev_name)) == 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 spi_raw_rw_check(td_u64 offset, const td_u8 *buffer, td_ulong length) { if (g_spiraw_ctl == NULL) { soc_print("Please initialize before use this function.\n"); return -1; } if (buffer == NULL) { return -1; } if ((offset >= g_spiraw_ctl->size) || (length == 0)) { return -1; } return 0; } static td_s32 spi_raw_permit(const struct mtd_partition *ptn, td_u64 offset, td_u64 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)); } static td_s32 spi_raw_read_block(struct mtd_partition *ptn, td_u64 *offset, td_u8 **buffer, td_ulong *length) { td_ulong num_read; if (*offset + *length > ptn->end) { num_read = (td_ulong)((ptn->end - *offset) + 1); } else { num_read = *length; } if (lseek(ptn->fd, (long)(*offset - ptn->start), SEEK_SET) != -1 && read(ptn->fd, *buffer, num_read) != num_read) { soc_print("read \"%s\" fail. error(%d)\n", ptn->mtddev, errno); return TD_FAILURE; } *buffer += num_read; *length -= num_read; *offset += num_read; return (td_s32)num_read; } /* * warning: * 1. startaddr should be alignment with pagesize * 2. this address maybe change when meet bad block */ td_s32 spi_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; (void)skip_badblock; if (rw_info == NULL) { return -1; } offset = rw_info->start_addr; if (spi_raw_rw_check(offset, buffer, length) < 0) { return -1; } for (ix = 0; ix < g_spiraw_ctl->num_partition && (length != 0); ix++) { struct mtd_partition *ptn = &g_spiraw_ctl->partition[ix]; /* lint -save -e655 */ if (spi_raw_permit(ptn, offset, length, ACCESS_RD) != 0) { rel = spi_raw_read_block(ptn, &offset, &buffer, &length); if (rel < 0) { return TD_FAILURE; } totalread += rel; } } rw_info->start_addr = offset; return totalread; } static td_s32 spi_raw_erase_check(td_u64 offset, td_u64 length) { if (g_spiraw_ctl == NULL) { soc_print("Please initialize before use this function.\n"); return -1; } if ((offset >= g_spiraw_ctl->size) || (length == 0)) { return -1; } if ((((td_ulong)offset & g_spiraw_ctl->blockmask) != 0) || (((td_ulong)length & g_spiraw_ctl->blockmask) != 0)) { soc_print("offset or length should be alignment with blocksize(0x%X)\n", (td_u32)g_spiraw_ctl->blocksize); return -1; } return 0; } static td_s64 spi_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); if (*offset + *length > ptn->end) { eraseinfo.length = (td_u64)((ptn->end - *offset) + 1); } else { eraseinfo.length = *length; } /* don't deal with */ if (ioctl(ptn->fd, MEMERASE64, &eraseinfo) != 0) { soc_print("Erase 0x%llx failed!\n", *offset); return TD_FAILURE; } *length -= eraseinfo.length; *offset += eraseinfo.length; return (td_s64)eraseinfo.length; } /* * warning: * 1. offset and length should be alignment with blocksize */ td_s64 spi_raw_erase(td_s32 fd, td_u64 startaddr, td_u64 length, td_u64 openaddr, td_u64 limit_len) { td_s32 ix; td_s64 totalerase = 0; td_u64 offset = startaddr; td_u64 temp_length = length; td_s64 rel; TD_UNUSED(openaddr); TD_UNUSED(fd); TD_UNUSED(limit_len); if (spi_raw_erase_check(offset, temp_length) < 0) { return -1; } if (offset + temp_length > g_spiraw_ctl->size) { temp_length = g_spiraw_ctl->size - offset; } for (ix = 0; ix < g_spiraw_ctl->num_partition && (temp_length != 0); ix++) { struct mtd_partition *ptn = &g_spiraw_ctl->partition[ix]; if (spi_raw_permit(ptn, offset, temp_length, ACCESS_WR) != 0) { if (ptn->readonly != 0) { soc_print("erase a read only partition \"%s\".\n", ptn->mtddev); return -1; } rel = spi_raw_erase_block(ptn, &offset, &temp_length); if (rel < 0) { return -1; } totalerase += rel; } } return totalerase; } static td_s32 spi_raw_write_block(struct mtd_partition *ptn, td_u64 *offset, td_u8 **buffer, td_ulong *length) { td_s32 num_write; if (*offset + *length > ptn->end) { num_write = (td_s32)((ptn->end - *offset) + 1); } else { num_write = (td_s32)*length; } if (lseek(ptn->fd, (off_t)(*offset - ptn->start), SEEK_SET) != -1 && write(ptn->fd, *buffer, (size_t)num_write) != (ssize_t)num_write) { soc_print("write \"%s\" fail. error(%d)\n", ptn->mtddev, errno); return TD_FAILURE; } *buffer += num_write; *length -= (td_u32)num_write; *offset += (td_ulong)num_write; return num_write; } /* * warning: * 1. startaddr should be alignment with pagesize */ td_s32 spi_raw_write(flash_rw_info_s *rw_info, td_u8 *buffer, td_ulong length) { td_s32 ix, rel; td_s32 totalwrite = 0; td_u64 offset; if (rw_info == NULL) { return -1; } offset = rw_info->start_addr; if (spi_raw_rw_check(offset, buffer, length) < 0) { return -1; } for (ix = 0; ix < g_spiraw_ctl->num_partition && (length != 0); ix++) { struct mtd_partition *ptn = &g_spiraw_ctl->partition[ix]; if (spi_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; } rel = spi_raw_write_block(ptn, &offset, &buffer, &length); if (rel < 0) { return TD_FAILURE; } totalwrite += rel; } } rw_info->start_addr = offset; return totalwrite; } td_s32 spi_raw_dump_partition(td_void) { td_s32 ix; struct mtd_partition *ptn = NULL; if (g_spiraw_ctl == 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_spiraw_ctl->num_partition; ix++) { ptn = &g_spiraw_ctl->partition[ix]; if (((unsigned int)ptn->perm & ACCESS_RD) == ACCESS_RD) { 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 spi_raw_destroy(td_void) { td_s32 ix; if (g_spiraw_ctl == NULL) { return 0; } for (ix = 0; ix < g_spiraw_ctl->num_partition; ix++) { if (g_spiraw_ctl->partition[ix].fd != INVALID_FD) { close(g_spiraw_ctl->partition[ix].fd); } } free(g_spiraw_ctl); g_spiraw_ctl = NULL; return 0; }