Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 03b2eae0 authored by Zhen Kong's avatar Zhen Kong
Browse files

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: default avatarZhen Kong <zkong@codeaurora.org>
parent b288f22b
Loading
Loading
Loading
Loading
+228 −51
Original line number Diff line number Diff line
@@ -146,6 +146,11 @@ enum qseecom_listener_unregister_kthread_state {
	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(qsee_bw_mutex);
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 qseecom_control {
	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 @@ struct qseecom_client_handle {
	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 @@ static int qseecom_free_ce_info(struct qseecom_dev_handle *data,
						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 @@ static int qseecom_load_app(struct qseecom_dev_handle *data, void __user *argp)
		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 @@ static int qseecom_unmap_ion_allocated_memory(struct qseecom_dev_handle *data)
	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 @@ static int qseecom_unload_app(struct qseecom_dev_handle *data,
			(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;

		/* 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;
			}
		}
	}
		ret = __qseecom_unload_app(data, data->client.app_id);

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 @@ static int qseecom_unload_app(struct qseecom_dev_handle *data,
			found_app = false;
		}
	}

unload_exit:
	if (found_app) {
		spin_lock_irqsave(&qseecom.registered_app_list_lock, flags1);
@@ -2832,6 +2859,102 @@ static int qseecom_unload_app(struct qseecom_dev_handle *data,
	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 @@ static int __qseecom_send_cmd(struct qseecom_dev_handle *data,
		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 @@ static int __qseecom_load_fw(struct qseecom_dev_handle *data, char *appname,
		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 @@ int qseecom_start_app(struct qseecom_handle **handle,
	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 @@ int qseecom_start_app(struct qseecom_handle **handle,
	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 @@ int qseecom_start_app(struct qseecom_handle **handle,
		kfree(*handle);
		*handle = NULL;
	}
	__wakeup_unload_app_kthread();
	return ret;
}
EXPORT_SYMBOL(qseecom_start_app);
@@ -4804,6 +4941,7 @@ int qseecom_shutdown_app(struct qseecom_handle **handle)
	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 @@ int qseecom_shutdown_app(struct qseecom_handle **handle)
		kzfree(kclient);
		*handle = NULL;
	}

	__wakeup_unload_app_kthread();
	return ret;
}
EXPORT_SYMBOL(qseecom_shutdown_app);
@@ -4854,6 +4992,7 @@ int qseecom_send_command(struct qseecom_handle *handle, void *send_buf,
	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 @@ static int __qseecom_qteec_issue_cmd(struct qseecom_dev_handle *data,
			(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 @@ static int qseecom_qteec_invoke_modfd_cmd(struct qseecom_dev_handle *data,
			(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 @@ static long qseecom_ioctl(struct file *file,
		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 @@ static long qseecom_ioctl(struct file *file,
		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 @@ static long qseecom_ioctl(struct file *file,
		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 @@ static int qseecom_release(struct inode *inode, struct file *file)
			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 @@ static int qseecom_probe(struct platform_device *pdev)
	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 @@ static int qseecom_probe(struct platform_device *pdev)
	}
	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 @@ static int qseecom_remove(struct platform_device *pdev)

	ion_client_destroy(qseecom.ion_clnt);

	kthread_stop(qseecom.unload_app_kthread_task);

	kthread_stop(qseecom.unregister_lsnr_kthread_task);

	cdev_del(&qseecom.cdev);