/* secure no-voliate memory(nand/emmc) driver for sunxi platform * * Copyright (C) 2014 Allwinner Ltd. * * Author: * Ryan Chen * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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");