/* Copyright (c) 2010-2013, 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 rlist; 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_qce_dev *pqce; }; /* * 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; struct list_head ready_commands; unsigned magic; struct list_head qce_dev; spinlock_t lock; struct mutex register_lock; bool registered; uint32_t total_units; }; struct ota_qce_dev { struct list_head qlist; /* qce handle */ void *qce; /* platform device */ struct platform_device *pdev; struct ota_async_req *active_command; struct tasklet_struct done_tasklet; struct ota_dev_control *podev; uint32_t unit; u32 totalReq; u32 errReq; }; #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_qce_dev *pqce, struct ota_async_req *areq); 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, }; #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; static struct dentry *_debug_dent; static char _debug_read_buf[DEBUG_MAX_RW_BUF]; static int _debug_qcota; static struct ota_dev_control *qcota_control(void) { return &qcota_dev; } static int qcota_open(struct inode *inode, struct file *file) { struct ota_dev_control *podev; podev = qcota_control(); 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_qce_dev *pqce = (struct ota_qce_dev *)data; struct ota_dev_control *podev = pqce->podev; 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 = pqce->active_command; if (unlikely(areq == NULL)) pr_err("ota_crypto: req_done, no active request\n"); pqce->active_command = NULL; again: if (!list_empty(&podev->ready_commands)) { new_req = container_of(podev->ready_commands.next, struct ota_async_req, rlist); list_del(&new_req->rlist); pqce->active_command = new_req; spin_unlock_irqrestore(&podev->lock, flags); new_req->err = 0; ret = start_req(pqce, new_req); /* start a new request */ } else { spin_unlock_irqrestore(&podev->lock, flags); }; if (areq) { complete(&areq->complete); areq = NULL; }; /* if error from issuing request */ if (unlikely(new_req && ret)) { new_req->err = ret; complete(&new_req->complete); ret = 0; new_req = NULL; spin_lock_irqsave(&podev->lock, flags); pqce->active_command = NULL; /* try to get next new request */ 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_qce_dev *pqce; pqce = areq->pqce; areq->req.f9_req.mac_i = (uint32_t) icv; if (ret) { pqce->errReq++; areq->err = -ENXIO; } else areq->err = 0; tasklet_schedule(&pqce->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_qce_dev *pqce; pqce = areq->pqce; if (ret) { pqce->errReq++; areq->err = -ENXIO; } else { areq->err = 0; } tasklet_schedule(&pqce->done_tasklet); } static int start_req(struct ota_qce_dev *pqce, 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; /* command should be on the podev->active_command */ areq->pqce = pqce; switch (areq->op) { case QCE_OTA_F8_OPER: pf8 = &areq->req.f8_req; ret = qce_f8_req(pqce->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(pqce->qce, p_mp_f8, areq, f8_cb); break; case QCE_OTA_F9_OPER: pf9 = &areq->req.f9_req; ret = qce_f9_req(pqce->qce, pf9, areq, f9_cb); break; default: ret = -ENOTSUPP; break; }; areq->err = ret; pqce->totalReq++; if (ret) pqce->errReq++; return ret; } static struct ota_qce_dev *schedule_qce(struct ota_dev_control *podev) { /* do this function with spinlock set */ struct ota_qce_dev *p; if (unlikely(list_empty(&podev->qce_dev))) { pr_err("%s: no valid qce to schedule\n", __func__); return NULL; } list_for_each_entry(p, &podev->qce_dev, qlist) { if (p->active_command == NULL) return p; } return NULL; } static int submit_req(struct ota_async_req *areq, struct ota_dev_control *podev) { unsigned long flags; int ret = 0; struct qcota_stat *pstat; struct ota_qce_dev *pqce; areq->err = 0; spin_lock_irqsave(&podev->lock, flags); pqce = schedule_qce(podev); if (pqce) { pqce->active_command = areq; spin_unlock_irqrestore(&podev->lock, flags); ret = start_req(pqce, areq); if (ret != 0) { spin_lock_irqsave(&podev->lock, flags); pqce->active_command = NULL; spin_unlock_irqrestore(&podev->lock, flags); } } else { list_add_tail(&areq->rlist, &podev->ready_commands); spin_unlock_irqrestore(&podev->lock, flags); } if (ret == 0) wait_for_completion(&areq->complete); pstat = &_qcota_stat; 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; 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; struct ota_qce_dev *pqce; unsigned long flags; podev = &qcota_dev; pqce = kzalloc(sizeof(*pqce), GFP_KERNEL); if (!pqce) { pr_err("qcota_probe: Memory allocation FAIL\n"); return -ENOMEM; } pqce->podev = podev; pqce->active_command = NULL; tasklet_init(&pqce->done_tasklet, req_done, (unsigned long)pqce); /* open qce */ handle = qce_open(pdev, &rc); if (handle == NULL) { pr_err("%s: device %s, can not open qce\n", __func__, pdev->name); goto err; } if (qce_hw_support(handle, &ce_support) < 0 || ce_support.ota == false) { pr_err("%s: device %s, qce does not support ota capability\n", __func__, pdev->name); rc = -ENODEV; goto err; } pqce->qce = handle; pqce->pdev = pdev; pqce->totalReq = 0; pqce->errReq = 0; platform_set_drvdata(pdev, pqce); mutex_lock(&podev->register_lock); rc = 0; if (podev->registered == false) { rc = misc_register(&podev->miscdevice); if (rc == 0) { pqce->unit = podev->total_units; podev->total_units++; podev->registered = true; }; } else { pqce->unit = podev->total_units; podev->total_units++; } mutex_unlock(&podev->register_lock); if (rc) { pr_err("ion: failed to register misc device.\n"); goto err; } spin_lock_irqsave(&podev->lock, flags); list_add_tail(&pqce->qlist, &podev->qce_dev); spin_unlock_irqrestore(&podev->lock, flags); return 0; err: if (handle) qce_close(handle); platform_set_drvdata(pdev, NULL); tasklet_kill(&pqce->done_tasklet); kfree(pqce); return rc; } static int qcota_remove(struct platform_device *pdev) { struct ota_dev_control *podev; struct ota_qce_dev *pqce; unsigned long flags; pqce = platform_get_drvdata(pdev); if (!pqce) return 0; if (pqce->qce) qce_close(pqce->qce); podev = pqce->podev; if (!podev) goto ret; spin_lock_irqsave(&podev->lock, flags); list_del(&pqce->qlist); spin_unlock_irqrestore(&podev->lock, flags); mutex_lock(&podev->register_lock); if (--podev->total_units == 0) { if (podev->miscdevice.minor != MISC_DYNAMIC_MINOR) misc_deregister(&podev->miscdevice); podev->registered = false; } mutex_unlock(&podev->register_lock); ret: tasklet_kill(&pqce->done_tasklet); kfree(pqce); return 0; } static struct of_device_id qcota_match[] = { { .compatible = "qcom,qcota", }, {} }; static struct platform_driver qcota_plat_driver = { .probe = qcota_probe, .remove = qcota_remove, .driver = { .name = "qcota", .owner = THIS_MODULE, .of_match_table = qcota_match, }, }; static int _disp_stats(void) { struct qcota_stat *pstat; int len = 0; struct ota_dev_control *podev = &qcota_dev; unsigned long flags; struct ota_qce_dev *p; pstat = &_qcota_stat; len = snprintf(_debug_read_buf, DEBUG_MAX_RW_BUF - 1, "\nQualcomm OTA crypto accelerator Statistics:\n"); 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); spin_lock_irqsave(&podev->lock, flags); list_for_each_entry(p, &podev->qce_dev, qlist) { len += snprintf( _debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " Engine %d Req : %d\n", p->unit, p->totalReq ); len += snprintf( _debug_read_buf + len, DEBUG_MAX_RW_BUF - len - 1, " Engine %d Req Error : %d\n", p->unit, p->errReq ); } spin_unlock_irqrestore(&podev->lock, flags); 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 len; len = _disp_stats(); 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) { struct ota_dev_control *podev = &qcota_dev; unsigned long flags; struct ota_qce_dev *p; memset((char *)&_qcota_stat, 0, sizeof(struct qcota_stat)); spin_lock_irqsave(&podev->lock, flags); list_for_each_entry(p, &podev->qce_dev, qlist) { p->totalReq = 0; p->errReq = 0; } spin_unlock_irqrestore(&podev->lock, flags); 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]; 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); } snprintf(name, DEBUG_MAX_FNAME-1, "stats-0"); _debug_qcota = 0; dent = debugfs_create_file(name, 0644, _debug_dent, &_debug_qcota, &_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; struct ota_dev_control *podev; rc = _qcota_debug_init(); if (rc) return rc; podev = &qcota_dev; INIT_LIST_HEAD(&podev->ready_commands); INIT_LIST_HEAD(&podev->qce_dev); spin_lock_init(&podev->lock); mutex_init(&podev->register_lock); podev->registered = false; podev->total_units = 0; 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.02"); module_init(qcota_init); module_exit(qcota_exit);