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.

559 lines
14 KiB

/*
* 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 <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <limits.h>
#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;
}