[PATCH v4 6/6] platform/x86/amd/hsmp: Serialize the data plane against socket teardown

From: Muralidhara M K

Date: Wed Jul 08 2026 - 00:27:02 EST


The HSMP data plane is lock-free: open /dev/hsmp fds and hwmon sysfs reads
call hsmp_send_message() without any coordination with driver teardown.
misc_deregister() does not drain already-open fds, so an in-flight message
can race a concurrent unbind and touch a freed socket array or an unmapped
mailbox.

Add hsmp_sock_rwsem and export it so the data plane and the teardown paths
take it directly. hsmp_send_message() holds it for read across the whole
bounds-check + MMIO access via guard(rwsem_read), so it is dropped on every
return path without an unlock label. A teardown path holds it for write to
drain any in-flight message and keep new ones out while it tears the socket
down.

Wire both teardown paths into the drain:

- plat.c: the devres release action takes the write lock, unmaps the
metric tables, destroys the per-socket mutexes and drops the global
socket pointer before devres frees the array.

- acpi.c: hsmp_acpi_remove() and the probe-failure cleanup take the write
lock (nested inside hsmp_acpi_probe_mutex) while they clear sock->dev
and, on the last unbind, unmap the mailbox and free the socket array via
hsmp_acpi_sock_release().

hsmp_sock_rwsem is deliberately separate from acpi.c's
hsmp_acpi_probe_mutex, which serializes the ACPI control-plane handshake
(socket-array allocation, misc registration state and the sock refcount
lifecycle). The ACPI probe path cannot reuse this rwsem for that: it drives
the data plane itself through hsmp_test(), which takes the rwsem for read,
so holding it for write across probe would deadlock. The two nest as
probe_mutex -> rwsem write.

Signed-off-by: Muralidhara M K <muralidhara.mk@xxxxxxx>
---
drivers/platform/x86/amd/hsmp/acpi.c | 19 ++++++++++++++++-
drivers/platform/x86/amd/hsmp/hsmp.c | 32 +++++++++++++++++++++++++++-
drivers/platform/x86/amd/hsmp/hsmp.h | 8 +++++++
drivers/platform/x86/amd/hsmp/plat.c | 8 +++++++
4 files changed, 65 insertions(+), 2 deletions(-)

