qseecom: fix race condition between load/unload app during reboot
To avoid race condition on app_access_lock between client thread
loading app and waiting on a listener response and another thread
unloading app during device reboot, make change to add unloading
app requests into a waiting list and start a separate kthread
to unload app when lock is released.
Change-Id: I24391bc34bb9a076b269067cfd885d62b0b53e3e
Signed-off-by: Zhen Kong <zkong@codeaurora.org>
diff --git a/drivers/misc/qseecom.c b/drivers/misc/qseecom.c
index 7c5321f..3b8c18f 100644
--- a/drivers/misc/qseecom.c
+++ b/drivers/misc/qseecom.c
@@ -146,6 +146,11 @@
LSNR_UNREG_KT_WAKEUP,
};
+enum qseecom_unload_app_kthread_state {
+ UNLOAD_APP_KT_SLEEP = 0,
+ UNLOAD_APP_KT_WAKEUP,
+};
+
static struct class *driver_class;
static dev_t qseecom_device_no;
@@ -153,6 +158,7 @@
static DEFINE_MUTEX(app_access_lock);
static DEFINE_MUTEX(clk_access_lock);
static DEFINE_MUTEX(listener_access_lock);
+static DEFINE_MUTEX(unload_app_pending_list_lock);
struct sglist_info {
@@ -307,6 +313,16 @@
struct task_struct *unregister_lsnr_kthread_task;
wait_queue_head_t unregister_lsnr_kthread_wq;
atomic_t unregister_lsnr_kthread_state;
+
+ struct list_head unload_app_pending_list_head;
+ struct task_struct *unload_app_kthread_task;
+ wait_queue_head_t unload_app_kthread_wq;
+ atomic_t unload_app_kthread_state;
+};
+
+struct qseecom_unload_app_pending_list {
+ struct list_head list;
+ struct qseecom_dev_handle *data;
};
struct qseecom_sec_buf_fd_info {
@@ -332,6 +348,7 @@
u32 app_arch;
struct qseecom_sec_buf_fd_info sec_buf_fd[MAX_ION_FD];
bool from_smcinvoke;
+ bool unload_pending;
};
struct qseecom_listener_handle {
@@ -409,6 +426,8 @@
void __user *argp);
static int qseecom_query_ce_info(struct qseecom_dev_handle *data,
void __user *argp);
+static int __qseecom_unload_app(struct qseecom_dev_handle *data,
+ uint32_t app_id);
static int get_qseecom_keymaster_status(char *str)
{
@@ -2561,8 +2580,11 @@
if (resp.result == QSEOS_RESULT_INCOMPLETE) {
ret = __qseecom_process_incomplete_cmd(data, &resp);
if (ret) {
- pr_err("process_incomplete_cmd failed err: %d\n",
- ret);
+ /* TZ has created app_id, need to unload it */
+ pr_err("incomp_cmd err %d, %d, unload %d %s\n",
+ ret, resp.result, resp.data,
+ load_img_req.img_name);
+ __qseecom_unload_app(data, resp.data);
if (!IS_ERR_OR_NULL(ihandle))
ion_free(qseecom.ion_clnt, ihandle);
ret = -EFAULT;
@@ -2682,18 +2704,61 @@
return ret;
}
+static int __qseecom_unload_app(struct qseecom_dev_handle *data,
+ uint32_t app_id)
+{
+ struct qseecom_unload_app_ireq req;
+ struct qseecom_command_scm_resp resp;
+ int ret = 0;
+
+ /* Populate the structure for sending scm call to load image */
+ req.qsee_cmd_id = QSEOS_APP_SHUTDOWN_COMMAND;
+ req.app_id = app_id;
+
+ /* SCM_CALL to unload the app */
+ ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &req,
+ sizeof(struct qseecom_unload_app_ireq),
+ &resp, sizeof(resp));
+ if (ret) {
+ pr_err("scm_call to unload app (id = %d) failed\n", app_id);
+ return -EFAULT;
+ }
+ switch (resp.result) {
+ case QSEOS_RESULT_SUCCESS:
+ pr_warn("App (%d) is unloaded\n", app_id);
+ break;
+ case QSEOS_RESULT_INCOMPLETE:
+ ret = __qseecom_process_incomplete_cmd(data, &resp);
+ if (ret)
+ pr_err("unload app %d fail proc incom cmd: %d,%d,%d\n",
+ app_id, ret, resp.result, resp.data);
+ else
+ pr_warn("App (%d) is unloaded\n", app_id);
+ break;
+ case QSEOS_RESULT_FAILURE:
+ pr_err("app (%d) unload_failed!!\n", app_id);
+ ret = -EFAULT;
+ break;
+ default:
+ pr_err("unload app %d get unknown resp.result %d\n",
+ app_id, resp.result);
+ ret = -EFAULT;
+ break;
+ }
+ return ret;
+}
+
static int qseecom_unload_app(struct qseecom_dev_handle *data,
bool app_crash)
{
unsigned long flags;
unsigned long flags1;
int ret = 0;
- struct qseecom_command_scm_resp resp;
struct qseecom_registered_app_list *ptr_app = NULL;
bool unload = false;
bool found_app = false;
bool found_dead_app = false;
- bool scm_called = false;
+ bool doublecheck = false;
if (!data) {
pr_err("Invalid/uninitialized device handle\n");
@@ -2743,48 +2808,9 @@
(char *)data->client.app_name);
if (unload) {
- struct qseecom_unload_app_ireq req;
- /* Populate the structure for sending scm call to load image */
- req.qsee_cmd_id = QSEOS_APP_SHUTDOWN_COMMAND;
- req.app_id = data->client.app_id;
+ ret = __qseecom_unload_app(data, data->client.app_id);
- /* SCM_CALL to unload the app */
- ret = qseecom_scm_call(SCM_SVC_TZSCHEDULER, 1, &req,
- sizeof(struct qseecom_unload_app_ireq),
- &resp, sizeof(resp));
- scm_called = true;
- if (ret) {
- pr_err("scm_call to unload app (id = %d) failed\n",
- req.app_id);
- ret = -EFAULT;
- goto scm_exit;
- } else {
- pr_warn("App id %d now unloaded\n", req.app_id);
- }
- if (resp.result == QSEOS_RESULT_FAILURE) {
- pr_err("app (%d) unload_failed!!\n",
- data->client.app_id);
- ret = -EFAULT;
- goto scm_exit;
- }
- if (resp.result == QSEOS_RESULT_SUCCESS)
- pr_debug("App (%d) is unloaded!!\n",
- data->client.app_id);
- if (resp.result == QSEOS_RESULT_INCOMPLETE) {
- ret = __qseecom_process_incomplete_cmd(data, &resp);
- if (ret) {
- pr_err("process_incomplete_cmd fail err: %d\n",
- ret);
- goto scm_exit;
- }
- }
- }
-
-scm_exit:
- if (scm_called) {
/* double check if this app_entry still exists */
- bool doublecheck = false;
-
spin_lock_irqsave(&qseecom.registered_app_list_lock, flags1);
list_for_each_entry(ptr_app,
&qseecom.registered_app_list_head, list) {
@@ -2804,6 +2830,7 @@
found_app = false;
}
}
+
unload_exit:
if (found_app) {
spin_lock_irqsave(&qseecom.registered_app_list_lock, flags1);
@@ -2832,6 +2859,102 @@
return ret;
}
+
+static int qseecom_prepare_unload_app(struct qseecom_dev_handle *data)
+{
+ struct qseecom_unload_app_pending_list *entry = NULL;
+
+ pr_debug("prepare to unload app(%d)(%s), pending %d\n",
+ data->client.app_id, data->client.app_name,
+ data->client.unload_pending);
+ if (data->client.unload_pending)
+ return 0;
+ entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+ if (!entry)
+ return -ENOMEM;
+ entry->data = data;
+ mutex_lock(&unload_app_pending_list_lock);
+ list_add_tail(&entry->list,
+ &qseecom.unload_app_pending_list_head);
+ mutex_unlock(&unload_app_pending_list_lock);
+ data->client.unload_pending = true;
+ pr_debug("unload ta %d pending\n", data->client.app_id);
+ return 0;
+}
+
+static void __wakeup_unload_app_kthread(void)
+{
+ atomic_set(&qseecom.unload_app_kthread_state,
+ UNLOAD_APP_KT_WAKEUP);
+ wake_up_interruptible(&qseecom.unload_app_kthread_wq);
+}
+
+static bool __qseecom_find_pending_unload_app(uint32_t app_id, char *app_name)
+{
+ struct qseecom_unload_app_pending_list *entry = NULL;
+ bool found = false;
+
+ mutex_lock(&unload_app_pending_list_lock);
+ list_for_each_entry(entry, &qseecom.unload_app_pending_list_head,
+ list) {
+ if ((entry->data->client.app_id == app_id) &&
+ (!strcmp(entry->data->client.app_name, app_name))) {
+ found = true;
+ break;
+ }
+ }
+ mutex_unlock(&unload_app_pending_list_lock);
+ return found;
+}
+
+static void __qseecom_processing_pending_unload_app(void)
+{
+ struct qseecom_unload_app_pending_list *entry = NULL;
+ struct list_head *pos;
+ int ret = 0;
+
+ mutex_lock(&unload_app_pending_list_lock);
+ while (!list_empty(&qseecom.unload_app_pending_list_head)) {
+ pos = qseecom.unload_app_pending_list_head.next;
+ entry = list_entry(pos,
+ struct qseecom_unload_app_pending_list, list);
+ if (entry && entry->data) {
+ pr_debug("process pending unload app %d (%s)\n",
+ entry->data->client.app_id,
+ entry->data->client.app_name);
+ mutex_unlock(&unload_app_pending_list_lock);
+ mutex_lock(&app_access_lock);
+ ret = qseecom_unload_app(entry->data, true);
+ if (ret)
+ pr_err("unload app %d pending failed %d\n",
+ entry->data->client.app_id, ret);
+ mutex_unlock(&app_access_lock);
+ mutex_lock(&unload_app_pending_list_lock);
+ kzfree(entry->data);
+ }
+ list_del(pos);
+ kzfree(entry);
+ }
+ mutex_unlock(&unload_app_pending_list_lock);
+}
+
+static int __qseecom_unload_app_kthread_func(void *data)
+{
+ while (!kthread_should_stop()) {
+ wait_event_interruptible(
+ qseecom.unload_app_kthread_wq,
+ atomic_read(&qseecom.unload_app_kthread_state)
+ == UNLOAD_APP_KT_WAKEUP);
+ pr_debug("kthread to unload app is called, state %d\n",
+ atomic_read(&qseecom.unload_app_kthread_state));
+ __qseecom_processing_pending_unload_app();
+ atomic_set(&qseecom.unload_app_kthread_state,
+ UNLOAD_APP_KT_SLEEP);
+ }
+ pr_warn("kthread to unload app stopped\n");
+ return 0;
+}
+
static phys_addr_t __qseecom_uvirt_to_kphys(struct qseecom_dev_handle *data,
unsigned long virt)
{
@@ -3284,6 +3407,13 @@
return -ENOENT;
}
+ if (__qseecom_find_pending_unload_app(data->client.app_id,
+ data->client.app_name)) {
+ pr_err("app %d (%s) unload is pending\n",
+ data->client.app_id, data->client.app_name);
+ return -ENOENT;
+ }
+
if (qseecom.qsee_version < QSEE_VERSION_40) {
send_data_req.app_id = data->client.app_id;
send_data_req.req_ptr = (uint32_t)(__qseecom_uvirt_to_kphys(
@@ -4430,10 +4560,14 @@
break;
case QSEOS_RESULT_INCOMPLETE:
ret = __qseecom_process_incomplete_cmd(data, &resp);
- if (ret)
- pr_err("process_incomplete_cmd FAILED\n");
- else
+ if (ret) {
+ pr_err("incomp_cmd err %d, %d, unload %d %s\n",
+ ret, resp.result, resp.data, appname);
+ __qseecom_unload_app(data, resp.data);
+ ret = -EFAULT;
+ } else {
*app_id = resp.data;
+ }
break;
case QSEOS_RESULT_FAILURE:
pr_err("scm call failed with response QSEOS_RESULT FAILURE\n");
@@ -4630,6 +4764,7 @@
uint32_t app_id = 0;
__wakeup_unregister_listener_kthread();
+ __wakeup_unload_app_kthread();
if (atomic_read(&qseecom.qseecom_state) != QSEECOM_STATE_READY) {
pr_err("Not allowed to be called in %d state\n",
@@ -4770,6 +4905,7 @@
spin_unlock_irqrestore(&qseecom.registered_kclient_list_lock, flags);
mutex_unlock(&app_access_lock);
+ __wakeup_unload_app_kthread();
return 0;
exit_ion_unmap_kernel:
@@ -4790,6 +4926,7 @@
kfree(*handle);
*handle = NULL;
}
+ __wakeup_unload_app_kthread();
return ret;
}
EXPORT_SYMBOL(qseecom_start_app);
@@ -4804,6 +4941,7 @@
bool found_handle = false;
__wakeup_unregister_listener_kthread();
+ __wakeup_unload_app_kthread();
if (atomic_read(&qseecom.qseecom_state) != QSEECOM_STATE_READY) {
pr_err("Not allowed to be called in %d state\n",
@@ -4840,7 +4978,7 @@
kzfree(kclient);
*handle = NULL;
}
-
+ __wakeup_unload_app_kthread();
return ret;
}
EXPORT_SYMBOL(qseecom_shutdown_app);
@@ -4854,6 +4992,7 @@
bool perf_enabled = false;
__wakeup_unregister_listener_kthread();
+ __wakeup_unload_app_kthread();
if (atomic_read(&qseecom.qseecom_state) != QSEECOM_STATE_READY) {
pr_err("Not allowed to be called in %d state\n",
@@ -6818,6 +6957,12 @@
(char *)data->client.app_name);
return -ENOENT;
}
+ if (__qseecom_find_pending_unload_app(data->client.app_id,
+ data->client.app_name)) {
+ pr_err("app %d (%s) unload is pending\n",
+ data->client.app_id, data->client.app_name);
+ return -ENOENT;
+ }
req->req_ptr = (void *)__qseecom_uvirt_to_kvirt(data,
(uintptr_t)req->req_ptr);
@@ -7021,6 +7166,12 @@
(char *)data->client.app_name);
return -ENOENT;
}
+ if (__qseecom_find_pending_unload_app(data->client.app_id,
+ data->client.app_name)) {
+ pr_err("app %d (%s) unload is pending\n",
+ data->client.app_id, data->client.app_name);
+ return -ENOENT;
+ }
/* validate offsets */
for (i = 0; i < MAX_ION_FD; i++) {
@@ -7173,6 +7324,7 @@
cmd != QSEECOM_IOCTL_SEND_MODFD_RESP &&
cmd != QSEECOM_IOCTL_SEND_MODFD_RESP_64)
__wakeup_unregister_listener_kthread();
+ __wakeup_unload_app_kthread();
switch (cmd) {
case QSEECOM_IOCTL_REGISTER_LISTENER_REQ: {
@@ -7425,6 +7577,7 @@
mutex_unlock(&app_access_lock);
if (ret)
pr_err("failed load_app request: %d\n", ret);
+ __wakeup_unload_app_kthread();
break;
}
case QSEECOM_IOCTL_UNLOAD_APP_REQ: {
@@ -7443,6 +7596,7 @@
mutex_unlock(&app_access_lock);
if (ret)
pr_err("failed unload_app request: %d\n", ret);
+ __wakeup_unload_app_kthread();
break;
}
case QSEECOM_IOCTL_GET_QSEOS_VERSION_REQ: {
@@ -7907,9 +8061,12 @@
mutex_unlock(&listener_access_lock);
break;
case QSEECOM_CLIENT_APP:
- mutex_lock(&app_access_lock);
- ret = qseecom_unload_app(data, true);
- mutex_unlock(&app_access_lock);
+ pr_debug("release app %d (%s)\n",
+ data->client.app_id, data->client.app_name);
+ if (data->client.app_id) {
+ free_private_data = false;
+ ret = qseecom_prepare_unload_app(data);
+ }
break;
case QSEECOM_SECURE_SERVICE:
case QSEECOM_GENERIC:
@@ -8780,6 +8937,8 @@
init_waitqueue_head(&qseecom.send_resp_wq);
init_waitqueue_head(&qseecom.register_lsnr_pending_wq);
init_waitqueue_head(&qseecom.unregister_lsnr_kthread_wq);
+ INIT_LIST_HEAD(&qseecom.unload_app_pending_list_head);
+ init_waitqueue_head(&qseecom.unload_app_kthread_wq);
qseecom.send_resp_flag = 0;
qseecom.qsee_version = QSEEE_VERSION_00;
@@ -8995,9 +9154,25 @@
}
atomic_set(&qseecom.unregister_lsnr_kthread_state,
LSNR_UNREG_KT_SLEEP);
+
+ /*create a kthread to process pending ta unloading task */
+ qseecom.unload_app_kthread_task = kthread_run(
+ __qseecom_unload_app_kthread_func,
+ NULL, "qseecom-unload-ta");
+ if (IS_ERR(qseecom.unload_app_kthread_task)) {
+ pr_err("failed to create kthread to unload ta\n");
+ rc = -EINVAL;
+ goto exit_kill_unreg_lsnr_kthread;
+ }
+ atomic_set(&qseecom.unload_app_kthread_state,
+ UNLOAD_APP_KT_SLEEP);
+
atomic_set(&qseecom.qseecom_state, QSEECOM_STATE_READY);
return 0;
+exit_kill_unreg_lsnr_kthread:
+ kthread_stop(qseecom.unregister_lsnr_kthread_task);
+
exit_deinit_clock:
__qseecom_deinit_clk(CLK_QSEE);
if ((qseecom.qsee.instance != qseecom.ce_drv.instance) &&
@@ -9111,6 +9286,8 @@
ion_client_destroy(qseecom.ion_clnt);
+ kthread_stop(qseecom.unload_app_kthread_task);
+
kthread_stop(qseecom.unregister_lsnr_kthread_task);
cdev_del(&qseecom.cdev);