msm: Fix mutex deadlock with circular wait
tty_unregister_driver should not be called in the tty_close fn.
This creates a mutex deadlock with circular wait in the tty core.
Hence move the driver unregister and all the cleanup to a separate
worker.
Change-Id: I75d4bc6e3d4745f2565cd99eb80e35e3c926b049
CRs-fixed: 428811
Signed-off-by: Venkat Gopalakrishnan <venkatg@codeaurora.org>
diff --git a/arch/arm/mach-msm/sdio_al_dloader.c b/arch/arm/mach-msm/sdio_al_dloader.c
index f48c32b..b0cb88f 100644
--- a/arch/arm/mach-msm/sdio_al_dloader.c
+++ b/arch/arm/mach-msm/sdio_al_dloader.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved.
+/* Copyright (c) 2011-2012, The Linux Foundation. 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
@@ -21,6 +21,7 @@
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/wakelock.h>
+#include <linux/workqueue.h>
#include <linux/mmc/card.h>
#include <linux/dma-mapping.h>
#include <mach/dma.h>
@@ -87,6 +88,8 @@
const char __user *buf, size_t count, loff_t *ppos);
#endif
+static void sdio_dld_tear_down(struct work_struct *work);
+DECLARE_WORK(cleanup, sdio_dld_tear_down);
/* STRUCTURES AND TYPES */
enum sdio_dld_op_mode {
@@ -224,6 +227,8 @@
static DEFINE_SPINLOCK(lock2);
static unsigned long lock_flags2;
+static atomic_t sdio_dld_in_use = ATOMIC_INIT(0);
+
/*
* sdio_op_mode sets the operation mode of the sdio_dloader -
* it may be in NORMAL_MODE, BOOT_TEST_MODE or AMSS_TEST_MODE
@@ -1108,6 +1113,10 @@
REAL_FUNC_TO_FUNC_IN_ARRAY(sdio_dld->sdioc_boot_func);
struct sdio_func *str_func = sdio_dld->card->sdio_func[func_in_array];
+ if (atomic_read(&sdio_dld_in_use) == 1)
+ return -EBUSY;
+
+ atomic_set(&sdio_dld_in_use, 1);
sdio_dld->tty_str = tty;
sdio_dld->tty_str->low_latency = 1;
sdio_dld->tty_str->icanon = 0;
@@ -1185,7 +1194,6 @@
*/
static void sdio_dld_close(struct tty_struct *tty, struct file *file)
{
- int status = 0;
struct sdioc_reg_chunk *reg = &sdio_dld->sdio_dloader_data.sdioc_reg;
/* informing the SDIOC that it can exit boot phase */
@@ -1200,20 +1208,6 @@
sdio_dld->dld_main_thread.exit_wait.wake_up_signal);
pr_debug(MODULE_NAME ": %s - CLOSING - WOKE UP...", __func__);
- del_timer_sync(&sdio_dld->timer);
- del_timer_sync(&sdio_dld->push_timer);
-
- sdio_dld_dealloc_local_buffers();
-
- tty_unregister_device(sdio_dld->tty_drv, 0);
-
- status = tty_unregister_driver(sdio_dld->tty_drv);
-
- if (status) {
- pr_err(MODULE_NAME ": %s - tty_unregister_driver() failed\n",
- __func__);
- }
-
#ifdef CONFIG_DEBUG_FS
gd.curr_i = curr_index;
gd.duration_ms = sdio_dld_info.time_msec;
@@ -1263,9 +1257,8 @@
if (sdio_dld->done_callback)
sdio_dld->done_callback();
- pr_info(MODULE_NAME ": %s - Freeing sdio_dld data structure, and "
- " returning...", __func__);
- kfree(sdio_dld);
+ schedule_work(&cleanup);
+ pr_info(MODULE_NAME ": %s - Bootloader done, returning...", __func__);
}
/**
@@ -2392,6 +2385,9 @@
struct sdio_func *str_func = NULL;
struct device *tty_dev;
+ if (atomic_read(&sdio_dld_in_use) == 1)
+ return -EBUSY;
+
if (num_of_devices == 0 || num_of_devices > MAX_NUM_DEVICES) {
pr_err(MODULE_NAME ": %s - invalid number of devices\n",
__func__);
@@ -2534,6 +2530,28 @@
return status;
}
+static void sdio_dld_tear_down(struct work_struct *work)
+{
+ int status = 0;
+
+ del_timer_sync(&sdio_dld->timer);
+ del_timer_sync(&sdio_dld->push_timer);
+
+ sdio_dld_dealloc_local_buffers();
+
+ tty_unregister_device(sdio_dld->tty_drv, 0);
+
+ status = tty_unregister_driver(sdio_dld->tty_drv);
+
+ if (status) {
+ pr_err(MODULE_NAME ": %s - tty_unregister_driver() failed\n",
+ __func__);
+ }
+
+ kfree(sdio_dld);
+ atomic_set(&sdio_dld_in_use, 0);
+}
+
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("SDIO Downloader");
MODULE_AUTHOR("Yaniv Gardi <ygardi@codeaurora.org>");