diff --git a/drivers/platform/x86/amd/hsmp/acpi.c b/drivers/platform/x86/amd/hsmp/acpi.c
index d35465079eea..814f0a812a85 100644
--- a/drivers/platform/x86/amd/hsmp/acpi.c
+++ b/drivers/platform/x86/amd/hsmp/acpi.c
@@ -24,6 +24,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
+#include <linux/rwsem.h>
#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/topology.h>
@@ -629,7 +630,9 @@ MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids);
* destroy the per-socket mutexes and free the socket array.
*
* Called with hsmp_acpi_probe_mutex held, serializing it against a concurrent
- * probe.
+ * probe, and with hsmp_sock_rwsem held for write, which has drained any
+ * in-flight hsmp_send_message() so unmapping the mailbox and freeing the array
+ * cannot race the lock-free data plane.
*/
static void hsmp_acpi_sock_release(void)
{
@@ -659,6 +662,10 @@ static void hsmp_acpi_sock_release(void)
* pass every gate in hsmp_send_message() and reach the unmapped mailbox.
*
* sock is NULL if probe failed before hsmp_parse_acpi_table() set the drvdata.
+ *
+ * Takes hsmp_sock_rwsem for write to serialize against the lock-free data
+ * plane: init_acpi() drives hsmp_test() and a previously probed socket may
+ * already have exposed /dev/hsmp.
*/
static void hsmp_acpi_probe_failure_cleanup(struct device *dev)
{
@@ -666,6 +673,8 @@ static void hsmp_acpi_probe_failure_cleanup(struct device *dev)

lockdep_assert_held(&hsmp_acpi_probe_mutex);

+ guard(rwsem_write)(&hsmp_sock_rwsem);
+
if (sock)
sock->dev = NULL;

@@ -731,6 +740,14 @@ static void hsmp_acpi_remove(struct platform_device *pdev)
*/
guard(mutex)(&hsmp_acpi_probe_mutex);

+ /*
+ * Drain the lock-free data plane and keep it out for the whole
+ * teardown. This covers the per-socket unbind, whose mailbox devres
+ * unmaps once we return, and the last unbind that frees the socket
+ * array in hsmp_acpi_sock_release().
+ */
+ guard(rwsem_write)(&hsmp_sock_rwsem);
+
/*
* Clear this socket's dev so hsmp_send_message() rejects it before
* devres unmaps the mailbox. On a non-final unbind the socket array
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
index e47e86116f16..ab77a4996ebb 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -15,6 +15,7 @@
#include <linux/device.h>
#include <linux/io.h>
#include <linux/mutex.h>
+#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>

@@ -43,6 +44,25 @@

static struct hsmp_plat_device hsmp_pdev;

+/*
+ * Serializes the lock-free data plane (hsmp_send_message() and the per-socket
+ * MMIO access it performs) against socket teardown. The data plane takes it
+ * for read so multiple sockets can be driven concurrently; a teardown path
+ * takes it for write while it clears sock->dev, frees the socket array or
+ * unmaps the mailbox, so a reader can never observe a half-torn-down or freed
+ * socket.
+ *
+ * This is distinct from acpi.c's hsmp_acpi_probe_mutex, which serializes the
+ * ACPI control-plane handshake: the one-time socket-array allocation, misc
+ * device registration state and the socket refcount lifecycle. The ACPI probe
+ * path cannot use this rwsem for that: it drives the data plane itself through
+ * hsmp_test(), which takes the rwsem for read, so holding it for write across
+ * probe would deadlock. A teardown path instead nests the rwsem write side
+ * inside that mutex.
+ */
+DECLARE_RWSEM(hsmp_sock_rwsem);
+EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP");
+
/*
* Send a message to the HSMP port via PCI-e config space registers
* or by writing to MMIO space.
@@ -214,6 +234,15 @@ int hsmp_send_message(struct hsmp_message *msg)
if (ret)
return ret;

+ /*
+ * Hold the teardown rwsem for read across the whole MMIO access. A
+ * teardown path takes it for write before clearing sock->dev, freeing
+ * the socket array or unmapping the mailbox, so the lock-free data
+ * plane can never dereference a freed socket or touch an unmapped
+ * mailbox. guard() drops it on every return path below.
+ */
+ guard(rwsem_read)(&hsmp_sock_rwsem);
+
if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets)
return -ENODEV;

@@ -234,7 +263,8 @@ int hsmp_send_message(struct hsmp_message *msg)
* non-NULL dev also guarantees virt_base_addr, the mailbox offsets and
* the semaphore are visible.
*
- * Pairs with smp_store_release(&sock->dev) in hsmp_parse_acpi_table().
+ * Held under hsmp_sock_rwsem; pairs with smp_store_release(&sock->dev)
+ * in hsmp_parse_acpi_table().
*/
if (!smp_load_acquire(&sock->dev))
return -ENODEV;
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.h b/drivers/platform/x86/amd/hsmp/hsmp.h
index bc2050dcfdb5..9c36a9252c73 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.h
+++ b/drivers/platform/x86/amd/hsmp/hsmp.h
@@ -17,6 +17,7 @@
#include <linux/miscdevice.h>
#include <linux/mutex.h>
#include <linux/pci.h>
+#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/sysfs.h>

@@ -70,6 +71,13 @@ void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev);
void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev);
void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev);
struct hsmp_plat_device *get_hsmp_pdev(void);
+
+/*
+ * Data-plane teardown rwsem. hsmp_send_message() holds it for read; a teardown
+ * path in plat.c/acpi.c holds it for write to drain the data plane while it
+ * clears sock->dev, frees the socket array or unmaps the mailbox.
+ */
+extern struct rw_semaphore hsmp_sock_rwsem;
#if IS_ENABLED(CONFIG_HWMON)
int hsmp_create_sensor(struct device *dev, u16 sock_ind);
#else
diff --git a/drivers/platform/x86/amd/hsmp/plat.c b/drivers/platform/x86/amd/hsmp/plat.c
index d46dcb8c7f03..f704095eeeb0 100644
--- a/drivers/platform/x86/amd/hsmp/plat.c
+++ b/drivers/platform/x86/amd/hsmp/plat.c
@@ -13,12 +13,14 @@

#include <linux/acpi.h>
#include <linux/build_bug.h>
+#include <linux/cleanup.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/kconfig.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/platform_device.h>
+#include <linux/rwsem.h>
#include <linux/sysfs.h>

#include <asm/amd/node.h>
@@ -206,11 +208,17 @@ static int init_platform_device(struct device *dev)
* per-socket mutexes need an explicit mutex_destroy(), so tear both down
* here. Registered as a devres action so it also runs on a probe failure
* after init_platform_device() has mapped some tables.
+ *
+ * Take the data-plane rwsem for write to drain any in-flight
+ * hsmp_send_message() and drop the global socket pointer before devres frees
+ * the array, so a late message bails out at its first check.
*/
static void hsmp_pltdrv_release(void *data)
{
+ guard(rwsem_write)(&hsmp_sock_rwsem);
hsmp_unmap_metric_tbls(hsmp_pdev);
hsmp_destroy_metric_read_locks(hsmp_pdev);
+ hsmp_pdev->sock = NULL;
}

static int hsmp_pltdrv_probe(struct platform_device *pdev)
--
2.34.1