Initial Contribution
msm-2.6.38: tag AU_LINUX_ANDROID_GINGERBREAD.02.03.04.00.142
Signed-off-by: Bryan Huntsman <bryanh@codeaurora.org>
diff --git a/drivers/misc/tzcom.c b/drivers/misc/tzcom.c
new file mode 100644
index 0000000..e947dee
--- /dev/null
+++ b/drivers/misc/tzcom.c
@@ -0,0 +1,910 @@
+/* Qualcomm TrustZone communicator driver
+ *
+ * Copyright (c) 2011, Code Aurora Forum. 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.
+ */
+
+#define KMSG_COMPONENT "TZCOM"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/platform_device.h>
+#include <linux/debugfs.h>
+#include <linux/cdev.h>
+#include <linux/uaccess.h>
+#include <linux/sched.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/android_pmem.h>
+#include <linux/io.h>
+#include <mach/scm.h>
+#include <mach/peripheral-loader.h>
+#include <linux/tzcom.h>
+#include "tzcomi.h"
+
+#define TZCOM_DEV "tzcom"
+
+#define TZSCHEDULER_CMD_ID 1 /* CMD id of the trustzone scheduler */
+
+#undef PDEBUG
+#define PDEBUG(fmt, args...) pr_debug("%s(%i, %s): " fmt "\n", \
+ __func__, current->pid, current->comm, ## args)
+
+#undef PERR
+#define PERR(fmt, args...) pr_err("%s(%i, %s): " fmt "\n", \
+ __func__, current->pid, current->comm, ## args)
+
+
+static struct class *driver_class;
+static dev_t tzcom_device_no;
+static struct cdev tzcom_cdev;
+
+static u8 *sb_in_virt;
+static s32 sb_in_phys;
+static size_t sb_in_length = 20 * SZ_1K;
+static u8 *sb_out_virt;
+static s32 sb_out_phys;
+static size_t sb_out_length = 20 * SZ_1K;
+
+static void *pil;
+
+static atomic_t svc_instance_ctr = ATOMIC_INIT(0);
+static DEFINE_MUTEX(sb_in_lock);
+static DEFINE_MUTEX(sb_out_lock);
+static DEFINE_MUTEX(send_cmd_lock);
+
+struct tzcom_callback_list {
+ struct list_head list;
+ struct tzcom_callback callback;
+};
+
+struct tzcom_registered_svc_list {
+ struct list_head list;
+ struct tzcom_register_svc_op_req svc;
+ wait_queue_head_t next_cmd_wq;
+ int next_cmd_flag;
+};
+
+struct tzcom_data_t {
+ struct list_head callback_list_head;
+ struct mutex callback_list_lock;
+ struct list_head registered_svc_list_head;
+ spinlock_t registered_svc_list_lock;
+ wait_queue_head_t cont_cmd_wq;
+ int cont_cmd_flag;
+ u32 handled_cmd_svc_instance_id;
+};
+
+static int tzcom_scm_call(const void *cmd_buf, size_t cmd_len,
+ void *resp_buf, size_t resp_len)
+{
+ return scm_call(SCM_SVC_TZSCHEDULER, TZSCHEDULER_CMD_ID,
+ cmd_buf, cmd_len, resp_buf, resp_len);
+}
+
+static s32 tzcom_virt_to_phys(u8 *virt)
+{
+ if (virt >= sb_in_virt &&
+ virt < (sb_in_virt + sb_in_length)) {
+ return sb_in_phys + (virt - sb_in_virt);
+ } else if (virt >= sb_out_virt &&
+ virt < (sb_out_virt + sb_out_length)) {
+ return sb_out_phys + (virt - sb_out_virt);
+ } else {
+ return virt_to_phys(virt);
+ }
+}
+
+static u8 *tzcom_phys_to_virt(s32 phys)
+{
+ if (phys >= sb_in_phys &&
+ phys < (sb_in_phys + sb_in_length)) {
+ return sb_in_virt + (phys - sb_in_phys);
+ } else if (phys >= sb_out_phys &&
+ phys < (sb_out_phys + sb_out_length)) {
+ return sb_out_virt + (phys - sb_out_phys);
+ } else {
+ return phys_to_virt(phys);
+ }
+}
+
+static int __tzcom_is_svc_unique(struct tzcom_data_t *data,
+ struct tzcom_register_svc_op_req svc)
+{
+ struct tzcom_registered_svc_list *ptr;
+ int unique = 1;
+ unsigned long flags;
+
+ spin_lock_irqsave(&data->registered_svc_list_lock, flags);
+ list_for_each_entry(ptr, &data->registered_svc_list_head, list) {
+ if (ptr->svc.svc_id == svc.svc_id) {
+ PERR("Service id: %u is already registered",
+ ptr->svc.svc_id);
+ unique = 0;
+ break;
+ } else if (svc.cmd_id_low >= ptr->svc.cmd_id_low &&
+ svc.cmd_id_low <= ptr->svc.cmd_id_high) {
+ PERR("Cmd id low falls in the range of another"
+ "registered service");
+ unique = 0;
+ break;
+ } else if (svc.cmd_id_high >= ptr->svc.cmd_id_low &&
+ svc.cmd_id_high <= ptr->svc.cmd_id_high) {
+ PERR("Cmd id high falls in the range of another"
+ "registered service");
+ unique = 0;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&data->registered_svc_list_lock, flags);
+ return unique;
+}
+
+static int tzcom_register_service(struct tzcom_data_t *data, void __user *argp)
+{
+ int ret;
+ unsigned long flags;
+ struct tzcom_register_svc_op_req rcvd_svc;
+ struct tzcom_registered_svc_list *new_entry;
+
+ ret = copy_from_user(&rcvd_svc, argp, sizeof(rcvd_svc));
+
+ if (ret) {
+ PDEBUG("copy_from_user failed");
+ return ret;
+ }
+
+ PDEBUG("svc_id: %u, cmd_id_low: %u, cmd_id_high: %u",
+ rcvd_svc.svc_id, rcvd_svc.cmd_id_low,
+ rcvd_svc.cmd_id_high);
+ if (!__tzcom_is_svc_unique(data, rcvd_svc)) {
+ PDEBUG("Provided service is not unique");
+ return -EINVAL;
+ }
+
+ rcvd_svc.instance_id = atomic_inc_return(&svc_instance_ctr);
+
+ ret = copy_to_user(argp, &rcvd_svc, sizeof(rcvd_svc));
+ if (ret) {
+ PDEBUG("copy_to_user failed");
+ return ret;
+ }
+
+ new_entry = kmalloc(sizeof(*new_entry), GFP_KERNEL);
+ if (!new_entry) {
+ pr_err("%s: kmalloc failed\n", __func__);
+ return -ENOMEM;
+ }
+ memcpy(&new_entry->svc, &rcvd_svc, sizeof(rcvd_svc));
+ new_entry->next_cmd_flag = 0;
+ init_waitqueue_head(&new_entry->next_cmd_wq);
+
+ spin_lock_irqsave(&data->registered_svc_list_lock, flags);
+ list_add_tail(&new_entry->list, &data->registered_svc_list_head);
+ spin_unlock_irqrestore(&data->registered_svc_list_lock, flags);
+
+
+ return ret;
+}
+
+static int tzcom_unregister_service(struct tzcom_data_t *data,
+ void __user *argp)
+{
+ int ret = 0;
+ unsigned long flags;
+ struct tzcom_unregister_svc_op_req req;
+ struct tzcom_registered_svc_list *ptr;
+ ret = copy_from_user(&req, argp, sizeof(req));
+ if (ret) {
+ PDEBUG("copy_from_user failed");
+ return ret;
+ }
+
+ spin_lock_irqsave(&data->registered_svc_list_lock, flags);
+ list_for_each_entry(ptr, &data->registered_svc_list_head, list) {
+ if (req.svc_id == ptr->svc.svc_id &&
+ req.instance_id == ptr->svc.instance_id) {
+ wake_up_all(&ptr->next_cmd_wq);
+ list_del(&ptr->list);
+ kfree(ptr);
+ spin_unlock_irqrestore(&data->registered_svc_list_lock,
+ flags);
+ return 0;
+ }
+ }
+ spin_unlock_irqrestore(&data->registered_svc_list_lock, flags);
+
+ return -EINVAL;
+}
+
+/**
+ * +---------+ +-----+ +-----------------+
+ * | TZCOM | | SCM | | TZCOM_SCHEDULER |
+ * +----+----+ +--+--+ +--------+--------+
+ * | | |
+ * | scm_call | |
+ * |------------------------------------->| |
+ * | cmd_buf = struct tzcom_command { | |
+ * | cmd_type, |------------------>|
+ * +------+------------- sb_in_cmd_addr, | |
+ * | | sb_in_cmd_len | |
+ * | | } | |
+ * | | resp_buf = struct tzcom_response { | |
+ * | cmd_status, | |
+ * | +---------- sb_in_rsp_addr, | |
+ * | | sb_in_rsp_len |<------------------|
+ * | | }
+ * | | struct tzcom_callback {---------+
+ * | | uint32_t cmd_id; |
+ * | | uint32_t sb_out_cb_data_len;|
+ * | +---------------+ uint32_t sb_out_cb_data_off;|
+ * | | } |
+ * | _________________________|_______________________________ |
+ * | +-----------------------+| +----------------------+ |
+ * +--->+ copy from req.cmd_buf |+>| copy to req.resp_buf | |
+ * +-----------------------+ +----------------------+ |
+ * _________________________________________________________ |
+ * INPUT SHARED BUFFER |
+ * +------------------------------------------------------------------------+
+ * | _________________________________________________________
+ * | +---------------------------------------------+
+ * +->| cmd_id | data_len | data_off | data... |
+ * +---------------------------------------------+
+ * |<------------>|copy to next_cmd.req_buf
+ * _________________________________________________________
+ * OUTPUT SHARED BUFFER
+ */
+static int tzcom_send_cmd(struct tzcom_data_t *data, void __user *argp)
+{
+ int ret = 0;
+ unsigned long flags;
+ u32 reqd_len_sb_in = 0;
+ u32 reqd_len_sb_out = 0;
+ struct tzcom_send_cmd_op_req req;
+ struct tzcom_command cmd;
+ struct tzcom_response resp;
+ struct tzcom_callback *next_callback;
+ void *cb_data = NULL;
+ struct tzcom_callback_list *new_entry;
+ struct tzcom_callback *cb;
+ size_t new_entry_len = 0;
+ struct tzcom_registered_svc_list *ptr_svc;
+
+ ret = copy_from_user(&req, argp, sizeof(req));
+ if (ret) {
+ PDEBUG("copy_from_user failed");
+ return ret;
+ }
+
+ if (req.cmd_buf == NULL || req.resp_buf == NULL) {
+ PDEBUG("cmd buffer or response buffer is null");
+ return -EINVAL;
+ }
+
+ if (req.cmd_len <= 0 || req.resp_len <= 0) {
+ PDEBUG("cmd buffer length or "
+ "response buffer length not valid");
+ return -EINVAL;
+ }
+ PDEBUG("received cmd_req.req: 0x%p",
+ req.cmd_buf);
+ PDEBUG("received cmd_req.rsp size: %u, ptr: 0x%p",
+ req.resp_len,
+ req.resp_buf);
+
+ reqd_len_sb_in = req.cmd_len + req.resp_len;
+ if (reqd_len_sb_in > sb_in_length) {
+ PDEBUG("Not enough memory to fit cmd_buf and "
+ "resp_buf. Required: %u, Available: %u",
+ reqd_len_sb_in, sb_in_length);
+ return -ENOMEM;
+ }
+
+ /* Copy req.cmd_buf to SB in and set req.resp_buf to SB in + cmd_len */
+ mutex_lock(&sb_in_lock);
+ PDEBUG("Before memcpy on sb_in");
+ memcpy(sb_in_virt, req.cmd_buf, req.cmd_len);
+ PDEBUG("After memcpy on sb_in");
+
+ /* cmd_type will always be a new here */
+ cmd.cmd_type = TZ_SCHED_CMD_NEW;
+ cmd.sb_in_cmd_addr = (u8 *) tzcom_virt_to_phys(sb_in_virt);
+ cmd.sb_in_cmd_len = req.cmd_len;
+
+ resp.cmd_status = TZ_SCHED_STATUS_INCOMPLETE;
+ resp.sb_in_rsp_addr = (u8 *) tzcom_virt_to_phys(sb_in_virt +
+ req.cmd_len);
+ resp.sb_in_rsp_len = req.resp_len;
+
+ PDEBUG("before call tzcom_scm_call, cmd_id = : %u", req.cmd_id);
+ PDEBUG("before call tzcom_scm_call, sizeof(cmd) = : %u", sizeof(cmd));
+
+ tzcom_scm_call((const void *) &cmd, sizeof(cmd), &resp, sizeof(resp));
+ mutex_unlock(&sb_in_lock);
+
+ while (resp.cmd_status != TZ_SCHED_STATUS_COMPLETE) {
+ /*
+ * If cmd is incomplete, get the callback cmd out from SB out
+ * and put it on the list
+ */
+ PDEBUG("cmd_status is incomplete.");
+ next_callback = (struct tzcom_callback *)sb_out_virt;
+
+ mutex_lock(&sb_out_lock);
+ reqd_len_sb_out = sizeof(*next_callback)
+ + next_callback->sb_out_cb_data_len;
+ if (reqd_len_sb_out > sb_out_length) {
+ PDEBUG("Not enough memory to"
+ " fit tzcom_callback buffer."
+ " Required: %u, Available: %u",
+ reqd_len_sb_out, sb_out_length);
+ mutex_unlock(&sb_out_lock);
+ return -ENOMEM;
+ }
+
+ /* Assumption is cb_data_off is sizeof(tzcom_callback) */
+ new_entry_len = sizeof(*new_entry)
+ + next_callback->sb_out_cb_data_len;
+ new_entry = kmalloc(new_entry_len, GFP_KERNEL);
+ if (!new_entry) {
+ PERR("kmalloc failed");
+ mutex_unlock(&sb_out_lock);
+ return -ENOMEM;
+ }
+
+ cb = &new_entry->callback;
+ cb->cmd_id = next_callback->cmd_id;
+ cb->sb_out_cb_data_len = next_callback->sb_out_cb_data_len;
+ cb->sb_out_cb_data_off = next_callback->sb_out_cb_data_off;
+
+ cb_data = (u8 *)next_callback
+ + next_callback->sb_out_cb_data_off;
+ memcpy((u8 *)cb + cb->sb_out_cb_data_off, cb_data,
+ next_callback->sb_out_cb_data_len);
+ mutex_unlock(&sb_out_lock);
+
+ mutex_lock(&data->callback_list_lock);
+ list_add_tail(&new_entry->list, &data->callback_list_head);
+ mutex_unlock(&data->callback_list_lock);
+
+ /*
+ * We don't know which service can handle the command. so we
+ * wake up all blocking services and let them figure out if
+ * they can handle the given command.
+ */
+ spin_lock_irqsave(&data->registered_svc_list_lock, flags);
+ list_for_each_entry(ptr_svc,
+ &data->registered_svc_list_head, list) {
+ ptr_svc->next_cmd_flag = 1;
+ wake_up_interruptible(&ptr_svc->next_cmd_wq);
+ }
+ spin_unlock_irqrestore(&data->registered_svc_list_lock,
+ flags);
+
+ PDEBUG("waking up next_cmd_wq and "
+ "waiting for cont_cmd_wq");
+ if (wait_event_interruptible(data->cont_cmd_wq,
+ data->cont_cmd_flag != 0)) {
+ PDEBUG("Interrupted: exiting send_cmd loop");
+ return -ERESTARTSYS;
+ }
+ data->cont_cmd_flag = 0;
+ cmd.cmd_type = TZ_SCHED_CMD_PENDING;
+ mutex_lock(&sb_in_lock);
+ tzcom_scm_call((const void *) &cmd, sizeof(cmd), &resp,
+ sizeof(resp));
+ mutex_unlock(&sb_in_lock);
+ }
+
+ mutex_lock(&sb_in_lock);
+ resp.sb_in_rsp_addr = sb_in_virt + cmd.sb_in_cmd_len;
+ resp.sb_in_rsp_len = req.resp_len;
+ mutex_unlock(&sb_in_lock);
+
+ /* Cmd is done now. Copy the response from SB in to user */
+ if (req.resp_len >= resp.sb_in_rsp_len) {
+ PDEBUG("Before memcpy resp_buf");
+ mutex_lock(&sb_in_lock);
+ memcpy(req.resp_buf, resp.sb_in_rsp_addr, resp.sb_in_rsp_len);
+ mutex_unlock(&sb_in_lock);
+ } else {
+ PDEBUG("Provided response buffer is smaller"
+ " than required. Required: %u,"
+ " Provided: %u",
+ resp.sb_in_rsp_len, req.resp_len);
+ ret = -ENOMEM;
+ }
+
+ PDEBUG("sending cmd_req.rsp "
+ "size: %u, ptr: 0x%p", req.resp_len,
+ req.resp_buf);
+ ret = copy_to_user(argp, &req, sizeof(req));
+ if (ret) {
+ PDEBUG("copy_to_user failed");
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct tzcom_registered_svc_list *__tzcom_find_svc(
+ struct tzcom_data_t *data,
+ uint32_t instance_id)
+{
+ struct tzcom_registered_svc_list *entry;
+ unsigned long flags;
+
+ spin_lock_irqsave(&data->registered_svc_list_lock, flags);
+ list_for_each_entry(entry,
+ &data->registered_svc_list_head, list) {
+ if (entry->svc.instance_id == instance_id)
+ break;
+ }
+ spin_unlock_irqrestore(&data->registered_svc_list_lock, flags);
+
+ return entry;
+}
+
+static int __tzcom_copy_cmd(struct tzcom_data_t *data,
+ struct tzcom_next_cmd_op_req *req,
+ struct tzcom_registered_svc_list *ptr_svc)
+{
+ int found = 0;
+ int ret = -EAGAIN;
+ struct tzcom_callback_list *entry;
+ struct tzcom_callback *cb;
+
+ PDEBUG("In here");
+ mutex_lock(&data->callback_list_lock);
+ PDEBUG("Before looping through cmd and svc lists.");
+ list_for_each_entry(entry, &data->callback_list_head, list) {
+ cb = &entry->callback;
+ if (req->svc_id == ptr_svc->svc.svc_id &&
+ req->instance_id == ptr_svc->svc.instance_id &&
+ cb->cmd_id >= ptr_svc->svc.cmd_id_low &&
+ cb->cmd_id <= ptr_svc->svc.cmd_id_high) {
+ PDEBUG("Found matching entry");
+ found = 1;
+ if (cb->sb_out_cb_data_len <= req->req_len) {
+ PDEBUG("copying cmd buffer %p to req "
+ "buffer %p, length: %u",
+ (u8 *)cb + cb->sb_out_cb_data_off,
+ req->req_buf, cb->sb_out_cb_data_len);
+ req->cmd_id = cb->cmd_id;
+ ret = copy_to_user(req->req_buf,
+ (u8 *)cb + cb->sb_out_cb_data_off,
+ cb->sb_out_cb_data_len);
+ if (ret) {
+ PDEBUG("copy_to_user failed");
+ break;
+ }
+ list_del(&entry->list);
+ kfree(entry);
+ ret = 0;
+ } else {
+ PDEBUG("callback data buffer is "
+ "larger than provided buffer."
+ "Required: %u, Provided: %u",
+ cb->sb_out_cb_data_len,
+ req->req_len);
+ ret = -ENOMEM;
+ }
+ break;
+ }
+ }
+ PDEBUG("After looping through cmd and svc lists.");
+ mutex_unlock(&data->callback_list_lock);
+ return ret;
+}
+
+static int tzcom_read_next_cmd(struct tzcom_data_t *data, void __user *argp)
+{
+ int ret = 0;
+ struct tzcom_next_cmd_op_req req;
+ struct tzcom_registered_svc_list *this_svc;
+
+ ret = copy_from_user(&req, argp, sizeof(req));
+ if (ret) {
+ PDEBUG("copy_from_user failed");
+ return ret;
+ }
+
+ if (req.instance_id > atomic_read(&svc_instance_ctr)) {
+ PDEBUG("Invalid instance_id for the request");
+ return -EINVAL;
+ }
+
+ if (!req.req_buf || req.req_len == 0) {
+ PDEBUG("Invalid request buffer or buffer length");
+ return -EINVAL;
+ }
+
+ PDEBUG("Before next_cmd loop");
+ this_svc = __tzcom_find_svc(data, req.instance_id);
+
+ while (1) {
+ PDEBUG("Before wait_event next_cmd.");
+ if (wait_event_interruptible(this_svc->next_cmd_wq,
+ this_svc->next_cmd_flag != 0)) {
+ PDEBUG("Interrupted: exiting wait_next_cmd loop");
+ /* woken up for different reason */
+ return -ERESTARTSYS;
+ }
+ PDEBUG("After wait_event next_cmd.");
+ this_svc->next_cmd_flag = 0;
+
+ ret = __tzcom_copy_cmd(data, &req, this_svc);
+ if (ret == 0) {
+ PDEBUG("Successfully found svc for cmd");
+ data->handled_cmd_svc_instance_id = req.instance_id;
+ break;
+ } else if (ret == -ENOMEM) {
+ PDEBUG("Not enough memory");
+ return ret;
+ }
+ }
+ ret = copy_to_user(argp, &req, sizeof(req));
+ if (ret) {
+ PDEBUG("copy_to_user failed");
+ return ret;
+ }
+ PDEBUG("copy_to_user is done.");
+ return ret;
+}
+
+static int tzcom_cont_cmd(struct tzcom_data_t *data, void __user *argp)
+{
+ int ret = 0;
+ struct tzcom_cont_cmd_op_req req;
+ ret = copy_from_user(&req, argp, sizeof(req));
+ if (ret) {
+ PDEBUG("copy_from_user failed");
+ return ret;
+ }
+
+ /*
+ * Only the svc instance that handled the cmd (in read_next_cmd method)
+ * can call continue cmd
+ */
+ if (data->handled_cmd_svc_instance_id != req.instance_id) {
+ PDEBUG("Only the service instance that handled the last "
+ "callback can continue cmd. "
+ "Expected: %u, Received: %u",
+ data->handled_cmd_svc_instance_id,
+ req.instance_id);
+ return -EINVAL;
+ }
+
+ if (req.resp_buf) {
+ mutex_lock(&sb_out_lock);
+ memcpy(sb_out_virt, req.resp_buf, req.resp_len);
+ mutex_unlock(&sb_out_lock);
+ }
+
+ data->cont_cmd_flag = 1;
+ wake_up_interruptible(&data->cont_cmd_wq);
+ return ret;
+}
+
+static long tzcom_ioctl(struct file *file, unsigned cmd,
+ unsigned long arg)
+{
+ int ret = 0;
+ struct tzcom_data_t *tzcom_data = file->private_data;
+ void __user *argp = (void __user *) arg;
+ PDEBUG("enter tzcom_ioctl()");
+ switch (cmd) {
+ case TZCOM_IOCTL_REGISTER_SERVICE_REQ: {
+ PDEBUG("ioctl register_service_req()");
+ ret = tzcom_register_service(tzcom_data, argp);
+ if (ret)
+ PDEBUG("failed tzcom_register_service: %d", ret);
+ break;
+ }
+ case TZCOM_IOCTL_UNREGISTER_SERVICE_REQ: {
+ PDEBUG("ioctl unregister_service_req()");
+ ret = tzcom_unregister_service(tzcom_data, argp);
+ if (ret)
+ PDEBUG("failed tzcom_unregister_service: %d", ret);
+ break;
+ }
+ case TZCOM_IOCTL_SEND_CMD_REQ: {
+ PDEBUG("ioctl send_cmd_req()");
+ /* Only one client allowed here at a time */
+ mutex_lock(&send_cmd_lock);
+ ret = tzcom_send_cmd(tzcom_data, argp);
+ mutex_unlock(&send_cmd_lock);
+ if (ret)
+ PDEBUG("failed tzcom_send_cmd: %d", ret);
+ break;
+ }
+ case TZCOM_IOCTL_READ_NEXT_CMD_REQ: {
+ PDEBUG("ioctl read_next_cmd_req()");
+ ret = tzcom_read_next_cmd(tzcom_data, argp);
+ if (ret)
+ PDEBUG("failed tzcom_read_next: %d", ret);
+ break;
+ }
+ case TZCOM_IOCTL_CONTINUE_CMD_REQ: {
+ PDEBUG("ioctl continue_cmd_req()");
+ ret = tzcom_cont_cmd(tzcom_data, argp);
+ if (ret)
+ PDEBUG("failed tzcom_cont_cmd: %d", ret);
+ break;
+ }
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static int tzcom_open(struct inode *inode, struct file *file)
+{
+ long pil_error;
+ struct tz_pr_init_sb_req_s sb_out_init_req;
+ struct tz_pr_init_sb_rsp_s sb_out_init_rsp;
+ void *rsp_addr_virt;
+ struct tzcom_command cmd;
+ struct tzcom_response resp;
+ struct tzcom_data_t *tzcom_data;
+
+ PDEBUG("In here");
+ if (pil == NULL) {
+ pil = pil_get("playrdy");
+ if (IS_ERR(pil)) {
+ PERR("Playready PIL image load failed");
+ pil_error = PTR_ERR(pil);
+ pil = NULL;
+ return pil_error;
+ }
+ PDEBUG("playrdy image loaded successfully");
+ }
+
+ sb_out_init_req.pr_cmd = TZ_SCHED_CMD_ID_INIT_SB_OUT;
+ sb_out_init_req.sb_len = sb_out_length;
+ sb_out_init_req.sb_ptr = tzcom_virt_to_phys(sb_out_virt);
+ PDEBUG("sb_out_init_req { pr_cmd: %d, sb_len: %u, "
+ "sb_ptr (phys): 0x%x }",
+ sb_out_init_req.pr_cmd,
+ sb_out_init_req.sb_len,
+ sb_out_init_req.sb_ptr);
+
+ mutex_lock(&sb_in_lock);
+ PDEBUG("Before memcpy on sb_in");
+ memcpy(sb_in_virt, &sb_out_init_req, sizeof(sb_out_init_req));
+ PDEBUG("After memcpy on sb_in");
+
+ /* It will always be a new cmd from this method */
+ cmd.cmd_type = TZ_SCHED_CMD_NEW;
+ cmd.sb_in_cmd_addr = (u8 *) tzcom_virt_to_phys(sb_in_virt);
+ cmd.sb_in_cmd_len = sizeof(sb_out_init_req);
+ PDEBUG("tzcom_command { cmd_type: %u, sb_in_cmd_addr: %p, "
+ "sb_in_cmd_len: %u }",
+ cmd.cmd_type, cmd.sb_in_cmd_addr, cmd.sb_in_cmd_len);
+
+ resp.cmd_status = 0;
+ resp.sb_in_rsp_addr = (u8 *)cmd.sb_in_cmd_addr + cmd.sb_in_cmd_len;
+ resp.sb_in_rsp_len = sizeof(sb_out_init_rsp);
+ PDEBUG("tzcom_response before scm { cmd_status: %u, "
+ "sb_in_rsp_addr: %p, sb_in_rsp_len: %u }",
+ resp.cmd_status, resp.sb_in_rsp_addr,
+ resp.sb_in_rsp_len);
+
+ PDEBUG("Before scm_call for sb_init");
+ tzcom_scm_call(&cmd, sizeof(cmd), &resp, sizeof(resp));
+ PDEBUG("After scm_call for sb_init");
+ PDEBUG("tzcom_response after scm { cmd_status: %u, "
+ "sb_in_rsp_addr: %p, sb_in_rsp_len: %u }",
+ resp.cmd_status, resp.sb_in_rsp_addr,
+ resp.sb_in_rsp_len);
+
+ if (resp.sb_in_rsp_addr) {
+ rsp_addr_virt = tzcom_phys_to_virt((unsigned long)
+ resp.sb_in_rsp_addr);
+ PDEBUG("Received response phys: %p, virt: %p",
+ resp.sb_in_rsp_addr,
+ rsp_addr_virt);
+ memcpy(&sb_out_init_rsp, rsp_addr_virt, resp.sb_in_rsp_len);
+ } else {
+ PERR("Error with SB initialization");
+ mutex_unlock(&sb_in_lock);
+ return -EPERM;
+ }
+ mutex_unlock(&sb_in_lock);
+
+ PDEBUG("sb_out_init_rsp { pr_cmd: %d, ret: %d }",
+ sb_out_init_rsp.pr_cmd, sb_out_init_rsp.ret);
+
+ if (sb_out_init_rsp.ret) {
+ PERR("sb_out_init_req failed: %d", sb_out_init_rsp.ret);
+ return -EPERM;
+ }
+
+ tzcom_data = kmalloc(sizeof(*tzcom_data), GFP_KERNEL);
+ if (!tzcom_data) {
+ PERR("kmalloc failed");
+ return -ENOMEM;
+ }
+ file->private_data = tzcom_data;
+
+ INIT_LIST_HEAD(&tzcom_data->callback_list_head);
+ mutex_init(&tzcom_data->callback_list_lock);
+
+ INIT_LIST_HEAD(&tzcom_data->registered_svc_list_head);
+ spin_lock_init(&tzcom_data->registered_svc_list_lock);
+
+ init_waitqueue_head(&tzcom_data->cont_cmd_wq);
+ tzcom_data->cont_cmd_flag = 0;
+ tzcom_data->handled_cmd_svc_instance_id = 0;
+ return 0;
+}
+
+static int tzcom_release(struct inode *inode, struct file *file)
+{
+ struct tzcom_data_t *tzcom_data = file->private_data;
+ struct tzcom_callback_list *lcb, *ncb;
+ struct tzcom_registered_svc_list *lsvc, *nsvc;
+ PDEBUG("In here");
+
+ wake_up_all(&tzcom_data->cont_cmd_wq);
+
+ list_for_each_entry_safe(lcb, ncb,
+ &tzcom_data->callback_list_head, list) {
+ list_del(&lcb->list);
+ kfree(lcb);
+ }
+
+ list_for_each_entry_safe(lsvc, nsvc,
+ &tzcom_data->registered_svc_list_head, list) {
+ wake_up_all(&lsvc->next_cmd_wq);
+ list_del(&lsvc->list);
+ kfree(lsvc);
+ }
+
+ kfree(tzcom_data);
+ return 0;
+}
+
+static const struct file_operations tzcom_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = tzcom_ioctl,
+ .open = tzcom_open,
+ .release = tzcom_release
+};
+
+static int __init tzcom_init(void)
+{
+ int rc;
+ struct device *class_dev;
+
+ PDEBUG("Hello tzcom");
+
+ rc = alloc_chrdev_region(&tzcom_device_no, 0, 1, TZCOM_DEV);
+ if (rc < 0) {
+ PERR("alloc_chrdev_region failed %d", rc);
+ return rc;
+ }
+
+ driver_class = class_create(THIS_MODULE, TZCOM_DEV);
+ if (IS_ERR(driver_class)) {
+ rc = -ENOMEM;
+ PERR("class_create failed %d", rc);
+ goto unregister_chrdev_region;
+ }
+
+ class_dev = device_create(driver_class, NULL, tzcom_device_no, NULL,
+ TZCOM_DEV);
+ if (!class_dev) {
+ PERR("class_device_create failed %d", rc);
+ rc = -ENOMEM;
+ goto class_destroy;
+ }
+
+ cdev_init(&tzcom_cdev, &tzcom_fops);
+ tzcom_cdev.owner = THIS_MODULE;
+
+ rc = cdev_add(&tzcom_cdev, MKDEV(MAJOR(tzcom_device_no), 0), 1);
+ if (rc < 0) {
+ PERR("cdev_add failed %d", rc);
+ goto class_device_destroy;
+ }
+
+ sb_in_phys = pmem_kalloc(sb_in_length, PMEM_MEMTYPE_EBI1 |
+ PMEM_ALIGNMENT_4K);
+ if (IS_ERR((void *)sb_in_phys)) {
+ PERR("could not allocte in kernel pmem buffers for sb_in");
+ rc = -ENOMEM;
+ goto class_device_destroy;
+ }
+ PDEBUG("physical_addr for sb_in: 0x%x", sb_in_phys);
+
+ sb_in_virt = (u8 *) ioremap((unsigned long)sb_in_phys,
+ sb_in_length);
+ if (!sb_in_virt) {
+ PERR("Shared buffer IN allocation failed.");
+ rc = -ENOMEM;
+ goto class_device_destroy;
+ }
+ PDEBUG("sb_in virt address: %p, phys address: 0x%x",
+ sb_in_virt, tzcom_virt_to_phys(sb_in_virt));
+
+ sb_out_phys = pmem_kalloc(sb_out_length, PMEM_MEMTYPE_EBI1 |
+ PMEM_ALIGNMENT_4K);
+ if (IS_ERR((void *)sb_out_phys)) {
+ PERR("could not allocte in kernel pmem buffers for sb_out");
+ rc = -ENOMEM;
+ goto class_device_destroy;
+ }
+ PDEBUG("physical_addr for sb_out: 0x%x", sb_out_phys);
+
+ sb_out_virt = (u8 *) ioremap((unsigned long)sb_out_phys,
+ sb_out_length);
+ if (!sb_out_virt) {
+ PERR("Shared buffer OUT allocation failed.");
+ rc = -ENOMEM;
+ goto class_device_destroy;
+ }
+ PDEBUG("sb_out virt address: %p, phys address: 0x%x",
+ sb_out_virt, tzcom_virt_to_phys(sb_out_virt));
+
+ /* Initialized in tzcom_open */
+ pil = NULL;
+
+ return 0;
+
+class_device_destroy:
+ if (sb_in_virt)
+ iounmap(sb_in_virt);
+ if (sb_in_phys)
+ pmem_kfree(sb_in_phys);
+ if (sb_out_virt)
+ iounmap(sb_out_virt);
+ if (sb_out_phys)
+ pmem_kfree(sb_out_phys);
+ device_destroy(driver_class, tzcom_device_no);
+class_destroy:
+ class_destroy(driver_class);
+unregister_chrdev_region:
+ unregister_chrdev_region(tzcom_device_no, 1);
+ return rc;
+}
+
+static void __exit tzcom_exit(void)
+{
+ PDEBUG("Goodbye tzcom");
+ if (sb_in_virt)
+ iounmap(sb_in_virt);
+ if (sb_in_phys)
+ pmem_kfree(sb_in_phys);
+ if (sb_out_virt)
+ iounmap(sb_out_virt);
+ if (sb_out_phys)
+ pmem_kfree(sb_out_phys);
+ if (pil != NULL) {
+ pil_put("playrdy");
+ pil = NULL;
+ }
+ device_destroy(driver_class, tzcom_device_no);
+ class_destroy(driver_class);
+ unregister_chrdev_region(tzcom_device_no, 1);
+}
+
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Sachin Shah <sachins@codeaurora.org>");
+MODULE_DESCRIPTION("Qualcomm TrustZone Communicator");
+MODULE_VERSION("1.00");
+
+module_init(tzcom_init);
+module_exit(tzcom_exit);