tzcom: Implement abort and cleanup of driver

Add a new IOCTL call to the driver to properly
abort all threads blocked on wait queues. Updated
release call for proper cleanup.

CRs-fixed: 303637, 304152
Signed-off-by: Sachin Shah <sachins@codeaurora.org>
diff --git a/drivers/misc/tzcom.c b/drivers/misc/tzcom.c
index ccd8444..63864ef 100644
--- a/drivers/misc/tzcom.c
+++ b/drivers/misc/tzcom.c
@@ -88,6 +88,9 @@
 	wait_queue_head_t cont_cmd_wq;
 	int               cont_cmd_flag;
 	u32               handled_cmd_svc_instance_id;
+	int               abort;
+	wait_queue_head_t abort_wq;
+	atomic_t          ioctl_count;
 };
 
 static int tzcom_scm_call(const void *cmd_buf, size_t cmd_len,
@@ -233,6 +236,13 @@
 	return -EINVAL;
 }
 
+static int __tzcom_is_cont_cmd(struct tzcom_data_t *data)
+{
+	int ret;
+	ret = (data->cont_cmd_flag != 0);
+	return ret || data->abort;
+}
+
 /**
  *   +---------+                              +-----+       +-----------------+
  *   |  TZCOM  |                              | SCM |       | TZCOM_SCHEDULER |
@@ -409,10 +419,15 @@
 		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)) {
+				__tzcom_is_cont_cmd(data))) {
 			PWARN("Interrupted: exiting send_cmd loop");
 			return -ERESTARTSYS;
 		}
+
+		if (data->abort) {
+			PERR("Aborting driver");
+			return -ENODEV;
+		}
 		data->cont_cmd_flag = 0;
 		cmd.cmd_type = TZ_SCHED_CMD_PENDING;
 		mutex_lock(&sb_in_lock);
@@ -515,6 +530,14 @@
 	return ret;
 }
 
+static int __tzcom_is_next_cmd(struct tzcom_data_t *data,
+		struct tzcom_registered_svc_list *svc)
+{
+	int ret;
+	ret = (svc->next_cmd_flag != 0);
+	return ret || data->abort;
+}
+
 static int tzcom_read_next_cmd(struct tzcom_data_t *data, void __user *argp)
 {
 	int ret = 0;
@@ -543,11 +566,16 @@
 	while (1) {
 		PDEBUG("Before wait_event next_cmd.");
 		if (wait_event_interruptible(this_svc->next_cmd_wq,
-				this_svc->next_cmd_flag != 0)) {
+				__tzcom_is_next_cmd(data, this_svc))) {
 			PWARN("Interrupted: exiting wait_next_cmd loop");
 			/* woken up for different reason */
 			return -ERESTARTSYS;
 		}
+
+		if (data->abort) {
+			PERR("Aborting driver");
+			return -ENODEV;
+		}
 		PDEBUG("After wait_event next_cmd.");
 		this_svc->next_cmd_flag = 0;
 
@@ -604,6 +632,41 @@
 	return ret;
 }
 
