[PATCH v2 RESEND] extcon: Fix sleeping in atomic context after connecting USB cable

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Sleeping in atomic context happens because:
1. Extcon is notifying with raw notifier chain under spin lock.
2. Notified charger-manager calls sleeping functions.

Actually the problem is not only charger-manager specific because
the extcon is also exporting as API its raw notifier chain with
extcon_register_notifier(). User registers its own notifier which may or
may not sleep, and extcon does not know that.

Solve the problem in a generic way: notify on cable change not directly
in extcon_update_state(), but from scheduled work on ordered workqueue.

Details on issue (steps to reproduce):
======================================
Kernel built with extcon and charger-manager. After connecting the USB
cable:
[    6.534930] max77693-muic max77693-muic: external connector is attached(chg_type:0x1, prev_chg_type:0x1)
[    6.544043] max77693-muic max77693-muic: CONTROL1 : 0x09, CONTROL2 : 0x04, state : attached
[    6.551539] BUG: sleeping function called from invalid context at kernel/locking/mutex.c:615
[    6.559691] in_atomic(): 1, irqs_disabled(): 128, pid: 1248, name: kworker/2:1
[    6.566892] 4 locks held by kworker/2:1/1248:
[    6.571229]  #0:  ("events"){.+.+.+}, at: [<c0039648>] process_one_work+0x114/0x3f4
[    6.578867]  #1:  ((&info->irq_work)){+.+...}, at: [<c0039648>] process_one_work+0x114/0x3f4
[    6.587287]  #2:  (&info->mutex){+.+...}, at: [<c038e56c>] max77693_muic_irq_work+0x28/0x120
[    6.595706]  #3:  (&(&edev->lock)->rlock){......}, at: [<c038c3b8>] extcon_update_state+0x20/0x204
[    6.604648] irq event stamp: 3944
[    6.607945] hardirqs last  enabled at (3943): [<c0068fb4>] vprintk_emit+0x1dc/0x5c0
[    6.615584] hardirqs last disabled at (3944): [<c048b5ac>] _raw_spin_lock_irqsave+0x1c/0x5c
[    6.623917] softirqs last  enabled at (0): [<c0020c7c>] copy_process.part.54+0x3f4/0x1520
[    6.632076] softirqs last disabled at (0): [<  (null)>]   (null)
[    6.638065] Preemption disabled at:[<  (null)>]   (null)
[    6.643359]
[    6.644843] CPU: 2 PID: 1248 Comm: kworker/2:1 Not tainted 3.17.0-next-20141007-00047-g1b95596dfa88 #302
[    6.654307] Workqueue: events max77693_muic_irq_work
[    6.659277] [<c0014d2c>] (unwind_backtrace) from [<c0011c98>] (show_stack+0x10/0x14)
[    6.666986] [<c0011c98>] (show_stack) from [<c0484d18>] (dump_stack+0x70/0xbc)
[    6.674186] [<c0484d18>] (dump_stack) from [<c0487b38>] (mutex_lock_nested+0x24/0x458)
[    6.682087] [<c0487b38>] (mutex_lock_nested) from [<c028ef6c>] (regmap_read+0x30/0x60)
[    6.689990] [<c028ef6c>] (regmap_read) from [<c033d830>] (max77693_charger_get_property+0x20c/0x244)
[    6.699113] [<c033d830>] (max77693_charger_get_property) from [<c03365f8>] (power_supply_get_property+0x20/0x2c)
[    6.709255] [<c03365f8>] (power_supply_get_property) from [<c033a0a4>] (is_ext_pwr_online+0x30/0xb8)
[    6.718364] [<c033a0a4>] (is_ext_pwr_online) from [<c033b6ac>] (charger_extcon_notifier+0x40/0x70)
[    6.727306] [<c033b6ac>] (charger_extcon_notifier) from [<c038bf4c>] (_call_per_cable+0x40/0x4c)
[    6.736080] [<c038bf4c>] (_call_per_cable) from [<c00402b4>] (notifier_call_chain+0x64/0xd4)
[    6.744492] [<c00402b4>] (notifier_call_chain) from [<c0040340>] (raw_notifier_call_chain+0x18/0x20)
[    6.753607] [<c0040340>] (raw_notifier_call_chain) from [<c038c440>] (extcon_update_state+0xa8/0x204)
[    6.762807] [<c038c440>] (extcon_update_state) from [<c038de44>] (max77693_muic_chg_handler+0x1f4/0x25c)
[    6.772267] [<c038de44>] (max77693_muic_chg_handler) from [<c038e650>] (max77693_muic_irq_work+0x10c/0x120)
[    6.781992] [<c038e650>] (max77693_muic_irq_work) from [<c00396b4>] (process_one_work+0x180/0x3f4)
[    6.790930] [<c00396b4>] (process_one_work) from [<c003998c>] (worker_thread+0x30/0x458)
[    6.799000] [<c003998c>] (worker_thread) from [<c003f374>] (kthread+0xd8/0xf4)
[    6.806209] [<c003f374>] (kthread) from [<c000f268>] (ret_from_fork+0x14/0x2c)

