[PATCH] mmc: vub300: defer host release from inactivity timer context

Tao Yu <[email protected]>
Newsgroups org.kernel.vger.linux-mmc,org.kernel.vger.linux-kernel,org.kernel.vger.stable
Message-ID <[email protected]>
The vub300 inactivity timer can drop the last controller reference after
the device has been disconnected:

  vub300_inactivity_timer_expired()
    kref_put()
      vub300_delete()
        mmc_free_host()
          mmc_host_classdev_release()
            wakeup_source_unregister()
              synchronize_srcu()

This release path currently runs from timer softirq context, but
synchronize_srcu() must be invoked from process context. With
DEBUG_OBJECTS_RCU_HEAD enabled this triggers a warning when SRCU
annotates its on-stack rcu_head from the softirq path, which can panic
the kernel when panic_on_warn is set.

Fix this by deferring the timer-held final put to a work item, so the
host teardown and wakeup-source unregistration always happen from
process context. Keep the other kref_put() call sites unchanged, as they
already run from sleepable context and do not need deferred release.

Reported-by: [email protected]
Fixes: 88095e7b473a ("mmc: Add new VUB300 USB-to-SD/SDIO/MMC driver")
Signed-off-by: Tao Yu <[email protected]>
---
 drivers/mmc/host/vub300.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/mmc/host/vub300.c b/drivers/mmc/host/vub300.c
index 2dae474dcd067..21f1b5d6ca862 100644
--- a/drivers/mmc/host/vub300.c
+++ b/drivers/mmc/host/vub300.c
@@ -344,6 +344,7 @@ struct vub300_mmc_host {
 	struct timer_list sg_transfer_timer;
 	struct usb_sg_request sg_request;
 	struct timer_list inactivity_timer;
+	struct work_struct release_work;
 	struct work_struct deadwork;
 	struct work_struct cmndwork;
 	struct delayed_work pollwork;
@@ -367,7 +368,7 @@ struct vub300_mmc_host {
 #define GET_SYSTEM_PORT_STATUS		0
 
 static void vub300_delete(struct kref *kref)
-{				/* kref callback - softirq */
+{				/* kref release callback */
 	struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
 	struct mmc_host *mmc = vub300->mmc;
 
@@ -443,6 +444,14 @@ static void vub300_queue_dead_work(struct vub300_mmc_host *vub300)
 	}
 }
 
+static void vub300_release_work_thread(struct work_struct *work)
+{
+	struct vub300_mmc_host *vub300 =
+		container_of(work, struct vub300_mmc_host, release_work);
+
+	kref_put(&vub300->kref, vub300_delete);
+}
+
 static void irqpoll_res_completed(struct urb *urb)
 {				/* urb completion handler - hardirq */
 	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
@@ -744,7 +753,7 @@ static void vub300_inactivity_timer_expired(struct timer_list *t)
 	struct vub300_mmc_host *vub300 = timer_container_of(vub300, t,
 							    inactivity_timer);
 	if (!vub300->interface) {
-		kref_put(&vub300->kref, vub300_delete);
+		queue_work(deadworkqueue, &vub300->release_work);
 	} else if (vub300->cmd) {
 		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
 	} else {
@@ -2324,6 +2333,7 @@ static int vub300_probe(struct usb_interface *interface,
 	usb_set_intfdata(interface, vub300);
 	INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
 	INIT_WORK(&vub300->cmndwork, vub300_cmndwork_thread);
+	INIT_WORK(&vub300->release_work, vub300_release_work_thread);
 	INIT_WORK(&vub300->deadwork, vub300_deadwork_thread);
 	kref_init(&vub300->kref);
 	timer_setup(&vub300->sg_transfer_timer, vub300_sg_timed_out, 0);
-- 
2.34.1
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.