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);