The first sleeping function is is_ext_pwr_online()
(drivers/power/charger-manager.c). The atomic context initiating the
flow is set up in extcon_update_state() (drivers/extcon/extcon-class.c).

Signed-off-by: Krzysztof Kozlowski <k.kozlowski@xxxxxxxxxxx>
Cc: <stable@xxxxxxxxxxxxxxx>
Fixes: 0ea625037826 ("Extcon: renamed files to comply with the standard naming.")

---

Changes since v1:
1. Re-work after discussions MyungJoo Ham. Don't change spin locks into
   mutexes but completely move raw_notifier_call_chain out of atomic
   context.
2. Mark cc-stable.
---
 drivers/extcon/extcon-class.c | 47 ++++++++++++++++++++++++++++++++++++++++++-
 include/linux/extcon.h        |  1 +
 2 files changed, 47 insertions(+), 1 deletion(-)

diff --git a/drivers/extcon/extcon-class.c b/drivers/extcon/extcon-class.c
index 4c2f2c543bb7..730e41cad935 100644
--- a/drivers/extcon/extcon-class.c
+++ b/drivers/extcon/extcon-class.c
@@ -34,6 +34,16 @@
 #include <linux/of.h>
 
 /*
+ * Not part of API.
+ * Work item for notifying on update state.
+ */
+struct notifier_work {
+	struct extcon_dev *edev;
+	u32 old_state;
+	struct work_struct work;
+};
+
+/*
  * extcon_cable_name suggests the standard cable names for commonly used
  * cable types.
  *
@@ -187,6 +197,32 @@ static ssize_t cable_state_show(struct device *dev,
 					       cable->cable_index));
 }
 
+static void update_state_notifier_worker(struct work_struct *_work)
+{
+	struct notifier_work *work = container_of(_work, struct notifier_work,
+			work);
+
+	raw_notifier_call_chain(&work->edev->nh, work->old_state, work->edev);
+	kfree(work);
+}
+
+static int queue_update_state_notifier(struct extcon_dev *edev, u32 old_state)
+{
+	struct notifier_work *work;
+
+	work = kzalloc(sizeof(*work), GFP_NOWAIT);
+	if (!work)
+		return -ENOMEM;
+
+	work->edev = edev;
+	work->old_state = old_state;
+	INIT_WORK(&work->work, update_state_notifier_worker);
+
+	queue_work(edev->noti_workqueue, &work->work);
+
+	return 0;
+}
+
 /**
  * extcon_update_state() - Update the cable attach states of the extcon device
  *			   only for the masked bits.
@@ -216,6 +252,7 @@ int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
 
 	if (edev->state != ((edev->state & ~mask) | (state & mask))) {
 		u32 old_state = edev->state;
+		int ret;
 
 		if (check_mutually_exclusive(edev, (edev->state & ~mask) |
 						   (state & mask))) {
@@ -226,7 +263,12 @@ int extcon_update_state(struct extcon_dev *edev, u32 mask, u32 state)
 		edev->state &= ~mask;
 		edev->state |= state & mask;
 
-		raw_notifier_call_chain(&edev->nh, old_state, edev);
+		ret = queue_update_state_notifier(edev, old_state);
+		if (ret) {
+			spin_unlock_irqrestore(&edev->lock, flags);
+			return ret;
+		}
+
 		/* This could be in interrupt handler */
 		prop_buf = (char *)get_zeroed_page(GFP_ATOMIC);
 		if (prop_buf) {
@@ -588,6 +630,8 @@ struct extcon_dev *extcon_dev_allocate(const char **supported_cable)
 	edev->max_supported = 0;
 	edev->supported_cable = supported_cable;
 
+	edev->noti_workqueue = create_singlethread_workqueue("extcon");
+
 	return edev;
 }
 
@@ -597,6 +641,7 @@ struct extcon_dev *extcon_dev_allocate(const char **supported_cable)
  */
 void extcon_dev_free(struct extcon_dev *edev)
 {
+	destroy_workqueue(edev->noti_workqueue);
 	kfree(edev);
 }
 EXPORT_SYMBOL_GPL(extcon_dev_free);
diff --git a/include/linux/extcon.h b/include/linux/extcon.h
index 36f49c405dfb..327bf5d85920 100644
--- a/include/linux/extcon.h
+++ b/include/linux/extcon.h
@@ -123,6 +123,7 @@ struct extcon_dev {
 	/* Internal data. Please do not set. */
 	struct device dev;
 	struct raw_notifier_head nh;
+	struct workqueue_struct *noti_workqueue;
 	struct list_head entry;
 	int max_supported;
 	spinlock_t lock;	/* could be called by irq handler */
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe stable" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html




[Index of Archives]     [Linux Kernel]     [Kernel Development Newbies]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]