ASoC: wcd9xxx: Fix MBHC irq handler deadlock scenario
There is a chance to encounter deadlock when either electrical mbhc irq
or mechanical mbhc irq releases resource lock to let polling thread finish
its job. This is due to the possibility of one irq handler can preempt
already running the other type of irq handler.
Introduce a new mutex to avoid this preemption.
CRs-fixed: 428653
Change-Id: Idf24c62224026c9c061de12c31e248c05b47de9d
Signed-off-by: Joonwoo Park <joonwoop@codeaurora.org>
diff --git a/drivers/mfd/wcd9xxx-irq.c b/drivers/mfd/wcd9xxx-irq.c
index 4c40c9e..fde658b 100644
--- a/drivers/mfd/wcd9xxx-irq.c
+++ b/drivers/mfd/wcd9xxx-irq.c
@@ -151,21 +151,35 @@
}
EXPORT_SYMBOL_GPL(wcd9xxx_unlock_sleep);
+void wcd9xxx_nested_irq_lock(struct wcd9xxx *wcd9xxx)
+{
+ mutex_lock(&wcd9xxx->nested_irq_lock);
+}
+
+void wcd9xxx_nested_irq_unlock(struct wcd9xxx *wcd9xxx)
+{
+ mutex_unlock(&wcd9xxx->nested_irq_lock);
+}
+
static void wcd9xxx_irq_dispatch(struct wcd9xxx *wcd9xxx, int irqbit)
{
if ((irqbit <= TABLA_IRQ_MBHC_INSERTION) &&
(irqbit >= TABLA_IRQ_MBHC_REMOVAL)) {
+ wcd9xxx_nested_irq_lock(wcd9xxx);
wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 +
BIT_BYTE(irqbit), BYTE_BIT_MASK(irqbit));
if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MODE, 0x02);
handle_nested_irq(wcd9xxx->irq_base + irqbit);
+ wcd9xxx_nested_irq_unlock(wcd9xxx);
} else {
+ wcd9xxx_nested_irq_lock(wcd9xxx);
handle_nested_irq(wcd9xxx->irq_base + irqbit);
wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_CLEAR0 +
BIT_BYTE(irqbit), BYTE_BIT_MASK(irqbit));
if (wcd9xxx_get_intf_type() == WCD9XXX_INTERFACE_TYPE_I2C)
wcd9xxx_reg_write(wcd9xxx, TABLA_A_INTR_MODE, 0x02);
+ wcd9xxx_nested_irq_unlock(wcd9xxx);
}
}
@@ -223,6 +237,7 @@
unsigned int i, cur_irq;
mutex_init(&wcd9xxx->irq_lock);
+ mutex_init(&wcd9xxx->nested_irq_lock);
if (!wcd9xxx->irq) {
dev_warn(wcd9xxx->dev,
@@ -235,6 +250,9 @@
dev_err(wcd9xxx->dev,
"No interrupt base specified, no interrupts\n");
return 0;
+ mutex_destroy(&wcd9xxx->nested_irq_lock);
+ mutex_destroy(&wcd9xxx->irq_lock);
+ mutex_destroy(&wcd9xxx->nested_irq_lock);
}
/* Mask the individual interrupt sources */
for (i = 0, cur_irq = wcd9xxx->irq_base; i < TABLA_NUM_IRQS; i++,
@@ -296,6 +314,7 @@
if (ret)
mutex_destroy(&wcd9xxx->irq_lock);
+ mutex_destroy(&wcd9xxx->nested_irq_lock);
return ret;
}
@@ -308,4 +327,5 @@
device_init_wakeup(wcd9xxx->dev, 0);
}
mutex_destroy(&wcd9xxx->irq_lock);
+ mutex_destroy(&wcd9xxx->nested_irq_lock);
}
diff --git a/include/linux/mfd/wcd9xxx/core.h b/include/linux/mfd/wcd9xxx/core.h
index 508bf3b..f83187d 100644
--- a/include/linux/mfd/wcd9xxx/core.h
+++ b/include/linux/mfd/wcd9xxx/core.h
@@ -129,6 +129,7 @@
struct mutex io_lock;
struct mutex xfer_lock;
struct mutex irq_lock;
+ struct mutex nested_irq_lock;
u8 version;
unsigned int irq_base;
@@ -177,6 +178,8 @@
bool wcd9xxx_lock_sleep(struct wcd9xxx *wcd9xxx);
void wcd9xxx_unlock_sleep(struct wcd9xxx *wcd9xxx);
+void wcd9xxx_nested_irq_lock(struct wcd9xxx *wcd9xxx);
+void wcd9xxx_nested_irq_unlock(struct wcd9xxx *wcd9xxx);
enum wcd9xxx_pm_state wcd9xxx_pm_cmpxchg(struct wcd9xxx *wcd9xxx,
enum wcd9xxx_pm_state o,
enum wcd9xxx_pm_state n);
diff --git a/sound/soc/codecs/wcd9310.c b/sound/soc/codecs/wcd9310.c
index cb40371..5dc08b3 100644
--- a/sound/soc/codecs/wcd9310.c
+++ b/sound/soc/codecs/wcd9310.c
@@ -121,7 +121,9 @@
#define TABLA_MBHC_GND_MIC_SWAP_THRESHOLD 2
-#define TABLA_ACQUIRE_LOCK(x) do { mutex_lock(&x); } while (0)
+#define TABLA_ACQUIRE_LOCK(x) do { \
+ mutex_lock_nested(&x, SINGLE_DEPTH_NESTING); \
+} while (0)
#define TABLA_RELEASE_LOCK(x) do { mutex_unlock(&x); } while (0)
static const DECLARE_TLV_DB_SCALE(digital_gain, 0, 1, 0);
@@ -7528,6 +7530,7 @@
{
bool insert;
struct tabla_priv *tabla = snd_soc_codec_get_drvdata(codec);
+ struct wcd9xxx *core = dev_get_drvdata(codec->dev->parent);
bool is_removed = false;
pr_debug("%s: enter\n", __func__);
@@ -7537,6 +7540,7 @@
usleep_range(TABLA_GPIO_IRQ_DEBOUNCE_TIME_US,
TABLA_GPIO_IRQ_DEBOUNCE_TIME_US);
+ wcd9xxx_nested_irq_lock(core);
TABLA_ACQUIRE_LOCK(tabla->codec_resource_lock);
/* cancel pending button press */
@@ -7608,6 +7612,7 @@
tabla->in_gpio_handler = false;
TABLA_RELEASE_LOCK(tabla->codec_resource_lock);
+ wcd9xxx_nested_irq_unlock(core);
pr_debug("%s: leave\n", __func__);
}