Skip to contentSkip to conversation

FAILED: patch "[PATCH] ACPI: NFIT: core: Fix possible deadlock and missing" failed to apply to 6.18-stable tree

PATCH New [email protected] 2 participants 6 messages Jul 9 – Jul 10

Subsystems (3)
lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

The patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.

To reproduce the conflict and resubmit, you may use the following commands:

git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x 18a00ed0e718473f5c3fcfa49df46c944575e60c
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026070931-earmuff-spirits-26aa@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'

Possible dependencies:

thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From 18a00ed0e718473f5c3fcfa49df46c944575e60c Mon Sep 17 00:00:00 2001
From: "Rafael J. Wysocki" <[email protected]>
Date: Wed, 3 Jun 2026 19:58:23 +0200
Subject: [PATCH] ACPI: NFIT: core: Fix possible deadlock and missing
notifications

After commit 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table"), ACPI NFIT driver removal may deadlock if an ACPI notify on the NFIT device is triggered concurrently. A similar deadlock may occur if an ACPI notify on the NFIT device is triggered during a failing driver probe.

The deadlock is possible because acpi_dev_remove_notify_handler() calls acpi_os_wait_events_complete() after removing the notify handler and the driver core invokes it under the NFIT platform device lock which is also acquired by acpi_nfit_notify(). Thus acpi_os_wait_events_complete() may be waiting for acpi_nfit_notify() to complete, but the latter may not be able to acquire the device lock which is being held by the driver core while the former is being executed.

Moreover, after commit 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one"), there are no sysfs notifications regarding NVDIMM devices because __acpi_nvdimm_notify() always bails out after checking the driver data pointer of the device's parent. That parent is the ACPI companion of the platform device used for driver binding, so its driver data pointer is always NULL after the commit in question which was overlooked by it.

A remedy for the deadlock is to use a special separate lock for ACPI notify synchronization with driver probe and removal instead of the device lock of the NFIT device, while a remedy for the second issue is to populate the driver data pointer of the NFIT device's ACPI companion when the driver is ready to operate, so do both these things. However, since the new lock is not held across the entire teardown and acpi_nfit_notify() should do nothing when teardown is in progress, make it check the driver data pointer of the NFIT device's ACPI companion, in analogy with the existing check in __acpi_nvdimm_notify(), and bail out if that pointer is NULL.

Fixes 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table")
Fixes 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one")
Cc All applicable <[email protected]> # 9995e4404ea4: ACPI: NFIT: core: Eliminate redundant local variable
drivers/acpi/nfit/core.c +51 -12
index aaa84ae7a20e..cb771d9cadb2 100644
@@ -56,6 +56,8 @@ MODULE_PARM_DESC(force_labels, "Opt-in to labels despite missing methods");
 LIST_HEAD(acpi_descs);
 DEFINE_MUTEX(acpi_desc_lock);
 
+DEFINE_MUTEX(acpi_notify_lock);
+
 static struct workqueue_struct *nfit_wq;
 
 struct nfit_table_prev {
@@ -1708,9 +1710,15 @@ static void acpi_nvdimm_notify(acpi_handle handle, u32 event, void *data)
 	struct acpi_device *adev = data;
 	struct device *dev = &adev->dev;
 
-	device_lock(dev->parent);
+	/*
+	 * Locking is needed here for synchronization with driver probe and
+	 * removal and the parent's driver data pointer is NULL when teardown
+	 * is in progress (while the parent here is expected to be the ACPI
+	 * companion of the platform device used for driver binding).
+	 */
+	guard(mutex)(&acpi_notify_lock);
+
 	__acpi_nvdimm_notify(dev, event);
-	device_unlock(dev->parent);
 }
 
 static bool acpi_nvdimm_has_method(struct acpi_device *adev, char *method)
