blob: 7d7a91ded1c6e91d1a05d11f4a1dce4a56cd689b [file] [log] [blame]
/*
* 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 = "sirfsec",
.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");