| /* |
| * sirf security driver for sirf chips |
| * |
| * Copyright 2014 (C) CSR plc. |
| * Huayi Li <huayi.li@csr.com> |
| * |
| * Licensed under the GPL-2 or later. |
| */ |
| #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
| |
| #include <linux/device.h> |
| #include <linux/firmware.h> |
| #include <linux/io.h> |
| #include <linux/miscdevice.h> |
| #include <linux/module.h> |
| #include <linux/of.h> |
| #include <linux/of_address.h> |
| #include <linux/proc_fs.h> |
| #include <linux/seq_file.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| #include <linux/kernel.h> |
| #include <linux/atomic.h> |
| #include <asm/cacheflush.h> |
| |
| #define SIRF_SECURITY_DEV 128 |
| |
| /* |
| * This version MUST be updated after the interface is modified, so that |
| * force the wrap driver know the update. |
| * format: unsigned integer, increased in each update. |
| */ |
| #define SIRF_SECURITY_INTERFACE_VERSION 1 |
| |
| /* |
| * define the IOCTRL code exported by sirf security |
| */ |
| #define IOCTRL_CATEGORY_BITS 8 |
| #define IOCTRL_CATEGORY_SHIFT \ |
| (sizeof(unsigned)*8 - (IOCTRL_CATEGORY_BITS)) |
| #define IOCTRL_CATEGORY_MASK \ |
| (((0x1<<(IOCTRL_CATEGORY_BITS)) - 1)<<IOCTRL_CATEGORY_SHIFT) |
| #define IOCTRL_CATEGORY(ioctrl) \ |
| (((ioctrl) & IOCTRL_CATEGORY_MASK) >> IOCTRL_CATEGORY_SHIFT) |
| |
| enum SIRF_SECURITY_IOCTRL_CATEGORY { |
| CONFIG_IOCTRL = 1, |
| CHIP_IOCTRL, |
| MAX_IOCTRL |
| }; |
| |
| #define SIRFSEC_IOCTRL_CODE(category, function) \ |
| (((category##_IOCTRL)<<IOCTRL_CATEGORY_SHIFT) | (function)) |
| |
| |
| /* config IOCTRL */ |
| #define SIRFSEC_IOCTRL_RUNTIME_INIT SIRFSEC_IOCTRL_CODE(CONFIG, 0) |
| #define SIRFSEC_IOCTRL_GET_INTERFACE_VERSION SIRFSEC_IOCTRL_CODE(CONFIG, 1) |
| #define SIRFSEC_IOCTRL_GET_VERSION SIRFSEC_IOCTRL_CODE(CONFIG, 2) |
| #define SIRFSEC_IOCTRL_ENABLE_MSG SIRFSEC_IOCTRL_CODE(CONFIG, 3) |
| #define SIRFSEC_IOCTRL_NEW_ADDRESS_MAP SIRFSEC_IOCTRL_CODE(CONFIG, 4) |
| |
| /* CHIP IOCTRL */ |
| #define SIRFSEC_IOCTRL_GET_UUID SIRFSEC_IOCTRL_CODE(CHIP, 0) |
| #define SIRFSEC_IOCTRL_GET_CHID SIRFSEC_IOCTRL_CODE(CHIP, 1) |
| |
| struct ADDRMAP { |
| void *va; |
| unsigned pa; |
| unsigned size; |
| unsigned flag; |
| }; |
| |
| /* |
| *PFN_SIRF_SECURITY_IOCTRL |
| * the entry function exported by sirf security . |
| * this function is at the offset 0 of sirf security binary |
| *parameters: |
| * ioctrl_code: |
| * the code to select which function on the object specified by handle. |
| * in_buf, in_buf_size: |
| * the input buffer point and its size in unit byte. |
| * out_buf, out_buf_size: |
| * the output buffer point and its size in unit byte. |
| * return: |
| * zero: failed; non-zero: successful. |
| */ |
| typedef unsigned (*PFN_SIRF_SECURITY_IOCTRL) ( |
| unsigned ioctrl_code, |
| void *in_buf, |
| unsigned in_buf_size, |
| void *out_buf, |
| unsigned out_buf_size); |
| |
| struct sirf_security { |
| /* os disk info */ |
| unsigned int version; |
| unsigned int size; |
| spinlock_t lock; |
| atomic_t ref; |
| /* address map */ |
| struct ADDRMAP *addr_map_tbl; |
| unsigned addr_entry_num; |
| /* sirf security call entry */ |
| PFN_SIRF_SECURITY_IOCTRL pfn_ioctrl; |
| /* miscdevie in global */ |
| struct miscdevice miscdev; |
| }; |
| |
| /* instance type for procfs */ |
| struct sirf_security_inst { |
| /* user input seed */ |
| unsigned long seed; |
| /* sirf security call entry copied from global */ |
| PFN_SIRF_SECURITY_IOCTRL pfn_ioctrl; |
| }; |
| |
| static int sirfsec_open(struct inode *inode, struct file *file) |
| { |
| /* file->private_data is miscdevice, keep it for further usage*/ |
| /* multi-open is disabled */ |
| struct sirf_security *ss = container_of(file->private_data, |
| struct sirf_security, miscdev); |
| |
| if (!atomic_add_unless(&ss->ref, 1, 1)) |
| return -EBUSY; |
| |
| return 0; |
| } |
| |
| static int sirfsec_close(struct inode *inode, struct file *file) |
| { |
| struct sirf_security *ss = container_of(file->private_data, |
| struct sirf_security, miscdev); |
| |
| atomic_dec(&ss->ref); |
| |
| return 0; |
| } |
| |
| static long sirfsec_ioctl(struct file *file, |
| unsigned int cmd, unsigned long arg) |
| { |
| void *ptr; |
| unsigned long len; |
| struct sirf_security *ss = container_of(file->private_data, |
| struct sirf_security, miscdev); |
| |
| /* leading int is the data length */ |
| if (access_ok(VERIFY_READ | VERIFY_WRITE, arg, sizeof(arg))) |
| len = *(unsigned long *)arg; |
| else |
| return -EFAULT; |
| |
| if (len > 0x400 || !ss) |
| return -EINVAL; |
| |
| /* use user ptr since firmware don't schedule*/ |
| if (access_ok(VERIFY_READ | VERIFY_WRITE, arg, len)) |
| ptr = (void *)arg; |
| else |
| return -EFAULT; |
| |
| if (!ss->pfn_ioctrl(cmd, ptr, len, ptr, len)) { |
| pr_info("user cmd: %x failed.\n", cmd); |
| return -EIO; |
| } |
| |
| return 0; |
| } |
| |
| static const struct file_operations sirfsec_fops = { |
| .owner = THIS_MODULE, |
| .open = sirfsec_open, |
| .release = sirfsec_close, |
| .unlocked_ioctl = sirfsec_ioctl, |
| }; |
| |
| static struct sirf_security sirf_sec = { |
| .miscdev.minor = SIRF_SECURITY_DEV, |
| .miscdev.name = "csrsec", |
| .miscdev.fops = &sirfsec_fops, |
| }; |
| |
| /* |
| * sirf security module include some controllers, all of these |
| * controllers are managed by sirf security module. |
| */ |
| |
| struct arch_res { |
| char *res_compatible; |
| int index; |
| }; |
| |
| struct sirf_security_resource { |
| const char *arch_compatible; |
| unsigned char res_num; |
| struct arch_res *arch_res; |
| }; |
| |
| struct arch_res prima2_res[] = { |
| {"sirf,prima2-intc", 0}, |
| {"sirf,prima2-clkc", 0}, |
| {"sirf,prima2-uart", 1}, |
| {"sirf,prima2-efuse", 0}, |
| {"sirf,prima2-rtciobg", 0}, |
| }; |
| |
| struct arch_res atlas6_res[] = { |
| {"sirf,prima2-intc", 0}, |
| {"sirf,atlas6-clkc", 0}, |
| {"sirf,prima2-uart", 1}, |
| {"sirf,prima2-efuse", 0}, |
| {"sirf,prima2-rtciobg", 0}, |
| }; |
| |
| static struct sirf_security_resource sirfsec_nres[] = { |
| { |
| .arch_compatible = "sirf,prima2", |
| .res_num = ARRAY_SIZE(prima2_res), |
| .arch_res = prima2_res, |
| }, |
| { |
| .arch_compatible = "sirf,atlas6", |
| .res_num = ARRAY_SIZE(atlas6_res), |
| .arch_res = atlas6_res, |
| }, |
| }; |
| |
| static int proc_uuid_show(struct seq_file *m, void *v) |
| { |
| unsigned int out[8]; |
| unsigned res; |
| struct sirf_security_inst *ssi = m->private; |
| |
| /* check seed validation, ZERO means uuid; otherwis chid */ |
| if (!ssi->seed) |
| res = ssi->pfn_ioctrl(SIRFSEC_IOCTRL_GET_UUID, |
| out, sizeof(out), NULL, 0); |
| else |
| res = ssi->pfn_ioctrl(SIRFSEC_IOCTRL_GET_CHID, |
| &ssi->seed, sizeof(ssi->seed), |
| out, sizeof(out)); |
| |
| ssi->seed = 0; |
| |
| if (!res) { |
| pr_info("SIRFSEC_IOCTRL_CHIP_CHECK failed.\n"); |
| return -EIO; |
| } |
| |
| seq_printf(m, "%08x%08x%08x%08x%08x%08x\n", |
| out[0], out[1], out[2], out[3], out[4], out[5]); |
| |
| return 0; |
| } |
| |
| static int uuid_proc_open(struct inode *inode, struct file *file) |
| { |
| /* multi callers may exist for getting procfs node informations, |
| alloc instance for each caller, free in release function */ |
| struct sirf_security_inst *ssi; |
| struct sirf_security *ss = |
| (struct sirf_security *)PDE_DATA(inode); |
| |
| ssi = kzalloc(sizeof(struct sirf_security_inst), GFP_KERNEL); |
| if (!ssi) |
| return -ENOMEM; |
| |
| /* copy necessary members */ |
| ssi->pfn_ioctrl = ss->pfn_ioctrl; |
| |
| return single_open(file, proc_uuid_show, ssi); |
| } |
| |
| /* processing user input seed -- a 'unsigned int' value in string format */ |
| static ssize_t uuid_proc_write(struct file *file, const char __user *buf, |
| size_t count, loff_t *ppos) |
| { |
| struct seq_file *m = file->private_data; |
| struct sirf_security_inst *ssi = m->private; |
| |
| if (kstrtoul_from_user(buf, count, 16, &ssi->seed)) |
| return -EINVAL; |
| |
| return count; |
| } |
| |
| static int uuid_proc_release(struct inode *inode, struct file *file) |
| { |
| struct seq_file *m = file->private_data; |
| |
| /* free instance in private data */ |
| kfree(m->private); |
| m->private = NULL; |
| |
| return single_release(inode, file); |
| } |
| |
| |
| static const struct file_operations uuid_proc_fops = { |
| .owner = THIS_MODULE, |
| .open = uuid_proc_open, |
| .read = seq_read, |
| .llseek = seq_lseek, |
| .write = uuid_proc_write, |
| .release = uuid_proc_release, |
| }; |
| |
| static int __init sirf_security_init(void) |
| { |
| const struct firmware *firmware; |
| void *buffer; |
| unsigned int version, enable; |
| int i, ret = 0, size; |
| int addr_map_tbl_size; |
| struct device_node *dn = NULL; |
| struct device *dev = NULL; |
| struct resource res; |
| int resource_index; |
| struct sirf_security_resource *sirfsec_nres_used; |
| const char *compat; |
| struct sirf_security *sirfsec = &sirf_sec; |
| |
| ret = misc_register(&sirfsec->miscdev); |
| if (ret) |
| return ret; |
| |
| dev = sirfsec->miscdev.this_device; |
| |
| if (request_firmware(&firmware, "sirfsecurity.bin", dev) < 0) { |
| dev_err(dev, "sirf security binary request failed"); |
| return -EIO; |
| } |
| |
| dev_info(dev, "request sirfsecurity.bin %p size %zu\n", |
| firmware->data, firmware->size); |
| |
| size = max_t(uint, firmware->size, 0x20000); |
| |
| buffer = devm_kzalloc(dev, size, GFP_KERNEL); |
| if (!buffer) { |
| dev_err(dev, "Can't allocate memory for binary!"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| memcpy(buffer, firmware->data, firmware->size); |
| |
| flush_cache_all(); |
| isb(); |
| |
| sirfsec_nres_used = &sirfsec_nres[0]; |
| for (i = 0; i < ARRAY_SIZE(sirfsec_nres); i++) { |
| compat = sirfsec_nres_used->arch_compatible; |
| ret = of_machine_is_compatible(compat); |
| if (ret) |
| break; |
| sirfsec_nres_used++; |
| } |
| |
| if (!ret) { |
| dev_err(dev, "no suitable sirf security binary!!\n"); |
| ret = -ENODEV; |
| goto out; |
| } |
| |
| dev_info(dev, "find sirf security binary(%s).\n", compat); |
| |
| sirfsec->pfn_ioctrl = (PFN_SIRF_SECURITY_IOCTRL)buffer; |
| |
| /* total controller */ |
| sirfsec->addr_entry_num = sirfsec_nres_used->res_num; |
| /* add 2 item, ram and zero-end */ |
| sirfsec->addr_entry_num += 1; |
| addr_map_tbl_size = sizeof(struct ADDRMAP) * sirfsec->addr_entry_num; |
| sirfsec->addr_map_tbl = devm_kzalloc(dev, |
| addr_map_tbl_size, GFP_KERNEL); |
| if (!sirfsec->addr_map_tbl) { |
| dev_err(dev, "unable to malloc buffer for address table.\n"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| /* get resouce from other controller */ |
| for (i = 0; i < sirfsec_nres_used->res_num; i++) { |
| dn = NULL; |
| resource_index = sirfsec_nres_used->arch_res[i].index; |
| /* when it is not the first node */ |
| do { |
| compat = sirfsec_nres_used->arch_res[i].res_compatible; |
| dn = of_find_compatible_node(dn, NULL, compat); |
| if (!dn) { |
| dev_err(dev, "unable to get %s node!\n", |
| compat); |
| ret = -ENODEV; |
| goto out; |
| } |
| } while (resource_index--); |
| |
| ret = of_address_to_resource(dn, 0, &res); |
| if (ret) { |
| dev_err(dev, "unable to get resource(%d)!\n", i); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| sirfsec->addr_map_tbl[i].pa = res.start; |
| sirfsec->addr_map_tbl[i].size = resource_size(&res); |
| sirfsec->addr_map_tbl[i].va = |
| devm_ioremap(dev, res.start, resource_size(&res)); |
| |
| if (!sirfsec->addr_map_tbl[i].va) { |
| dev_err(dev, "unable to ioremap!\n"); |
| ret = -ENOMEM; |
| goto out; |
| } |
| } |
| |
| if (!sirfsec->pfn_ioctrl(SIRFSEC_IOCTRL_RUNTIME_INIT, &size, |
| sizeof(size), NULL, 0)) { |
| dev_info(dev, "struct SIRFSEC_IOCTRL_RUNTIME_INIT failed.\n"); |
| ret = -EIO; |
| goto out; |
| } |
| |
| /* check the interface version */ |
| if (!sirfsec->pfn_ioctrl(SIRFSEC_IOCTRL_GET_INTERFACE_VERSION, |
| NULL, 0, &version, sizeof(version))) { |
| dev_info(dev, "get interface version failed.\n"); |
| ret = -EIO; |
| goto out; |
| } |
| |
| dev_info(dev, "private binary's interface version is %d\n", version); |
| if (SIRF_SECURITY_INTERFACE_VERSION != version) { |
| dev_err(dev, "interface ver mismatch (using version %d).", |
| SIRF_SECURITY_INTERFACE_VERSION); |
| ret = -EIO; |
| goto out; |
| } |
| |
| /* get binary version */ |
| if (!sirfsec->pfn_ioctrl(SIRFSEC_IOCTRL_GET_VERSION, NULL, 0, |
| &version, sizeof(version))) { |
| dev_err(dev, "SIRFSEC_IOCTRL_GET_VERSION failed.\n"); |
| ret = -EIO; |
| goto out; |
| } |
| dev_info(dev, "sirf security binary version is 0x%x\n", version); |
| |
| /* tell binary the address map */ |
| if (!sirfsec->pfn_ioctrl(SIRFSEC_IOCTRL_NEW_ADDRESS_MAP, |
| sirfsec->addr_map_tbl, addr_map_tbl_size, |
| NULL, 0)) { |
| dev_err(dev, "SIRFSEC_IOCTRL_NEW_ADDRESS_MAP failed.\r\n"); |
| ret = -EIO; |
| goto out; |
| } |
| |
| /* enable debug message */ |
| enable = 1; |
| if (!sirfsec->pfn_ioctrl(SIRFSEC_IOCTRL_ENABLE_MSG, &enable, |
| sizeof(enable), NULL, 0)) { |
| dev_err(dev, "SIRFSEC_IOCTRL_ENABLE_MSG failed.\r\n"); |
| ret = -EIO; |
| goto out; |
| } |
| |
| if (!proc_create_data("sirf_sinfo", S_IRUGO, NULL, |
| &uuid_proc_fops, sirfsec)) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| atomic_set(&sirfsec->ref, 0); |
| |
| out: |
| release_firmware(firmware); |
| |
| if (ret) |
| misc_deregister(&sirfsec->miscdev); |
| |
| return ret; |
| } |
| module_init(sirf_security_init); |
| |
| static void __exit sirf_security_exit(void) |
| { |
| misc_deregister(&sirf_sec.miscdev); |
| remove_proc_entry("sirf_sinfo", NULL); |
| } |
| module_exit(sirf_security_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_FIRMWARE("sirfsecurity.bin"); |