msm: subsystem_restart: Make subsystem_restart interrupt context safe
To allow for correct behavior during sleep modes, it is
necessary that clients call subsystem restart within the
interrupt handler assigned to the error interrupt for their
subsystems. This implies that the subsystem_restart() API
needs to be able to safely run in an interrupt context.
Use a workqueue instead of a kthread to perform subsystem
restart, since kthreads cannot be created in an atomic
context. Note that concurrency managed workqueues (cmwq) allow
the behavior of the subsystem restart API to be preserved
even after replacing the kthread with a workqueue. This
is because with cmwq, a work item that sleeps during its
execution will not prevent the next-scheduled-work item
from executing. Thus multiple restart sequences can execute
at the same time, as was the case with kthreads.
Change-Id: I6adaaed0e2312de1d508f289328dc2d5e64679a5
Signed-off-by: Vikram Mulukutla <markivx@codeaurora.org>
diff --git a/arch/arm/mach-msm/subsystem_restart.c b/arch/arm/mach-msm/subsystem_restart.c
index c3fd8c5..bc4bd21 100644
--- a/arch/arm/mach-msm/subsystem_restart.c
+++ b/arch/arm/mach-msm/subsystem_restart.c
@@ -45,11 +45,12 @@
struct subsys_data *subsys_ptrs[];
};
-struct restart_thread_data {
+struct restart_wq_data {
struct subsys_data *subsys;
struct wake_lock ssr_wake_lock;
char wakelockname[64];
int coupled;
+ struct work_struct work;
};
struct restart_log {
@@ -60,6 +61,7 @@
static int restart_level;
static int enable_ramdumps;
+struct workqueue_struct *ssr_wq;
static LIST_HEAD(restart_log_list);
static LIST_HEAD(subsystem_list);
@@ -301,9 +303,10 @@
mutex_unlock(&restart_log_mutex);
}
-static int subsystem_restart_thread(void *data)
+static void subsystem_restart_wq_func(struct work_struct *work)
{
- struct restart_thread_data *r_work = data;
+ struct restart_wq_data *r_work = container_of(work,
+ struct restart_wq_data, work);
struct subsys_data **restart_list;
struct subsys_data *subsys = r_work->subsys;
struct subsys_soc_restart_order *soc_restart_order = NULL;
@@ -435,15 +438,14 @@
out:
wake_unlock(&r_work->ssr_wake_lock);
wake_lock_destroy(&r_work->ssr_wake_lock);
- kfree(data);
- do_exit(0);
+ kfree(r_work);
}
int subsystem_restart(const char *subsys_name)
{
struct subsys_data *subsys;
- struct task_struct *tsk;
- struct restart_thread_data *data = NULL;
+ struct restart_wq_data *data = NULL;
+ int rc;
if (!subsys_name) {
pr_err("Invalid subsystem name.\n");
@@ -464,7 +466,7 @@
}
if (restart_level != RESET_SOC) {
- data = kzalloc(sizeof(struct restart_thread_data), GFP_KERNEL);
+ data = kzalloc(sizeof(struct restart_wq_data), GFP_KERNEL);
if (!data) {
restart_level = RESET_SOC;
pr_warn("Failed to alloc restart data. Resetting.\n");
@@ -493,18 +495,12 @@
data->wakelockname);
wake_lock(&data->ssr_wake_lock);
- /* Let the kthread handle the actual restarting. Using a
- * workqueue will not work since all restart requests are
- * serialized and it prevents the short circuiting of
- * restart requests for subsystems already in a restart
- * sequence.
- */
- tsk = kthread_run(subsystem_restart_thread, data,
- "subsystem_restart_thread");
- if (IS_ERR(tsk))
- panic("%s: Unable to create thread to restart %s",
- __func__, subsys->name);
+ INIT_WORK(&data->work, subsystem_restart_wq_func);
+ rc = schedule_work(&data->work);
+ if (rc < 0)
+ panic("%s: Unable to schedule work to restart %s",
+ __func__, subsys->name);
break;
case RESET_SOC:
@@ -608,6 +604,11 @@
restart_level = RESET_SOC;
+ ssr_wq = alloc_workqueue("ssr_wq", 0, 0);
+
+ if (!ssr_wq)
+ panic("Couldn't allocate workqueue for subsystem restart.\n");
+
ret = ssr_init_soc_restart_orders();
return ret;