543 lines
11 KiB
C
543 lines
11 KiB
C
/* secure no-voliate memory(nand/emmc) driver for sunxi platform
|
|
*
|
|
* Copyright (C) 2014 Allwinner Ltd.
|
|
*
|
|
* Author:
|
|
* Ryan Chen <yanjianbo@allwinnertech.com>
|
|
*
|
|
* This software is licensed under the terms of the GNU General Public
|
|
* License version 2, as published by the Free Software Foundation, and
|
|
* may be copied, distributed, and modified under those terms.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
*/
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/printk.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/fs.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/crc32.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/io.h>
|
|
#include <linux/module.h>
|
|
#include <linux/ioctl.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/workqueue.h>
|
|
|
|
#include "sst_storage.h"
|
|
|
|
#ifdef OEM_STORE_IN_FS
|
|
#define EMMC_SEC_STORE "/data/oem_secure_store"
|
|
#endif
|
|
|
|
#define SEC_BLK_SIZE (4096)
|
|
#define MAX_SECURE_STORAGE_MAX_ITEM (32)
|
|
static unsigned int secure_storage_inited;
|
|
|
|
/*
|
|
* EMMC parameters
|
|
*/
|
|
#define SDMMC_SECTOR_SIZE (512)
|
|
#define SDMMC_SECURE_STORAGE_START_ADD (6*1024*1024/512)
|
|
#define SDMMC_ITEM_SIZE (4*1024/512)
|
|
static char *sd_oem_path = "/dev/block/mmcblk0";
|
|
|
|
/*
|
|
* Nand parameters
|
|
*/
|
|
static char *nand_oem_path = "/dev/block/by-name/bootloader";
|
|
static struct secblc_op_t secblk_op;
|
|
static fcry_pt nfcr;
|
|
/*
|
|
* secure storage map
|
|
*
|
|
* section 0:
|
|
* name1:length1
|
|
* name2:length2
|
|
* ...
|
|
* section 1:
|
|
* data1 ( name1 data )
|
|
* section 2 :
|
|
* data2 ( name2 data )
|
|
* ...
|
|
*/
|
|
|
|
#define FLASH_TYPE_NAND 0
|
|
#define FLASH_TYPE_SD1 1
|
|
#define FLASH_TYPE_SD2 2
|
|
#define FLASH_TYPE_UNKNOW -1
|
|
|
|
static int flash_boot_type = FLASH_TYPE_UNKNOW;
|
|
|
|
#define SST_STORAGE_READ _IO('V', 1)
|
|
#define SST_STORAGE_WRITE _IO('V', 2)
|
|
|
|
extern char *saved_command_line;
|
|
|
|
void sunxi_dump(void *addr, unsigned int size)
|
|
{
|
|
int j;
|
|
char *buf = (char *)addr;
|
|
|
|
for (j = 0; j < size; j++) {
|
|
printk("%02x ", buf[j] & 0xff);
|
|
if (j%15 == 0 && j) {
|
|
printk("\n");
|
|
}
|
|
}
|
|
printk("\n");
|
|
return ;
|
|
}
|
|
|
|
int _sst_user_ioctl(char *filename, int ioctl, void *param)
|
|
{
|
|
struct file *fd;
|
|
int ret = -1;
|
|
|
|
mm_segment_t old_fs = get_fs();
|
|
|
|
if (!filename) {
|
|
pr_err("- filename NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
fd = filp_open(filename, O_WRONLY|O_CREAT, 0666);
|
|
|
|
if (IS_ERR(fd)) {
|
|
pr_err(" -file open fail\n");
|
|
return -1;
|
|
}
|
|
do {
|
|
if ((fd->f_op == NULL) || (fd->f_op->unlocked_ioctl == NULL)) {
|
|
pr_info(" -file can't to write!!\n");
|
|
break;
|
|
}
|
|
|
|
ret = fd->f_op->unlocked_ioctl(
|
|
fd,
|
|
ioctl,
|
|
(unsigned long)(param));
|
|
|
|
} while (false);
|
|
|
|
vfs_fsync(fd, 0);
|
|
filp_close(fd, NULL);
|
|
set_fs(old_fs);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int _sst_user_read(char *filename, char *buf, ssize_t len, int offset)
|
|
{
|
|
struct file *fd;
|
|
int retLen = -1;
|
|
mm_segment_t old_fs;
|
|
|
|
if (!filename || !buf) {
|
|
pr_err("- filename/buf NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
fd = filp_open(filename, O_RDONLY, 0);
|
|
|
|
if (IS_ERR(fd)) {
|
|
pr_err(" -file open fail\n");
|
|
return -1;
|
|
}
|
|
do {
|
|
if ((fd->f_op == NULL) || (fd->f_op->read == NULL)) {
|
|
pr_err(" -file can't to open!!\n");
|
|
break;
|
|
}
|
|
|
|
if (fd->f_pos != offset) {
|
|
if (fd->f_op->llseek) {
|
|
if (fd->f_op->llseek(fd, offset, 0) != offset) {
|
|
pr_err(" -failed to seek!!\n");
|
|
break;
|
|
}
|
|
} else {
|
|
fd->f_pos = offset;
|
|
}
|
|
}
|
|
|
|
retLen = fd->f_op->read(fd,
|
|
buf,
|
|
len,
|
|
&fd->f_pos);
|
|
|
|
} while (false);
|
|
|
|
filp_close(fd, NULL);
|
|
set_fs(old_fs);
|
|
|
|
return retLen;
|
|
}
|
|
|
|
int _sst_user_write(char *filename, char *buf, ssize_t len, int offset)
|
|
{
|
|
struct file *fd;
|
|
int retLen = -1;
|
|
mm_segment_t old_fs = get_fs();
|
|
|
|
pr_info("Write to %s\n", filename);
|
|
if (!filename || !buf) {
|
|
pr_err("- filename/buf NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
old_fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
fd = filp_open(filename, O_WRONLY|O_CREAT, 0666);
|
|
|
|
if (IS_ERR(fd)) {
|
|
pr_err(" -file open fail %s \n", filename);
|
|
return -1;
|
|
}
|
|
do {
|
|
if ((fd->f_op == NULL) || (fd->f_op->write == NULL)) {
|
|
pr_err(" -file can't to write!!\n");
|
|
break;
|
|
}
|
|
|
|
if (fd->f_pos != offset) {
|
|
if (fd->f_op->llseek) {
|
|
if (fd->f_op->llseek(fd, offset, 0) != offset) {
|
|
pr_err(" -failed to seek!!\n");
|
|
break;
|
|
}
|
|
} else {
|
|
fd->f_pos = offset;
|
|
}
|
|
}
|
|
|
|
retLen = fd->f_op->write(fd,
|
|
buf,
|
|
len,
|
|
&fd->f_pos);
|
|
|
|
} while (false);
|
|
|
|
vfs_fsync(fd, 0);
|
|
filp_close(fd, NULL);
|
|
set_fs(old_fs);
|
|
|
|
pr_info("Write %x to %s done\n", retLen, filename);
|
|
return retLen;
|
|
}
|
|
|
|
static int get_para_from_cmdline(const char *cmdline, const char *name, char *value)
|
|
{
|
|
char *value_p = value;
|
|
|
|
if (!cmdline || !name || !value) {
|
|
return -1;
|
|
}
|
|
|
|
for (; *cmdline != 0;) {
|
|
if (*cmdline++ == ' ') {
|
|
if (0 == strncmp(cmdline, name, strlen(name))) {
|
|
cmdline += strlen(name);
|
|
if (*cmdline++ != '=') {
|
|
continue;
|
|
}
|
|
while (*cmdline != 0 && *cmdline != ' ') {
|
|
*value_p++ = *cmdline++;
|
|
}
|
|
return value_p - value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_flash_type(void)
|
|
{
|
|
char ctype[16];
|
|
|
|
memset(ctype, 0, 16);
|
|
if (get_para_from_cmdline(saved_command_line , "boot_type",
|
|
ctype) <= 0) {
|
|
pr_err("Get boot type cmd line fail\n");
|
|
return -1;
|
|
}
|
|
|
|
flash_boot_type = simple_strtol(ctype, NULL, 10);
|
|
pr_info("Boot type %d\n", flash_boot_type);
|
|
return 0;
|
|
}
|
|
|
|
/*nand secure storage read/write*/
|
|
static int _nand_read(int id, char *buf, ssize_t len)
|
|
{
|
|
if (!buf) {
|
|
pr_err("-buf NULL\n");
|
|
return -1;
|
|
}
|
|
if (id > MAX_SECURE_STORAGE_MAX_ITEM) {
|
|
pr_err("out of range id %x\n", id);
|
|
return -1;
|
|
}
|
|
secblk_op.item = id;
|
|
secblk_op.buf = buf;
|
|
secblk_op.len = len;
|
|
|
|
return _sst_user_ioctl(nand_oem_path, SECBLK_READ, &secblk_op);
|
|
}
|
|
|
|
static int _nand_write(int id, char *buf, ssize_t len)
|
|
{
|
|
if (!buf) {
|
|
pr_err("- buf NULL\n");
|
|
return -1;
|
|
}
|
|
|
|
if (id > MAX_SECURE_STORAGE_MAX_ITEM) {
|
|
pr_err("out of range id %x\n", id);
|
|
return -1;
|
|
}
|
|
secblk_op.item = id;
|
|
secblk_op.buf = buf;
|
|
secblk_op.len = len;
|
|
|
|
return _sst_user_ioctl(nand_oem_path, SECBLK_WRITE, &secblk_op);
|
|
}
|
|
|
|
/*emmc secure storage read/write*/
|
|
static int _sd_read(char *buf, int len, int offset)
|
|
{
|
|
int ret ;
|
|
|
|
ret = _sst_user_read(sd_oem_path, buf, len, offset);
|
|
if (ret != len) {
|
|
pr_err("_sst_user_read: read request len 0x%x, actual read 0x%x\n",
|
|
len, ret);
|
|
return -1;
|
|
}
|
|
return 0 ;
|
|
}
|
|
|
|
static int _sd_write(char *buf, int len, int offset)
|
|
{
|
|
int ret;
|
|
|
|
ret = _sst_user_write(sd_oem_path, buf, len, offset);
|
|
if (ret != len) {
|
|
pr_err("_sst_user_write: write request len 0x%x, actual write 0x%x\n",
|
|
len, ret);
|
|
return -1;
|
|
}
|
|
return 0 ;
|
|
}
|
|
|
|
static int nv_write(int id, char *buf, ssize_t len, ssize_t offset)
|
|
{
|
|
int ret ;
|
|
switch (flash_boot_type) {
|
|
case FLASH_TYPE_NAND:
|
|
ret = _nand_write(id, buf, len);
|
|
break;
|
|
case FLASH_TYPE_SD1:
|
|
case FLASH_TYPE_SD2:
|
|
ret = _sd_write(buf, len, offset);
|
|
break;
|
|
default:
|
|
pr_err("Unknown no-volatile device\n");
|
|
ret = -1 ;
|
|
break;
|
|
}
|
|
|
|
return ret ;
|
|
}
|
|
|
|
static int nv_read(int id, char *buf, ssize_t len, ssize_t offset)
|
|
{
|
|
int ret ;
|
|
switch (flash_boot_type) {
|
|
case FLASH_TYPE_NAND:
|
|
ret = _nand_read(id, buf, len);
|
|
break;
|
|
case FLASH_TYPE_SD1:
|
|
case FLASH_TYPE_SD2:
|
|
ret = _sd_read(buf, len, offset);
|
|
break;
|
|
default:
|
|
pr_err("Unknown no-volatile device\n");
|
|
ret = -1 ;
|
|
break;
|
|
}
|
|
|
|
return ret ;
|
|
}
|
|
|
|
static int sst_storage_open(struct inode *inode, struct file *file)
|
|
{
|
|
int ret = 0;
|
|
return ret;
|
|
}
|
|
static int sst_storage_release(struct inode *inode, struct file *file)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static long sst_storage_ioctl(struct file *file, unsigned int ioctl_num,
|
|
unsigned long ioctl_param)
|
|
{
|
|
int err = 0;
|
|
|
|
mutex_lock(&nfcr->mutex);
|
|
if (copy_from_user(&nfcr->key_store, (void __user *)ioctl_param, sizeof(nfcr->key_store))) {
|
|
err = -EFAULT;
|
|
goto _out;
|
|
}
|
|
nfcr->cmd = ioctl_num;
|
|
|
|
#if 0
|
|
pr_info("size of struct sst_storage_data %d\n", sizeof(struct sst_storage_data));
|
|
pr_info("sst_storage_ioctl: ioctl_num=%d\n", ioctl_num);
|
|
pr_info("nfcr = %p\n", nfcr);
|
|
pr_info("id = %d\n", nfcr->key_store.id);
|
|
pr_info("len = %d\n", nfcr->key_store.len);
|
|
pr_info("offset = %d\n", nfcr->key_store.offset);
|
|
#endif
|
|
|
|
switch (ioctl_num) {
|
|
case SST_STORAGE_READ:
|
|
schedule_work(&nfcr->work);
|
|
wait_for_completion(&nfcr->work_end);
|
|
/*sunxi_dump(nfcr->temp_data, 50);*/
|
|
err = nfcr->ret;
|
|
if (!err) {
|
|
if (copy_to_user((void __user *)(unsigned long)nfcr->key_store.buf, nfcr->temp_data, nfcr->key_store.len)) {
|
|
err = -EFAULT;
|
|
pr_err("copy_to_user: err:%d\n", err);
|
|
goto _out;
|
|
}
|
|
}
|
|
break;
|
|
case SST_STORAGE_WRITE:
|
|
if (copy_from_user(nfcr->temp_data, (void __user *)(unsigned long)nfcr->key_store.buf, nfcr->key_store.len)) {
|
|
err = -EFAULT;
|
|
pr_err("copy_from_user: err:%d\n", err);
|
|
goto _out;
|
|
}
|
|
schedule_work(&nfcr->work);
|
|
wait_for_completion(&nfcr->work_end);
|
|
err = nfcr->ret;
|
|
break;
|
|
default:
|
|
pr_err("sst_storage_ioctl: un supported cmd:%d\n", ioctl_num);
|
|
break;
|
|
}
|
|
_out:
|
|
memset(nfcr->temp_data, 0, SEC_BLK_SIZE);
|
|
memset(&nfcr->key_store, 0, sizeof(nfcr->key_store));
|
|
nfcr->cmd = -1;
|
|
mutex_unlock(&nfcr->mutex);
|
|
return err;
|
|
}
|
|
|
|
static const struct file_operations sst_storage_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = sst_storage_open,
|
|
.release = sst_storage_release,
|
|
.unlocked_ioctl = sst_storage_ioctl,
|
|
.compat_ioctl = sst_storage_ioctl,
|
|
};
|
|
|
|
struct miscdevice sst_storage_device = {
|
|
.minor = MISC_DYNAMIC_MINOR,
|
|
.name = "sst_storage",
|
|
.fops = &sst_storage_ops,
|
|
};
|
|
|
|
static void sst_storage_work(struct work_struct *data)
|
|
{
|
|
fcry_pt fcpt = container_of(data, struct fcrypt, work);
|
|
|
|
switch (fcpt->cmd) {
|
|
case SST_STORAGE_READ:
|
|
fcpt->ret = nv_read(fcpt->key_store.id, fcpt->temp_data, fcpt->key_store.len, fcpt->key_store.offset);
|
|
break;
|
|
case SST_STORAGE_WRITE:
|
|
fcpt->ret = nv_write(fcpt->key_store.id, fcpt->temp_data, fcpt->key_store.len, fcpt->key_store.offset);
|
|
break;
|
|
default:
|
|
fcpt->ret = -1;
|
|
pr_err("sst_storage_work: un supported cmd:%d\n", fcpt->cmd);
|
|
break;
|
|
}
|
|
|
|
if ((fcpt->cmd == SST_STORAGE_READ) || (fcpt->cmd == SST_STORAGE_WRITE))
|
|
complete(&fcpt->work_end);
|
|
}
|
|
|
|
static void __exit sunxi_secure_storage_exit(void)
|
|
{
|
|
int ret;
|
|
pr_debug("sunxi secure storage driver exit\n");
|
|
|
|
ret = misc_deregister(&sst_storage_device);
|
|
if (ret) {
|
|
pr_err("%s: cannot deregister miscdev.(return value-%d)\n", __func__, ret);
|
|
}
|
|
kfree(nfcr->temp_data);
|
|
kfree(nfcr);
|
|
}
|
|
|
|
static int __init sunxi_secure_storage_init(void)
|
|
{
|
|
int ret;
|
|
|
|
if (!secure_storage_inited) {
|
|
get_flash_type();
|
|
}
|
|
secure_storage_inited = 1;
|
|
|
|
ret = misc_register(&sst_storage_device);
|
|
if (ret) {
|
|
pr_err("%s: cannot deregister miscdev.(return value-%d)\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
nfcr = kmalloc(sizeof(*nfcr), GFP_KERNEL);
|
|
if (!nfcr) {
|
|
pr_err(" - Malloc failed\n");
|
|
return -1;
|
|
}
|
|
memset(nfcr, 0, sizeof(*nfcr));
|
|
|
|
nfcr->temp_data = kmalloc(SEC_BLK_SIZE, GFP_KERNEL);
|
|
if (!nfcr->temp_data) {
|
|
pr_err("sst_storage_ioctl: error to kmalloc\n");
|
|
return -1;
|
|
}
|
|
memset(nfcr->temp_data, 0, SEC_BLK_SIZE);
|
|
INIT_WORK(&nfcr->work, sst_storage_work);
|
|
init_completion(&nfcr->work_end);
|
|
mutex_init(&nfcr->mutex);
|
|
|
|
return 0;
|
|
}
|
|
|
|
module_init(sunxi_secure_storage_init);
|
|
module_exit(sunxi_secure_storage_exit);
|
|
MODULE_LICENSE ("GPL");
|
|
MODULE_AUTHOR ("yanjianbo");
|
|
MODULE_DESCRIPTION ("secure storage");
|