[PATCH v4 2/4] mmc: sdhci: Implement panic-context write support

Kamal Dasu posted 4 patches 2 weeks ago
[PATCH v4 2/4] mmc: sdhci: Implement panic-context write support
Posted by Kamal Dasu 2 weeks ago
Implement the panic-context host operations for SDHCI controllers:

  sdhci_panic_prepare(): Reset the controller, drain any pending
  requests by polling Present State, and clear interrupt status to
  start from a known-good state.

  sdhci_panic_poll_completion(): Poll for command and data completion
  using register reads instead of waiting for interrupts.

  sdhci_panic_complete(): Clear interrupt status and restore the
  host to normal operation after panic I/O.

Make sdhci_send_command_retry() panic-safe by using mdelay() instead
of usleep_range() when oops_in_progress is set, and suppress WARN
output during panic.

Add oops_in_progress guards to sdhci_timeout_timer() and
sdhci_timeout_data_timer() to prevent spurious timeout handling
during panic writes.

Signed-off-by: Kamal Dasu <kamal.dasu@broadcom.com>
---
 drivers/mmc/host/sdhci.c | 169 ++++++++++++++++++++++++++++++++++++++-
 drivers/mmc/host/sdhci.h |   6 ++
 2 files changed, 171 insertions(+), 4 deletions(-)

diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index fec9329e1edb..c40084e07eca 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -15,6 +15,7 @@
 #include <linux/ktime.h>
 #include <linux/highmem.h>
 #include <linux/io.h>
+#include <linux/iopoll.h>
 #include <linux/module.h>
 #include <linux/dma-mapping.h>
 #include <linux/slab.h>
