390 lines
9.4 KiB
C
390 lines
9.4 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
#define pr_fmt(fmt) "sunxi-spinand: " fmt
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/spi/spi.h>
|
|
#include <linux/mtd/aw-spinand.h>
|
|
#include <linux/mtd/partitions.h>
|
|
#include <linux/mtd/mtd.h>
|
|
|
|
#define SEC_STO_MAGIC 0x5CAA
|
|
#define SEC_STO_ITEM_BYTES (4096)
|
|
|
|
struct sec_sto_oob {
|
|
__u8 badflag;
|
|
__u16 magic;
|
|
__u32 checksum;
|
|
} __packed;
|
|
|
|
static inline bool is_init_end(struct aw_spinand_sec_sto *sec_sto)
|
|
{
|
|
return !!sec_sto->init_end;
|
|
}
|
|
|
|
static int is_secure_storage_block(struct aw_spinand_chip *chip,
|
|
unsigned int blk, unsigned int page)
|
|
{
|
|
struct aw_spinand_chip_request req = {0};
|
|
struct aw_spinand_chip_ops *ops = chip->ops;
|
|
struct sec_sto_oob oob;
|
|
int ret;
|
|
|
|
req.block = blk;
|
|
req.page = page;
|
|
req.oobbuf = &oob;
|
|
req.ooblen = sizeof(struct sec_sto_oob);
|
|
ret = ops->phy_read_page(chip, &req);
|
|
if (ret) {
|
|
pr_err("check valid secure storage failed with %d back\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
return le16_to_cpu(oob.magic) == (u16)SEC_STO_MAGIC;
|
|
}
|
|
|
|
static int get_blocks_for_secure_storage(struct aw_spinand_sec_sto *sec_sto)
|
|
{
|
|
struct aw_spinand_chip *chip = sec_sto->chip;
|
|
struct aw_spinand_chip_ops *ops = chip->ops;
|
|
struct aw_spinand_chip_request req = {0};
|
|
unsigned int i = 0, blk[2] = {0};
|
|
unsigned int start, end;
|
|
int ret;
|
|
|
|
start = sec_sto->startblk;
|
|
end = sec_sto->endblk;
|
|
/* find the frist two valid blocks */
|
|
for (; start < end; start++) {
|
|
req.block = start;
|
|
|
|
ret = ops->phy_is_bad(chip, &req);
|
|
if (ret == true)
|
|
continue;
|
|
|
|
blk[i++] = start;
|
|
if (i >= 2)
|
|
break;
|
|
}
|
|
if (i < 2) {
|
|
pr_err("no enough good blk between [%u %u) for secure storage\n",
|
|
start, end);
|
|
return -ENOSPC;
|
|
}
|
|
|
|
sec_sto->blk[0] = blk[0];
|
|
sec_sto->blk[1] = blk[1];
|
|
return 0;
|
|
}
|
|
|
|
static unsigned int secure_storage_checksum(char *_buf, unsigned int len)
|
|
{
|
|
unsigned int sum = 0;
|
|
unsigned int i;
|
|
unsigned char *buf = (unsigned char *)_buf;
|
|
|
|
for (i = 0; i < len; i++)
|
|
sum += buf[i];
|
|
return sum;
|
|
}
|
|
|
|
static int read_check_secure_storage_page(struct aw_spinand_chip *chip,
|
|
unsigned int blk, int page, char *mbuf, unsigned int len,
|
|
struct sec_sto_oob *oob)
|
|
{
|
|
struct aw_spinand_chip_ops *ops = chip->ops;
|
|
struct aw_spinand_chip_request req = {0};
|
|
struct sec_sto_oob _oob;
|
|
unsigned int checksum;
|
|
int ret;
|
|
|
|
req.block = blk;
|
|
req.page = page;
|
|
req.databuf = mbuf;
|
|
req.datalen = len;
|
|
req.oobbuf = oob ? oob : &_oob;
|
|
req.ooblen = sizeof(struct sec_sto_oob);
|
|
|
|
ret = ops->phy_read_page(chip, &req);
|
|
if (ret)
|
|
return ret;
|
|
|
|
checksum = secure_storage_checksum(mbuf, len);
|
|
if (oob)
|
|
return (unsigned int)oob->checksum != checksum;
|
|
else
|
|
return (unsigned int)_oob.checksum != checksum;
|
|
}
|
|
|
|
static int write_check_secure_storage_page(struct aw_spinand_chip *chip,
|
|
unsigned int blk, int page, char *mbuf, unsigned int len,
|
|
struct sec_sto_oob *oob)
|
|
{
|
|
struct aw_spinand_chip_ops *ops = chip->ops;
|
|
struct aw_spinand_chip_request req = {0};
|
|
int ret;
|
|
|
|
req.block = blk;
|
|
req.page = page;
|
|
req.databuf = mbuf;
|
|
req.datalen = len;
|
|
req.oobbuf = oob;
|
|
req.ooblen = oob ? sizeof(struct sec_sto_oob) : 0;
|
|
|
|
if (req.page == 0) {
|
|
ret = ops->phy_erase_block(chip, &req);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
ret = ops->phy_write_page(chip, &req);
|
|
if (ret)
|
|
pr_err("write secure storage failed: %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
#define BOTH_SEC_BLKS_GOOD 0
|
|
#define FIRST_SEC_BLK_GOOD 1
|
|
#define SECOND_SEC_BLK_GOOD 2
|
|
static int check_secure_storage(struct aw_spinand_sec_sto *sec_sto)
|
|
{
|
|
struct aw_spinand_chip *chip = sec_sto->chip;
|
|
struct aw_spinand_info *info = chip->info;
|
|
int ret, ret1, ret2, i, blk1, blk2;
|
|
unsigned int pagecnt;
|
|
char *buf;
|
|
|
|
buf = kmalloc(info->phy_page_size(chip), GFP_KERNEL);
|
|
if (!buf)
|
|
return -ENOMEM;
|
|
|
|
blk1 = sec_sto->blk[0];
|
|
blk2 = sec_sto->blk[1];
|
|
pagecnt = info->phy_block_size(chip) / info->phy_page_size(chip);
|
|
for (i = 0; i < pagecnt; i++) {
|
|
ret1 = read_check_secure_storage_page(chip, blk1, i, buf,
|
|
info->phy_page_size(chip), NULL);
|
|
ret2 = read_check_secure_storage_page(chip, blk2, i, buf,
|
|
info->phy_page_size(chip), NULL);
|
|
if (ret1 != 0 || ret2 != 0)
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* retX negative: read failed
|
|
* retX 0: read and check good
|
|
* retX 1: read good but check failed
|
|
*/
|
|
if (ret1 == 0 && ret2 == 0)
|
|
ret = BOTH_SEC_BLKS_GOOD;
|
|
else if (ret1 == 0 && ret2 != 0)
|
|
ret = FIRST_SEC_BLK_GOOD;
|
|
else if (ret2 == 0 && ret1 != 0)
|
|
ret = SECOND_SEC_BLK_GOOD;
|
|
else if (ret1 == 1 && ret2 == 1)
|
|
ret = BOTH_SEC_BLKS_GOOD;
|
|
else
|
|
ret = -EINVAL;
|
|
|
|
if (ret < 0)
|
|
pr_err("nand secure storage check page %u fail\n", i);
|
|
kfree(buf);
|
|
return ret;
|
|
}
|
|
|
|
static int repair_secure_storage(struct aw_spinand_sec_sto *sec_sto,
|
|
int good_blk_index)
|
|
{
|
|
struct aw_spinand_chip *chip = sec_sto->chip;
|
|
struct aw_spinand_chip_ops *ops = chip->ops;
|
|
unsigned int from, to;
|
|
int ret;
|
|
|
|
if (good_blk_index == FIRST_SEC_BLK_GOOD) {
|
|
from = sec_sto->blk[0];
|
|
to = sec_sto->blk[1];
|
|
} else {
|
|
from = sec_sto->blk[1];
|
|
to = sec_sto->blk[0];
|
|
}
|
|
|
|
ret = ops->phy_copy_block(chip, from, to);
|
|
if (ret)
|
|
pr_err("copy phy block from %u to %u failed: %d\n", from, to, ret);
|
|
return ret;
|
|
}
|
|
|
|
static int init_secure_storage(struct aw_spinand_sec_sto *sec_sto, int update)
|
|
{
|
|
int ret;
|
|
|
|
ret = get_blocks_for_secure_storage(sec_sto);
|
|
if (ret)
|
|
return ret;
|
|
pr_info("spinand secure storage ok for phy blk %u and %u\n",
|
|
sec_sto->blk[0], sec_sto->blk[1]);
|
|
|
|
/*
|
|
* both of blk1 and blk2 has no valid data, it may be no any data had
|
|
* been writen to secure storage.
|
|
*/
|
|
ret = is_secure_storage_block(sec_sto->chip, sec_sto->blk[0], 0);
|
|
if (ret == false) {
|
|
ret = is_secure_storage_block(sec_sto->chip, sec_sto->blk[1], 0);
|
|
if (ret == false) {
|
|
pr_info("secure storage blks have never used before\n");
|
|
goto end;
|
|
}
|
|
}
|
|
|
|
ret = check_secure_storage(sec_sto);
|
|
if (ret < 0) {
|
|
pr_err("check secure storage failed with %d\n", ret);
|
|
return ret;
|
|
} else if (ret > 0) {
|
|
pr_info("try to repair secure storage from block %u\n",
|
|
sec_sto->blk[ret - 1]);
|
|
ret = repair_secure_storage(sec_sto, ret);
|
|
if (ret) {
|
|
pr_err("repair secure storage failed with %d\n", ret);
|
|
return ret;
|
|
}
|
|
}
|
|
end:
|
|
pr_debug("init secure storage ok\n");
|
|
sec_sto->init_end = true;
|
|
return 0;
|
|
}
|
|
|
|
int aw_spinand_secure_storage_read(struct aw_spinand_sec_sto *sec_sto,
|
|
int item, char *buf, unsigned int len)
|
|
{
|
|
struct aw_spinand_chip *chip = sec_sto->chip;
|
|
struct aw_spinand_info *info = chip->info;
|
|
int ret = 0, pages_cnt_per_item, minlen, i;
|
|
char *pagebuf;
|
|
unsigned int page, pagesize;
|
|
|
|
pr_debug("try to read item %d with len %d\n", item, len);
|
|
if (!sec_sto || !sec_sto->chip)
|
|
return -EINVAL;
|
|
|
|
if (len % 1024) {
|
|
pr_err("secure storage need len (%d) align to 1024B\n", len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!is_init_end(sec_sto)) {
|
|
ret = init_secure_storage(sec_sto, 1);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
pagebuf = kzalloc(info->phy_page_size(chip), GFP_KERNEL);
|
|
if (!pagebuf) {
|
|
pr_err("no memory for reading secure storage page\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
pagesize = info->phy_page_size(chip);
|
|
pages_cnt_per_item = DIV_ROUND_UP(len, pagesize);
|
|
for (i = 0; i < pages_cnt_per_item; i++) {
|
|
page = item * pages_cnt_per_item + i;
|
|
|
|
ret = read_check_secure_storage_page(chip, sec_sto->blk[0],
|
|
page, pagebuf, pagesize, NULL);
|
|
if (ret)
|
|
ret = read_check_secure_storage_page(chip,
|
|
sec_sto->blk[1], page, pagebuf,
|
|
pagesize, NULL);
|
|
|
|
if (ret < 0) {
|
|
pr_err("read secure storage page failed with %d back\n", ret);
|
|
break;
|
|
} else if (ret == true) {
|
|
pr_info("secure storage has no valid data on item %d\n", item);
|
|
break;
|
|
}
|
|
|
|
minlen = min(len, pagesize);
|
|
memcpy(buf + i * pagesize, pagebuf, minlen);
|
|
len -= minlen;
|
|
}
|
|
|
|
kfree(pagebuf);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(aw_spinand_secure_storage_read);
|
|
|
|
int aw_spinand_secure_storage_write(struct aw_spinand_sec_sto *sec_sto,
|
|
int item, char *buf, unsigned int len)
|
|
{
|
|
struct aw_spinand_chip *chip = sec_sto->chip;
|
|
struct aw_spinand_info *info = chip->info;
|
|
int ret = 0, pages_cnt_per_item, minlen;
|
|
char *pagebuf;
|
|
struct sec_sto_oob oob;
|
|
unsigned int pagenum, pagesize, pagecnt;
|
|
|
|
pr_debug("try to write item %d with len %d\n", item, len);
|
|
if (!sec_sto || !sec_sto->chip)
|
|
return -EINVAL;
|
|
|
|
if (len % 1024) {
|
|
pr_err("secure storage need len (%d) align to 1024B\n", len);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!is_init_end(sec_sto)) {
|
|
ret = init_secure_storage(sec_sto, 1);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
pagesize = info->phy_page_size(chip);
|
|
pagecnt = info->phy_block_size(chip) / info->phy_page_size(chip);
|
|
pagebuf = kzalloc(pagesize, GFP_KERNEL);
|
|
if (!pagebuf)
|
|
return -ENOMEM;
|
|
|
|
pages_cnt_per_item = DIV_ROUND_UP(len, pagesize);
|
|
for (pagenum = 0; pagenum < pagecnt; pagenum++) {
|
|
if (pagenum / pages_cnt_per_item == item) {
|
|
unsigned int off;
|
|
|
|
memset(pagebuf, 0xFF, pagesize);
|
|
minlen = min(len, pagesize);
|
|
off = pagesize * (pagenum % pages_cnt_per_item);
|
|
len -= minlen;
|
|
|
|
memcpy(pagebuf, buf + off, minlen);
|
|
|
|
oob.badflag = 0xFF;
|
|
oob.magic = cpu_to_le16(SEC_STO_MAGIC);
|
|
oob.checksum = secure_storage_checksum(pagebuf, pagesize);
|
|
} else {
|
|
memset(pagebuf, 0, pagesize);
|
|
ret = read_check_secure_storage_page(chip,
|
|
sec_sto->blk[0], pagenum,
|
|
pagebuf, pagesize, &oob);
|
|
if (ret < 0)
|
|
goto out;
|
|
}
|
|
ret = write_check_secure_storage_page(chip, sec_sto->blk[1],
|
|
pagenum, pagebuf, pagesize, &oob);
|
|
if (ret)
|
|
goto out;
|
|
}
|
|
|
|
ret = repair_secure_storage(sec_sto, SECOND_SEC_BLK_GOOD);
|
|
if (!ret)
|
|
pr_info("write secure storage itme %d ok\n", item);
|
|
out:
|
|
kfree(pagebuf);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(aw_spinand_secure_storage_write);
|