/* Copyright (c) 2010-2017, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * 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. * */ /* Qualcomm Over the Air (OTA) Crypto driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qce.h" #include "qce_ota.h" enum qce_ota_oper_enum { QCE_OTA_F8_OPER = 0, QCE_OTA_MPKT_F8_OPER = 1, QCE_OTA_F9_OPER = 2, QCE_OTA_OPER_LAST }; struct ota_dev_control; struct ota_async_req { struct list_head list; struct completion complete; int err; enum qce_ota_oper_enum op; union { struct qce_f9_req f9_req; struct qce_f8_req f8_req; struct qce_f8_multi_pkt_req f8_mp_req; } req; struct ota_dev_control *podev; }; /* * Register ourselves as a misc device to be able to access the ota * from userspace. */ #define QCOTA_DEV "qcota" struct ota_dev_control { /* misc device */ struct miscdevice miscdevice; /* qce handle */ void *qce; /* platform device */ struct platform_device *pdev; unsigned magic; struct list_head ready_commands; struct ota_async_req *active_command; spinlock_t lock; struct tasklet_struct done_tasklet; }; #define OTA_MAGIC 0x4f544143 static long qcota_ioctl(struct file *file, unsigned cmd, unsigned long arg); static int qcota_open(struct inode *inode, struct file *file); static int qcota_release(struct inode *inode, struct file *file); static int start_req(struct ota_dev_control *podev); static const struct file_operations qcota_fops = { .owner = THIS_MODULE, .unlocked_ioctl = qcota_ioctl, .open = qcota_open, .release = qcota_release, }; static struct ota_dev_control qcota_dev[] = { { .miscdevice = { .minor = MISC_DYNAMIC_MINOR, .name = "qcota0", .fops = &qcota_fops, }, .magic = OTA_MAGIC, }, { .miscdevice = { .minor = MISC_DYNAMIC_MINOR, .name = "qcota1", .fops = &qcota_fops, }, .magic = OTA_MAGIC, }, { .miscdevice = { .minor = MISC_DYNAMIC_MINOR, .name = "qcota2", .fops = &qcota_fops, }, .magic = OTA_MAGIC, } }; #define MAX_OTA_DEVICE ARRAY_SIZE(qcota_dev) #define DEBUG_MAX_FNAME 16 #define DEBUG_MAX_RW_BUF 1024 struct qcota_stat { u32 f8_req; u32 f8_mp_req; u32 f9_req; u32 f8_op_success; u32 f8_op_fail; u32 f8_mp_op_success; u32 f8_mp_op_fail; u32 f9_op_success; u32 f9_op_fail; }; static struct qcota_stat _qcota_stat[MAX_OTA_DEVICE]; static struct dentry *_debug_dent; static char _debug_read_buf[DEBUG_MAX_RW_BUF]; static int _debug_qcota[MAX_OTA_DEVICE]; static struct ota_dev_control *qcota_minor_to_control(unsigned n) { int i; for (i = 0; i < MAX_OTA_DEVICE; i++) { if (qcota_dev[i].miscdevice.minor == n) return &qcota_dev[i]; } return NULL; } static int qcota_open(struct inode *inode, struct file *file) { struct ota_dev_control *podev; podev = qcota_minor_to_control(MINOR(inode->i_rdev)); if (podev == NULL) { pr_err("%s: no such device %d\n", __func__, MINOR(inode->i_rdev)); return -ENOENT; } file->private_data = podev; return 0; } static int qcota_release(struct inode *inode, struct file *file) { struct ota_dev_control *podev; podev = file->private_data; if (podev != NULL && podev->magic != OTA_MAGIC) { pr_err("%s: invalid handle %p\n", __func__, podev); } file->private_data = NULL; return 0; } static void req_done(unsigned long data) { struct ota_dev_control *podev = (struct ota_dev_control *)data; struct ota_async_req *areq; unsigned long flags; struct ota_async_req *new_req = NULL; int ret = 0; spin_lock_irqsave(&podev->lock, flags); areq = podev->active_command; podev->active_command = NULL; again: if (!list_empty(&podev->ready_commands)) { new_req = container_of(podev->ready_commands.next, struct ota_async_req, list); list_del(&new_req->list); podev->active_command = new_req; new_req->err = 0; ret = start_req(podev); } spin_unlock_irqrestore(&podev->lock, flags); if (areq) complete(&areq->complete); if (new_req && ret) { complete(&new_req->complete); spin_lock_irqsave(&podev->lock, flags); podev->active_command = NULL; areq = NULL; ret = 0; new_req = NULL; goto again; } return; } static void f9_cb(void *cookie, unsigned char *icv, unsigned char *iv, int ret) { struct ota_async_req *areq = (struct ota_async_req *) cookie; struct ota_dev_control *podev; struct qcota_stat *pstat; podev = areq->podev; pstat = &_qcota_stat[podev->pdev->id]; areq->req.f9_req.mac_i = (uint32_t) icv; if (ret) areq->err = -ENXIO; else areq->err = 0; tasklet_schedule(&podev->done_tasklet); }; static void f8_cb(void *cookie, unsigned char *icv, unsigned char *iv, int ret) { struct ota_async_req *areq = (struct ota_async_req *) cookie; struct ota_dev_control *podev; struct qcota_stat *pstat; podev = areq->podev; pstat = &_qcota_stat[podev->pdev->id]; if (ret) areq->err = -ENXIO; else areq->err = 0; tasklet_schedule(&podev->done_tasklet); }; static int start_req(struct ota_dev_control *podev) { struct ota_async_req *areq; struct qce_f9_req *pf9; struct qce_f8_multi_pkt_req *p_mp_f8; struct qce_f8_req *pf8; int ret = 0; /* start the command on the podev->active_command */ areq = podev->active_command; areq->podev = podev; switch (areq->op) { case QCE_OTA_F8_OPER: pf8 = &areq->req.f8_req; ret = qce_f8_req(podev->qce, pf8, areq, f8_cb); break; case QCE_OTA_MPKT_F8_OPER: p_mp_f8 = &areq->req.f8_mp_req; ret = qce_f8_multi_pkt_req(podev->qce, p_mp_f8, areq, f8_cb); break; case QCE_OTA_F9_OPER: pf9 = &areq->req.f9_req; ret = qce_f9_req(podev->qce, pf9, areq, f9_cb); break; default: ret = -ENOTSUPP; break; }; areq->err = ret; return ret; }; static int submit_req(struct ota_async_req *areq, struct ota_dev_control *podev) { unsigned long flags; int ret = 0; struct qcota_stat *pstat; areq->err = 0; spin_lock_irqsave(&podev->lock, flags); if (podev->active_command == NULL) { podev->active_command = areq; ret = start_req(podev); } else { list_add_tail(&areq->list, &podev->ready_commands); } if (ret != 0) podev->active_command = NULL; spin_unlock_irqrestore(&podev->lock, flags); if (ret == 0) wait_for_completion(&areq->complete); pstat = &_qcota_stat[podev->pdev->id]; switch (areq->op) { case QCE_OTA_F8_OPER: if (areq->err) pstat->f8_op_fail++; else pstat->f8_op_success++; break; case QCE_OTA_MPKT_F8_OPER: if (areq->err) pstat->f8_mp_op_fail++; else pstat->f8_mp_op_success++; break; case QCE_OTA_F9_OPER: default: if (areq->err) pstat->f9_op_fail++; else pstat->f9_op_success++; break; }; return areq->err; }; static long qcota_ioctl(struct file *file, unsigned cmd, unsigned long arg) { int err = 0; struct ota_dev_control *podev; uint8_t *user_src; uint8_t *user_dst; uint8_t *k_buf = NULL; struct ota_async_req areq; uint32_t total; struct qcota_stat *pstat; podev = file->private_data; if (podev == NULL || podev->magic != OTA_MAGIC) { pr_err("%s: invalid handle %p\n", __func__, podev); return -ENOENT; } /* Verify user arguments. */ if (_IOC_TYPE(cmd) != QCOTA_IOC_MAGIC) return -ENOTTY; init_completion(&areq.complete); pstat = &_qcota_stat[podev->pdev->id]; switch (cmd) { case QCOTA_F9_REQ: if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(struct qce_f9_req))) return -EFAULT; if (__copy_from_user(&areq.req.f9_req, (void __user *)arg, sizeof(struct qce_f9_req))) return -EFAULT; user_src = areq.req.f9_req.message; if (!access_ok(VERIFY_READ, (void __user *)user_src, areq.req.f9_req.msize)) return -EFAULT; k_buf = kmalloc(areq.req.f9_req.msize, GFP_KERNEL); if (k_buf == NULL) return -ENOMEM; if (__copy_from_user(k_buf, (void __user *)user_src, areq.req.f9_req.msize)) { kfree(k_buf); return -EFAULT; } areq.req.f9_req.message = k_buf; areq.op = QCE_OTA_F9_OPER; pstat->f9_req++; err = submit_req(&areq, podev); areq.req.f9_req.message = user_src; if (err == 0 && __copy_to_user((void __user *)arg, &areq.req.f9_req, sizeof(struct qce_f9_req))) { err = -EFAULT; } kfree(k_buf); break; case QCOTA_F8_REQ: if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(struct qce_f8_req))) return -EFAULT; if (__copy_from_user(&areq.req.f8_req, (void __user *)arg, sizeof(struct qce_f8_req))) return -EFAULT; total = areq.req.f8_req.data_len; user_src = areq.req.f8_req.data_in; if (user_src != NULL) { if (!access_ok(VERIFY_READ, (void __user *) user_src, total)) return -EFAULT; }; user_dst = areq.req.f8_req.data_out; if (!access_ok(VERIFY_WRITE, (void __user *) user_dst, total)) return -EFAULT; k_buf = kmalloc(total, GFP_KERNEL); if (k_buf == NULL) return -ENOMEM; /* k_buf returned from kmalloc should be cache line aligned */ if (user_src && __copy_from_user(k_buf, (void __user *)user_src, total)) { kfree(k_buf); return -EFAULT; } if (user_src) areq.req.f8_req.data_in = k_buf; else areq.req.f8_req.data_in = NULL; areq.req.f8_req.data_out = k_buf; areq.op = QCE_OTA_F8_OPER; pstat->f8_req++; err = submit_req(&areq, podev); if (err == 0 && __copy_to_user(user_dst, k_buf, total)) err = -EFAULT; kfree(k_buf); break; case QCOTA_F8_MPKT_REQ: if (!access_ok(VERIFY_WRITE, (void __user *)arg, sizeof(struct qce_f8_multi_pkt_req))) return -EFAULT; if (__copy_from_user(&areq.req.f8_mp_req, (void __user *)arg, sizeof(struct qce_f8_multi_pkt_req))) return -EFAULT; total = areq.req.f8_mp_req.num_pkt * areq.req.f8_mp_req.qce_f8_req.data_len; user_src = areq.req.f8_mp_req.qce_f8_req.data_in; if (!access_ok(VERIFY_READ, (void __user *) user_src, total)) return -EFAULT; user_dst = areq.req.f8_mp_req.qce_f8_req.data_out; if (!access_ok(VERIFY_WRITE, (void __user *) user_dst, total)) return -EFAULT; k_buf = kmalloc(total, GFP_KERNEL); if (k_buf == NULL) return -ENOMEM; /* k_buf returned from kmalloc should be cache line aligned */ if (__copy_from_user(k_buf, (void __user *)user_src, total)) { kfree(k_buf); return -EFAULT; } areq.req.f8_mp_req.qce_f8_req.data_out = k_buf; areq.req.f8_mp_req.qce_f8_req.data_in = k_buf; areq.op = QCE_OTA_MPKT_F8_OPER; pstat->f8_mp_req++; err = submit_req(&areq, podev); if (err == 0 && __copy_to_user(user_dst, k_buf, total)) err = -EFAULT; kfree(k_buf); break; default: return -ENOTTY; } return err; } static int qcota_probe(struct platform_device *pdev) { void *handle = NULL; int rc = 0; struct ota_dev_control *podev; struct ce_hw_support ce_support; if (pdev->id >= MAX_OTA_DEVICE) { pr_err("%s: device id %d exceeds allowed %d\n", __func__, pdev->id, MAX_OTA_DEVICE); return -ENOENT; } podev = &qcota_dev[pdev->id]; INIT_LIST_HEAD(&podev->ready_commands); podev->active_command = NULL; spin_lock_init(&podev->lock); tasklet_init(&podev->done_tasklet, req_done, (unsigned long)podev); /* open qce */ handle = qce_open(pdev, &rc); if (handle == NULL) { pr_err("%s: device id %d, can not open qce\n", __func__, pdev->id); platform_set_drvdata(pdev, NULL); return rc; } if (qce_hw_support(handle, &ce_support) < 0 || ce_support.ota == false) { pr_err("%s: device id %d, qce does not support ota capability\n", __func__, pdev->id); rc = -ENODEV; goto err; } podev->qce = handle; podev->pdev = pdev; platform_set_drvdata(pdev, podev); rc = misc_register(&podev->miscdevice); if (rc < 0) goto err; return 0; err: if (handle) qce_close(handle); platform_set_drvdata(pdev, NULL); podev->qce = NULL; podev->pdev = NULL; return rc; }; static int qcota_remove(struct platform_device *pdev) { struct ota_dev_control *podev; podev = platform_get_drvdata(pdev); if (!podev) return 0; if (podev->qce) qce_close(podev->qce); if (podev->miscdevice.minor != MISC_DYNAMIC_MINOR) misc_deregister(&podev->miscdevice); tasklet_kill(&podev->done_tasklet); return 0; }; static struct platform_driver qcota_plat_driver = { .probe = qcota_probe, .remove = qcota_remove, .driver = { .name = "qcota", .owner = THIS_MODULE, }, }; static int _disp_stats(int id) { struct qcota_stat *pstat; int len = 0; pstat = &_qcota_stat[id]; len = snprintf(_debug_read_buf, DEBUG_MAX_RW_BUF - 1, "\nQualcomm OTA crypto accelerator %d Statistics:\n", id + 1); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F8 request : %d\n", pstat->f8_req); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F8 operation success : %d\n", pstat->f8_op_success); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F8 operation fail : %d\n", pstat->f8_op_fail); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F8 MP request : %d\n", pstat->f8_mp_req); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F8 MP operation success: %d\n", pstat->f8_mp_op_success); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F8 MP operation fail : %d\n", pstat->f8_mp_op_fail); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F9 request : %d\n", pstat->f9_req); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F9 operation success : %d\n", pstat->f9_op_success); len += snprintf(_debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " F9 operation fail : %d\n", pstat->f9_op_fail); return len; } static int _debug_stats_open(struct inode *inode, struct file *file) { file->private_data = inode->i_private; return 0; } static ssize_t _debug_stats_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { int rc = -EINVAL; int qcota = *((int *) file->private_data); int len; len = _disp_stats(qcota); if (len <= count) rc = simple_read_from_buffer((void __user *) buf, len, ppos, (void *) _debug_read_buf, len); return rc; } static ssize_t _debug_stats_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { int qcota = *((int *) file->private_data); memset((char *)&_qcota_stat[qcota], 0, sizeof(struct qcota_stat)); return count; }; static const struct file_operations _debug_stats_ops = { .open = _debug_stats_open, .read = _debug_stats_read, .write = _debug_stats_write, }; static int _qcota_debug_init(void) { int rc; char name[DEBUG_MAX_FNAME]; int i; struct dentry *dent; _debug_dent = debugfs_create_dir("qcota", NULL); if (IS_ERR(_debug_dent)) { pr_err("qcota debugfs_create_dir fail, error %ld\n", PTR_ERR(_debug_dent)); return PTR_ERR(_debug_dent); } for (i = 0; i < MAX_OTA_DEVICE; i++) { snprintf(name, DEBUG_MAX_FNAME-1, "stats-%d", i+1); _debug_qcota[i] = i; dent = debugfs_create_file(name, 0644, _debug_dent, &_debug_qcota[i], &_debug_stats_ops); if (dent == NULL) { pr_err("qcota debugfs_create_file fail, error %ld\n", PTR_ERR(dent)); rc = PTR_ERR(dent); goto err; } } return 0; err: debugfs_remove_recursive(_debug_dent); return rc; } static int __init qcota_init(void) { int rc; rc = _qcota_debug_init(); if (rc) return rc; return platform_driver_register(&qcota_plat_driver); } static void __exit qcota_exit(void) { debugfs_remove_recursive(_debug_dent); platform_driver_unregister(&qcota_plat_driver); } MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Rohit Vaswani "); MODULE_DESCRIPTION("Qualcomm Ota Crypto driver"); MODULE_VERSION("1.01"); module_init(qcota_init); module_exit(qcota_exit);