@@ -1765,17 +1766,22 @@ static bool sdhci_send_command_retry(struct sdhci_host *host,
 
 	while (!sdhci_send_command(host, cmd)) {
 		if (!timeout--) {
-			pr_err("%s: Controller never released inhibit bit(s).\n",
-			       mmc_hostname(host->mmc));
+			if (!oops_in_progress) {
+				pr_err("%s: Controller never released inhibit bit(s).\n",
+				       mmc_hostname(host->mmc));
+				sdhci_dumpregs(host);
+			}
 			sdhci_err_stats_inc(host, CTRL_TIMEOUT);
-			sdhci_dumpregs(host);
 			cmd->error = -EIO;
 			return false;
 		}
 
 		spin_unlock_irqrestore(&host->lock, flags);
 
-		usleep_range(1000, 1250);
+		if (unlikely(oops_in_progress))
+			mdelay(1);
+		else
+			usleep_range(1000, 1250);
 
 		present = host->mmc->ops->get_cd(host->mmc);
 
@@ -3076,6 +3082,152 @@ static void sdhci_card_event(struct mmc_host *mmc)
 	spin_unlock_irqrestore(&host->lock, flags);
 }
 
+/*
+ * Panic-context operations for pstore backends.
+ * These run with interrupts disabled and other CPUs stopped.
+ */
+
+#define SDHCI_PANIC_POLL_ITERATIONS	2000
+#define SDHCI_PANIC_POLL_DELAY_US	500
+#define SDHCI_PANIC_MIN_POLL_COUNT	300
+#define SDHCI_PANIC_RESET_TIMEOUT_US	100000
+#define SDHCI_PANIC_DRAIN_TIMEOUT_US	100000
+
+/**
+ * sdhci_panic_prepare - Prepare SDHCI controller for panic-context I/O
+ * @mmc: MMC host structure
+ *
+ * Called during kernel panic. Drains any in-flight request, resets the
+ * CMD and DATA lines, then clears software state under spinlock.
+ * The drain + reset ensures no stopped CPU is still inside sdhci_irq
+ * holding host->lock by the time we take it.
+ */
+int sdhci_panic_prepare(struct mmc_host *mmc)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	unsigned long flags;
+	u32 present;
+	u8 val;
+	int ret;
+
+	/*
+	 * If the controller has a request in flight, give it a short
+	 * bounded time to finish. The CMD/DATA reset below will
+	 * force-abort anything that doesn't complete in time.
+	 */
+	present = sdhci_readl(host, SDHCI_PRESENT_STATE);
+	if (present & (SDHCI_CMD_INHIBIT | SDHCI_DATA_INHIBIT)) {
+		readl_poll_timeout_atomic(host->ioaddr + SDHCI_PRESENT_STATE,
+					  present,
+					  !(present & (SDHCI_CMD_INHIBIT |
+						       SDHCI_DATA_INHIBIT)),
+					  10, SDHCI_PANIC_DRAIN_TIMEOUT_US);
+	}
+
+	sdhci_writeb(host, SDHCI_RESET_CMD | SDHCI_RESET_DATA,
+		     SDHCI_SOFTWARE_RESET);
+
+	ret = readb_poll_timeout_atomic(host->ioaddr + SDHCI_SOFTWARE_RESET,
+					val, !(val & (SDHCI_RESET_CMD | SDHCI_RESET_DATA)),
+					10, SDHCI_PANIC_RESET_TIMEOUT_US);
+
+	spin_lock_irqsave(&host->lock, flags);
+	host->cmd = NULL;
+	host->data = NULL;
+	host->data_cmd = NULL;
+	host->mrqs_done[0] = NULL;
+	host->mrqs_done[1] = NULL;
+	spin_unlock_irqrestore(&host->lock, flags);
+
+	if (host->ops && host->ops->panic_prepare)
+		host->ops->panic_prepare(host);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(sdhci_panic_prepare);
+
+/**
+ * sdhci_panic_poll_completion - Poll SDHCI registers for request completion
+ * @mmc: MMC host structure
+ * @mrq: MMC request being polled for completion
+ *
+ * Checks interrupt status and present state registers to determine if a
+ * request has completed. Used during panic when interrupts are disabled.
+ */
+bool sdhci_panic_poll_completion(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	unsigned int poll_count;
+	u32 int_status, present;
+
+	for (poll_count = 0; poll_count < SDHCI_PANIC_POLL_ITERATIONS;
+	     poll_count++) {
+		cpu_relax();
+		udelay(SDHCI_PANIC_POLL_DELAY_US);
+
+		int_status = sdhci_readl(host, SDHCI_INT_STATUS);
+
+		if (int_status & SDHCI_INT_ERROR) {
+			if (mrq->cmd)
+				mrq->cmd->error = -EIO;
+			if (mrq->data)
+				mrq->data->error = -EIO;
+			sdhci_writel(host, int_status, SDHCI_INT_STATUS);
+			return true;
+		}
+
+		if (int_status & SDHCI_INT_RESPONSE)
+			sdhci_writel(host, SDHCI_INT_RESPONSE,
+				     SDHCI_INT_STATUS);
+
+		if (int_status & SDHCI_INT_DATA_END)
+			sdhci_writel(host, SDHCI_INT_DATA_END,
+				     SDHCI_INT_STATUS);
+
+		/*
+		 * Use the same completion heuristic as the working v5
+		 * implementation: after a minimum number of poll
+		 * iterations, treat the request as complete when the
+		 * DATA_INHIBIT bit clears (controller is idle).
+		 */
+		if (poll_count >= SDHCI_PANIC_MIN_POLL_COUNT) {
+			present = sdhci_readl(host, SDHCI_PRESENT_STATE);
+			if (!(present & SDHCI_DATA_INHIBIT))
+				return true;
+		}
+	}
+
+	if (mrq->cmd)
+		mrq->cmd->error = -ETIMEDOUT;
+	if (mrq->data)
+		mrq->data->error = -ETIMEDOUT;
+	return false;
+}
+EXPORT_SYMBOL_GPL(sdhci_panic_poll_completion);
+
+/**
+ * sdhci_panic_complete - Clean up SDHCI state after a panic-context request
+ * @mmc: MMC host structure
+ * @mrq: MMC request that has completed
+ *
+ * Clears host software state under spinlock so the next panic request
+ * starts clean.
+ */
+void sdhci_panic_complete(struct mmc_host *mmc, struct mmc_request *mrq)
+{
+	struct sdhci_host *host = mmc_priv(mmc);
+	unsigned long flags;
+
+	spin_lock_irqsave(&host->lock, flags);
+	host->cmd = NULL;
+	host->data = NULL;
+	host->data_cmd = NULL;
+	host->mrqs_done[0] = NULL;
+	host->mrqs_done[1] = NULL;
+	spin_unlock_irqrestore(&host->lock, flags);
+}
+EXPORT_SYMBOL_GPL(sdhci_panic_complete);
+
 static const struct mmc_host_ops sdhci_ops = {
 	.request	= sdhci_request,
 	.post_req	= sdhci_post_req,
@@ -3091,6 +3243,9 @@ static const struct mmc_host_ops sdhci_ops = {
 	.execute_tuning			= sdhci_execute_tuning,
 	.card_event			= sdhci_card_event,
 	.card_busy	= sdhci_card_busy,
+	.panic_prepare			= sdhci_panic_prepare,
+	.panic_poll_completion		= sdhci_panic_poll_completion,
+	.panic_complete			= sdhci_panic_complete,
 };
 
 /*****************************************************************************\
@@ -3242,6 +3397,9 @@ static void sdhci_timeout_timer(struct timer_list *t)
 
 	host = timer_container_of(host, t, timer);
 
+	if (oops_in_progress)
+		return;
+
 	spin_lock_irqsave(&host->lock, flags);
 
 	if (host->cmd && !sdhci_data_line_cmd(host->cmd)) {
@@ -3264,6 +3422,9 @@ static void sdhci_timeout_data_timer(struct timer_list *t)
 
 	host = timer_container_of(host, t, data_timer);
 
+	if (oops_in_progress)
+		return;
+
 	spin_lock_irqsave(&host->lock, flags);
 
 	if (host->data || host->data_cmd ||
diff --git a/drivers/mmc/host/sdhci.h b/drivers/mmc/host/sdhci.h
index b6a571d866fa..396eca56439f 100644
--- a/drivers/mmc/host/sdhci.h
+++ b/drivers/mmc/host/sdhci.h
@@ -724,6 +724,7 @@ struct sdhci_ops {
 	void    (*dump_vendor_regs)(struct sdhci_host *host);
 	void	(*dump_uhs2_regs)(struct sdhci_host *host);
 	void    (*uhs2_pre_detect_init)(struct sdhci_host *host);
+	void	(*panic_prepare)(struct sdhci_host *host);
 };
 
 #ifdef CONFIG_MMC_SDHCI_IO_ACCESSORS
@@ -906,6 +907,11 @@ void sdhci_switch_external_dma(struct sdhci_host *host, bool en);
 void sdhci_set_data_timeout_irq(struct sdhci_host *host, bool enable);
 void __sdhci_set_timeout(struct sdhci_host *host, struct mmc_command *cmd);
 
+int sdhci_panic_prepare(struct mmc_host *mmc);
+bool sdhci_panic_poll_completion(struct mmc_host *mmc,
+				 struct mmc_request *mrq);
+void sdhci_panic_complete(struct mmc_host *mmc, struct mmc_request *mrq);
+
 #if defined(CONFIG_DYNAMIC_DEBUG) || \
 	(defined(CONFIG_DYNAMIC_DEBUG_CORE) && defined(DYNAMIC_DEBUG_MODULE))
 #define SDHCI_DBG_ANYWAY 0
-- 
2.34.1