+static int tzcom_abort(struct tzcom_data_t *data)
+{
+	int ret = 0;
+	unsigned long flags;
+	struct tzcom_registered_svc_list *lsvc, *nsvc;
+	if (data->abort) {
+		PERR("Already aborting");
+		return -EINVAL;
+	}
+
+	data->abort = 1;
+
+	PDEBUG("Waking up cont_cmd_wq");
+	wake_up_all(&data->cont_cmd_wq);
+
+	spin_lock_irqsave(&data->registered_svc_list_lock, flags);
+	PDEBUG("Before waking up service wait queues");
+	list_for_each_entry_safe(lsvc, nsvc,
+			&data->registered_svc_list_head, list) {
+		wake_up_all(&lsvc->next_cmd_wq);
+	}
+	spin_unlock_irqrestore(&data->registered_svc_list_lock, flags);
+
+	PDEBUG("ioctl_count before loop: %d", atomic_read(&data->ioctl_count));
+	while (atomic_read(&data->ioctl_count) > 0) {
+		if (wait_event_interruptible(data->abort_wq,
+				atomic_read(&data->ioctl_count) <= 0)) {
+			PERR("Interrupted from abort");
+			ret = -ERESTARTSYS;
+			break;
+		}
+	}
+	return ret;
+}
+
 static long tzcom_ioctl(struct file *file, unsigned cmd,
 		unsigned long arg)
 {
@@ -611,17 +674,28 @@
 	struct tzcom_data_t *tzcom_data = file->private_data;
 	void __user *argp = (void __user *) arg;
 	PDEBUG("enter tzcom_ioctl()");
+	if (tzcom_data->abort) {
+		PERR("Aborting tzcom driver");
+		return -ENODEV;
+	}
+
 	switch (cmd) {
 	case TZCOM_IOCTL_REGISTER_SERVICE_REQ: {
 		PDEBUG("ioctl register_service_req()");
+		atomic_inc(&tzcom_data->ioctl_count);
 		ret = tzcom_register_service(tzcom_data, argp);
+		atomic_dec(&tzcom_data->ioctl_count);
+		wake_up_interruptible(&tzcom_data->abort_wq);
 		if (ret)
 			PERR("failed tzcom_register_service: %d", ret);
 		break;
 	}
 	case TZCOM_IOCTL_UNREGISTER_SERVICE_REQ: {
 		PDEBUG("ioctl unregister_service_req()");
+		atomic_inc(&tzcom_data->ioctl_count);
 		ret = tzcom_unregister_service(tzcom_data, argp);
+		atomic_dec(&tzcom_data->ioctl_count);
+		wake_up_interruptible(&tzcom_data->abort_wq);
 		if (ret)
 			PERR("failed tzcom_unregister_service: %d", ret);
 		break;
@@ -630,7 +704,10 @@
 		PDEBUG("ioctl send_cmd_req()");
 		/* Only one client allowed here at a time */
 		mutex_lock(&send_cmd_lock);
+		atomic_inc(&tzcom_data->ioctl_count);
 		ret = tzcom_send_cmd(tzcom_data, argp);
+		atomic_dec(&tzcom_data->ioctl_count);
+		wake_up_interruptible(&tzcom_data->abort_wq);
 		mutex_unlock(&send_cmd_lock);
 		if (ret)
 			PERR("failed tzcom_send_cmd: %d", ret);
@@ -638,18 +715,31 @@
 	}
 	case TZCOM_IOCTL_READ_NEXT_CMD_REQ: {
 		PDEBUG("ioctl read_next_cmd_req()");
+		atomic_inc(&tzcom_data->ioctl_count);
 		ret = tzcom_read_next_cmd(tzcom_data, argp);
+		atomic_dec(&tzcom_data->ioctl_count);
+		wake_up_interruptible(&tzcom_data->abort_wq);
 		if (ret)
 			PERR("failed tzcom_read_next: %d", ret);
 		break;
 	}
 	case TZCOM_IOCTL_CONTINUE_CMD_REQ: {
 		PDEBUG("ioctl continue_cmd_req()");
+		atomic_inc(&tzcom_data->ioctl_count);
 		ret = tzcom_cont_cmd(tzcom_data, argp);
+		atomic_dec(&tzcom_data->ioctl_count);
+		wake_up_interruptible(&tzcom_data->abort_wq);
 		if (ret)
 			PERR("failed tzcom_cont_cmd: %d", ret);
 		break;
 	}
+	case TZCOM_IOCTL_ABORT_REQ: {
+		PDEBUG("ioctl abort_req()");
+		ret = tzcom_abort(tzcom_data);
+		if (ret)
+			PERR("failed tzcom_abort: %d", ret);
+		break;
+	}
 	default:
 		return -EINVAL;
 	}
@@ -754,6 +844,9 @@
 	init_waitqueue_head(&tzcom_data->cont_cmd_wq);
 	tzcom_data->cont_cmd_flag = 0;
 	tzcom_data->handled_cmd_svc_instance_id = 0;
+	tzcom_data->abort = 0;
+	init_waitqueue_head(&tzcom_data->abort_wq);
+	atomic_set(&tzcom_data->ioctl_count, 0);
 	return 0;
 }
 
@@ -762,23 +855,35 @@
 	struct tzcom_data_t *tzcom_data = file->private_data;
 	struct tzcom_callback_list *lcb, *ncb;
 	struct tzcom_registered_svc_list *lsvc, *nsvc;
+	unsigned long flags;
 	PDEBUG("In here");
 
-	wake_up_all(&tzcom_data->cont_cmd_wq);
+	if (!tzcom_data->abort) {
+		PDEBUG("Calling abort");
+		tzcom_abort(tzcom_data);
+	}
 
+	PDEBUG("Before removing callback list");
+	mutex_lock(&tzcom_data->callback_list_lock);
 	list_for_each_entry_safe(lcb, ncb,
 			&tzcom_data->callback_list_head, list) {
 		list_del(&lcb->list);
 		kfree(lcb);
 	}
+	mutex_unlock(&tzcom_data->callback_list_lock);
+	PDEBUG("After removing callback list");
 
+	PDEBUG("Before removing svc list");
+	spin_lock_irqsave(&tzcom_data->registered_svc_list_lock, flags);
 	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);
 	}
+	spin_unlock_irqrestore(&tzcom_data->registered_svc_list_lock, flags);
+	PDEBUG("After removing svc list");
 
+	PDEBUG("Freeing tzcom data");
 	kfree(tzcom_data);
 	return 0;
 }
diff --git a/include/linux/tzcom.h b/include/linux/tzcom.h
index 1a3c774..a1b3dfc 100644
--- a/include/linux/tzcom.h
+++ b/include/linux/tzcom.h
@@ -98,4 +98,6 @@
 #define TZCOM_IOCTL_CONTINUE_CMD_REQ \
 	_IOWR(TZCOM_IOC_MAGIC, 5, struct tzcom_cont_cmd_op_req)
 
+#define TZCOM_IOCTL_ABORT_REQ _IO(TZCOM_IOC_MAGIC, 6)
+
 #endif /* __TZCOM_H_ */