@@ -3156,11 +3164,10 @@ EXPORT_SYMBOL_GPL(acpi_nfit_init);
 static int acpi_nfit_flush_probe(struct nvdimm_bus_descriptor *nd_desc)
 {
 	struct acpi_nfit_desc *acpi_desc = to_acpi_desc(nd_desc);
-	struct device *dev = acpi_desc->dev;
 
-	/* Bounce the device lock to flush acpi_nfit_add / acpi_nfit_notify */
-	device_lock(dev);
-	device_unlock(dev);
+	/* Bounce the notify lock to flush acpi_nfit_probe / acpi_nfit_notify */
+	mutex_lock(&acpi_notify_lock);
+	mutex_unlock(&acpi_notify_lock);
 
 	/* Bounce the init_mutex to complete initial registration */
 	mutex_lock(&acpi_desc->init_mutex);
@@ -3292,10 +3299,17 @@ static void acpi_nfit_put_table(void *table)
 static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data)
 {
 	struct device *dev = data;
+	struct acpi_device *adev = ACPI_COMPANION(dev);
 
-	device_lock(dev);
-	__acpi_nfit_notify(dev, handle, event);
-	device_unlock(dev);
+	/*
+	 * Locking is needed here for synchronization with driver probe and
+	 * removal and the ACPI companion's driver data pointer is NULL when
+	 * teardown is in progress.
+	 */
+	guard(mutex)(&acpi_notify_lock);
+
+	if (dev_get_drvdata(&adev->dev))
+		__acpi_nfit_notify(dev, handle, event);
 }
 
 void acpi_nfit_shutdown(void *data)
@@ -3337,11 +3351,18 @@ static int acpi_nfit_probe(struct platform_device *pdev)
 	struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL };
 	struct acpi_nfit_desc *acpi_desc;
 	struct device *dev = &pdev->dev;
+	struct acpi_device *adev = ACPI_COMPANION(dev);
 	struct acpi_table_header *tbl;
 	acpi_status status = AE_OK;
 	acpi_size sz;
 	int rc = 0;
 
+	/*
+	 * Prevent acpi_nfit_notify() from progressing until the probe is
+	 * complete in case there is a concurrent event to process.
+	 */
+	guard(mutex)(&acpi_notify_lock);
+
 	rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
 					      acpi_nfit_notify, dev);
 	if (rc)
@@ -3357,6 +3378,11 @@ static int acpi_nfit_probe(struct platform_device *pdev)
 		 * data in the format of a series of NFIT Structures.
 		 */
 		dev_dbg(dev, "failed to find NFIT at startup\n");
+		/*
+		 * Let acpi_nfit_update_notify() run in case it will need to
+		 * allocate the acpi_desc object.
+		 */
+		dev_set_drvdata(&adev->dev, dev);
 		return 0;
 	}
 
@@ -3374,7 +3400,7 @@ static int acpi_nfit_probe(struct platform_device *pdev)
 	acpi_desc->acpi_header = *tbl;
 
 	/* Evaluate _FIT and override with that if present */
-	status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf);
+	status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf);
 	if (ACPI_SUCCESS(status) && buf.length > 0) {
 		union acpi_object *obj = buf.pointer;
 
@@ -3391,14 +3417,27 @@ static int acpi_nfit_probe(struct platform_device *pdev)
 				+ sizeof(struct acpi_table_nfit),
 				sz - sizeof(struct acpi_table_nfit));
 
-	if (rc)
+	if (rc) {
 		acpi_nfit_shutdown(acpi_desc);
+		return rc;
+	}
 
-	return rc;
+	/*
+	 * Let notify handlers operate (the actual value of the ACPI companion's
+	 * driver data pointer does not matter here so long as it is not NULL).
+	 */
+	dev_set_drvdata(&adev->dev, dev);
+	return 0;
 }
 
 static void acpi_nfit_remove(struct platform_device *pdev)
 {
+	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+
+	guard(mutex)(&acpi_notify_lock);
+
+	/* Make notify handlers bail out early going forward. */
+	dev_set_drvdata(&adev->dev, NULL);
 	acpi_nfit_shutdown(platform_get_drvdata(pdev));
 }
 
maintainer
#1 lore raw

PATCH 1/5 ACPI: driver: Check ACPI_COMPANION() against NULL during probe

From: "Rafael J. Wysocki" <[email protected]>

[ Upstream commit e4865a56d013e86e46ea6acea15bb6eae01898ff ]

Since every platform driver can be forced to match a device that doesn't match its list of device IDs because of device_match_driver_override(), platform drivers that rely on the existence of a device's ACPI companion object should verify its presence.

