From patchwork Fri Feb 21 09:39:13 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 867319 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 91392205ADE for ; Fri, 21 Feb 2025 09:39:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740130772; cv=none; b=PAc3RGJiBdxaW5w7ztmudUIkODl23EZ+ogEwMwFzAepBbCGM0OR5r3xKJR28Ur5IW2VNJ3/H17xoJ41N3XOyGKasSm58yq2jFOy+G8IC06CCSwg7zVUmwVT63cr8ISSNKQBaQl4KEA1TuEhjs/gFueufPdxwwLcAk75MFsIej4A= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740130772; c=relaxed/simple; bh=wFpnAs22fXuT0PMFbK/YpY8JUGZCfXPre3Hs3PJ6MSI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=n/vZ3gWd/joXzzg/BhzRKZdkFufwXr5ESXjbE4lwWyDwg2c7T/EAdBDMhdFyLn2MugI7LxWE5AAjIPqFyVM9oH3aY3ESVDEZn/NUm+jbQw/linhW+4X657Ao7GhSIh7I53ciLkR5dvnk7kYpYKti+PRTJcNfkagMNy190Nf0QJE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1tlPV9-0001zT-QE; Fri, 21 Feb 2025 10:39:19 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1tlPV9-0024ut-1B; Fri, 21 Feb 2025 10:39:19 +0100 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.96) (envelope-from ) id 1tlPV9-00GXbP-0y; Fri, 21 Feb 2025 10:39:19 +0100 From: Oleksij Rempel To: Ulf Hansson Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, linux-mmc@vger.kernel.org, Greg Kroah-Hartman , Mark Brown , "Rafael J. Wysocki" , =?utf-8?q?S=C3=B8ren_Andersen?= , Christian Loehle Subject: [PATCH v3 1/6] mmc: core: Handle undervoltage events and register regulator notifiers Date: Fri, 21 Feb 2025 10:39:13 +0100 Message-Id: <20250221093918.3942378-2-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250221093918.3942378-1-o.rempel@pengutronix.de> References: <20250221093918.3942378-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-mmc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-mmc@vger.kernel.org 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 --- 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 #include #include +#include #include @@ -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 #include #include +#include #include #include @@ -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 */ From patchwork Fri Feb 21 09:39:14 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 867321 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 70457205E13 for ; Fri, 21 Feb 2025 09:39:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740130770; cv=none; b=Ds+zcZ3kz596SxavX70insAByRVgYU8LFLX1JRyNSGJyKeFcJ+dygf10Fzy0kT0rhwFQRd5zSDp1RqFllh/4NeA3PtAJaOjx83D6KBpFZLB7iVNwWeiMlk87fDGvGRVhCQmGmdN0+1+lujDCJV2BUT3XizKlLxwLlvpHkZ1XyLI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740130770; c=relaxed/simple; bh=Adh/Q3rnUh98xXUXofXQcb8ZYwFqHSH9eh8GRfjl3Jg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=pIHYyZEHlEqY/KnxUGnxzuEzACJs9DQ7Rd09FtuqQ+5JQ9hJ/pPlPQEGYGZ7pB42tY5Jk4wApBO1yVbgJmRxOLoREN6b+U2jOLWP1Vy9a+9yCJir3CVRkiKEWRyVIZ12e6dgeXqsf8LGCbTSsjoZqwILYmKvI4ng/NW1l8L9E7U= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1tlPV9-0001zU-QE; Fri, 21 Feb 2025 10:39:19 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1tlPV9-0024uu-1G; Fri, 21 Feb 2025 10:39:19 +0100 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.96) (envelope-from ) id 1tlPV9-00GXbZ-13; Fri, 21 Feb 2025 10:39:19 +0100 From: Oleksij Rempel To: Ulf Hansson Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, linux-mmc@vger.kernel.org, Greg Kroah-Hartman , Mark Brown , "Rafael J. Wysocki" , =?utf-8?q?S=C3=B8ren_Andersen?= , Christian Loehle Subject: [PATCH v3 2/6] mmc: core: make mmc_interrupt_hpi() global Date: Fri, 21 Feb 2025 10:39:14 +0100 Message-Id: <20250221093918.3942378-3-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250221093918.3942378-1-o.rempel@pengutronix.de> References: <20250221093918.3942378-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-mmc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-mmc@vger.kernel.org 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 --- 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, From patchwork Fri Feb 21 09:39:16 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Oleksij Rempel X-Patchwork-Id: 867318 Received: from metis.whiteo.stw.pengutronix.de (metis.whiteo.stw.pengutronix.de [185.203.201.7]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8F6DA205AD8 for ; Fri, 21 Feb 2025 09:39:28 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=185.203.201.7 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740130772; cv=none; b=TGwulrFiQ7oQFYh3VzWW0wkAJSauy7IOCTm9QbdVPrKAVzful8ezgyoWegbPXiyACPhKFndkybfPL9aJPdEQEbYP60vCyvaMdf1JWnG6ZsrWC8A6mJhHDd/ug4ZYsOphctGFVB6RxzjJ0Y8iMEyWGzdKBQCuRSntT2gq7ejP5Jg= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1740130772; c=relaxed/simple; bh=Te/2oDU0bmE2Fg5WRyJhiZEACxVu08M1C+WaM9/E2yY=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=qVkh+UQ9z7vUfRiLGIj/NdEwTTZfxQFhsvLXQyA5qo1jTSNXehyd0sm3N//u07xxmtw5LmbcPc8asObJDedgDS2ug3Fn39zBdTw1AUloGzFWnvs1Q9SF5Uh3tsmz4hGG52xIYWOg+pZ9NYAsyKC31K6aIm2VljV8URWfgOGqGBs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de; spf=pass smtp.mailfrom=pengutronix.de; arc=none smtp.client-ip=185.203.201.7 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=pengutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=pengutronix.de Received: from drehscheibe.grey.stw.pengutronix.de ([2a0a:edc0:0:c01:1d::a2]) by metis.whiteo.stw.pengutronix.de with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1tlPV9-0001zW-QE; Fri, 21 Feb 2025 10:39:19 +0100 Received: from dude04.red.stw.pengutronix.de ([2a0a:edc0:0:1101:1d::ac]) by drehscheibe.grey.stw.pengutronix.de with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1tlPV9-0024uw-1S; Fri, 21 Feb 2025 10:39:19 +0100 Received: from ore by dude04.red.stw.pengutronix.de with local (Exim 4.96) (envelope-from ) id 1tlPV9-00GXbu-1B; Fri, 21 Feb 2025 10:39:19 +0100 From: Oleksij Rempel To: Ulf Hansson Cc: Oleksij Rempel , kernel@pengutronix.de, linux-kernel@vger.kernel.org, linux-mmc@vger.kernel.org, Greg Kroah-Hartman , Mark Brown , "Rafael J. Wysocki" , =?utf-8?q?S=C3=B8ren_Andersen?= , Christian Loehle Subject: [PATCH v3 4/6] mmc: core: add undervoltage handler for MMC/eMMC devices Date: Fri, 21 Feb 2025 10:39:16 +0100 Message-Id: <20250221093918.3942378-5-o.rempel@pengutronix.de> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20250221093918.3942378-1-o.rempel@pengutronix.de> References: <20250221093918.3942378-1-o.rempel@pengutronix.de> Precedence: bulk X-Mailing-List: linux-mmc@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 X-SA-Exim-Connect-IP: 2a0a:edc0:0:c01:1d::a2 X-SA-Exim-Mail-From: ore@pengutronix.de X-SA-Exim-Scanned: No (on metis.whiteo.stw.pengutronix.de); SAEximRunCond expanded to false X-PTX-Original-Recipient: linux-mmc@vger.kernel.org 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 --- 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, }; /*