[PATCH 2/2] mmc: sdio: handle interrupt corner case during suspend

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

 



Sdio device may trigger interrupt after its function suspended
while system not suspended yet.

This patch handle above cases by below two methods:
1. For multiple sdio_funcs, abort suspend if interrupt happen after
its func suspended while some other funcs not suspended yet.
2. If interrupt happens after all sdio_funcs suspended, wakeup the
system.

Signed-off-by: Jialing Fu <jlfu@xxxxxxxxxxx>
Signed-off-by: Kevin Liu <kliu5@xxxxxxxxxxx>
---
 drivers/mmc/core/sdio.c       |   57 ++++++++++++++++++++++++++++++++++------
 drivers/mmc/core/sdio_irq.c   |    5 +++
 include/linux/mmc/host.h      |    5 +++
 include/linux/mmc/sdio_func.h |    6 ++++
 4 files changed, 64 insertions(+), 9 deletions(-)

diff --git a/drivers/mmc/core/sdio.c b/drivers/mmc/core/sdio.c
index 81140b9..d9b4a11 100644
--- a/drivers/mmc/core/sdio.c
+++ b/drivers/mmc/core/sdio.c
@@ -888,6 +888,14 @@ out:
 	}
 }
 
+void mmc_sdio_irq_wakeup(struct mmc_host *host)
+{
+	pr_warning("%s: submit a wakeup event\n",
+		       mmc_hostname(host));
+	/* set the process time to 3 seconds */
+	pm_wakeup_event(mmc_dev(host), 3000);
+}
+
 /*
  * SDIO suspend.  We need to suspend all functions separately.
  * Therefore all registered functions must have drivers with suspend
@@ -897,8 +905,17 @@ static int mmc_sdio_suspend(struct mmc_host *host)
 {
 	int i, err = 0;
 
+	atomic_set(&host->sdio_suspend_abort, 0);
 	for (i = 0; i < host->card->sdio_funcs; i++) {
 		struct sdio_func *func = host->card->sdio_func[i];
+
+		/* Abort suspend if irq come after its sdio_func suspended */
+		if (i && atomic_cmpxchg(&host->sdio_suspend_abort, 1, 0)) {
+			atomic_set(&host->sdio_suspend_abort, 0);
+			err = -EBUSY;
+			break;
+		}
+
 		if (func && sdio_func_present(func) && func->dev.driver) {
 			const struct dev_pm_ops *pmops = func->dev.driver->pm;
 			if (!pmops || !pmops->suspend || !pmops->resume) {
@@ -906,15 +923,20 @@ static int mmc_sdio_suspend(struct mmc_host *host)
 				err = -ENOSYS;
 			} else
 				err = pmops->suspend(&func->dev);
+
 			if (err)
 				break;
+			else
+				func->func_status = FUNC_SUSPENDED;
 		}
 	}
+
 	while (err && --i >= 0) {
 		struct sdio_func *func = host->card->sdio_func[i];
 		if (func && sdio_func_present(func) && func->dev.driver) {
 			const struct dev_pm_ops *pmops = func->dev.driver->pm;
 			pmops->resume(&func->dev);
+			func->func_status = FUNC_RESUMED;
 		}
 	}
 
@@ -924,17 +946,30 @@ static int mmc_sdio_suspend(struct mmc_host *host)
 		mmc_release_host(host);
 	}
 
-	/*
-	* If sdio host can wakeup system, its interrupt will _NOT_ be disabled
-	* during suspending. So the card interrupt may occur after host has
-	* suspended. Claim the host here to avoid sdio irq thread handling the
-	* pending interrupt while sdio host suspended. The pending interrupt
-	* will be handled after the host released in resume when sdio host has
-	* been resumed.
-	*/
-	if (!err && device_may_wakeup(mmc_dev(host)))
+	if (!err && device_may_wakeup(mmc_dev(host))) {
+		/*
+		 * All interrupts after this will wakeup system
+		 */
+		atomic_set(&host->sdio_suspended, 1);
+
+		/*
+		 * If sdio host can wakeup system, its interrupt will _NOT_ be disabled
+		 * during suspending. So the card interrupt may occur after host has
+		 * suspended. Claim the host here to avoid sdio irq thread handling the
+		 * pending interrupt while sdio host suspended. The pending interrupt
+		 * will be handled after the host released in resume when sdio host has
+		 * been resumed.
+		 */
 		mmc_claim_host(host);
 
+		/*
+		 * Double check whether sdio IRQ happens after all func
+		 * suspended. If this case occur, wakeup system.
+		 */
+		if (atomic_cmpxchg(&host->sdio_suspend_abort, 1, 0))
+			mmc_sdio_irq_wakeup(host);
+	}
+
 	return err;
 }
 