Accordingly, add requisite ACPI_COMPANION() or ACPI_HANDLE() checks against NULL to 13 platform drivers handling core ACPI devices.

Also change the value returned by the ACPI thermal zone driver when the device's ACPI companion is not present to -ENODEV for consistency with the other drivers.

Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")

---
 drivers/acpi/ac.c            | 6 +++++-
 drivers/acpi/acpi_pad.c      | 6 +++++-
 drivers/acpi/acpi_tad.c      | 6 +++++-
 drivers/acpi/pfr_telemetry.c | 6 +++++-
 drivers/acpi/pfr_update.c    | 6 +++++-
 drivers/acpi/thermal.c       | 2 +-
 6 files changed, 26 insertions(+), 6 deletions(-)
drivers/acpi/ac.c +5 -1
index 1f69be8f51a29b..8738c42a3ae40a 100644
@@ -203,11 +203,15 @@ static const struct dmi_system_id ac_dmi_table[]  __initconst = {
 
 static int acpi_ac_probe(struct platform_device *pdev)
 {
-	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
 	struct power_supply_config psy_cfg = {};
+	struct acpi_device *adev;
 	struct acpi_ac *ac;
 	int result;
 
+	adev = ACPI_COMPANION(&pdev->dev);
+	if (!adev)
+		return -ENODEV;
+
 	ac = kzalloc(sizeof(struct acpi_ac), GFP_KERNEL);
 	if (!ac)
 		return -ENOMEM;
drivers/acpi/acpi_pad.c +5 -1
index c9a0bcaba2e4c1..dea7b2b4e54459 100644
@@ -426,9 +426,13 @@ static void acpi_pad_notify(acpi_handle handle, u32 event,
 
 static int acpi_pad_probe(struct platform_device *pdev)
 {
-	struct acpi_device *adev = ACPI_COMPANION(&pdev->dev);
+	struct acpi_device *adev;
 	acpi_status status;
 
+	adev = ACPI_COMPANION(&pdev->dev);
+	if (!adev)
+		return -ENODEV;
+
 	strscpy(acpi_device_name(adev), ACPI_PROCESSOR_AGGREGATOR_DEVICE_NAME);
 	strscpy(acpi_device_class(adev), ACPI_PROCESSOR_AGGREGATOR_CLASS);
 
drivers/acpi/acpi_tad.c +5 -1
index 33418dd6768a1b..fcebfa7d507f5d 100644
@@ -588,12 +588,16 @@ static void acpi_tad_remove(struct platform_device *pdev)
 static int acpi_tad_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
-	acpi_handle handle = ACPI_HANDLE(dev);
 	struct acpi_tad_driver_data *dd;
+	acpi_handle handle;
 	acpi_status status;
 	unsigned long long caps;
 	int ret;
 
+	handle = ACPI_HANDLE(dev);
+	if (!handle)
+		return -ENODEV;
+
 	ret = acpi_install_cmos_rtc_space_handler(handle);
 	if (ret < 0) {
 		dev_info(dev, "Unable to install space handler\n");
drivers/acpi/pfr_telemetry.c +5 -1
index 32bdf8cbe8f237..2387376832a1b6 100644
@@ -360,10 +360,14 @@ static void pfrt_log_put_idx(void *data)
 
 static int acpi_pfrt_log_probe(struct platform_device *pdev)
 {
-	acpi_handle handle = ACPI_HANDLE(&pdev->dev);
 	struct pfrt_log_device *pfrt_log_dev;
+	acpi_handle handle;
 	int ret;
 
+	handle = ACPI_HANDLE(&pdev->dev);
+	if (!handle)
+		return -ENODEV;
+
 	if (!acpi_has_method(handle, "_DSM")) {
 		dev_dbg(&pdev->dev, "Missing _DSM\n");
 		return -ENODEV;
drivers/acpi/pfr_update.c +5 -1
index 11b1c282800525..6283105bb0e8b2 100644
@@ -538,10 +538,14 @@ static void pfru_put_idx(void *data)
 
 static int acpi_pfru_probe(struct platform_device *pdev)
 {
-	acpi_handle handle = ACPI_HANDLE(&pdev->dev);
 	struct pfru_device *pfru_dev;
+	acpi_handle handle;
 	int ret;
 
+	handle = ACPI_HANDLE(&pdev->dev);
+	if (!handle)
+		return -ENODEV;
+
 	if (!acpi_has_method(handle, "_DSM")) {
 		dev_dbg(&pdev->dev, "Missing _DSM\n");
 		return -ENODEV;
drivers/acpi/thermal.c +1 -1
index 8537395b417b0f..e5e970daca9e5f 100644
@@ -789,7 +789,7 @@ static int acpi_thermal_add(struct acpi_device *device)
 	int i;
 
 	if (!device)
-		return -EINVAL;
+		return -ENODEV;
 
 	tz = kzalloc(sizeof(struct acpi_thermal), GFP_KERNEL);
 	if (!tz)
Signature · 1 line
2.53.0
maintainer
#2 lore raw

PATCH 2/5 ACPI: bus: Introduce devm_acpi_install_notify_handler()

From: "Rafael J. Wysocki" <[email protected]>

[ Upstream commit ca70ce555a1eb8edf5fb1d5575f1fe19c9ae17f4 ]

Introduce devm_acpi_install_notify_handler() for installing an ACPI notify handler managed by devres that will be removed automatically on driver detach.

It installs the notify handler on the device object in the ACPI namespace that corresponds to the owner device's ACPI companion, if present (an error is returned if the owner device doesn't have an ACPI companion).

Currently, there is no way to manually remove the notify handler installed by it because none of its users brought on subsequently will need to do that.

Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")

---
 drivers/acpi/bus.c      | 66 +++++++++++++++++++++++++++++++++++++++++
 include/acpi/acpi_bus.h |  2 ++
 2 files changed, 68 insertions(+)
drivers/acpi/bus.c +66 -0
index a984ccd4a2a0c1..c2ea10e6a26648 100644
@@ -622,6 +622,72 @@ void acpi_dev_remove_notify_handler(struct acpi_device *adev,
 }
 EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler);
 
+struct acpi_notify_handler_devres {
+	acpi_notify_handler handler;
+	u32 handler_type;
+};
+
+static void devm_acpi_notify_handler_release(struct device *dev, void *res)
+{
+	struct acpi_notify_handler_devres *dr = res;
+
+	acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type,
+				       dr->handler);
+}
+
+/**
+ * devm_acpi_install_notify_handler - Install an ACPI notify handler for a
+ * 				      managed device
+ * @dev: Device to install a notify handler for
+ * @handler_type: Type of the notify handler
+ * @handler: Handler function to install
+ * @context: Data passed back to the handler function
+ *
+ * This function performs the same function as acpi_dev_install_notify_handler()
+ * called for the ACPI companion of @dev with the same @handler_type, @handler,
+ * and @context arguments, but the ACPI notify handler installed by it will be
+ * automatically removed on driver detach.
+ *
+ * Callers should ensure that all resources used by @handler have been allocated
+ * prior to invoking this function, in which case those resources should be
+ * devres-managed so that they won't be released before the notify handler
+ * removal.  Otherwise, special synchronization between @handler and the
+ * management of those resources is required.
+ *
+ * When the request fails, an error message is printed.  Don't add extra error
+ * messages at the call sites.
+ *
+ * Return: 0 on success or a negative error number.
+ */
+int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
+				     acpi_notify_handler handler, void *context)
+{
+	struct acpi_notify_handler_devres *dr;
+	struct acpi_device *adev;
+	int ret;
+
+	adev = ACPI_COMPANION(dev);
+	if (!adev)
+		return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__);
+
+	dr = devres_alloc(devm_acpi_notify_handler_release, sizeof(*dr), GFP_KERNEL);
+	if (!dr)
+		return -ENOMEM;
+
+	ret = acpi_dev_install_notify_handler(adev, handler_type, handler, context);
+	if (ret) {
+		devres_free(dr);
+		return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n");
+	}
+
+	dr->handler = handler;
+	dr->handler_type = handler_type;
+	devres_add(dev, dr);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(devm_acpi_install_notify_handler);
+
 /* Handle events targeting \_SB device (at present only graceful shutdown) */
 
 #define ACPI_SB_NOTIFY_SHUTDOWN_REQUEST 0x81
include/acpi/acpi_bus.h +2 -0
index aad1a95e6863d4..9a8a796a870d1a 100644
@@ -625,6 +625,8 @@ int acpi_dev_install_notify_handler(struct acpi_device *adev,
 void acpi_dev_remove_notify_handler(struct acpi_device *adev,
 				    u32 handler_type,
 				    acpi_notify_handler handler);
+int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
+				     acpi_notify_handler handler, void *context);
 extern int acpi_notifier_call_chain(struct acpi_device *, u32, u32);
 extern int register_acpi_notifier(struct notifier_block *);
 extern int unregister_acpi_notifier(struct notifier_block *);
Signature · 1 line
2.53.0
maintainer
#3 lore raw

PATCH 3/5 ACPI: NFIT: core: Use devm_acpi_install_notify_handler()

In reply to Sasha Levin

From: "Rafael J. Wysocki" <[email protected]>

[ Upstream commit 198541ad53c0d0d891fedea4098f9953a0f566c0 ]

Now that devm_acpi_install_notify_handler() is available, use it in acpi_nfit_probe() instead of a custom devm action removing an ACPI notify handler installed via acpi_dev_install_notify_handler().

Also drop the explicit ACPI_COMPANION() check against NULL that is not necessary any more becuase devm_acpi_install_notify_handler() carries out an equivalent check internally and use ACPI_HANDLE() to retrieve the platform device's ACPI handle.

No intentional functional impact.

Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")

---
 drivers/acpi/nfit/core.c | 27 +++++++--------------------
 1 file changed, 7 insertions(+), 20 deletions(-)
drivers/acpi/nfit/core.c +7 -20
index 3eb56b77cb6d93..c00cb8661b0b52 100644
@@ -3283,19 +3283,11 @@ static void acpi_nfit_put_table(void *table)
 
 static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data)
 {
-	struct acpi_device *adev = data;
-
-	device_lock(&adev->dev);
-	__acpi_nfit_notify(&adev->dev, handle, event);
-	device_unlock(&adev->dev);
-}
-
-static void acpi_nfit_remove_notify_handler(void *data)
-{
-	struct acpi_device *adev = data;
+	struct device *dev = data;
 
-	acpi_dev_remove_notify_handler(adev, ACPI_DEVICE_NOTIFY,
-				       acpi_nfit_notify);
+	device_lock(dev);
+	__acpi_nfit_notify(dev, handle, event);
+	device_unlock(dev);
 }
 
 void acpi_nfit_shutdown(void *data)
@@ -3338,13 +3330,8 @@ static int acpi_nfit_add(struct acpi_device *adev)
 	acpi_size sz;
 	int rc = 0;
 
-	rc = acpi_dev_install_notify_handler(adev, ACPI_DEVICE_NOTIFY,
-					     acpi_nfit_notify, adev);
-	if (rc)
-		return rc;
-
-	rc = devm_add_action_or_reset(dev, acpi_nfit_remove_notify_handler,
-					adev);
+	rc = devm_acpi_install_notify_handler(dev, ACPI_DEVICE_NOTIFY,
+					      acpi_nfit_notify, dev);
 	if (rc)
 		return rc;
 
@@ -3375,7 +3362,7 @@ static int acpi_nfit_add(struct acpi_device *adev)
 	acpi_desc->acpi_header = *tbl;
 
 	/* Evaluate _FIT and override with that if present */
-	status = acpi_evaluate_object(adev->handle, "_FIT", NULL, &buf);
+	status = acpi_evaluate_object(ACPI_HANDLE(dev), "_FIT", NULL, &buf);
 	if (ACPI_SUCCESS(status) && buf.length > 0) {
 		union acpi_object *obj = buf.pointer;
 
Signature · 1 line
2.53.0
maintainer
#4 lore raw

PATCH 4/5 ACPI: NFIT: core: Fix acpi_nfit_init() error cleanup

In reply to Sasha Levin

From: "Rafael J. Wysocki" <[email protected]>

[ Upstream commit 38bf27511ef41bffebd157ec3eba41fc89ba59cd ]

If acpi_nfit_init() fails after adding the acpi_desc object to the acpi_descs list, that object is never removed from that list because the acpi_nfit_shutdown() devm action is not added for the NFIT device in that case. Next, the acpi_nfit_init() failure causes acpi_nfit_probe() to fail, the acpi_desc object is freed, and a dangling pointer is left behind in the acpi_descs. Any subsequent ACPI Machine Check Exception will trigger nfit_handle_mce() which iterates over acpi_descs and so a use-after-free will occur.

Moreover, if acpi_nfit_probe() returns 0 after installing a notify handler for the NFIT device and without allocating the acpi_desc object and setting the NFIT device's driver data pointer, the acpi_desc object will be allocated by acpi_nfit_update_notify() and acpi_nfit_init() will be called to initialize it. Regardless of whether or not acpi_nfit_init() fails in that case, the acpi_nfit_shutdown() devm action is not added for the NFIT device and acpi_desc is never removed from the acpi_descs list. If the acpi_desc object is freed subsequently on driver removal, any subsequent ACPI MCE will lead to a use-after-free like in the previous case.

To address the first issue mentioned above, make acpi_nfit_probe() call acpi_nfit_shutdown() directly on acpi_nfit_init() failures and to address the other one, add a remove callback to the driver and make it call acpi_nfit_shutdown(). Also, since it is now possible to pass NULL to acpi_nfit_shutdown() or the acpi_desc object passed to it may not have been initialized, add checks against NULL for acpi_desc and its nvdimm_bus field to that function and make acpi_nfit_unregister() clear the latter after unregistering the NVDIMM bus.

Fixes a61fe6f7902e ("nfit, tools/testing/nvdimm: unify common init for acpi_nfit_desc")
Fixes fbabd829fe76 ("acpi, nfit: fix module unload vs workqueue shutdown race")
Cc All applicable <[email protected]>

Stable-dep-of: 18a00ed0e718 ("ACPI: NFIT: core: Fix possible deadlock and missing notifications")

---
 drivers/acpi/nfit/core.c | 18 +++++++++++++++---
 1 file changed, 15 insertions(+), 3 deletions(-)
drivers/acpi/nfit/core.c +15 -3
index c00cb8661b0b52..dd7cd4b2152a57 100644
@@ -3061,6 +3061,8 @@ static void acpi_nfit_unregister(void *data)
 	struct acpi_nfit_desc *acpi_desc = data;
 
 	nvdimm_bus_unregister(acpi_desc->nvdimm_bus);
+	/* The nvdimm_bus object may have been freed, so clear the pointer. */
+	acpi_desc->nvdimm_bus = NULL;
 }
 
 int acpi_nfit_init(struct acpi_nfit_desc *acpi_desc, void *data, acpi_size sz)
@@ -3293,7 +3295,10 @@ static void acpi_nfit_notify(acpi_handle handle, u32 event, void *data)
 void acpi_nfit_shutdown(void *data)
 {
 	struct acpi_nfit_desc *acpi_desc = data;
-	struct device *bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus);
+	struct device *bus_dev;
+
+	if (!acpi_desc || !acpi_desc->nvdimm_bus)
+		return;
 
 	/*
 	 * Destruct under acpi_desc_lock so that nfit_handle_mce does not
@@ -3308,6 +3313,7 @@ void acpi_nfit_shutdown(void *data)
 	mutex_unlock(&acpi_desc->init_mutex);
 	cancel_delayed_work_sync(&acpi_desc->dwork);
 
+	bus_dev = to_nvdimm_bus_dev(acpi_desc->nvdimm_bus);
 	/*
 	 * Bounce the nvdimm bus lock to make sure any in-flight
 	 * acpi_nfit_ars_rescan() submissions have had a chance to
@@ -3380,9 +3386,14 @@ static int acpi_nfit_add(struct acpi_device *adev)
 				sz - sizeof(struct acpi_table_nfit));
 
 	if (rc)
-		return rc;
+		acpi_nfit_shutdown(acpi_desc);
 
-	return devm_add_action_or_reset(dev, acpi_nfit_shutdown, acpi_desc);
+	return rc;
+}
+
+static void acpi_nfit_remove(struct acpi_device *adev)
+{
+	acpi_nfit_shutdown(dev_get_drvdata(&adev->dev));
 }
 
 static void acpi_nfit_update_notify(struct device *dev, acpi_handle handle)
@@ -3466,6 +3477,7 @@ static struct acpi_driver acpi_nfit_driver = {
 	.ids = acpi_nfit_ids,
 	.ops = {
 		.add = acpi_nfit_add,
+		.remove = acpi_nfit_remove,
 	},
 };
 
Signature · 1 line
2.53.0
maintainer
#5 lore raw

PATCH 5/5 ACPI: NFIT: core: Fix possible deadlock and missing notifications

In reply to Sasha Levin

From: "Rafael J. Wysocki" <[email protected]>

[ Upstream commit 18a00ed0e718473f5c3fcfa49df46c944575e60c ]

After commit 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table"), ACPI NFIT driver removal may deadlock if an ACPI notify on the NFIT device is triggered concurrently. A similar deadlock may occur if an ACPI notify on the NFIT device is triggered during a failing driver probe.

The deadlock is possible because acpi_dev_remove_notify_handler() calls acpi_os_wait_events_complete() after removing the notify handler and the driver core invokes it under the NFIT platform device lock which is also acquired by acpi_nfit_notify(). Thus acpi_os_wait_events_complete() may be waiting for acpi_nfit_notify() to complete, but the latter may not be able to acquire the device lock which is being held by the driver core while the former is being executed.

Moreover, after commit 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one"), there are no sysfs notifications regarding NVDIMM devices because __acpi_nvdimm_notify() always bails out after checking the driver data pointer of the device's parent. That parent is the ACPI companion of the platform device used for driver binding, so its driver data pointer is always NULL after the commit in question which was overlooked by it.

A remedy for the deadlock is to use a special separate lock for ACPI notify synchronization with driver probe and removal instead of the device lock of the NFIT device, while a remedy for the second issue is to populate the driver data pointer of the NFIT device's ACPI companion when the driver is ready to operate, so do both these things. However, since the new lock is not held across the entire teardown and acpi_nfit_notify() should do nothing when teardown is in progress, make it check the driver data pointer of the NFIT device's ACPI companion, in analogy with the existing check in __acpi_nvdimm_notify(), and bail out if that pointer is NULL.

Fixes 9b311b7313d6 ("ACPI: NFIT: Install Notify() handler before getting NFIT table")
Fixes 03667e146f81 ("ACPI: NFIT: core: Convert the driver to a platform one")
Cc All applicable <[email protected]> # 9995e4404ea4: ACPI: NFIT: core: Eliminate redundant local variable
---
 drivers/acpi/bus.c       |  6 +++-
 drivers/acpi/nfit/core.c | 62 +++++++++++++++++++++++++++++++---------
 2 files changed, 54 insertions(+), 14 deletions(-)
drivers/acpi/bus.c +5 -1
index c2ea10e6a26648..222a6bc6164489 100644
@@ -623,6 +623,7 @@ void acpi_dev_remove_notify_handler(struct acpi_device *adev,
 EXPORT_SYMBOL_GPL(acpi_dev_remove_notify_handler);
 
 struct acpi_notify_handler_devres {
+	struct acpi_device *adev;
 	acpi_notify_handler handler;
 	u32 handler_type;
 };
@@ -631,7 +632,7 @@ static void devm_acpi_notify_handler_release(struct device *dev, void *res)
 {
 	struct acpi_notify_handler_devres *dr = res;
 
-	acpi_dev_remove_notify_handler(ACPI_COMPANION(dev), dr->handler_type,
+	acpi_dev_remove_notify_handler(dr->adev, dr->handler_type,
 				       dr->handler);
 }
 
@@ -667,6 +668,8 @@ int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
 	int ret;
 
 	adev = ACPI_COMPANION(dev);
+	if (!adev && dev->bus == &acpi_bus_type)
+		adev = to_acpi_device(dev);
 	if (!adev)
 		return dev_err_probe(dev, -ENODEV, "No ACPI companion in %s()\n", __func__);
 
@@ -680,6 +683,7 @@ int devm_acpi_install_notify_handler(struct device *dev, u32 handler_type,
 		return dev_err_probe(dev, ret, "Failed to install an ACPI notify handler\n");
 	}
 
+	dr->adev = adev;
 	dr->handler = handler;
 	dr->handler_type = handler_type;
 	devres_add(dev, dr);
drivers/acpi/nfit/core.c +49 -13

118 lines not on this page · raw message

Signature · 1 line
2.53.0