* [PATCH v3 1/6] mmc: core: Handle undervoltage events and register regulator notifiers
2025-02-21 9:39 [PATCH v3 0/6] mmc: handle undervoltage events and prevent eMMC corruption Oleksij Rempel
@ 2025-02-21 9:39 ` Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 2/6] mmc: core: make mmc_interrupt_hpi() global Oleksij Rempel
` (4 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-02-21 9:39 UTC (permalink / raw)
To: Ulf Hansson
Cc: Oleksij Rempel, kernel, linux-kernel, linux-mmc,
Greg Kroah-Hartman, Mark Brown, Rafael J. Wysocki,
Søren Andersen, Christian Loehle
Extend the MMC core to handle undervoltage events by implementing
infrastructure to notify the MMC bus about voltage drops.
Background & Decision at LPC24:
This solution was proposed and refined during LPC24 in the talk
"Graceful Under Pressure: Prioritizing Shutdown to Protect Your Data in
Embedded Systems," which aimed to address how Linux should handle power
fluctuations in embedded devices to prevent data corruption or storage
damage.
At the time, multiple possible solutions were considered:
1. Triggering a system-wide suspend or shutdown: when undervoltage is
detected, with device-specific prioritization to ensure critical
components shut down first.
- This approach was disliked by Greg Kroah-Hartman, as it introduced
complexity and was not suitable for all use cases.
2. Notifying relevant devices through the regulator framework: to allow
graceful per-device handling.
- This approach was agreed upon as the most acceptable by participants
in the discussion, including Greg Kroah-Hartman, Mark Brown,
and Rafael J. Wysocki.
- This patch implements that decision by integrating undervoltage
handling into the MMC subsystem.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v3:
- filter supported cards at early stage
- add locking in mmc_handle_regulator_event()
- claim/release host in mmc_handle_undervoltage()
---
drivers/mmc/core/core.c | 30 +++++++++
drivers/mmc/core/core.h | 2 +
drivers/mmc/core/regulator.c | 124 +++++++++++++++++++++++++++++++++++
include/linux/mmc/host.h | 8 +++
4 files changed, 164 insertions(+)
diff --git a/drivers/mmc/core/core.c b/drivers/mmc/core/core.c
index 5241528f8b90..06adfb54825b 100644
--- a/drivers/mmc/core/core.c
+++ b/drivers/mmc/core/core.c
@@ -1399,6 +1399,36 @@ void mmc_power_cycle(struct mmc_host *host, u32 ocr)
mmc_power_up(host, ocr);
}
+/**
+ * mmc_handle_undervoltage - Handle an undervoltage event on the MMC bus
+ * @host: The MMC host that detected the undervoltage condition
+ *
+ * This function is called when an undervoltage event is detected on one of
+ * the MMC regulators.
+ *
+ * Returns: 0 on success or a negative error code on failure.
+ */
+int mmc_handle_undervoltage(struct mmc_host *host)
+{
+ int ret;
+
+ mmc_claim_host(host);
+
+ if (!host->bus_ops->handle_undervoltage) {
+ mmc_release_host(host);
+ return 0;
+ }
+
+ dev_warn(mmc_dev(host), "%s: Undervoltage detected, initiating emergency stop\n",
+ mmc_hostname(host));
+
+ ret = host->bus_ops->handle_undervoltage(host);
+
+ mmc_release_host(host);
+
+ return ret;
+}
+
/*
* Assign a mmc bus handler to a host. Only one bus handler may control a
* host at any given time.
diff --git a/drivers/mmc/core/core.h b/drivers/mmc/core/core.h
index fc9c066e6468..b77f053039ab 100644
--- a/drivers/mmc/core/core.h
+++ b/drivers/mmc/core/core.h
@@ -31,6 +31,7 @@ struct mmc_bus_ops {
int (*sw_reset)(struct mmc_host *);
bool (*cache_enabled)(struct mmc_host *);
int (*flush_cache)(struct mmc_host *);
+ int (*handle_undervoltage)(struct mmc_host *host);
};
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops);
@@ -59,6 +60,7 @@ void mmc_power_off(struct mmc_host *host);
void mmc_power_cycle(struct mmc_host *host, u32 ocr);
void mmc_set_initial_state(struct mmc_host *host);
u32 mmc_vddrange_to_ocrmask(int vdd_min, int vdd_max);
+int mmc_handle_undervoltage(struct mmc_host *host);
static inline void mmc_delay(unsigned int ms)
{
diff --git a/drivers/mmc/core/regulator.c b/drivers/mmc/core/regulator.c
index 3dae2e9b7978..1074567e242f 100644
--- a/drivers/mmc/core/regulator.c
+++ b/drivers/mmc/core/regulator.c
@@ -7,6 +7,7 @@
#include <linux/err.h>
#include <linux/log2.h>
#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
#include <linux/mmc/host.h>
@@ -262,6 +263,107 @@ static inline int mmc_regulator_get_ocrmask(struct regulator *supply)
#endif /* CONFIG_REGULATOR */
+static void mmc_undervoltage_workfn(struct work_struct *work)
+{
+ struct mmc_supply *supply;
+ struct mmc_host *mmc;
+
+ supply = container_of(work, struct mmc_supply, uv_work);
+ mmc = container_of(supply, struct mmc_host, supply);
+
+ mmc_handle_undervoltage(mmc);
+}
+
+static int mmc_handle_regulator_event(struct mmc_host *mmc,
+ const char *regulator_name,
+ unsigned long event)
+{
+ unsigned long flags;
+
+ switch (event) {
+ case REGULATOR_EVENT_UNDER_VOLTAGE:
+ /* Currently we support only MMC cards */
+ spin_lock_irqsave(&mmc->lock, flags);
+ if (mmc->undervoltage || !mmc->card ||
+ !mmc_card_mmc(mmc->card)) {
+ spin_unlock_irqrestore(&mmc->lock, flags);
+ return NOTIFY_OK;
+ }
+
+ mmc->undervoltage = true;
+ spin_unlock_irqrestore(&mmc->lock, flags);
+
+ queue_work(system_highpri_wq, &mmc->supply.uv_work);
+ break;
+ default:
+ return NOTIFY_DONE;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int mmc_vmmc_notifier_callback(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct mmc_supply *supply;
+ struct mmc_host *mmc;
+
+ supply = container_of(nb, struct mmc_supply, vmmc_nb);
+ mmc = container_of(supply, struct mmc_host, supply);
+
+ return mmc_handle_regulator_event(mmc, "vmmc", event);
+}
+
+static int mmc_vqmmc_notifier_callback(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct mmc_supply *supply;
+ struct mmc_host *mmc;
+
+ supply = container_of(nb, struct mmc_supply, vqmmc_nb);
+ mmc = container_of(supply, struct mmc_host, supply);
+
+ return mmc_handle_regulator_event(mmc, "vqmmc", event);
+}
+
+static int mmc_vqmmc2_notifier_callback(struct notifier_block *nb,
+ unsigned long event, void *data)
+{
+ struct mmc_supply *supply;
+ struct mmc_host *mmc;
+
+ supply = container_of(nb, struct mmc_supply, vqmmc2_nb);
+ mmc = container_of(supply, struct mmc_host, supply);
+
+ return mmc_handle_regulator_event(mmc, "vqmmc2", event);
+}
+
+static void
+mmc_register_regulator_notifier(struct mmc_host *mmc,
+ struct regulator *regulator,
+ struct notifier_block *nb,
+ int (*callback)(struct notifier_block *,
+ unsigned long, void *),
+ const char *name)
+{
+ struct device *dev = mmc_dev(mmc);
+ int ret;
+
+ nb->notifier_call = callback;
+ ret = devm_regulator_register_notifier(regulator, nb);
+ if (ret)
+ dev_warn(dev, "Failed to register %s notifier: %pe\n", name,
+ ERR_PTR(ret));
+}
+
+static void mmc_undervoltage_work_cleanup(void *data)
+{
+ struct mmc_supply *supply = data;
+
+ /* Ensure the work is canceled or flushed here */
+ cancel_work_sync(&supply->uv_work);
+}
+
/**
* mmc_regulator_get_supply - try to get VMMC and VQMMC regulators for a host
* @mmc: the host to regulate
@@ -281,6 +383,13 @@ int mmc_regulator_get_supply(struct mmc_host *mmc)
mmc->supply.vqmmc = devm_regulator_get_optional(dev, "vqmmc");
mmc->supply.vqmmc2 = devm_regulator_get_optional(dev, "vqmmc2");
+ INIT_WORK(&mmc->supply.uv_work, mmc_undervoltage_workfn);
+
+ ret = devm_add_action_or_reset(dev, mmc_undervoltage_work_cleanup,
+ &mmc->supply);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to add cleanup action\n");
+
if (IS_ERR(mmc->supply.vmmc)) {
if (PTR_ERR(mmc->supply.vmmc) == -EPROBE_DEFER)
return dev_err_probe(dev, -EPROBE_DEFER,
@@ -293,6 +402,11 @@ int mmc_regulator_get_supply(struct mmc_host *mmc)
mmc->ocr_avail = ret;
else
dev_warn(dev, "Failed getting OCR mask: %d\n", ret);
+
+ mmc_register_regulator_notifier(mmc, mmc->supply.vmmc,
+ &mmc->supply.vmmc_nb,
+ mmc_vmmc_notifier_callback,
+ "vmmc");
}
if (IS_ERR(mmc->supply.vqmmc)) {
@@ -301,12 +415,22 @@ int mmc_regulator_get_supply(struct mmc_host *mmc)
"vqmmc regulator not available\n");
dev_dbg(dev, "No vqmmc regulator found\n");
+ } else {
+ mmc_register_regulator_notifier(mmc, mmc->supply.vqmmc,
+ &mmc->supply.vqmmc_nb,
+ mmc_vqmmc_notifier_callback,
+ "vqmmc");
}
if (IS_ERR(mmc->supply.vqmmc2)) {
if (PTR_ERR(mmc->supply.vqmmc2) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_dbg(dev, "No vqmmc2 regulator found\n");
+ } else {
+ mmc_register_regulator_notifier(mmc, mmc->supply.vqmmc2,
+ &mmc->supply.vqmmc2_nb,
+ mmc_vqmmc2_notifier_callback,
+ "vqmmc2");
}
return 0;
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 68f09a955a90..4e147ad82804 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -11,6 +11,7 @@
#include <linux/device.h>
#include <linux/fault-inject.h>
#include <linux/debugfs.h>
+#include <linux/workqueue.h>
#include <linux/mmc/core.h>
#include <linux/mmc/card.h>
@@ -342,6 +343,12 @@ struct mmc_supply {
struct regulator *vmmc; /* Card power supply */
struct regulator *vqmmc; /* Optional Vccq supply */
struct regulator *vqmmc2; /* Optional supply for phy */
+
+ struct notifier_block vmmc_nb; /* Notifier for vmmc */
+ struct notifier_block vqmmc_nb; /* Notifier for vqmmc */
+ struct notifier_block vqmmc2_nb; /* Notifier for vqmmc2 */
+
+ struct work_struct uv_work; /* Undervoltage work */
};
struct mmc_ctx {
@@ -493,6 +500,7 @@ struct mmc_host {
unsigned int retune_crc_disable:1; /* don't trigger retune upon crc */
unsigned int can_dma_map_merge:1; /* merging can be used */
unsigned int vqmmc_enabled:1; /* vqmmc regulator is enabled */
+ unsigned int undervoltage:1; /* Undervoltage state */
int rescan_disable; /* disable card detection */
int rescan_entered; /* used with nonremovable devices */
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 2/6] mmc: core: make mmc_interrupt_hpi() global
2025-02-21 9:39 [PATCH v3 0/6] mmc: handle undervoltage events and prevent eMMC corruption Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 1/6] mmc: core: Handle undervoltage events and register regulator notifiers Oleksij Rempel
@ 2025-02-21 9:39 ` Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 3/6] mmc: core: refactor _mmc_suspend() for undervoltage handling Oleksij Rempel
` (3 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-02-21 9:39 UTC (permalink / raw)
To: Ulf Hansson
Cc: Oleksij Rempel, kernel, linux-kernel, linux-mmc,
Greg Kroah-Hartman, Mark Brown, Rafael J. Wysocki,
Søren Andersen, Christian Loehle
Make mmc_interrupt_hpi() non-static. This enables usage of HPI outside
mmc_ops.c and will be used in a follow-up patch.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/mmc/core/mmc_ops.c | 2 +-
drivers/mmc/core/mmc_ops.h | 1 +
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/mmc/core/mmc_ops.c b/drivers/mmc/core/mmc_ops.c
index 5c8e62e8f331..dbb1b5ec4132 100644
--- a/drivers/mmc/core/mmc_ops.c
+++ b/drivers/mmc/core/mmc_ops.c
@@ -903,7 +903,7 @@ static int mmc_send_hpi_cmd(struct mmc_card *card)
* Issued High Priority Interrupt, and check for card status
* until out-of prg-state.
*/
-static int mmc_interrupt_hpi(struct mmc_card *card)
+int mmc_interrupt_hpi(struct mmc_card *card)
{
int err;
u32 status;
diff --git a/drivers/mmc/core/mmc_ops.h b/drivers/mmc/core/mmc_ops.h
index 0df3ebd900d1..a16361dc5909 100644
--- a/drivers/mmc/core/mmc_ops.h
+++ b/drivers/mmc/core/mmc_ops.h
@@ -37,6 +37,7 @@ int mmc_send_cid(struct mmc_host *host, u32 *cid);
int mmc_spi_read_ocr(struct mmc_host *host, int highcap, u32 *ocrp);
int mmc_spi_set_crc(struct mmc_host *host, int use_crc);
int mmc_bus_test(struct mmc_card *card, u8 bus_width);
+int mmc_interrupt_hpi(struct mmc_card *card);
int mmc_can_ext_csd(struct mmc_card *card);
int mmc_switch_status(struct mmc_card *card, bool crc_err_fatal);
bool mmc_prepare_busy_cmd(struct mmc_host *host, struct mmc_command *cmd,
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 3/6] mmc: core: refactor _mmc_suspend() for undervoltage handling
2025-02-21 9:39 [PATCH v3 0/6] mmc: handle undervoltage events and prevent eMMC corruption Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 1/6] mmc: core: Handle undervoltage events and register regulator notifiers Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 2/6] mmc: core: make mmc_interrupt_hpi() global Oleksij Rempel
@ 2025-02-21 9:39 ` Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices Oleksij Rempel
` (2 subsequent siblings)
5 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-02-21 9:39 UTC (permalink / raw)
To: Ulf Hansson
Cc: Oleksij Rempel, kernel, linux-kernel, linux-mmc,
Greg Kroah-Hartman, Mark Brown, Rafael J. Wysocki,
Søren Andersen, Christian Loehle
Introduce an is_undervoltage parameter to _mmc_suspend() to apply a
short power-off sequence and optionally flush the cache. This refactoring
prepares for undervoltage support in a follow-up patch.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v3:
- add comments
- make sure _mmc_flush_cache is not executed in the undervoltage case
---
drivers/mmc/core/mmc.c | 30 +++++++++++++++++++++---------
1 file changed, 21 insertions(+), 9 deletions(-)
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 6a23be214543..9270bde445ad 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -2104,20 +2104,32 @@ static int _mmc_flush_cache(struct mmc_host *host)
return err;
}
-static int _mmc_suspend(struct mmc_host *host, bool is_suspend)
+static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
+ bool is_undervoltage)
{
+ unsigned int notify_type;
int err = 0;
- unsigned int notify_type = is_suspend ? EXT_CSD_POWER_OFF_SHORT :
- EXT_CSD_POWER_OFF_LONG;
+
+ /* In case of undervoltage, we don't have much time, so use short. */
+ if (is_undervoltage || is_suspend)
+ notify_type = EXT_CSD_POWER_OFF_SHORT;
+ else
+ notify_type = EXT_CSD_POWER_OFF_LONG;
mmc_claim_host(host);
if (mmc_card_suspended(host->card))
goto out;
- err = _mmc_flush_cache(host);
- if (err)
- goto out;
+ /*
+ * For the undervoltage case, we care more about device integrity.
+ * Avoid cache flush and notify the device to power off quickly.
+ */
+ if (!is_undervoltage) {
+ err = _mmc_flush_cache(host);
+ if (err)
+ goto out;
+ }
if (mmc_can_poweroff_notify(host->card) &&
((host->caps2 & MMC_CAP2_FULL_PWR_CYCLE) || !is_suspend ||
@@ -2144,7 +2156,7 @@ static int mmc_suspend(struct mmc_host *host)
{
int err;
- err = _mmc_suspend(host, true);
+ err = _mmc_suspend(host, true, false);
if (!err) {
pm_runtime_disable(&host->card->dev);
pm_runtime_set_suspended(&host->card->dev);
@@ -2191,7 +2203,7 @@ static int mmc_shutdown(struct mmc_host *host)
err = _mmc_resume(host);
if (!err)
- err = _mmc_suspend(host, false);
+ err = _mmc_suspend(host, false, false);
return err;
}
@@ -2215,7 +2227,7 @@ static int mmc_runtime_suspend(struct mmc_host *host)
if (!(host->caps & MMC_CAP_AGGRESSIVE_PM))
return 0;
- err = _mmc_suspend(host, true);
+ err = _mmc_suspend(host, true, false);
if (err)
pr_err("%s: error %d doing aggressive suspend\n",
mmc_hostname(host), err);
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices
2025-02-21 9:39 [PATCH v3 0/6] mmc: handle undervoltage events and prevent eMMC corruption Oleksij Rempel
` (2 preceding siblings ...)
2025-02-21 9:39 ` [PATCH v3 3/6] mmc: core: refactor _mmc_suspend() for undervoltage handling Oleksij Rempel
@ 2025-02-21 9:39 ` Oleksij Rempel
2025-02-21 10:39 ` Christian Loehle
2025-03-07 13:02 ` Adrian Hunter
2025-02-21 9:39 ` [PATCH v3 5/6] mmc: block: abort requests and suppress errors after undervoltage shutdown Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 6/6] mmc: sdhci: prevent command execution " Oleksij Rempel
5 siblings, 2 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-02-21 9:39 UTC (permalink / raw)
To: Ulf Hansson
Cc: Oleksij Rempel, kernel, linux-kernel, linux-mmc,
Greg Kroah-Hartman, Mark Brown, Rafael J. Wysocki,
Søren Andersen, Christian Loehle
Introduce `_mmc_handle_undervoltage()` to handle undervoltage events for
MMC/eMMC devices. This function interrupts ongoing operations using High
Priority Interrupt (HPI) and performs a controlled suspend. After
completing the sequence, the card is marked as removed to prevent
further interactions, ensuring that no further commands are issued after
an emergency stop.
Implementation Details:
1. **Interrupt ongoing operations**:
- If the eMMC is executing a long-running operation (e.g., erase, trim,
or write),
attempt to stop it using HPI (`mmc_interrupt_hpi()`).
- If HPI fails, an error is logged, but the sequence continues.
2. **Suspend the card in an emergency state**:
- Call `__mmc_suspend()` with `is_undervoltage = true`, which ensures:
- The power-off notification uses `EXT_CSD_POWER_OFF_SHORT`.
- Cache flushing is skipped to minimize time delays.
- If power-off notify is unsupported, alternative methods like sleep
or deselect are used to transition the card into a safe state.
3. **Mark the card as removed**:
- This prevents further commands from being issued to the card after
undervoltage shutdown, avoiding potential corruption.
To support this, introduce `__mmc_suspend()` and `__mmc_resume()` as
internal helpers that omit `mmc_claim_host()/mmc_release_host()`,
allowing them to be called when the host is already claimed.
The caller of `_mmc_handle_undervoltage()` is responsible for invoking
`mmc_claim_host()` before calling this function and `mmc_release_host()`
afterward to ensure exclusive access to the host during the emergency
shutdown process.
Device Handling Considerations:
- **For eMMC storage**: The new undervoltage handler applies the correct
power-down sequence using power-off notify or alternative methods.
- **For SD cards**: The current implementation does not handle undervoltage
events for SD cards. Future extensions may be needed to implement proper
handling.
Testing:
This implementation was tested on an iMX8MP-based system, verifying that
the undervoltage sequence correctly stops ongoing operations and
prevents further MMC transactions after the event. The board had
approximately 100ms of available power hold-up time. The Power Off
Notification was sent ~4ms after the board was detached from the power
supply, allowing sufficient time for the eMMC to handle the event
properly.
The testing was performed using a logic analyzer to monitor command
sequences and timing. While this method confirms that the expected
sequence was executed, it does not provide insights into the actual
internal behavior of the eMMC storage.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v3:
- reword commit message.
- add comments in the code
- do not try to resume sleeping device
---
drivers/mmc/core/mmc.c | 115 ++++++++++++++++++++++++++++++++++++-----
1 file changed, 102 insertions(+), 13 deletions(-)
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index 9270bde445ad..a50cdd550a22 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -2104,8 +2104,8 @@ static int _mmc_flush_cache(struct mmc_host *host)
return err;
}
-static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
- bool is_undervoltage)
+static int __mmc_suspend(struct mmc_host *host, bool is_suspend,
+ bool is_undervoltage)
{
unsigned int notify_type;
int err = 0;
@@ -2116,8 +2116,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
else
notify_type = EXT_CSD_POWER_OFF_LONG;
- mmc_claim_host(host);
-
if (mmc_card_suspended(host->card))
goto out;
@@ -2145,7 +2143,18 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
mmc_card_set_suspended(host->card);
}
out:
+ return err;
+}
+
+static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
+ bool is_undervoltage)
+{
+ int err;
+
+ mmc_claim_host(host);
+ err = __mmc_suspend(host, is_suspend, is_undervoltage);
mmc_release_host(host);
+
return err;
}
@@ -2165,6 +2174,20 @@ static int mmc_suspend(struct mmc_host *host)
return err;
}
+static int __mmc_resume(struct mmc_host *host)
+{
+ int err;
+
+ if (!mmc_card_suspended(host->card))
+ return 0;
+
+ mmc_power_up(host, host->card->ocr);
+ err = mmc_init_card(host, host->card->ocr, host->card);
+ mmc_card_clr_suspended(host->card);
+
+ return err;
+}
+
/*
* This function tries to determine if the same card is still present
* and, if so, restore all state to it.
@@ -2174,16 +2197,9 @@ static int _mmc_resume(struct mmc_host *host)
int err = 0;
mmc_claim_host(host);
-
- if (!mmc_card_suspended(host->card))
- goto out;
-
- mmc_power_up(host, host->card->ocr);
- err = mmc_init_card(host, host->card->ocr, host->card);
- mmc_card_clr_suspended(host->card);
-
-out:
+ err = __mmc_resume(host);
mmc_release_host(host);
+
return err;
}
@@ -2194,6 +2210,13 @@ static int mmc_shutdown(struct mmc_host *host)
{
int err = 0;
+ /*
+ * In case of undervoltage, the card will be powered off by
+ * _mmc_handle_undervoltage()
+ */
+ if (host->undervoltage)
+ return 0;
+
/*
* In a specific case for poweroff notify, we need to resume the card
* before we can shutdown it properly.
@@ -2285,6 +2308,71 @@ static int _mmc_hw_reset(struct mmc_host *host)
return mmc_init_card(host, card->ocr, card);
}
+/**
+ * _mmc_handle_undervoltage - Handle an undervoltage event for MMC/eMMC devices
+ * @host: MMC host structure
+ *
+ * This function is triggered when an undervoltage condition is detected.
+ * It attempts to safely stop ongoing operations and transition the device
+ * into a low-power or safe state to prevent data corruption.
+ *
+ * Steps performed:
+ * 1. If no card is present, return immediately.
+ * 2. Attempt to interrupt any ongoing operations using High Priority Interrupt
+ * (HPI).
+ * 3. Perform an emergency suspend using EXT_CSD_POWER_OFF_SHORT if possible.
+ * - If power-off notify is not supported, fallback mechanisms like sleep or
+ * deselecting the card are attempted.
+ * - Cache flushing is skipped to reduce execution time.
+ * 4. Mark the card as removed to prevent further interactions after
+ * undervoltage.
+ *
+ * Note: This function does not handle host claiming or releasing. The caller
+ * must ensure that the host is properly claimed before calling this
+ * function and released afterward.
+ *
+ * Returns: 0 on success, or a negative error code if any step fails.
+ */
+static int _mmc_handle_undervoltage(struct mmc_host *host)
+{
+ struct mmc_card *card = host->card;
+ int err = 0;
+
+ /* If there is no card attached, nothing to do */
+ if (!card)
+ return 0;
+
+ /*
+ * Try to interrupt a long-running operation (such as an erase, trim,
+ * or write) using High Priority Interrupt (HPI). This helps ensure
+ * the card is in a safe state before power loss.
+ */
+ err = mmc_interrupt_hpi(card);
+ if (err)
+ pr_err("%s: Interrupt HPI failed, error %d\n",
+ mmc_hostname(host), err);
+
+ /*
+ * Perform an emergency suspend to power off the eMMC quickly.
+ * This ensures the device enters a safe state before power is lost.
+ * We first attempt EXT_CSD_POWER_OFF_SHORT, but if power-off notify
+ * is not supported, we fall back to sleep mode or deselecting the card.
+ * Cache flushing is skipped to minimize delay.
+ */
+ err = __mmc_suspend(host, false, true);
+ if (err)
+ pr_err("%s: error %d doing suspend\n", mmc_hostname(host), err);
+
+ /*
+ * Mark the card as removed to prevent further operations.
+ * This ensures the system does not attempt to access the device
+ * after an undervoltage event, avoiding potential corruption.
+ */
+ mmc_card_set_removed(card);
+
+ return err;
+}
+
static const struct mmc_bus_ops mmc_ops = {
.remove = mmc_remove,
.detect = mmc_detect,
@@ -2297,6 +2385,7 @@ static const struct mmc_bus_ops mmc_ops = {
.hw_reset = _mmc_hw_reset,
.cache_enabled = _mmc_cache_enabled,
.flush_cache = _mmc_flush_cache,
+ .handle_undervoltage = _mmc_handle_undervoltage,
};
/*
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices
2025-02-21 9:39 ` [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices Oleksij Rempel
@ 2025-02-21 10:39 ` Christian Loehle
2025-02-27 8:47 ` Avri Altman
2025-03-07 13:02 ` Adrian Hunter
1 sibling, 1 reply; 11+ messages in thread
From: Christian Loehle @ 2025-02-21 10:39 UTC (permalink / raw)
To: Oleksij Rempel, Ulf Hansson
Cc: kernel, linux-kernel, linux-mmc, Greg Kroah-Hartman, Mark Brown,
Rafael J. Wysocki, Søren Andersen, Adrian Hunter,
'Avri Altman'
(+CC Avri and Adrian)
On 2/21/25 09:39, Oleksij Rempel wrote:
> Introduce `_mmc_handle_undervoltage()` to handle undervoltage events for
> MMC/eMMC devices. This function interrupts ongoing operations using High
> Priority Interrupt (HPI) and performs a controlled suspend. After
> completing the sequence, the card is marked as removed to prevent
> further interactions, ensuring that no further commands are issued after
> an emergency stop.
>
> Implementation Details:
> 1. **Interrupt ongoing operations**:
> - If the eMMC is executing a long-running operation (e.g., erase, trim,
> or write),
> attempt to stop it using HPI (`mmc_interrupt_hpi()`).
> - If HPI fails, an error is logged, but the sequence continues.
>
> 2. **Suspend the card in an emergency state**:
> - Call `__mmc_suspend()` with `is_undervoltage = true`, which ensures:
> - The power-off notification uses `EXT_CSD_POWER_OFF_SHORT`.
> - Cache flushing is skipped to minimize time delays.
> - If power-off notify is unsupported, alternative methods like sleep
> or deselect are used to transition the card into a safe state.
>
> 3. **Mark the card as removed**:
> - This prevents further commands from being issued to the card after
> undervoltage shutdown, avoiding potential corruption.
>
> To support this, introduce `__mmc_suspend()` and `__mmc_resume()` as
> internal helpers that omit `mmc_claim_host()/mmc_release_host()`,
> allowing them to be called when the host is already claimed.
>
> The caller of `_mmc_handle_undervoltage()` is responsible for invoking
> `mmc_claim_host()` before calling this function and `mmc_release_host()`
> afterward to ensure exclusive access to the host during the emergency
> shutdown process.
>
> Device Handling Considerations:
> - **For eMMC storage**: The new undervoltage handler applies the correct
> power-down sequence using power-off notify or alternative methods.
> - **For SD cards**: The current implementation does not handle undervoltage
> events for SD cards. Future extensions may be needed to implement proper
> handling.
>
> Testing:
> This implementation was tested on an iMX8MP-based system, verifying that
> the undervoltage sequence correctly stops ongoing operations and
> prevents further MMC transactions after the event. The board had
> approximately 100ms of available power hold-up time. The Power Off
> Notification was sent ~4ms after the board was detached from the power
> supply, allowing sufficient time for the eMMC to handle the event
> properly.
>
> The testing was performed using a logic analyzer to monitor command
> sequences and timing. While this method confirms that the expected
> sequence was executed, it does not provide insights into the actual
> internal behavior of the eMMC storage.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> changes v3:
> - reword commit message.
> - add comments in the code
> - do not try to resume sleeping device
> ---
> drivers/mmc/core/mmc.c | 115 ++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 102 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
> index 9270bde445ad..a50cdd550a22 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -2104,8 +2104,8 @@ static int _mmc_flush_cache(struct mmc_host *host)
> return err;
> }
>
> -static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> - bool is_undervoltage)
> +static int __mmc_suspend(struct mmc_host *host, bool is_suspend,
> + bool is_undervoltage)
> {
> unsigned int notify_type;
> int err = 0;
> @@ -2116,8 +2116,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> else
> notify_type = EXT_CSD_POWER_OFF_LONG;
>
> - mmc_claim_host(host);
> -
> if (mmc_card_suspended(host->card))
> goto out;
>
> @@ -2145,7 +2143,18 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> mmc_card_set_suspended(host->card);
> }
> out:
> + return err;
> +}
> +
> +static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> + bool is_undervoltage)
> +{
> + int err;
> +
> + mmc_claim_host(host);
> + err = __mmc_suspend(host, is_suspend, is_undervoltage);
> mmc_release_host(host);
> +
> return err;
> }
>
> @@ -2165,6 +2174,20 @@ static int mmc_suspend(struct mmc_host *host)
> return err;
> }
>
> +static int __mmc_resume(struct mmc_host *host)
> +{
> + int err;
> +
> + if (!mmc_card_suspended(host->card))
> + return 0;
> +
> + mmc_power_up(host, host->card->ocr);
> + err = mmc_init_card(host, host->card->ocr, host->card);
> + mmc_card_clr_suspended(host->card);
> +
> + return err;
> +}
> +
> /*
> * This function tries to determine if the same card is still present
> * and, if so, restore all state to it.
> @@ -2174,16 +2197,9 @@ static int _mmc_resume(struct mmc_host *host)
> int err = 0;
>
> mmc_claim_host(host);
> -
> - if (!mmc_card_suspended(host->card))
> - goto out;
> -
> - mmc_power_up(host, host->card->ocr);
> - err = mmc_init_card(host, host->card->ocr, host->card);
> - mmc_card_clr_suspended(host->card);
> -
> -out:
> + err = __mmc_resume(host);
> mmc_release_host(host);
> +
> return err;
> }
>
> @@ -2194,6 +2210,13 @@ static int mmc_shutdown(struct mmc_host *host)
> {
> int err = 0;
>
> + /*
> + * In case of undervoltage, the card will be powered off by
> + * _mmc_handle_undervoltage()
> + */
> + if (host->undervoltage)
> + return 0;
> +
> /*
> * In a specific case for poweroff notify, we need to resume the card
> * before we can shutdown it properly.
> @@ -2285,6 +2308,71 @@ static int _mmc_hw_reset(struct mmc_host *host)
> return mmc_init_card(host, card->ocr, card);
> }
>
> +/**
> + * _mmc_handle_undervoltage - Handle an undervoltage event for MMC/eMMC devices
> + * @host: MMC host structure
> + *
> + * This function is triggered when an undervoltage condition is detected.
> + * It attempts to safely stop ongoing operations and transition the device
> + * into a low-power or safe state to prevent data corruption.
> + *
> + * Steps performed:
> + * 1. If no card is present, return immediately.
> + * 2. Attempt to interrupt any ongoing operations using High Priority Interrupt
> + * (HPI).
> + * 3. Perform an emergency suspend using EXT_CSD_POWER_OFF_SHORT if possible.
> + * - If power-off notify is not supported, fallback mechanisms like sleep or
> + * deselecting the card are attempted.
> + * - Cache flushing is skipped to reduce execution time.
> + * 4. Mark the card as removed to prevent further interactions after
> + * undervoltage.
The good path now looks like the best bet to me.
The PON fallbacks are still questionable IMO.
Deselect, if taking the spec seriously, cannot really give a hint to FTL.
While daisy-chaining MMC and SD is rather rare these days, that's still the
behavior vendors implement AFAIK.
Sleep with it's vendor-defined timeouts can be anything, so this could even
trigger a cache flush internally, after we avoided sending one because of
undervoltage.
If we were to rely on actually getting 100ms heads-up we could check that
against the timeout reported by the card, but IME those are wild guesses
rather than measured values.
> + *
> + * Note: This function does not handle host claiming or releasing. The caller
> + * must ensure that the host is properly claimed before calling this
> + * function and released afterward.
> + *
> + * Returns: 0 on success, or a negative error code if any step fails.
> + */
> +static int _mmc_handle_undervoltage(struct mmc_host *host)
> +{
> + struct mmc_card *card = host->card;
> + int err = 0;
> +
> + /* If there is no card attached, nothing to do */
> + if (!card)
> + return 0;
> +
> + /*
> + * Try to interrupt a long-running operation (such as an erase, trim,
> + * or write) using High Priority Interrupt (HPI). This helps ensure
> + * the card is in a safe state before power loss.
I don't know about safe state before power loss, it's getting it in a
state where we can execute the below.
> + */
> + err = mmc_interrupt_hpi(card);
> + if (err)
> + pr_err("%s: Interrupt HPI failed, error %d\n",
> + mmc_hostname(host), err);
> +
> + /*
> + * Perform an emergency suspend to power off the eMMC quickly.
> + * This ensures the device enters a safe state before power is lost.
> + * We first attempt EXT_CSD_POWER_OFF_SHORT, but if power-off notify
> + * is not supported, we fall back to sleep mode or deselecting the card.
> + * Cache flushing is skipped to minimize delay.
> + */
> + err = __mmc_suspend(host, false, true);
> + if (err)
> + pr_err("%s: error %d doing suspend\n", mmc_hostname(host), err);
> +
> + /*
> + * Mark the card as removed to prevent further operations.
> + * This ensures the system does not attempt to access the device
> + * after an undervoltage event, avoiding potential corruption.
> + */
> + mmc_card_set_removed(card);
> +
> + return err;
> +}
> +
> static const struct mmc_bus_ops mmc_ops = {
> .remove = mmc_remove,
> .detect = mmc_detect,
> @@ -2297,6 +2385,7 @@ static const struct mmc_bus_ops mmc_ops = {
> .hw_reset = _mmc_hw_reset,
> .cache_enabled = _mmc_cache_enabled,
> .flush_cache = _mmc_flush_cache,
> + .handle_undervoltage = _mmc_handle_undervoltage,
> };
>
> /*
No need to resend anything at this point, I'm curious what others have
to say on the series.
^ permalink raw reply [flat|nested] 11+ messages in thread* RE: [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices
2025-02-21 10:39 ` Christian Loehle
@ 2025-02-27 8:47 ` Avri Altman
0 siblings, 0 replies; 11+ messages in thread
From: Avri Altman @ 2025-02-27 8:47 UTC (permalink / raw)
To: Christian Loehle, Oleksij Rempel, Ulf Hansson
Cc: kernel@pengutronix.de, linux-kernel@vger.kernel.org,
linux-mmc@vger.kernel.org, Greg Kroah-Hartman, Mark Brown,
Rafael J. Wysocki, Søren Andersen, Adrian Hunter,
'Avri Altman'
> (+CC Avri and Adrian)
Personally, I don't have a strong opinion of any of this.
I expect the hw to handle ungraceful voltage drops via its internal mechanisms.
Up to some point, that is.
Thanks,
Avri
>
> On 2/21/25 09:39, Oleksij Rempel wrote:
> > Introduce `_mmc_handle_undervoltage()` to handle undervoltage events
> > for MMC/eMMC devices. This function interrupts ongoing operations
> > using High Priority Interrupt (HPI) and performs a controlled suspend.
> > After completing the sequence, the card is marked as removed to
> > prevent further interactions, ensuring that no further commands are
> > issued after an emergency stop.
> >
> > Implementation Details:
> > 1. **Interrupt ongoing operations**:
> > - If the eMMC is executing a long-running operation (e.g., erase, trim,
> > or write),
> > attempt to stop it using HPI (`mmc_interrupt_hpi()`).
> > - If HPI fails, an error is logged, but the sequence continues.
> >
> > 2. **Suspend the card in an emergency state**:
> > - Call `__mmc_suspend()` with `is_undervoltage = true`, which ensures:
> > - The power-off notification uses `EXT_CSD_POWER_OFF_SHORT`.
> > - Cache flushing is skipped to minimize time delays.
> > - If power-off notify is unsupported, alternative methods like sleep
> > or deselect are used to transition the card into a safe state.
> >
> > 3. **Mark the card as removed**:
> > - This prevents further commands from being issued to the card after
> > undervoltage shutdown, avoiding potential corruption.
> >
> > To support this, introduce `__mmc_suspend()` and `__mmc_resume()` as
> > internal helpers that omit `mmc_claim_host()/mmc_release_host()`,
> > allowing them to be called when the host is already claimed.
> >
> > The caller of `_mmc_handle_undervoltage()` is responsible for invoking
> > `mmc_claim_host()` before calling this function and
> > `mmc_release_host()` afterward to ensure exclusive access to the host
> > during the emergency shutdown process.
> >
> > Device Handling Considerations:
> > - **For eMMC storage**: The new undervoltage handler applies the correct
> > power-down sequence using power-off notify or alternative methods.
> > - **For SD cards**: The current implementation does not handle
> undervoltage
> > events for SD cards. Future extensions may be needed to implement proper
> > handling.
> >
> > Testing:
> > This implementation was tested on an iMX8MP-based system, verifying
> > that the undervoltage sequence correctly stops ongoing operations and
> > prevents further MMC transactions after the event. The board had
> > approximately 100ms of available power hold-up time. The Power Off
> > Notification was sent ~4ms after the board was detached from the
> > power supply, allowing sufficient time for the eMMC to handle the
> > event properly.
> >
> > The testing was performed using a logic analyzer to monitor command
> > sequences and timing. While this method confirms that the expected
> > sequence was executed, it does not provide insights into the actual
> > internal behavior of the eMMC storage.
> >
> > Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> > ---
> > changes v3:
> > - reword commit message.
> > - add comments in the code
> > - do not try to resume sleeping device
> > ---
> > drivers/mmc/core/mmc.c | 115
> > ++++++++++++++++++++++++++++++++++++-----
> > 1 file changed, 102 insertions(+), 13 deletions(-)
> >
> > diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c index
> > 9270bde445ad..a50cdd550a22 100644
> > --- a/drivers/mmc/core/mmc.c
> > +++ b/drivers/mmc/core/mmc.c
> > @@ -2104,8 +2104,8 @@ static int _mmc_flush_cache(struct mmc_host
> *host)
> > return err;
> > }
> >
> > -static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> > - bool is_undervoltage)
> > +static int __mmc_suspend(struct mmc_host *host, bool is_suspend,
> > + bool is_undervoltage)
> > {
> > unsigned int notify_type;
> > int err = 0;
> > @@ -2116,8 +2116,6 @@ static int _mmc_suspend(struct mmc_host
> *host, bool is_suspend,
> > else
> > notify_type = EXT_CSD_POWER_OFF_LONG;
> >
> > - mmc_claim_host(host);
> > -
> > if (mmc_card_suspended(host->card))
> > goto out;
> >
> > @@ -2145,7 +2143,18 @@ static int _mmc_suspend(struct mmc_host
> *host, bool is_suspend,
> > mmc_card_set_suspended(host->card);
> > }
> > out:
> > + return err;
> > +}
> > +
> > +static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> > + bool is_undervoltage)
> > +{
> > + int err;
> > +
> > + mmc_claim_host(host);
> > + err = __mmc_suspend(host, is_suspend, is_undervoltage);
> > mmc_release_host(host);
> > +
> > return err;
> > }
> >
> > @@ -2165,6 +2174,20 @@ static int mmc_suspend(struct mmc_host
> *host)
> > return err;
> > }
> >
> > +static int __mmc_resume(struct mmc_host *host) {
> > + int err;
> > +
> > + if (!mmc_card_suspended(host->card))
> > + return 0;
> > +
> > + mmc_power_up(host, host->card->ocr);
> > + err = mmc_init_card(host, host->card->ocr, host->card);
> > + mmc_card_clr_suspended(host->card);
> > +
> > + return err;
> > +}
> > +
> > /*
> > * This function tries to determine if the same card is still present
> > * and, if so, restore all state to it.
> > @@ -2174,16 +2197,9 @@ static int _mmc_resume(struct mmc_host
> *host)
> > int err = 0;
> >
> > mmc_claim_host(host);
> > -
> > - if (!mmc_card_suspended(host->card))
> > - goto out;
> > -
> > - mmc_power_up(host, host->card->ocr);
> > - err = mmc_init_card(host, host->card->ocr, host->card);
> > - mmc_card_clr_suspended(host->card);
> > -
> > -out:
> > + err = __mmc_resume(host);
> > mmc_release_host(host);
> > +
> > return err;
> > }
> >
> > @@ -2194,6 +2210,13 @@ static int mmc_shutdown(struct mmc_host
> *host)
> > {
> > int err = 0;
> >
> > + /*
> > + * In case of undervoltage, the card will be powered off by
> > + * _mmc_handle_undervoltage()
> > + */
> > + if (host->undervoltage)
> > + return 0;
> > +
> > /*
> > * In a specific case for poweroff notify, we need to resume the card
> > * before we can shutdown it properly.
> > @@ -2285,6 +2308,71 @@ static int _mmc_hw_reset(struct mmc_host
> *host)
> > return mmc_init_card(host, card->ocr, card); }
> >
> > +/**
> > + * _mmc_handle_undervoltage - Handle an undervoltage event for
> > +MMC/eMMC devices
> > + * @host: MMC host structure
> > + *
> > + * This function is triggered when an undervoltage condition is detected.
> > + * It attempts to safely stop ongoing operations and transition the
> > +device
> > + * into a low-power or safe state to prevent data corruption.
> > + *
> > + * Steps performed:
> > + * 1. If no card is present, return immediately.
> > + * 2. Attempt to interrupt any ongoing operations using High Priority
> Interrupt
> > + * (HPI).
> > + * 3. Perform an emergency suspend using EXT_CSD_POWER_OFF_SHORT if
> possible.
> > + * - If power-off notify is not supported, fallback mechanisms like sleep or
> > + * deselecting the card are attempted.
> > + * - Cache flushing is skipped to reduce execution time.
> > + * 4. Mark the card as removed to prevent further interactions after
> > + * undervoltage.
>
> The good path now looks like the best bet to me.
> The PON fallbacks are still questionable IMO.
> Deselect, if taking the spec seriously, cannot really give a hint to FTL.
> While daisy-chaining MMC and SD is rather rare these days, that's still the
> behavior vendors implement AFAIK.
> Sleep with it's vendor-defined timeouts can be anything, so this could even
> trigger a cache flush internally, after we avoided sending one because of
> undervoltage.
> If we were to rely on actually getting 100ms heads-up we could check that
> against the timeout reported by the card, but IME those are wild guesses
> rather than measured values.
>
> > + *
> > + * Note: This function does not handle host claiming or releasing. The caller
> > + * must ensure that the host is properly claimed before calling this
> > + * function and released afterward.
> > + *
> > + * Returns: 0 on success, or a negative error code if any step fails.
> > + */
> > +static int _mmc_handle_undervoltage(struct mmc_host *host) {
> > + struct mmc_card *card = host->card;
> > + int err = 0;
> > +
> > + /* If there is no card attached, nothing to do */
> > + if (!card)
> > + return 0;
> > +
> > + /*
> > + * Try to interrupt a long-running operation (such as an erase, trim,
> > + * or write) using High Priority Interrupt (HPI). This helps ensure
> > + * the card is in a safe state before power loss.
>
> I don't know about safe state before power loss, it's getting it in a state where
> we can execute the below.
>
> > + */
> > + err = mmc_interrupt_hpi(card);
> > + if (err)
> > + pr_err("%s: Interrupt HPI failed, error %d\n",
> > + mmc_hostname(host), err);
> > +
> > + /*
> > + * Perform an emergency suspend to power off the eMMC quickly.
> > + * This ensures the device enters a safe state before power is lost.
> > + * We first attempt EXT_CSD_POWER_OFF_SHORT, but if power-off
> notify
> > + * is not supported, we fall back to sleep mode or deselecting the card.
> > + * Cache flushing is skipped to minimize delay.
> > + */
> > + err = __mmc_suspend(host, false, true);
> > + if (err)
> > + pr_err("%s: error %d doing suspend\n",
> mmc_hostname(host), err);
> > +
> > + /*
> > + * Mark the card as removed to prevent further operations.
> > + * This ensures the system does not attempt to access the device
> > + * after an undervoltage event, avoiding potential corruption.
> > + */
> > + mmc_card_set_removed(card);
> > +
> > + return err;
> > +}
> > +
> > static const struct mmc_bus_ops mmc_ops = {
> > .remove = mmc_remove,
> > .detect = mmc_detect,
> > @@ -2297,6 +2385,7 @@ static const struct mmc_bus_ops mmc_ops = {
> > .hw_reset = _mmc_hw_reset,
> > .cache_enabled = _mmc_cache_enabled,
> > .flush_cache = _mmc_flush_cache,
> > + .handle_undervoltage = _mmc_handle_undervoltage,
> > };
> >
> > /*
>
> No need to resend anything at this point, I'm curious what others have to say
> on the series.
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices
2025-02-21 9:39 ` [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices Oleksij Rempel
2025-02-21 10:39 ` Christian Loehle
@ 2025-03-07 13:02 ` Adrian Hunter
1 sibling, 0 replies; 11+ messages in thread
From: Adrian Hunter @ 2025-03-07 13:02 UTC (permalink / raw)
To: Oleksij Rempel, Ulf Hansson
Cc: kernel, linux-kernel, linux-mmc, Greg Kroah-Hartman, Mark Brown,
Rafael J. Wysocki, Søren Andersen, Christian Loehle
On 21/02/25 11:39, Oleksij Rempel wrote:
> Introduce `_mmc_handle_undervoltage()` to handle undervoltage events for
> MMC/eMMC devices. This function interrupts ongoing operations using High
> Priority Interrupt (HPI) and performs a controlled suspend. After
> completing the sequence, the card is marked as removed to prevent
> further interactions, ensuring that no further commands are issued after
> an emergency stop.
Please cc me also, and since Avri and Christian have reviewed earlier
versions, at least them too.
I'd suggest cc'ing others too because at the moment there doesn't
seem to be a lot of interest in this.
>
> Implementation Details:
> 1. **Interrupt ongoing operations**:
> - If the eMMC is executing a long-running operation (e.g., erase, trim,
> or write),
Those cannot be happening if you have the host claimed.
> attempt to stop it using HPI (`mmc_interrupt_hpi()`).
> - If HPI fails, an error is logged, but the sequence continues.
>
> 2. **Suspend the card in an emergency state**:
> - Call `__mmc_suspend()` with `is_undervoltage = true`, which ensures:
> - The power-off notification uses `EXT_CSD_POWER_OFF_SHORT`.
> - Cache flushing is skipped to minimize time delays.
> - If power-off notify is unsupported, alternative methods like sleep
> or deselect are used to transition the card into a safe state.
>
> 3. **Mark the card as removed**:
> - This prevents further commands from being issued to the card after
> undervoltage shutdown, avoiding potential corruption.
>
> To support this, introduce `__mmc_suspend()` and `__mmc_resume()` as
> internal helpers that omit `mmc_claim_host()/mmc_release_host()`,
> allowing them to be called when the host is already claimed.
>
> The caller of `_mmc_handle_undervoltage()` is responsible for invoking
> `mmc_claim_host()` before calling this function and `mmc_release_host()`
> afterward to ensure exclusive access to the host during the emergency
> shutdown process.
>
> Device Handling Considerations:
> - **For eMMC storage**: The new undervoltage handler applies the correct
> power-down sequence using power-off notify or alternative methods.
> - **For SD cards**: The current implementation does not handle undervoltage
> events for SD cards. Future extensions may be needed to implement proper
> handling.
>
> Testing:
> This implementation was tested on an iMX8MP-based system, verifying that
> the undervoltage sequence correctly stops ongoing operations and
> prevents further MMC transactions after the event. The board had
> approximately 100ms of available power hold-up time. The Power Off
> Notification was sent ~4ms after the board was detached from the power
> supply, allowing sufficient time for the eMMC to handle the event
> properly.
>
> The testing was performed using a logic analyzer to monitor command
> sequences and timing. While this method confirms that the expected
> sequence was executed, it does not provide insights into the actual
> internal behavior of the eMMC storage.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> changes v3:
> - reword commit message.
> - add comments in the code
> - do not try to resume sleeping device
> ---
> drivers/mmc/core/mmc.c | 115 ++++++++++++++++++++++++++++++++++++-----
> 1 file changed, 102 insertions(+), 13 deletions(-)
>
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
> index 9270bde445ad..a50cdd550a22 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -2104,8 +2104,8 @@ static int _mmc_flush_cache(struct mmc_host *host)
> return err;
> }
>
> -static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> - bool is_undervoltage)
> +static int __mmc_suspend(struct mmc_host *host, bool is_suspend,
> + bool is_undervoltage)
> {
> unsigned int notify_type;
> int err = 0;
> @@ -2116,8 +2116,6 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> else
> notify_type = EXT_CSD_POWER_OFF_LONG;
>
> - mmc_claim_host(host);
> -
> if (mmc_card_suspended(host->card))
> goto out;
>
> @@ -2145,7 +2143,18 @@ static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> mmc_card_set_suspended(host->card);
> }
> out:
> + return err;
> +}
> +
> +static int _mmc_suspend(struct mmc_host *host, bool is_suspend,
> + bool is_undervoltage)
> +{
> + int err;
> +
> + mmc_claim_host(host);
> + err = __mmc_suspend(host, is_suspend, is_undervoltage);
> mmc_release_host(host);
> +
> return err;
> }
>
> @@ -2165,6 +2174,20 @@ static int mmc_suspend(struct mmc_host *host)
> return err;
> }
>
> +static int __mmc_resume(struct mmc_host *host)
> +{
> + int err;
> +
> + if (!mmc_card_suspended(host->card))
> + return 0;
> +
> + mmc_power_up(host, host->card->ocr);
> + err = mmc_init_card(host, host->card->ocr, host->card);
> + mmc_card_clr_suspended(host->card);
> +
> + return err;
> +}
> +
> /*
> * This function tries to determine if the same card is still present
> * and, if so, restore all state to it.
> @@ -2174,16 +2197,9 @@ static int _mmc_resume(struct mmc_host *host)
> int err = 0;
>
> mmc_claim_host(host);
> -
> - if (!mmc_card_suspended(host->card))
> - goto out;
> -
> - mmc_power_up(host, host->card->ocr);
> - err = mmc_init_card(host, host->card->ocr, host->card);
> - mmc_card_clr_suspended(host->card);
> -
> -out:
> + err = __mmc_resume(host);
> mmc_release_host(host);
> +
> return err;
> }
>
> @@ -2194,6 +2210,13 @@ static int mmc_shutdown(struct mmc_host *host)
> {
> int err = 0;
>
> + /*
> + * In case of undervoltage, the card will be powered off by
> + * _mmc_handle_undervoltage()
> + */
> + if (host->undervoltage)
> + return 0;
> +
> /*
> * In a specific case for poweroff notify, we need to resume the card
> * before we can shutdown it properly.
> @@ -2285,6 +2308,71 @@ static int _mmc_hw_reset(struct mmc_host *host)
> return mmc_init_card(host, card->ocr, card);
> }
>
> +/**
> + * _mmc_handle_undervoltage - Handle an undervoltage event for MMC/eMMC devices
> + * @host: MMC host structure
> + *
> + * This function is triggered when an undervoltage condition is detected.
> + * It attempts to safely stop ongoing operations and transition the device
> + * into a low-power or safe state to prevent data corruption.
> + *
> + * Steps performed:
> + * 1. If no card is present, return immediately.
> + * 2. Attempt to interrupt any ongoing operations using High Priority Interrupt
> + * (HPI).
> + * 3. Perform an emergency suspend using EXT_CSD_POWER_OFF_SHORT if possible.
> + * - If power-off notify is not supported, fallback mechanisms like sleep or
> + * deselecting the card are attempted.
> + * - Cache flushing is skipped to reduce execution time.
> + * 4. Mark the card as removed to prevent further interactions after
> + * undervoltage.
> + *
> + * Note: This function does not handle host claiming or releasing. The caller
> + * must ensure that the host is properly claimed before calling this
> + * function and released afterward.
> + *
> + * Returns: 0 on success, or a negative error code if any step fails.
> + */
> +static int _mmc_handle_undervoltage(struct mmc_host *host)
> +{
> + struct mmc_card *card = host->card;
> + int err = 0;
> +
> + /* If there is no card attached, nothing to do */
> + if (!card)
> + return 0;
> +
> + /*
> + * Try to interrupt a long-running operation (such as an erase, trim,
> + * or write) using High Priority Interrupt (HPI). This helps ensure
> + * the card is in a safe state before power loss.
> + */
> + err = mmc_interrupt_hpi(card);
> + if (err)
> + pr_err("%s: Interrupt HPI failed, error %d\n",
> + mmc_hostname(host), err);
> +
> + /*
> + * Perform an emergency suspend to power off the eMMC quickly.
> + * This ensures the device enters a safe state before power is lost.
> + * We first attempt EXT_CSD_POWER_OFF_SHORT, but if power-off notify
> + * is not supported, we fall back to sleep mode or deselecting the card.
> + * Cache flushing is skipped to minimize delay.
> + */
> + err = __mmc_suspend(host, false, true);
> + if (err)
> + pr_err("%s: error %d doing suspend\n", mmc_hostname(host), err);
> +
> + /*
> + * Mark the card as removed to prevent further operations.
> + * This ensures the system does not attempt to access the device
> + * after an undervoltage event, avoiding potential corruption.
> + */
> + mmc_card_set_removed(card);
> +
> + return err;
> +}
> +
> static const struct mmc_bus_ops mmc_ops = {
> .remove = mmc_remove,
> .detect = mmc_detect,
> @@ -2297,6 +2385,7 @@ static const struct mmc_bus_ops mmc_ops = {
> .hw_reset = _mmc_hw_reset,
> .cache_enabled = _mmc_cache_enabled,
> .flush_cache = _mmc_flush_cache,
> + .handle_undervoltage = _mmc_handle_undervoltage,
> };
>
> /*
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v3 5/6] mmc: block: abort requests and suppress errors after undervoltage shutdown
2025-02-21 9:39 [PATCH v3 0/6] mmc: handle undervoltage events and prevent eMMC corruption Oleksij Rempel
` (3 preceding siblings ...)
2025-02-21 9:39 ` [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices Oleksij Rempel
@ 2025-02-21 9:39 ` Oleksij Rempel
2025-02-21 9:39 ` [PATCH v3 6/6] mmc: sdhci: prevent command execution " Oleksij Rempel
5 siblings, 0 replies; 11+ messages in thread
From: Oleksij Rempel @ 2025-02-21 9:39 UTC (permalink / raw)
To: Ulf Hansson
Cc: Oleksij Rempel, kernel, linux-kernel, linux-mmc,
Greg Kroah-Hartman, Mark Brown, Rafael J. Wysocki,
Søren Andersen, Christian Loehle
Extend the existing card removal checks in mmc_blk_mq_complete_rq() and
mmc_mq_queue_rq() to also account for the undervoltage state. If the host
has entered undervoltage shutdown, mark requests as quiet and abort them
early to prevent unnecessary retries and error logging. This ensures no
further operations are attempted on the card after an emergency stop.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
drivers/mmc/core/block.c | 2 +-
drivers/mmc/core/queue.c | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/drivers/mmc/core/block.c b/drivers/mmc/core/block.c
index 4830628510e6..ecb87da0e257 100644
--- a/drivers/mmc/core/block.c
+++ b/drivers/mmc/core/block.c
@@ -2129,7 +2129,7 @@ static void mmc_blk_mq_complete_rq(struct mmc_queue *mq, struct request *req)
} else if (mqrq->retries++ < MMC_MAX_RETRIES) {
blk_mq_requeue_request(req, true);
} else {
- if (mmc_card_removed(mq->card))
+ if (mmc_card_removed(mq->card) || mq->card->host->undervoltage)
req->rq_flags |= RQF_QUIET;
blk_mq_end_request(req, BLK_STS_IOERR);
}
diff --git a/drivers/mmc/core/queue.c b/drivers/mmc/core/queue.c
index ab662f502fe7..f46e01988fe8 100644
--- a/drivers/mmc/core/queue.c
+++ b/drivers/mmc/core/queue.c
@@ -239,7 +239,7 @@ static blk_status_t mmc_mq_queue_rq(struct blk_mq_hw_ctx *hctx,
bool get_card, cqe_retune_ok;
blk_status_t ret;
- if (mmc_card_removed(mq->card)) {
+ if (mmc_card_removed(mq->card) || mq->card->host->undervoltage) {
req->rq_flags |= RQF_QUIET;
return BLK_STS_IOERR;
}
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* [PATCH v3 6/6] mmc: sdhci: prevent command execution after undervoltage shutdown
2025-02-21 9:39 [PATCH v3 0/6] mmc: handle undervoltage events and prevent eMMC corruption Oleksij Rempel
` (4 preceding siblings ...)
2025-02-21 9:39 ` [PATCH v3 5/6] mmc: block: abort requests and suppress errors after undervoltage shutdown Oleksij Rempel
@ 2025-02-21 9:39 ` Oleksij Rempel
2025-03-07 12:36 ` Adrian Hunter
5 siblings, 1 reply; 11+ messages in thread
From: Oleksij Rempel @ 2025-02-21 9:39 UTC (permalink / raw)
To: Ulf Hansson
Cc: Oleksij Rempel, kernel, linux-kernel, linux-mmc,
Greg Kroah-Hartman, Mark Brown, Rafael J. Wysocki,
Søren Andersen, Christian Loehle
Introduce an emergency_stop flag in struct mmc_host to block further
MMC/SD commands after an undervoltage shutdown. If emergency_stop is
set, sdhci_send_command() will reject new requests with -EBUSY and log a
warning. This helps diagnose and identify code paths that may still
attempt writes after the undervoltage shutdown sequence has completed.
Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
changes v3:
- add comments
---
drivers/mmc/core/mmc.c | 7 +++++++
drivers/mmc/host/sdhci.c | 9 +++++++++
include/linux/mmc/host.h | 1 +
3 files changed, 17 insertions(+)
diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
index a50cdd550a22..0cd6b81d0678 100644
--- a/drivers/mmc/core/mmc.c
+++ b/drivers/mmc/core/mmc.c
@@ -2370,6 +2370,13 @@ static int _mmc_handle_undervoltage(struct mmc_host *host)
*/
mmc_card_set_removed(card);
+ /*
+ * Signal the host controller driver that we are in emergency stop mode.
+ * This prevents any new storage requests from being issued, ensuring
+ * that no further operations take place while in this state.
+ */
+ host->emergency_stop = true;
+
return err;
}
diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
index f4a7733a8ad2..8d67f27e7d9e 100644
--- a/drivers/mmc/host/sdhci.c
+++ b/drivers/mmc/host/sdhci.c
@@ -1658,6 +1658,15 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
WARN_ON(host->cmd);
+ if (host->mmc->emergency_stop) {
+ pr_warn("%s: Ignoring normal request, emergency stop is active\n",
+ mmc_hostname(host->mmc));
+ WARN_ON_ONCE(1);
+
+ cmd->error = -EBUSY;
+ return true;
+ }
+
/* Initially, a command has no error */
cmd->error = 0;
diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
index 4e147ad82804..5dfe2cdde59f 100644
--- a/include/linux/mmc/host.h
+++ b/include/linux/mmc/host.h
@@ -501,6 +501,7 @@ struct mmc_host {
unsigned int can_dma_map_merge:1; /* merging can be used */
unsigned int vqmmc_enabled:1; /* vqmmc regulator is enabled */
unsigned int undervoltage:1; /* Undervoltage state */
+ unsigned int emergency_stop:1; /* Emergency stop. No transfers are allowed. */
int rescan_disable; /* disable card detection */
int rescan_entered; /* used with nonremovable devices */
--
2.39.5
^ permalink raw reply related [flat|nested] 11+ messages in thread* Re: [PATCH v3 6/6] mmc: sdhci: prevent command execution after undervoltage shutdown
2025-02-21 9:39 ` [PATCH v3 6/6] mmc: sdhci: prevent command execution " Oleksij Rempel
@ 2025-03-07 12:36 ` Adrian Hunter
0 siblings, 0 replies; 11+ messages in thread
From: Adrian Hunter @ 2025-03-07 12:36 UTC (permalink / raw)
To: Oleksij Rempel, Ulf Hansson
Cc: kernel, linux-kernel, linux-mmc, Greg Kroah-Hartman, Mark Brown,
Rafael J. Wysocki, Søren Andersen, Christian Loehle
On 21/02/25 11:39, Oleksij Rempel wrote:
> Introduce an emergency_stop flag in struct mmc_host to block further
> MMC/SD commands after an undervoltage shutdown. If emergency_stop is
> set, sdhci_send_command() will reject new requests with -EBUSY and log a
> warning. This helps diagnose and identify code paths that may still
> attempt writes after the undervoltage shutdown sequence has completed.
>
> Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
> ---
> changes v3:
> - add comments
> ---
> drivers/mmc/core/mmc.c | 7 +++++++
> drivers/mmc/host/sdhci.c | 9 +++++++++
> include/linux/mmc/host.h | 1 +
> 3 files changed, 17 insertions(+)
>
> diff --git a/drivers/mmc/core/mmc.c b/drivers/mmc/core/mmc.c
> index a50cdd550a22..0cd6b81d0678 100644
> --- a/drivers/mmc/core/mmc.c
> +++ b/drivers/mmc/core/mmc.c
> @@ -2370,6 +2370,13 @@ static int _mmc_handle_undervoltage(struct mmc_host *host)
> */
> mmc_card_set_removed(card);
>
> + /*
> + * Signal the host controller driver that we are in emergency stop mode.
> + * This prevents any new storage requests from being issued, ensuring
> + * that no further operations take place while in this state.
> + */
> + host->emergency_stop = true;
> +
> return err;
> }
>
> diff --git a/drivers/mmc/host/sdhci.c b/drivers/mmc/host/sdhci.c
> index f4a7733a8ad2..8d67f27e7d9e 100644
> --- a/drivers/mmc/host/sdhci.c
> +++ b/drivers/mmc/host/sdhci.c
> @@ -1658,6 +1658,15 @@ static bool sdhci_send_command(struct sdhci_host *host, struct mmc_command *cmd)
>
> WARN_ON(host->cmd);
>
> + if (host->mmc->emergency_stop) {
This is unnecessary.
The host controller driver should not have to block requests
because the mmc core layer should not be requesting them.
We definitely wouldn't want to be duplicating this code in
every host controller driver, so let's not start.
> + pr_warn("%s: Ignoring normal request, emergency stop is active\n",
> + mmc_hostname(host->mmc));
> + WARN_ON_ONCE(1);
> +
> + cmd->error = -EBUSY;
> + return true;
> + }
> +
> /* Initially, a command has no error */
> cmd->error = 0;
>
> diff --git a/include/linux/mmc/host.h b/include/linux/mmc/host.h
> index 4e147ad82804..5dfe2cdde59f 100644
> --- a/include/linux/mmc/host.h
> +++ b/include/linux/mmc/host.h
> @@ -501,6 +501,7 @@ struct mmc_host {
> unsigned int can_dma_map_merge:1; /* merging can be used */
> unsigned int vqmmc_enabled:1; /* vqmmc regulator is enabled */
> unsigned int undervoltage:1; /* Undervoltage state */
> + unsigned int emergency_stop:1; /* Emergency stop. No transfers are allowed. */
>
> int rescan_disable; /* disable card detection */
> int rescan_entered; /* used with nonremovable devices */
^ permalink raw reply [flat|nested] 11+ messages in thread