@@ -967,6 +1002,8 @@ static int mmc_sdio_resume(struct mmc_host *host)
 
 	if (!err && host->sdio_irqs)
 		wake_up_process(host->sdio_irq_thread);
+
+	atomic_set(&host->sdio_suspended, 0);
 	mmc_release_host(host);
 
 	/*
@@ -982,8 +1019,10 @@ static int mmc_sdio_resume(struct mmc_host *host)
 	for (i = 0; !err && i < host->card->sdio_funcs; i++) {
 		struct sdio_func *func = host->card->sdio_func[i];
 		if (func && sdio_func_present(func) && func->dev.driver) {
+
 			const struct dev_pm_ops *pmops = func->dev.driver->pm;
 			err = pmops->resume(&func->dev);
+			func->func_status = FUNC_RESUMED;
 		}
 	}
 
diff --git a/drivers/mmc/core/sdio_irq.c b/drivers/mmc/core/sdio_irq.c
index 3d8ceb4..cf03463 100644
--- a/drivers/mmc/core/sdio_irq.c
+++ b/drivers/mmc/core/sdio_irq.c
@@ -43,6 +43,9 @@ static int process_sdio_pending_irqs(struct mmc_host *host)
 	func = card->sdio_single_irq;
 	if (func && host->sdio_irq_pending) {
 		func->irq_handler(func);
+		if (func->func_status == FUNC_SUSPENDED)
+			atomic_set(&host->sdio_suspend_abort, 1);
+
 		return 1;
 	}
 
@@ -64,6 +67,8 @@ static int process_sdio_pending_irqs(struct mmc_host *host)
 				ret = -EINVAL;
 			} else if (func->irq_handler) {
 				func->irq_handler(func);
+				if (func->func_status == FUNC_SUSPENDED)
+					atomic_set(&host->sdio_suspend_abort, 1);
 				count++;
 			} else {
 				pr_warning("%s: pending IRQ with no handler\n",
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 61a10c1..72984ce 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -316,6 +316,8 @@ struct mmc_host {
 	struct task_struct	*sdio_irq_thread;
 	bool			sdio_irq_pending;
 	atomic_t		sdio_irq_thread_abort;
+	atomic_t		sdio_suspend_abort;
+	atomic_t		sdio_suspended;
 
 	mmc_pm_flag_t		pm_flags;	/* requested pm features */
 
@@ -367,11 +369,14 @@ extern void mmc_detect_change(struct mmc_host *, unsigned long delay);
 extern void mmc_request_done(struct mmc_host *, struct mmc_request *);
 
 extern int mmc_cache_ctrl(struct mmc_host *, u8);
+extern void mmc_sdio_irq_wakeup(struct mmc_host *host);
 
 static inline void mmc_signal_sdio_irq(struct mmc_host *host)
 {
 	host->ops->enable_sdio_irq(host, 0);
 	host->sdio_irq_pending = true;
+	if (atomic_read(&host->sdio_suspended))
+		mmc_sdio_irq_wakeup(host);
 	wake_up_process(host->sdio_irq_thread);
 }
 
diff --git a/include/linux/mmc/sdio_func.h b/include/linux/mmc/sdio_func.h
index 50f0bc9..59f4c23 100644
--- a/include/linux/mmc/sdio_func.h
+++ b/include/linux/mmc/sdio_func.h
@@ -32,6 +32,11 @@ struct sdio_func_tuple {
 	unsigned char data[0];
 };
 
+enum sdio_func_status {
+	FUNC_RESUMED = 0,	/* default value */
+	FUNC_SUSPENDED,
+};
+
 /*
  * SDIO function devices
  */
@@ -59,6 +64,7 @@ struct sdio_func {
 	const char		**info;		/* info strings */
 
 	struct sdio_func_tuple *tuples;
+	enum sdio_func_status	func_status;	/* SDIO function driver state */
 };
 
 #define sdio_func_present(f)	((f)->state & SDIO_STATE_PRESENT)
-- 
1.7.0.4

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


[Index of Archives]     [Linux USB Devel]     [Linux Media]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux