From 92bd616b71438cf11cb7b990b02a68738d6606d7 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:21:50 +0530 Subject: [PATCH 01/24] amd_hsmp: Drop the out-of-tree variable-sized metrics table support Revert the out-of-tree variable-sized metrics table support, which this tree carried as commit 88c2e50faba4 ("platform/x86/amd/hsmp: Add support for variable-sized metrics tables") and again, reworked, as commit b0adf526da40 ("platform/x86/amd/hsmp: Add support for variable-sized metrics tables"). That approach derived the metric table size from the CPU family/model plus the table version reported by HSMP_GET_METRIC_TABLE_VER, stored it once per platform in hsmp_plat_device.hsmp_table_size, and grew hsmp_metric_tbl_read() an offset argument so the sysfs binary attribute could be read in chunks. Upstream solved the same problem differently: the region size is taken straight from HSMP_GET_METRIC_TABLE_DRAM_ADDR args[2], stored per socket, and the table is handed to userspace as one atomic snapshot through the new HSMP_IOCTL_GET_TELEMETRY_DATA ioctl instead of a chunked sysfs read. Those upstream commits are ported in the following patches, so drop this implementation first to keep the driver in sync with upstream and avoid carrying two competing mechanisms. hsmp_metric_tbl_read() is restored to the upstream form, which includes the sock->metric_tbl_addr NULL check from upstream commit 2c78fb287e1f ("platform/x86/amd/hsmp: Ensure sock->metric_tbl_addr is non-NULL"), and the metrics_bin attribute in acpi.c gets its .size back so the two front-ends agree again. The proto_ver gate stays at '>= HSMP_PROTO_VER6' as introduced by commit c8d23ffedb3f ("ACPI Driver: Update in Metric Table Logic for ACPI Based Driver"); upstream arrives at the same condition later in this series. Signed-off-by: Muralidhara M K --- acpi.c | 3 +- amd_hsmp.h | 86 ------------------------------------------------------ hsmp.c | 86 +++++++++++------------------------------------------- hsmp.h | 3 +- plat.c | 2 +- 5 files changed, 21 insertions(+), 159 deletions(-) diff --git a/acpi.c b/acpi.c index 1390abc..5bcddd9 100644 --- a/acpi.c +++ b/acpi.c @@ -262,7 +262,7 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj struct device *dev = container_of(kobj, struct device, kobj); struct hsmp_socket *sock = dev_get_drvdata(dev); - return hsmp_metric_tbl_read(sock, buf, count, off); + return hsmp_metric_tbl_read(sock, buf, count); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) @@ -585,6 +585,7 @@ static int init_acpi(struct device *dev) static HSMP_CONST struct bin_attribute hsmp_metric_tbl_attr = { .attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, HSMP_BIN_READ = hsmp_metric_tbl_acpi_read, + .size = sizeof(struct hsmp_metric_table), }; static HSMP_CONST struct bin_attribute *hsmp_attr_list[] = { diff --git a/amd_hsmp.h b/amd_hsmp.h index 1ddeccc..f198fed 100644 --- a/amd_hsmp.h +++ b/amd_hsmp.h @@ -608,92 +608,6 @@ struct hsmp_metric_table { __u32 gfxclk_frequency[8]; }; -#define F1A_M50_M5F_MAX_CORES_PER_CCD_32 32 -#define F1A_M50_M5F_MAX_FREQ_TABLE_SIZE 4 -#define F1A_M50_M5F_MAX_XGMI 8 -#define F1A_M50_M5F_MAX_PCIE 8 -#define F1A_M50_M5F_MAX_CCD 8 - -/* Metrics table (supported only with proto version 7) */ -struct hsmp_metric_table_f1a_m50_5f_iod { - __u32 num_active_ccds; - __u32 accumulation_counter; - - /* TEMPERATURE */ - __u64 max_socket_temperature_acc; - - /* POWER */ - __u32 socket_power_limit; - __u32 max_socket_power_limit; - __u64 socket_power_acc; - __u64 core_power_acc; - __u64 uncore_power_acc; - - /* ENERGY */ - __u64 timestamp; - __u64 socket_energy_acc; - __u64 core_energy_acc; - __u64 uncore_energy_acc; - - /* FREQUENCY */ - __u64 fclk_frequency_acc; - __u64 uclk_frequency_acc; - __u64 ddr_rate_acc; - __u64 lclk_frequency_acc[F1A_M50_M5F_MAX_FREQ_TABLE_SIZE]; - - /* FREQUENCY RANGE */ - __u32 fclk_frequency_table[F1A_M50_M5F_MAX_FREQ_TABLE_SIZE]; - __u32 uclk_frequency_table[F1A_M50_M5F_MAX_FREQ_TABLE_SIZE]; - __u32 ddr_rate_table[F1A_M50_M5F_MAX_FREQ_TABLE_SIZE]; - __u32 max_df_pstate_range; - __u32 min_df_pstate_range; - __u32 lclk_frequency_table[F1A_M50_M5F_MAX_FREQ_TABLE_SIZE]; - __u32 max_lclk_dpm_range; - __u32 min_lclk_dpm_range; - - /* XGMI */ - __u64 xgmi_bit_rate[F1A_M50_M5F_MAX_XGMI]; - __u64 xgmi_read_bandwidth[F1A_M50_M5F_MAX_XGMI]; - __u64 xgmi_write_bandwidth[F1A_M50_M5F_MAX_XGMI]; - - /* ACTIVITY */ - __u64 socket_c0_residency_acc; - __u64 socket_df_cstate_residency_acc; - __u64 dram_read_bandwidth_acc; - __u64 dram_write_bandwidth_acc; - __u32 max_dram_bandwidth; - __u64 pcie_bandwidth_acc[F1A_M50_M5F_MAX_PCIE]; - - /* THROTTLERS */ - __u32 prochot_residency_acc; - __u32 ppt_residency_acc; - __u32 thm_residency_acc; - __u32 vrhot_residency_acc; - __u32 cpu_tdc_residency_acc; - __u32 soc_tdc_residency_acc; - __u32 io_mem_tdc_residency_acc; - __u32 fit_residency_acc; -}; - -struct hsmp_metric_table_f1a_m50_5f_ccd { - __u32 core_apicid_of_thread0[F1A_M50_M5F_MAX_CORES_PER_CCD_32]; - __u64 core_c0[F1A_M50_M5F_MAX_CORES_PER_CCD_32]; - __u64 core_cc1[F1A_M50_M5F_MAX_CORES_PER_CCD_32]; - __u64 core_cc6[F1A_M50_M5F_MAX_CORES_PER_CCD_32]; - __u64 core_frequency[F1A_M50_M5F_MAX_CORES_PER_CCD_32]; - __u64 core_frequency_effective[F1A_M50_M5F_MAX_CORES_PER_CCD_32]; - __u64 core_power[F1A_M50_M5F_MAX_CORES_PER_CCD_32]; -}; - -/* - * Future processors within the same family and model may support a - * variable number of CCDs and cores - */ -struct hsmp_metric_table_f1a_m50_5f { - struct hsmp_metric_table_f1a_m50_5f_iod iod; - struct hsmp_metric_table_f1a_m50_5f_ccd ccd[F1A_M50_M5F_MAX_CCD]; -}; - /* Reset to default packing */ #pragma pack() diff --git a/hsmp.c b/hsmp.c index eae0edd..3cd1c0d 100644 --- a/hsmp.c +++ b/hsmp.c @@ -368,48 +368,34 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) return 0; } -/** - * hsmp_metric_tbl_read - Read metric table - * - * This function maintains ABI compatibility for external consumers. - * It reads from offset 0, which works for all metrics table formats. - * External modules using this function will continue to work without - * modification. - * - * Return: number of bytes read or negative error code - */ - -ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, - size_t size, loff_t off) +ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) { struct hsmp_message msg = { 0 }; - size_t var_size, remaining; int ret; if (!sock || !buf) return -EINVAL; - if (off < 0 || off > hsmp_pdev.hsmp_table_size) { - dev_err(sock->dev, "Invalid offset\n"); + if (!sock->metric_tbl_addr) { + dev_err(sock->dev, "Metrics table address not available\n"); + return -ENOMEM; + } + + /* Do not support lseek(), also don't allow more than the size of metric table */ + if (size != sizeof(struct hsmp_metric_table)) { + dev_err(sock->dev, "Wrong buffer size\n"); return -EINVAL; } - /* Compute remaining bytes using explicit cast to avoid signed/unsigned mixing */ - remaining = hsmp_pdev.hsmp_table_size - (size_t)off; - var_size = min_t(size_t, size, remaining); - if (off == 0) { - msg.msg_id = HSMP_GET_METRIC_TABLE; - msg.sock_ind = sock->sock_ind; + msg.msg_id = HSMP_GET_METRIC_TABLE; + msg.sock_ind = sock->sock_ind; - ret = hsmp_send_message(&msg); - if (ret) { - dev_err(sock->dev, "Failed to send HSMP_GET_METRIC_TABLE, ret: %d\n", ret); + ret = hsmp_send_message(&msg); + if (ret) return ret; - } - } - memcpy_fromio(buf, (u8 __iomem *)sock->metric_tbl_addr + off, var_size); + memcpy_fromio(buf, sock->metric_tbl_addr, size); - return var_size; + return size; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); @@ -420,10 +406,8 @@ EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, AMD_HSMP); int hsmp_get_tbl_dram_base(u16 sock_ind) { struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; - struct hsmp_message msg_tbl_ver = { 0 }; struct hsmp_message msg = { 0 }; phys_addr_t dram_addr; - u32 table_ver; int ret; msg.sock_ind = sock_ind; @@ -443,44 +427,8 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) dev_err(sock->dev, "Invalid DRAM address for metric table\n"); return -ENOMEM; } - - /* Get metric table version */ - msg_tbl_ver.sock_ind = sock_ind; - msg_tbl_ver.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_VER].response_sz; - msg_tbl_ver.msg_id = HSMP_GET_METRIC_TABLE_VER; - - ret = hsmp_send_message(&msg_tbl_ver); - if (ret) - return ret; - - table_ver = msg_tbl_ver.args[0]; - - hsmp_pdev.hsmp_table_size = 0; - /* Determine metric table size based on CPU family/model and table version */ - switch (boot_cpu_data.x86) { - case 0x1A: - if (boot_cpu_data.x86_model >= 0x50 && - boot_cpu_data.x86_model <= 0x5F && - table_ver == 0x00700000) { - hsmp_pdev.hsmp_table_size = sizeof(struct hsmp_metric_table_f1a_m50_5f); - } - break; - case 0x19: - if (boot_cpu_data.x86_model >= 0x90 && - boot_cpu_data.x86_model <= 0x9F) { - hsmp_pdev.hsmp_table_size = sizeof(struct hsmp_metric_table); - } - break; - } - - if (!hsmp_pdev.hsmp_table_size) { - dev_err(sock->dev, - "Metric table not supported for F%02Xh_M%02Xh (table version: 0x%08X)\n", - boot_cpu_data.x86, boot_cpu_data.x86_model, table_ver); - return -EOPNOTSUPP; - } - - sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, hsmp_pdev.hsmp_table_size); + sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, + sizeof(struct hsmp_metric_table)); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); return -ENOMEM; diff --git a/hsmp.h b/hsmp.h index bec23e2..11160a7 100644 --- a/hsmp.h +++ b/hsmp.h @@ -78,7 +78,6 @@ struct hsmp_plat_device { u32 proto_ver; u16 num_sockets; bool is_probed; - size_t hsmp_table_size; }; int hsmp_cache_proto_ver(u16 sock_ind); @@ -87,7 +86,7 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); void hsmp_misc_deregister(void); int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); -ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size, loff_t off); +ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); #if IS_ENABLED(CONFIG_HWMON) int hsmp_create_sensor(struct device *dev, u16 sock_ind); diff --git a/plat.c b/plat.c index 6f28289..bda6138 100644 --- a/plat.c +++ b/plat.c @@ -84,7 +84,7 @@ static ssize_t hsmp_metric_tbl_plat_read(struct file *filp, struct kobject *kobj sock = &hsmp_pdev->sock[sock_ind]; - return hsmp_metric_tbl_read(sock, buf, count, off); + return hsmp_metric_tbl_read(sock, buf, count); } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) From 601fd3852704b2890b4ceeddff5cbc9f444e2381 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:22:02 +0530 Subject: [PATCH 02/24] amd_hsmp: Fix "tmeout" typo in the SMU timeout message The mailbox timeout path prints "SMU tmeout". Upstream spells it "SMU timeout"; restore that so the two trees match. Signed-off-by: Muralidhara M K --- hsmp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hsmp.c b/hsmp.c index 3cd1c0d..a3ae8d6 100644 --- a/hsmp.c +++ b/hsmp.c @@ -125,7 +125,7 @@ static int __hsmp_send_message(struct hsmp_socket *sock, struct hsmp_message *ms } if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) { - dev_err(sock->dev, "Message ID 0x%X failure : SMU tmeout (status = 0x%X)\n", + dev_err(sock->dev, "Message ID 0x%X failure : SMU timeout (status = 0x%X)\n", msg->msg_id, mbox_status); return -ETIMEDOUT; } else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) { From fd5cdfd96092bab1d5af0f5d5599122102d0c90e Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:22:55 +0530 Subject: [PATCH 03/24] platform/x86/amd/hsmp: Clamp ioctl/send_message indices (Spectre v1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although validate_message() checks msg_id, a mispredicted branch can still allow speculative indexing into hsmp_msg_desc_table[]. Clamp msg.msg_id with array_index_nospec() at entry to hsmp_ioctl_msg() so downstream dereferences (including via is_get_msg() and hsmp_send_message()) see a bounded index. Similarly, hsmp_send_message() bounds-checks msg->sock_ind before indexing hsmp_pdev.sock[], but a mispredicted branch can still speculatively use the raw index (Spectre v1, CVE-2017-5753). Apply array_index_nospec() after the check so every caller that reaches hsmp_pdev.sock[] through this helper sees a clamped socket index-including hsmp_ioctl_msg() and any other path that hands a user-derived struct hsmp_message to hsmp_send_message(). Out-of-tree note: linux/nospec.h is included explicitly here rather than relying on it being pulled in transitively, which is not guaranteed across the kernel versions this module builds against. Reviewed-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260612042610.1629037-7-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit d20457b46eca76b9bb716dd31af591cad21607b5) --- hsmp.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/hsmp.c b/hsmp.c index a3ae8d6..4d90051 100644 --- a/hsmp.c +++ b/hsmp.c @@ -18,6 +18,7 @@ #endif #include #include +#include #include #include #include @@ -210,6 +211,7 @@ static int validate_message(struct hsmp_message *msg) int hsmp_send_message(struct hsmp_message *msg) { struct hsmp_socket *sock; + unsigned int sock_ind; int ret; if (!msg) @@ -220,7 +222,15 @@ int hsmp_send_message(struct hsmp_message *msg) if (!hsmp_pdev.sock || msg->sock_ind >= hsmp_pdev.num_sockets) return -ENODEV; - sock = &hsmp_pdev.sock[msg->sock_ind]; + + /* + * Sanitize sock_ind after the bounds check. A mispredicted branch can + * still let the CPU speculatively use msg->sock_ind as an index into + * hsmp_pdev.sock[] (Spectre v1, CVE-2017-5753), including for callers + * other than hsmp_ioctl_msg() that pass a user-derived socket index. + */ + sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; ret = down_interruptible(&sock->hsmp_sem); if (ret < 0) @@ -328,6 +338,19 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX) return -ENOMSG; + /* + * Sanitize the user-controlled msg_id against speculative + * execution. The bounds check above retires the out-of-range + * case with -ENOMSG, but a mispredicted branch can still let the + * CPU speculatively use msg_id as an index into + * hsmp_msg_desc_table[] (here and in validate_message() / + * is_get_msg() called downstream via hsmp_send_message()), and + * pull arbitrary kernel memory into the cache (Spectre v1, + * CVE-2017-5753). Clamp once into msg.msg_id so every downstream + * dereference sees the sanitized value. + */ + msg.msg_id = array_index_nospec(msg.msg_id, HSMP_MSG_ID_MAX); + switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { case FMODE_WRITE: /* From 000c8717a77355bb069cfbd8ba0fe5ed5d7ccfea Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:23:34 +0530 Subject: [PATCH 04/24] platform/x86/amd/hsmp: Validate ACPI UID before parsing socket index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_get_uid() passed the device UID directly to kstrtou16(uid + 2) without checking it. A NULL UID or one shorter than three characters would dereference a NULL pointer or read past the end of the string. Reject such UIDs with -EINVAL before stripping the "ID" prefix. Signed-off-by: Muralidhara M K Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260625123337.886435-3-muralidhara.mk@amd.com Link: https://patch.msgid.link/20260629155634.1807598-2-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen (cherry picked from commit 4c7d1b4cd75a775326e6802608440f7642284ea6) --- acpi.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/acpi.c b/acpi.c index 5bcddd9..d1ec58d 100644 --- a/acpi.c +++ b/acpi.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -93,6 +94,8 @@ static inline int hsmp_get_uid(struct device *dev, u16 *sock_ind) * bytes to integer. */ uid = acpi_device_uid(ACPI_COMPANION(dev)); + if (!uid || strlen(uid) < 3) + return -EINVAL; return kstrtou16(uid + 2, 10, sock_ind); } From b831b6cb493c19710dd0199128d7516f01872117 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:24:01 +0530 Subject: [PATCH 05/24] platform/x86/amd/hsmp: Validate _DSD mailbox sub-package element count MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_read_acpi_dsd() dereferenced elements[0] and elements[1] of each mailbox sub-package before confirming the package actually held two elements, allowing an out-of-bounds read on a malformed _DSD. Verify package.count >= 2 first, then fetch the string and integer objects. Signed-off-by: Muralidhara M K Reviewed-by: Ilpo Järvinen Link: https://patch.msgid.link/20260625123337.886435-3-muralidhara.mk@amd.com Link: https://patch.msgid.link/20260629155634.1807598-3-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen (cherry picked from commit 0d7a2664494534d6152be9bd9bf1cb1d089c67d3) --- acpi.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/acpi.c b/acpi.c index d1ec58d..b10b714 100644 --- a/acpi.c +++ b/acpi.c @@ -167,12 +167,18 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) union acpi_object *msgobj, *msgstr, *msgint; msgobj = &mailbox_package->package.elements[j]; - msgstr = &msgobj->package.elements[0]; - msgint = &msgobj->package.elements[1]; /* package should have 1 string and 1 integer object */ if (msgobj->type != ACPI_TYPE_PACKAGE || - msgstr->type != ACPI_TYPE_STRING || + msgobj->package.count < 2) { + ret = -EINVAL; + goto free_buf; + } + + msgstr = &msgobj->package.elements[0]; + msgint = &msgobj->package.elements[1]; + + if (msgstr->type != ACPI_TYPE_STRING || msgint->type != ACPI_TYPE_INTEGER) { ret = -EINVAL; goto free_buf; From 7cde2dbd46fb68ecd8b134dec85b4aec35e3c91e Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:24:57 +0530 Subject: [PATCH 06/24] platform/x86/amd/hsmp: Pass struct device explicitly to ACPI mailbox parsers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_read_acpi_crs() and hsmp_read_acpi_dsd() read the ACPI handle and emit error messages via sock->dev. Pass the struct device explicitly to both helpers instead of reading it back from sock->dev. This is a pure refactor with no functional change; it prepares for publishing sock->dev as the data-plane readiness gate only after the socket has been fully initialized, so the parsers must not depend on sock->dev already being set. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260629155634.1807598-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit b558cbed39af2f7dc6cd86c00916566fc6f170d3) --- acpi.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/acpi.c b/acpi.c index b10b714..25b1e48 100644 --- a/acpi.c +++ b/acpi.c @@ -123,7 +123,7 @@ static acpi_status hsmp_resource(struct acpi_resource *res, void *data) return AE_OK; } -static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) +static int hsmp_read_acpi_dsd(struct device *dev, struct hsmp_socket *sock) { struct acpi_buffer buf = { ACPI_ALLOCATE_BUFFER, NULL }; union acpi_object *guid, *mailbox_package; @@ -132,10 +132,10 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) int ret = 0; int j; - status = acpi_evaluate_object_typed(ACPI_HANDLE(sock->dev), "_DSD", NULL, + status = acpi_evaluate_object_typed(ACPI_HANDLE(dev), "_DSD", NULL, &buf, ACPI_TYPE_PACKAGE); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", + dev_err(dev, "Failed to read mailbox reg offsets from DSD table, err: %s\n", acpi_format_exception(status)); return -ENODEV; } @@ -158,7 +158,7 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) guid = &dsd->package.elements[0]; mailbox_package = &dsd->package.elements[1]; if (!is_acpi_hsmp_uuid(guid) || mailbox_package->type != ACPI_TYPE_PACKAGE) { - dev_err(sock->dev, "Invalid hsmp _DSD table data\n"); + dev_err(dev, "Invalid hsmp _DSD table data\n"); ret = -EINVAL; goto free_buf; } @@ -208,14 +208,14 @@ static int hsmp_read_acpi_dsd(struct hsmp_socket *sock) return ret; } -static int hsmp_read_acpi_crs(struct hsmp_socket *sock) +static int hsmp_read_acpi_crs(struct device *dev, struct hsmp_socket *sock) { acpi_status status; - status = acpi_walk_resources(ACPI_HANDLE(sock->dev), METHOD_NAME__CRS, + status = acpi_walk_resources(ACPI_HANDLE(dev), METHOD_NAME__CRS, hsmp_resource, sock); if (ACPI_FAILURE(status)) { - dev_err(sock->dev, "Failed to look up MP1 base address from CRS method, err: %s\n", + dev_err(dev, "Failed to look up MP1 base address from CRS method, err: %s\n", acpi_format_exception(status)); return -EINVAL; } @@ -223,14 +223,14 @@ static int hsmp_read_acpi_crs(struct hsmp_socket *sock) return -EINVAL; #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 5, 0) /* The mapped region should be un-cached */ - sock->virt_base_addr = devm_ioremap_uc(sock->dev, sock->mbinfo.base_addr, + sock->virt_base_addr = devm_ioremap_uc(dev, sock->mbinfo.base_addr, sock->mbinfo.size); #else - sock->virt_base_addr = devm_ioremap_nocache(sock->dev, sock->mbinfo.base_addr, + sock->virt_base_addr = devm_ioremap_nocache(dev, sock->mbinfo.base_addr, sock->mbinfo.size); #endif if (!sock->virt_base_addr) { - dev_err(sock->dev, "Failed to ioremap MP1 base address\n"); + dev_err(dev, "Failed to ioremap MP1 base address\n"); return -ENOMEM; } @@ -252,12 +252,12 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) dev_set_drvdata(dev, sock); /* Read MP1 base address from CRS method */ - ret = hsmp_read_acpi_crs(sock); + ret = hsmp_read_acpi_crs(dev, sock); if (ret) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(sock); + return hsmp_read_acpi_dsd(dev, sock); } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, From b89b4080fc52c746e833fbd15ecb09ee11ab68b7 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:25:31 +0530 Subject: [PATCH 07/24] platform/x86/amd/hsmp: Gate the data plane on a fully initialized socket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hsmp_parse_acpi_table() published sock->dev before hsmp_read_acpi_crs() had mapped virt_base_addr. sock->dev is the readiness gate for the lock-free data plane, so on a multi-socket system - where socket 0 exposes /dev/hsmp before later sockets finish probing - an ioctl aimed at a socket still in bring-up could pass the gate and dereference a NULL virt_base_addr. Publish sock->dev last with smp_store_release() once virt_base_addr, the mailbox offsets and the semaphore are initialized, and read it with smp_load_acquire() in hsmp_send_message() so a non-NULL dev guarantees the rest of the socket state is visible. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260629155634.1807598-5-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit e5e511f5d9e05e075d114318e707e191ada6e145) --- acpi.c | 18 ++++++++++++++++-- hsmp.c | 13 +++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/acpi.c b/acpi.c index 25b1e48..877a394 100644 --- a/acpi.c +++ b/acpi.c @@ -244,7 +244,6 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) int ret; sock->sock_ind = sock_ind; - sock->dev = dev; sock->amd_hsmp_rdwr = amd_hsmp_acpi_rdwr; sema_init(&sock->hsmp_sem, 1); @@ -257,7 +256,22 @@ static int hsmp_parse_acpi_table(struct device *dev, u16 sock_ind) return ret; /* Read mailbox offsets from DSD table */ - return hsmp_read_acpi_dsd(dev, sock); + ret = hsmp_read_acpi_dsd(dev, sock); + if (ret) + return ret; + + /* + * Publish sock->dev last. hsmp_send_message() uses it (via + * smp_load_acquire()) as the readiness gate for the lock-free data + * plane, so it must become visible only after virt_base_addr, the + * mailbox offsets and the semaphore are fully initialized. On a + * multi-socket system socket 0 exposes /dev/hsmp before later sockets + * finish probing, so without this an ioctl aimed at a socket still in + * bring-up could pass the gate and dereference a NULL virt_base_addr. + */ + smp_store_release(&sock->dev, dev); + + return 0; } static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj, diff --git a/hsmp.c b/hsmp.c index 4d90051..7dc57cb 100644 --- a/hsmp.c +++ b/hsmp.c @@ -232,6 +232,19 @@ int hsmp_send_message(struct hsmp_message *msg) sock_ind = array_index_nospec(msg->sock_ind, hsmp_pdev.num_sockets); sock = &hsmp_pdev.sock[sock_ind]; + /* + * A slot exists for every possible socket, but it is only usable once + * that socket has actually been probed. Reject messages aimed at a + * socket that was never brought up or is still in bring-up, so we never + * operate on a zero-initialized semaphore or an unmapped mailbox. A + * 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(). + */ + if (!smp_load_acquire(&sock->dev)) + return -ENODEV; + ret = down_interruptible(&sock->hsmp_sem); if (ret < 0) return ret; From b06b54bfd658d70b7d5050851378fff25adf475c Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:27:21 +0530 Subject: [PATCH 08/24] platform/x86/amd/hsmp: Serialize ACPI HSMP probe and remove with an rwsem MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add hsmp_sock_rwsem and export it, then hold it for write across ACPI probe, remove and init_acpi() so concurrent per-socket platform probes cannot race the is_probed handshake or the one-time socket-array allocation. Use lockdep_assert_held_write() in init_acpi() to catch incorrect locking under lockdep. An rw_semaphore is used rather than a plain mutex because an upcoming change adds a read side so data-plane messages run concurrently with each other while probe/remove hold it for write to drain in-flight messages. Introducing it as an rwsem now keeps the lock type stable across that change. Out-of-tree note: upstream takes the rwsem with guard(rwsem_write) from linux/cleanup.h, which only exists from v6.5. This module builds against older kernels too, so probe and remove call down_write()/up_write() explicitly instead. The locking scope is unchanged. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-2-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit 9c40a57258f6cb4109de46467966498e6f90ece6) --- acpi.c | 42 ++++++++++++++++++++++++++++++++++++------ hsmp.c | 13 +++++++++++++ hsmp.h | 7 +++++++ 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/acpi.c b/acpi.c index 877a394..00ba77e 100644 --- a/acpi.c +++ b/acpi.c @@ -27,8 +27,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -558,11 +560,20 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att return len; } +/* + * Bring up one ACPI HSMP socket: parse its ACPI table, run the mailbox + * handshake and register its sysfs/hwmon interfaces. + * + * Called with hsmp_sock_rwsem held for write by hsmp_acpi_probe(), so the + * per-socket bring-up cannot race a concurrent probe or remove. + */ static int init_acpi(struct device *dev) { u16 sock_ind; int ret; + lockdep_assert_held_write(&hsmp_sock_rwsem); + ret = hsmp_get_uid(dev, &sock_ind); if (ret) return ret; @@ -683,37 +694,52 @@ static int hsmp_acpi_probe(struct platform_device *pdev) if (!hsmp_pdev) return -ENOMEM; + /* + * Multiple ACPI socket devices probe in parallel, but the is_probed + * handshake and the one-time socket-array allocation below must run + * exactly once. Serialize the whole bring-up against concurrent + * probe/remove by holding the socket rwsem for write. + */ + down_write(&hsmp_sock_rwsem); + if (!hsmp_pdev->is_probed) { hsmp_pdev->num_sockets = topology_max_packages(); if (!hsmp_pdev->num_sockets) { dev_err(&pdev->dev, "No CPU sockets detected\n"); - return -ENODEV; + ret = -ENODEV; + goto unlock; } hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, sizeof(*hsmp_pdev->sock), GFP_KERNEL); - if (!hsmp_pdev->sock) - return -ENOMEM; + if (!hsmp_pdev->sock) { + ret = -ENOMEM; + goto unlock; + } } ret = init_acpi(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); - return ret; + goto unlock; } if (!hsmp_pdev->is_probed) { ret = hsmp_misc_register(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to register misc device\n"); - return ret; + goto unlock; } hsmp_pdev->is_probed = true; dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); } - return 0; + ret = 0; +unlock: + up_write(&hsmp_sock_rwsem); + + return ret; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 11, 0) @@ -722,6 +748,8 @@ static void hsmp_acpi_remove(struct platform_device *pdev) static int hsmp_acpi_remove(struct platform_device *pdev) #endif { + down_write(&hsmp_sock_rwsem); + /* * We register only one misc_device even on multi-socket system. * So, deregister should happen only once. @@ -730,6 +758,8 @@ static int hsmp_acpi_remove(struct platform_device *pdev) hsmp_misc_deregister(); hsmp_pdev->is_probed = false; } + + up_write(&hsmp_sock_rwsem); #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) return 0; #endif diff --git a/hsmp.c b/hsmp.c index 7dc57cb..0d06bcb 100644 --- a/hsmp.c +++ b/hsmp.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,18 @@ static struct hsmp_plat_device hsmp_pdev; +/* + * Serializes AMD HSMP socket bring-up and teardown: ACPI probe and remove take + * it for write so concurrent per-socket probes cannot race the is_probed + * handshake or the one-time socket-array allocation. + */ +DECLARE_RWSEM(hsmp_sock_rwsem); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, "AMD_HSMP"); +#else +EXPORT_SYMBOL_NS_GPL(hsmp_sock_rwsem, AMD_HSMP); +#endif + /* * Send a message to the HSMP port via PCI-e config space registers * or by writing to MMIO space. diff --git a/hsmp.h b/hsmp.h index 11160a7..fb7a495 100644 --- a/hsmp.h +++ b/hsmp.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -94,4 +95,10 @@ int hsmp_create_sensor(struct device *dev, u16 sock_ind); static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return 0; } #endif int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); + +/* + * Serializes HSMP socket bring-up and teardown. ACPI probe and remove take it + * for write. + */ +extern struct rw_semaphore hsmp_sock_rwsem; #endif /* HSMP_H */ From 259643da0978f4a2021e7b4e4c3c5238e41fd05f Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:28:14 +0530 Subject: [PATCH 09/24] platform/x86/amd/hsmp: Map the metric table with ioremap() and unmap it explicitly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The metric-table DRAM region is mapped with devm_ioremap(), which ties the mapping to the socket device's devres scope. An upcoming change lets the ACPI front-end share the socket array across sockets and run its own coordinated teardown, so the mapping can no longer be pinned to a single per-socket devres scope. Map it with plain ioremap() instead and add hsmp_unmap_metric_tbls(), which drops every socket's metric_tbl_addr mapping. The platform driver registers that helper with devm_add_action_or_reset() so the mappings are released on both remove and probe failure, while the socket array itself stays devm-managed. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-3-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit 5622564fc6924c1607944f0ad22be81ed6fd38fb) --- hsmp.c | 23 +++++++++++++++++++++-- hsmp.h | 1 + plat.c | 17 +++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/hsmp.c b/hsmp.c index 0d06bcb..433c5f9 100644 --- a/hsmp.c +++ b/hsmp.c @@ -18,6 +18,7 @@ #endif #include #include +#include #include #include #include @@ -452,6 +453,25 @@ EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, AMD_HSMP); #endif +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) +{ + struct hsmp_socket *sock; + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) { + sock = &pdev->sock[i]; + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } + } +} +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, "AMD_HSMP"); +#else +EXPORT_SYMBOL_NS_GPL(hsmp_unmap_metric_tbls, AMD_HSMP); +#endif + int hsmp_get_tbl_dram_base(u16 sock_ind) { struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; @@ -476,8 +496,7 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) dev_err(sock->dev, "Invalid DRAM address for metric table\n"); return -ENOMEM; } - sock->metric_tbl_addr = devm_ioremap(sock->dev, dram_addr, - sizeof(struct hsmp_metric_table)); + sock->metric_tbl_addr = ioremap(dram_addr, sizeof(struct hsmp_metric_table)); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); return -ENOMEM; diff --git a/hsmp.h b/hsmp.h index fb7a495..0202dea 100644 --- a/hsmp.h +++ b/hsmp.h @@ -87,6 +87,7 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg); void hsmp_misc_deregister(void); int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); +void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); #if IS_ENABLED(CONFIG_HWMON) diff --git a/plat.c b/plat.c index bda6138..701b822 100644 --- a/plat.c +++ b/plat.c @@ -237,6 +237,19 @@ static int init_platform_device(struct device *dev) return 0; } +/* + * The socket array is devm-managed and freed by the driver core, but the + * metric-table DRAM regions are mapped with plain ioremap() during probe and + * are therefore not covered by devres. + * + * Drop those mappings from a devres action so both remove and probe failure + * unmap them exactly once, before the socket array they refer to is freed. + */ +static void hsmp_pltdrv_release(void *data) +{ + hsmp_unmap_metric_tbls(hsmp_pdev); +} + static int hsmp_pltdrv_probe(struct platform_device *pdev) { int ret; @@ -247,6 +260,10 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (!hsmp_pdev->sock) return -ENOMEM; + ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); + if (ret) + return ret; + ret = init_platform_device(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); From 18bb60c9e28093047dd78613b23cda4ee5138afc Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:29:34 +0530 Subject: [PATCH 10/24] platform/x86/amd/hsmp: Serialize per-socket metric table reads with a mutex MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HSMP_GET_METRIC_TABLE makes the firmware refill a shared per-socket metric DRAM region, which hsmp_metric_tbl_read() then copies out with memcpy_fromio(). Two concurrent readers of the metrics_bin sysfs attribute on the same socket can race: one can trigger a fresh fill while the other is mid-copy and return a torn snapshot. (The hwmon path does not touch this region; it only issues power messages via hsmp_send_message().) Embed a struct mutex metric_read_lock in each hsmp_socket and hold it across the fill-and-copy in hsmp_metric_tbl_read(). Add hsmp_init_metric_read_locks() and hsmp_destroy_metric_read_locks(), which take only struct hsmp_plat_device and iterate pdev->sock[] over pdev->num_sockets so the caller cannot pass a count that disagrees with the array. Wire them into both front-ends' probe and teardown paths so the mutex is always initialized before metrics_bin is exposed: the platform driver and the ACPI driver both drive hsmp_metric_tbl_read() through the same 0444 metrics_bin attribute. Doing this in one patch avoids a bisection point where an ACPI read would lock an uninitialized mutex. Out-of-tree note: upstream holds the mutex with guard(mutex) from linux/cleanup.h, which only exists from v6.5, so this uses explicit mutex_lock()/mutex_unlock() around the same region. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit 8da4fedd5d1147d26e6ddbd7be2f1f5519df37dd) --- acpi.c | 3 +++ hsmp.c | 41 ++++++++++++++++++++++++++++++++++++++++- hsmp.h | 5 +++++ plat.c | 3 +++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/acpi.c b/acpi.c index 00ba77e..da28931 100644 --- a/acpi.c +++ b/acpi.c @@ -717,6 +717,8 @@ static int hsmp_acpi_probe(struct platform_device *pdev) ret = -ENOMEM; goto unlock; } + + hsmp_init_metric_read_locks(hsmp_pdev); } ret = init_acpi(&pdev->dev); @@ -756,6 +758,7 @@ static int hsmp_acpi_remove(struct platform_device *pdev) */ if (hsmp_pdev->is_probed) { hsmp_misc_deregister(); + hsmp_destroy_metric_read_locks(hsmp_pdev); hsmp_pdev->is_probed = false; } diff --git a/hsmp.c b/hsmp.c index 433c5f9..ddf2ca0 100644 --- a/hsmp.c +++ b/hsmp.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -440,11 +441,23 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) msg.msg_id = HSMP_GET_METRIC_TABLE; msg.sock_ind = sock->sock_ind; + /* + * HSMP_GET_METRIC_TABLE makes firmware refill this socket's shared + * metric DRAM region, which is then copied out below. Hold the + * per-socket lock across the fill-and-copy so concurrent readers of the + * same socket cannot return a torn snapshot. + */ + mutex_lock(&sock->metric_read_lock); + ret = hsmp_send_message(&msg); - if (ret) + if (ret) { + mutex_unlock(&sock->metric_read_lock); return ret; + } memcpy_fromio(buf, sock->metric_tbl_addr, size); + mutex_unlock(&sock->metric_read_lock); + return size; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) @@ -453,6 +466,32 @@ EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, AMD_HSMP); #endif +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_init(&pdev->sock[i].metric_read_lock); +} +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, "AMD_HSMP"); +#else +EXPORT_SYMBOL_NS_GPL(hsmp_init_metric_read_locks, AMD_HSMP); +#endif + +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev) +{ + u16 i; + + for (i = 0; i < pdev->num_sockets; i++) + mutex_destroy(&pdev->sock[i].metric_read_lock); +} +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) +EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, "AMD_HSMP"); +#else +EXPORT_SYMBOL_NS_GPL(hsmp_destroy_metric_read_locks, AMD_HSMP); +#endif + void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) { struct hsmp_socket *sock; diff --git a/hsmp.h b/hsmp.h index 0202dea..97ed762 100644 --- a/hsmp.h +++ b/hsmp.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -66,6 +67,8 @@ struct hsmp_socket { void __iomem *metric_tbl_addr; void __iomem *virt_base_addr; struct semaphore hsmp_sem; + /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ + struct mutex metric_read_lock; char name[HSMP_ATTR_GRP_NAME_SIZE]; struct pci_dev *root; struct device *dev; @@ -88,6 +91,8 @@ void hsmp_misc_deregister(void); int hsmp_misc_register(struct device *dev); int hsmp_get_tbl_dram_base(u16 sock_ind); void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev); +void hsmp_init_metric_read_locks(struct hsmp_plat_device *pdev); +void hsmp_destroy_metric_read_locks(struct hsmp_plat_device *pdev); ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size); struct hsmp_plat_device *get_hsmp_pdev(void); #if IS_ENABLED(CONFIG_HWMON) diff --git a/plat.c b/plat.c index 701b822..ca77034 100644 --- a/plat.c +++ b/plat.c @@ -248,6 +248,7 @@ static int init_platform_device(struct device *dev) static void hsmp_pltdrv_release(void *data) { hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); } static int hsmp_pltdrv_probe(struct platform_device *pdev) @@ -260,6 +261,8 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (!hsmp_pdev->sock) return -ENOMEM; + hsmp_init_metric_read_locks(hsmp_pdev); + ret = devm_add_action_or_reset(&pdev->dev, hsmp_pltdrv_release, NULL); if (ret) return ret; From 7d7fc6041b6c3d1bcb3a891b15cf94be02f8b0e7 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:29:56 +0530 Subject: [PATCH 11/24] platform/x86/amd/hsmp: Clear mdev.this_device on deregister MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit misc_deregister() destroys the device but leaves miscdevice.this_device pointing at the freed struct device. Clear it so any later check of this_device, and a subsequent re-register, does not observe a stale pointer. An upcoming change uses this_device to track whether /dev/hsmp is registered across the shared ACPI sockets and relies on it being NULL after deregister. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-5-muralidhara.mk@amd.com Signed-off-by: Ilpo Järvinen (cherry picked from commit 6bd243d5abd91ee7d7fa58a9ed60381da49b4b0a) --- hsmp.c | 1 + 1 file changed, 1 insertion(+) diff --git a/hsmp.c b/hsmp.c index ddf2ca0..f44766a 100644 --- a/hsmp.c +++ b/hsmp.c @@ -595,6 +595,7 @@ EXPORT_SYMBOL_NS_GPL(hsmp_misc_register, AMD_HSMP); void hsmp_misc_deregister(void) { misc_deregister(&hsmp_pdev.mdev); + hsmp_pdev.mdev.this_device = NULL; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) EXPORT_SYMBOL_NS_GPL(hsmp_misc_deregister, "AMD_HSMP"); From f6ee414d22e1e2f68c8cb072db54ef067daa05af Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:32:21 +0530 Subject: [PATCH 12/24] platform/x86/amd/hsmp: ACPI HSMP refcounted sockets and coordinated release MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The ACPI driver binds one platform device per socket but shares a single socket array and a single /dev/hsmp misc device across them. Replace the is_probed flag with state that tracks this shared ownership: - miscdevice.this_device tells whether /dev/hsmp is registered, so the misc device is registered on the first socket and torn down last. A preceding change clears mdev.this_device on deregister so this gate stays reliable across a re-probe. - a kref tracks the sockets that share the array. The first probe initializes it, each further probe takes a reference and every remove (or probe failure) drops one; the last put runs the release callback. All get/put happen under hsmp_sock_rwsem held for write, so the counting is already serialized and kref's atomic is not strictly needed, but kref gives the clearer get/put interface and a release callback. The shared socket array is allocated with kcalloc() on the first probe and freed by the release callback once the last reference is dropped. hsmp_acpi_sock_release() is the single teardown helper, run from kref_put(): it deregisters /dev/hsmp if registered, unmaps any metric-table DRAM, destroys the per-socket mutexes and frees the array. The remove path and the probe-failure path both reach it through the last put, so the teardown lives in one place. Both paths also clear this socket's dev, so a message issued after a non-final unbind (or to a socket that failed to probe on a multi-socket system, whose array stays alive and whose remove() is never called) cannot reach the mailbox that devres is about to unmap. Two lifetime fixes fall out of the array persisting across a non-final unbind: - hsmp_get_tbl_dram_base() iounmap()s any stale metric_tbl_addr before remapping, so a rebind does not leak one mapping per cycle. It runs during (re)probe before the metric sysfs attribute is exposed, so no reader can be using the old mapping. - The ACPI path registers /dev/hsmp unparented by passing NULL to hsmp_misc_register(). Its per-socket devices can be unbound individually and out of order and the misc device outlives all but the last of them, so parenting it to one socket's device would leave a dangling parent. hsmp_misc_register() now takes the parent from its caller, so the platform driver keeps parenting /dev/hsmp to its single device. hsmp_sock_rwsem is held for write across probe and remove, so the release and probe-failure cleanup run with it already held; an upcoming change adds its read side so the same lock also drains the data plane. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-6-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit 9b2895edb2b3ec1fb8a5adadb5305f6459151d38) --- acpi.c | 131 ++++++++++++++++++++++++++++++++++++++++++++++++--------- hsmp.c | 20 +++++++++ hsmp.h | 1 - 3 files changed, 132 insertions(+), 20 deletions(-) diff --git a/acpi.c b/acpi.c index da28931..4b1e35b 100644 --- a/acpi.c +++ b/acpi.c @@ -27,10 +27,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -57,6 +59,17 @@ static struct hsmp_plat_device *hsmp_pdev; +/* + * Tracks the ACPI socket platform devices that share the socket array and the + * /dev/hsmp misc device. The first probe initializes it, each further probe + * takes a reference and every remove (or probe failure) drops one; the last + * put frees the shared state via hsmp_acpi_sock_release(). All get/put run + * under hsmp_sock_rwsem held for write, so the counting is already serialized + * and the atomic in kref is not strictly needed; kref is used for the clearer + * get/put interface and its release callback. + */ +static struct kref hsmp_acpi_sock_kref; + struct hsmp_sys_attr { struct device_attribute dattr; u32 msg_id; @@ -686,6 +699,60 @@ static const struct acpi_device_id amd_hsmp_acpi_ids[] = { }; MODULE_DEVICE_TABLE(acpi, amd_hsmp_acpi_ids); +/* + * kref release: tear down the shared ACPI socket state once the last socket + * drops its reference. Deregister /dev/hsmp if it was registered, unmap any + * metric-table DRAM, destroy the per-socket mutexes and free the socket array. + * + * Runs from kref_put() with hsmp_sock_rwsem held for write, since the remove + * and probe-failure paths both drop their reference under that lock. The write + * lock has drained any in-flight hsmp_send_message(), so unmapping the mailbox + * and freeing the array cannot race the data plane. + */ +static void hsmp_acpi_sock_release(struct kref *kref) +{ + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (!IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) + hsmp_misc_deregister(); + hsmp_unmap_metric_tbls(hsmp_pdev); + hsmp_destroy_metric_read_locks(hsmp_pdev); + kfree(hsmp_pdev->sock); + hsmp_pdev->sock = NULL; + hsmp_pdev->num_sockets = 0; + hsmp_pdev->proto_ver = 0; +} + +/** + * hsmp_acpi_probe_failure_cleanup() - Undo a failed ACPI socket probe. + * @dev: ACPI companion device whose probe failed. + * + * This device already took a reference on entry to hsmp_acpi_probe(), so clear + * its sock->dev and drop that reference; the shared state is released if it was + * the last one. + * + * Clearing sock->dev matters on multi-socket systems: when a non-first socket + * fails, the array stays alive (owned by an already-probed socket) and + * remove() is never called for this device, yet devres unmaps its mailbox once + * probe() returns. Without clearing dev, a later message to this index would + * 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. + * + * Called from hsmp_acpi_probe(), which already holds hsmp_sock_rwsem for write. + */ +static void hsmp_acpi_probe_failure_cleanup(struct device *dev) +{ + struct hsmp_socket *sock = dev_get_drvdata(dev); + + lockdep_assert_held_write(&hsmp_sock_rwsem); + + if (sock) + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); +} + static int hsmp_acpi_probe(struct platform_device *pdev) { int ret; @@ -695,14 +762,15 @@ static int hsmp_acpi_probe(struct platform_device *pdev) return -ENOMEM; /* - * Multiple ACPI socket devices probe in parallel, but the is_probed - * handshake and the one-time socket-array allocation below must run - * exactly once. Serialize the whole bring-up against concurrent - * probe/remove by holding the socket rwsem for write. + * Multiple ACPI socket devices probe in parallel, but the one-time + * socket-array allocation and /dev/hsmp registration below must run + * exactly once. Hold the socket rwsem for write across the whole + * bring-up so it cannot race a concurrent probe or remove, and so the + * probe-failure teardown drains the data plane. */ down_write(&hsmp_sock_rwsem); - if (!hsmp_pdev->is_probed) { + if (!hsmp_pdev->sock) { hsmp_pdev->num_sockets = topology_max_packages(); if (!hsmp_pdev->num_sockets) { dev_err(&pdev->dev, "No CPU sockets detected\n"); @@ -710,31 +778,47 @@ static int hsmp_acpi_probe(struct platform_device *pdev) goto unlock; } - hsmp_pdev->sock = devm_kcalloc(&pdev->dev, hsmp_pdev->num_sockets, - sizeof(*hsmp_pdev->sock), - GFP_KERNEL); + hsmp_pdev->sock = kcalloc(hsmp_pdev->num_sockets, + sizeof(*hsmp_pdev->sock), + GFP_KERNEL); if (!hsmp_pdev->sock) { ret = -ENOMEM; goto unlock; } hsmp_init_metric_read_locks(hsmp_pdev); + kref_init(&hsmp_acpi_sock_kref); + } else { + kref_get(&hsmp_acpi_sock_kref); } + /* + * This socket now holds a reference (kref_init on the first socket, + * kref_get afterwards). Every failure path below drops it via + * hsmp_acpi_probe_failure_cleanup(), and a successful probe hands it to + * hsmp_acpi_remove(). + */ ret = init_acpi(&pdev->dev); if (ret) { dev_err(&pdev->dev, "Failed to initialize HSMP interface.\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); goto unlock; } - if (!hsmp_pdev->is_probed) { - ret = hsmp_misc_register(&pdev->dev); + if (IS_ERR_OR_NULL(hsmp_pdev->mdev.this_device)) { + /* + * Register /dev/hsmp unparented. It is a singleton shared by all + * ACPI sockets and outlives all but the last of them, so + * parenting it to this socket's device would leave a dangling + * parent once that socket is unbound. + */ + ret = hsmp_misc_register(NULL); if (ret) { dev_err(&pdev->dev, "Failed to register misc device\n"); + hsmp_acpi_probe_failure_cleanup(&pdev->dev); goto unlock; } - hsmp_pdev->is_probed = true; - dev_dbg(&pdev->dev, "AMD HSMP ACPI is probed successfully\n"); + dev_dbg(&pdev->dev, "AMD HSMP ACPI misc device registered\n"); } ret = 0; @@ -750,17 +834,26 @@ static void hsmp_acpi_remove(struct platform_device *pdev) static int hsmp_acpi_remove(struct platform_device *pdev) #endif { + struct hsmp_socket *sock = dev_get_drvdata(&pdev->dev); + + /* + * Serialize the kref_put() and any release it triggers against a + * concurrent probe, and drain the data plane 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(). + */ down_write(&hsmp_sock_rwsem); /* - * We register only one misc_device even on multi-socket system. - * So, deregister should happen only once. + * Clear this socket's dev so hsmp_send_message() rejects it before + * devres unmaps the mailbox. On a non-final unbind the socket array + * stays alive, so without this a later message to this index would + * reach an unmapped iomem region. */ - if (hsmp_pdev->is_probed) { - hsmp_misc_deregister(); - hsmp_destroy_metric_read_locks(hsmp_pdev); - hsmp_pdev->is_probed = false; - } + sock->dev = NULL; + + kref_put(&hsmp_acpi_sock_kref, hsmp_acpi_sock_release); up_write(&hsmp_sock_rwsem); #if LINUX_VERSION_CODE < KERNEL_VERSION(6, 11, 0) diff --git a/hsmp.c b/hsmp.c index f44766a..9ba1ef3 100644 --- a/hsmp.c +++ b/hsmp.c @@ -535,6 +535,18 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) dev_err(sock->dev, "Invalid DRAM address for metric table\n"); return -ENOMEM; } + /* + * The ACPI socket array is shared across sockets and outlives a + * per-socket unbind, so metric_tbl_addr may hold a mapping from an + * earlier bind of this socket. Unmap it before remapping so an + * unbind/rebind cycle does not leak a metric-table mapping. This runs + * during probe before the metric sysfs attribute is exposed, so no + * reader can be using it. + */ + if (sock->metric_tbl_addr) { + iounmap(sock->metric_tbl_addr); + sock->metric_tbl_addr = NULL; + } sock->metric_tbl_addr = ioremap(dram_addr, sizeof(struct hsmp_metric_table)); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); @@ -580,6 +592,14 @@ int hsmp_misc_register(struct device *dev) hsmp_pdev.mdev.name = HSMP_CDEV_NAME; hsmp_pdev.mdev.minor = MISC_DYNAMIC_MINOR; hsmp_pdev.mdev.fops = &hsmp_fops; + /* + * The caller chooses the parent. The platform driver has a single + * device whose lifetime matches /dev/hsmp and parents it there. The + * ACPI driver passes NULL: its /dev/hsmp is a singleton shared by + * per-socket devices that can be unbound individually and out of order, + * so parenting it to one would leave it attached to an already-removed + * device. + */ hsmp_pdev.mdev.parent = dev; hsmp_pdev.mdev.nodename = HSMP_DEVNODE_NAME; hsmp_pdev.mdev.mode = 0644; diff --git a/hsmp.h b/hsmp.h index 97ed762..b528aeb 100644 --- a/hsmp.h +++ b/hsmp.h @@ -81,7 +81,6 @@ struct hsmp_plat_device { struct hsmp_socket *sock; u32 proto_ver; u16 num_sockets; - bool is_probed; }; int hsmp_cache_proto_ver(u16 sock_ind); From 3295e0ff658e1b4423697499991c15121b2a9917 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:34:25 +0530 Subject: [PATCH 13/24] platform/x86/amd/hsmp: Serialize the data plane against socket teardown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before this change the HSMP data plane runs without any coordination with driver teardown: open /dev/hsmp fds and hwmon sysfs reads call hsmp_send_message() while probe and remove bring sockets up and down. 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 the read side of hsmp_sock_rwsem to the data plane. Split the message send into hsmp_send_message_locked(), which does the bounds check and MMIO access and asserts the rwsem is held, and hsmp_send_message(), which wraps it in the read side. Probe and remove hold the rwsem for write, so they drain in-flight messages and keep new ones out while they tear a socket down. The probe-time senders run under the probe write lock and so must not take the rwsem again: route hsmp_test(), hsmp_cache_proto_ver() and hsmp_get_tbl_dram_base() through hsmp_send_message_locked() to avoid recursive locking. A single rwsem therefore covers both the data plane and the probe/remove handshake, with no separate probe lock: - acpi.c already holds it for write across probe for the socket-array and misc-registration handshake, so the mailbox handshake now nests under that same lock. - plat.c takes it for write around init_platform_device(). It is not held across devm_add_action_or_reset() so the release action, which also takes it for write, cannot deadlock if that registration fails. Out-of-tree note: upstream uses guard(rwsem_read) and scoped_guard() from linux/cleanup.h, which only exists from v6.5; this uses explicit down_read()/up_read() and down_write()/up_write() over the same regions. Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260723094656.3806028-7-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit c7acedb2db001160b1ec41cdcc759dd664150666) --- hsmp.c | 47 +++++++++++++++++++++++++++++++++++++++-------- hsmp.h | 4 ++-- plat.c | 23 ++++++++++++++++++++--- 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/hsmp.c b/hsmp.c index 9ba1ef3..6861e31 100644 --- a/hsmp.c +++ b/hsmp.c @@ -53,9 +53,12 @@ static struct hsmp_plat_device hsmp_pdev; /* - * Serializes AMD HSMP socket bring-up and teardown: ACPI probe and remove take - * it for write so concurrent per-socket probes cannot race the is_probed - * handshake or the one-time socket-array allocation. + * Gates the AMD HSMP data plane against socket bring-up and teardown. + * + * hsmp_send_message() takes it for read, so open /dev/hsmp fds and hwmon reads + * run concurrently. Probe and remove take it for write: probe brings sockets + * up (running the mailbox handshake via hsmp_send_message_locked()) and remove + * tears them down, both excluding and draining the data plane. */ DECLARE_RWSEM(hsmp_sock_rwsem); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) @@ -223,12 +226,20 @@ static int validate_message(struct hsmp_message *msg) return 0; } -int hsmp_send_message(struct hsmp_message *msg) +/* + * Core message send. The caller must hold hsmp_sock_rwsem: the data plane + * takes it for read so many messages run concurrently, while the probe-time + * senders run under the write lock taken by probe. Holding it here serializes + * every message against socket teardown, which also holds it for write. + */ +static int hsmp_send_message_locked(struct hsmp_message *msg) { struct hsmp_socket *sock; unsigned int sock_ind; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!msg) return -EINVAL; ret = validate_message(msg); @@ -255,7 +266,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; @@ -270,6 +282,25 @@ int hsmp_send_message(struct hsmp_message *msg) return ret; } + +int hsmp_send_message(struct hsmp_message *msg) +{ + int ret; + + /* + * Data-plane entry point: open /dev/hsmp fds and hwmon sysfs reads issue + * messages from here. Take hsmp_sock_rwsem for read so messages run + * concurrently with each other but are drained and kept out while + * probe/remove hold it for write to tear a socket down. + */ + down_read(&hsmp_sock_rwsem); + + ret = hsmp_send_message_locked(msg); + + up_read(&hsmp_sock_rwsem); + + return ret; +} #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) EXPORT_SYMBOL_NS_GPL(hsmp_send_message, "AMD_HSMP"); #else @@ -318,7 +349,7 @@ int hsmp_test(u16 sock_ind, u32 value) msg.args[0] = value; msg.sock_ind = sock_ind; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -522,7 +553,7 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) msg.response_sz = hsmp_msg_desc_table[HSMP_GET_METRIC_TABLE_DRAM_ADDR].response_sz; msg.msg_id = HSMP_GET_METRIC_TABLE_DRAM_ADDR; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) return ret; @@ -569,7 +600,7 @@ int hsmp_cache_proto_ver(u16 sock_ind) msg.sock_ind = sock_ind; msg.response_sz = hsmp_msg_desc_table[HSMP_GET_PROTO_VER].response_sz; - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (!ret) hsmp_pdev.proto_ver = msg.args[0]; diff --git a/hsmp.h b/hsmp.h index b528aeb..78bd9f5 100644 --- a/hsmp.h +++ b/hsmp.h @@ -102,8 +102,8 @@ static inline int hsmp_create_sensor(struct device *dev, u16 sock_ind) { return int hsmp_msg_get_nargs(u16 sock_ind, u32 msg_id, u32 *data, u8 num_args); /* - * Serializes HSMP socket bring-up and teardown. ACPI probe and remove take it - * for write. + * Gates the HSMP data plane: hsmp_send_message() takes it for read; probe and + * remove take it for write to bring sockets up and tear them down. */ extern struct rw_semaphore hsmp_sock_rwsem; #endif /* HSMP_H */ diff --git a/plat.c b/plat.c index ca77034..431d6b8 100644 --- a/plat.c +++ b/plat.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include "hsmp.h" @@ -240,15 +241,21 @@ static int init_platform_device(struct device *dev) /* * The socket array is devm-managed and freed by the driver core, but the * metric-table DRAM regions are mapped with plain ioremap() during probe and - * are therefore not covered by devres. + * the per-socket mutexes need an explicit mutex_destroy(), neither of which + * devres covers. * - * Drop those mappings from a devres action so both remove and probe failure - * unmap them exactly once, before the socket array they refer to is freed. + * Take the data-plane rwsem for write to drain any in-flight + * hsmp_send_message(), unmap the metric tables, destroy the mutexes and drop + * the global socket pointer, all before devres frees the array. Registered as + * a devres action so it runs on both remove and probe failure. */ static void hsmp_pltdrv_release(void *data) { + down_write(&hsmp_sock_rwsem); hsmp_unmap_metric_tbls(hsmp_pdev); hsmp_destroy_metric_read_locks(hsmp_pdev); + hsmp_pdev->sock = NULL; + up_write(&hsmp_sock_rwsem); } static int hsmp_pltdrv_probe(struct platform_device *pdev) @@ -267,7 +274,17 @@ static int hsmp_pltdrv_probe(struct platform_device *pdev) if (ret) return ret; + /* + * init_platform_device() runs the mailbox handshake via the probe-only + * senders, which issue messages through hsmp_send_message_locked() and + * so require hsmp_sock_rwsem held. Hold it for write, matching probe's + * role as a socket bring-up path. The lock is not held across + * devm_add_action_or_reset() above so the release action, which also + * takes it for write, does not deadlock if that registration fails. + */ + down_write(&hsmp_sock_rwsem); ret = init_platform_device(&pdev->dev); + up_write(&hsmp_sock_rwsem); if (ret) { dev_err(&pdev->dev, "Failed to init HSMP mailbox\n"); return ret; From d89c1f0ec3793052ed85fa6ac590a8e77795fd8d Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:36:34 +0530 Subject: [PATCH 14/24] platform/x86/amd/hsmp: Add HSMP messages for Family 1Ah, Model 50h-5Fh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Family 1Ah Model 50h-5Fh firmware exposes new HSMP messages (0x29-0x2A, 0x33-0x3A) for PC6/CC6 control, CCD power/thermal monitoring, DIMM sideband access, floor- and SDPS-limit control, and command-enable discovery. The same firmware extends three existing SET-only messages (HSMP_SET_XGMI_LINK_WIDTH 0x0C, HSMP_SET_DF_PSTATE 0x0D, HSMP_SET_PSTATE_MAX_MIN 0x22) with a read-back path selected by bit[31] of args[0] (0 = set, 1 = get). Add the new IDs and convert the three messages to HSMP_SET_GET. Also add PQoS-related HSMP messages HSMP_PQOS_TRAFFIC_PRIORITY (0x3B) and HSMP_PQOS_FLOATING_BW (0x3C) with matching hsmp_msg_desc_table[] descriptors so userspace can reach the new functionality. Backward compatibility is preserved on prior platforms: new IDs previously occupied HSMP_RSVD slots, and existing userspace that leaves bit[31] = 0 continues to take a pure SET path. Converting the three SET messages to HSMP_SET_GET also keeps them accepted by validate_message(), which already applies a relaxed upper-bound check on response_sz for that type. The message IDs and descriptors already existed out-of-tree; this replaces them with the upstreamed wording and descriptors so the two trees no longer drift. That also picks up upstream commit 0d5e2d9b8fcb ("platform/x86/amd/hsmp: mark hsmp_msg_desc_table[] as maybe_unused"), which this tree had missed. Two out-of-tree deltas are kept on top: - HSMP_SET_XGMI_PSTATE_RANGE (0x26) stays {1, 1, HSMP_SET_GET}. Family 1Ah Model 50h-5Fh firmware supports the bit[31] read-back for it, but that conversion has not been upstreamed yet; downgrading it to HSMP_SET here would make an existing GET fail with -EINVAL once the response_sz upper-bound check lands. - the hsmp_send_message() prototype, which this build needs since it has no in-kernel asm/amd/hsmp.h to pull it from. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-2-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit d30569cb5aadcc623accf0422132d0bc0286a9ff) --- amd_hsmp.h | 215 +++++++++++++++++++++++++++-------------------------- 1 file changed, 110 insertions(+), 105 deletions(-) diff --git a/amd_hsmp.h b/amd_hsmp.h index f198fed..718bf66 100644 --- a/amd_hsmp.h +++ b/amd_hsmp.h @@ -53,21 +53,21 @@ enum hsmp_message_ids { HSMP_SET_XGMI_PSTATE_RANGE, /* 26h Set xGMI P-state range */ HSMP_CPU_RAIL_ISO_FREQ_POLICY, /* 27h Get/Set Cpu Iso frequency policy */ HSMP_DFC_ENABLE_CTRL, /* 28h Enable/Disable DF C-state */ - HSMP_PC6_ENABLE, /* 29h Get/Set PC6 Enable/Disable Status */ - HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 Enable/Disable Status */ + HSMP_PC6_ENABLE, /* 29h Get/Set PC6 enable/disable status */ + HSMP_CC6_ENABLE, /* 2Ah Get/Set CC6 enable/disable status */ HSMP_GET_RAPL_UNITS = 0x30, /* 30h Get scaling factor for energy */ HSMP_GET_RAPL_CORE_COUNTER, /* 31h Get core energy counter value */ HSMP_GET_RAPL_PACKAGE_COUNTER, /* 32h Get package energy counter value */ - HSMP_DIMM_SB_RD, /* 33h Get data from a specified device on the DIMM.*/ - HSMP_READ_CCD_POWER, /* 34h Get the average power consumed by CCD */ - HSMP_READ_TDELTA, /* 35h Get thermal solution behaviour */ - HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get temperature of SVI3 VR controlller rails */ - HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get/Set supported HSMP commands */ - HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set supported Floor Limit commands */ - HSMP_DIMM_SB_WR, /* 39h Set data to a specified device on the DIMM.*/ - HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPSLimit. */ + HSMP_DIMM_SB_RD, /* 33h Get DIMM sideband data */ + HSMP_READ_CCD_POWER, /* 34h Get average CCD power */ + HSMP_READ_TDELTA, /* 35h Get thermal behaviour */ + HSMP_GET_SVI3_VR_CTRL_TEMP, /* 36h Get SVI3 VR controller rail temp */ + HSMP_GET_ENABLED_HSMP_CMDS, /* 37h Get supported HSMP commands */ + HSMP_SET_GET_FLOOR_LIMIT, /* 38h Get/Set core floor frequency limit */ + HSMP_DIMM_SB_WR, /* 39h Set DIMM sideband data */ + HSMP_SDPS_LIMIT, /* 3Ah Get/Set SDPS limit */ HSMP_PQOS_TRAFFIC_PRIORITY, /* 3Bh Get/Set traffic priority */ - HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set floating bandwidth */ + HSMP_PQOS_FLOATING_BW, /* 3Ch Get/Set max floating bandwidth */ HSMP_MSG_ID_MAX, }; @@ -80,10 +80,10 @@ struct hsmp_message { }; enum hsmp_msg_type { - HSMP_RSVD = -1, - HSMP_SET = 0, - HSMP_GET = 1, - HSMP_SET_GET = 2, + HSMP_RSVD = -1, + HSMP_SET = 0, + HSMP_GET = 1, + HSMP_SET_GET = 2, }; enum hsmp_proto_versions { @@ -108,7 +108,8 @@ struct hsmp_msg_desc { * * Not supported messages would return -ENOMSG. */ -static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { +static const struct hsmp_msg_desc hsmp_msg_desc_table[] + __attribute__((unused)) = { /* RESERVED */ {0, 0, HSMP_RSVD}, @@ -182,15 +183,24 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { /* * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0/1 - * input: args[0] = set/get XGMI Link width[31] + min link width[15:8] + max link width[7:0] - * output: args[0] = current min link width[15:8] + current max link width[7:0] + * input: args[0] = set/get XGMI Link width[31] (0 = set, 1 = get) + + * min link width[15:8] + max link width[7:0] + * Link width encoding: 0 = x4, 1 = x8, 2 = x16. + * On SET, max must be >= min. On GET, [15:0] are reserved. + * output: args[0] = reserved[31:16] + min link width[15:8] + + * max link width[7:0] */ {1, 1, HSMP_SET_GET}, /* - * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0/1 - * input: args[0] = set/get df pstate[31] + df pstate[7:0] - * output: args[0] = APB Enabled/Disabled[8]+current df pstate[7:0] + * HSMP_SET_DF_PSTATE (APBDisable), num_args = 1, response_sz = 0/1 + * input: args[0] = set APB_DISABLE / get APB state[31] + * (0 = set & lock DF P-state, 1 = get) + + * reserved[30:8] + + * DF P-state[7:0] (0..2; reserved on GET) + * output: args[0] = reserved[31:9] + + * APB state[8] (1 = disabled, 0 = enabled) + + * locked DF P-state[7:0] if [8] = 1, else reserved */ {1, 1, HSMP_SET_GET}, @@ -260,7 +270,7 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { /* * HSMP_GET_DIMM_THERMAL, num_args = 1, response_sz = 1 * input: args[0] = DIMM address[7:0] - * output: args[0] = temperature in degree celcius[31:21] + update rate in ms[16:8] + + * output: args[0] = temperature in degree celsius[31:21] + update rate in ms[16:8] + * DIMM address[7:0] */ {1, 1, HSMP_GET}, @@ -273,7 +283,7 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { /* * HSMP_GET_CCLK_CORE_LIMIT, num_args = 1, response_sz = 1 - * input: args[0] = apic id of the core[31:0] + * input: args[0] = apic id [31:0] * output: args[0] = frequency in MHz[31:0] */ {1, 1, HSMP_GET}, @@ -318,16 +328,30 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { {1, 1, HSMP_SET}, /* - * HSMP_SET_POWER_MODE, num_args = 1, response_sz = 0/1 - * input: args[0] = set/get power mode[31] + power efficiency mode[2:0] - * output: args[0] = current power efficiency mode[2:0] + * HSMP_SET_POWER_MODE (PwrEfficiencyModeSelection), + * num_args = 1, response_sz = 1 + * input: args[0] = set/get policy[31] (0 = set, 1 = get) + + * high util point[30:24] + + * low util point[23:17] + + * PPT limit[16:5] + + * reserved[4:3] + mode selection[2:0] + * [30:5] are valid only when [2:0] is a balanced core mode + * (4 or 5). [2:0] is reserved when getting (bit[31] = 1). + * output: args[0] same layout, [31] reserved, [2:0] = arbitrated + * current efficiency mode. */ {1, 1, HSMP_SET_GET}, /* - * HSMP_SET_PSTATE_MAX_MIN, num_args = 1, response_sz = 0/1 - * input: args[0] = set/get power mode[31] + min df pstate[15:8] + max df pstate[7:0] - * output: args[0] = min df pstate[15:8] + max df pstate[7:0] + * HSMP_SET_PSTATE_MAX_MIN (DfPstateRange), num_args = 1, response_sz = 0/1 + * input: args[0] = set/get DF P-state range[31] (0 = set, 1 = get) + + * reserved[30:16] + + * min DF P-state[15:8] + max DF P-state[7:0] + * DF P-state encoding: 0 = DFP0 (high performance), + * 1 = DFP1, 2 = DFP2 (low performance). + * [15:0] are reserved when getting (args[0] bit[31] = 1). + * output: args[0] = reserved[31:16] + min DF P-state[15:8] + + * max DF P-state[7:0] */ {1, 1, HSMP_SET_GET}, @@ -351,8 +375,9 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { /* * HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0/1 - * input: args[0] = set/get XGMI pstate range[31] + min xGMI p-state[15:8] + max xGMI state[7:0] - * output: args[0] = min xGMI p-state[15:8] + max xGMI state[7:0] + * input: args[0] = set/get xGMI p-state range[31] + + * min xGMI p-state[15:8] + max xGMI p-state[7:0] + * output: args[0] = min xGMI p-state[15:8] + max xGMI p-state[7:0] */ {1, 1, HSMP_SET_GET}, @@ -372,16 +397,26 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { {1, 1, HSMP_SET_GET}, /* - * HSMP_PC6_REQUEST, num_args = 1, response_sz = 0/1 - * input: args[0] = set/get PC6 control[31] + disable/enable PC6[0] - * output: args[0] = current PC6 control status[0] + * HSMP_PC6_ENABLE (Pc6Enable), num_args = 1, response_sz = 0/1 + * input: args[0] = set/get PC6 control[31] (0 = set, 1 = get) + + * reserved[30:1] + + * enable PC6[0] (0 = disable, 1 = enable; + * reserved on GET) + * output: args[0] = reserved[31:1] + current PC6 control[0] + * (last value configured via HSMP or APML) */ {1, 1, HSMP_SET_GET}, /* - * HSMP_CC6_REQUEST, num_args = 1, response_sz = 0/1 - * input: args[0] = set/get CC6 control[31] + disable/enable CC6[0] - * output: args[0] = current CC6 control status[0] + * HSMP_CC6_ENABLE (CC6Enable), num_args = 1, response_sz = 0/1 + * Configures CC6 enable for all cores; changing the setting does + * not by itself transition cores in or out of CC6. + * input: args[0] = set/get CC6 control[31] (0 = set, 1 = get) + + * reserved[30:1] + + * enable CC6[0] (0 = disable, 1 = enable; + * reserved on GET) + * output: args[0] = reserved[31:1] + current CC6 control[0] + * (last value configured via HSMP or APML) */ {1, 1, HSMP_SET_GET}, @@ -400,7 +435,7 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { /* * HSMP_GET_RAPL_CORE_COUNTER, num_args = 1, response_sz = 1 - * input: args[0] = Apic id[15:0] + * input: args[0] = apic id[15:0] * output: args[0] = lower 32 bits of energy * output: args[1] = upper 32 bits of energy */ @@ -415,115 +450,85 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { /* * HSMP_DIMM_SB_RD, num_args = 1, response_sz = 1 - * input: args[0] = - * [07:00] DIMM address - * [11:08] LID of device - * [22:12] Register offset in given reg space - * [23] Register space - * output: args[0] = [03:00] Read data byte + * input: args[0] = reg space[23] + reg offset[22:12] + + * device LID[11:8] + DIMM address[7:0] + * output: args[0] = read data byte[3:0] */ {1, 1, HSMP_GET}, /* * HSMP_READ_CCD_POWER, num_args = 1, response_sz = 1 - * input: args[0] = [15:00] ApicId of core - * output: args[0] = [31:00] CCD power(mWatts) + * input: args[0] = apic id of core[15:0] + * output: args[0] = CCD power(mWatts)[31:0] */ {1, 1, HSMP_GET}, /* * HSMP_READ_TDELTA, num_args = 0, response_sz = 1 - * input: None - * output: args[0] = [31:00] Thermal Behaviour + * output: args[0] = thermal behaviour[31:0] */ {0, 1, HSMP_GET}, /* * HSMP_GET_SVI3_VR_CTRL_TEMP, num_args = 1, response_sz = 1 - * input: args[0] = - * [00] Read SVI3 temperature data - * [03:01] SVI3 rail index - * output: args[0] = - * [30:28] SVI3 rail index - * [27:00] SVI3 rail temperature(degree C) + * input: args[0] = SVI3 rail index[3:1] + read temperature[0] + * output: args[0] = SVI3 rail index[30:28] + + * rail temperature in degree C[27:0] */ {1, 1, HSMP_GET}, /* * HSMP_GET_ENABLED_HSMP_CMDS, num_args = 1, response_sz = 3 - * input: args[0] = [00] HSMP command mask - * output: args[0], args[1], args[2] = status of HSMP command + * input: args[0] = HSMP command mask[0] + * output: status of HSMP command = args[0], args[1], args[2] */ {1, 3, HSMP_GET}, /* * HSMP_SET_GET_FLOOR_LIMIT, num_args = 1, response_sz = 1 - * input: args[0] = - * [31:30]=Set or Get: - * 00=Set the Floor frequency per core. - * 01=Set the Floor frequency for all cores. - * 10=Get the Floor frequency of a core. - * 11=Get the Effective Floor frequency per core. - * [29:28]=Reserved. - * [27:16]=ApicId. - * Note: DataIn[27:16] are Reserved if DataIn[31:30]==01. - * - * If DataIn[31]=0 - * [15:0]=Floor frequency limit. - * Else - * [15:0]=Reserved. - * - * output: args[0] = - * If DataIn[31:30]=11 - * [15:0]=Effective Floor frequency limit(MHz). - * Else - * [15:0]=Floor frequency limit (MHz). - * The output will be None if DataIn[31]=0. + * input: args[0] = op[31:30] + reserved[29:28] + + * apic id[27:16] + floor frequency MHz[15:0] + * op encoding: 00 = set per-core floor, + * 01 = set all-cores floor (apic id reserved), + * 10 = get per-core floor, + * 11 = get per-core effective floor. + * Floor frequency field is reserved on GET (bit[31] = 1). + * output: args[0] = floor frequency MHz[15:0] + * (effective for op 11, configured for op 10; + * reserved on SET) */ {1, 1, HSMP_SET_GET}, /* * HSMP_DIMM_SB_WR, num_args = 1, response_sz = 0 - * input: args[0] = - * [07:00] DIMM address - * [11:08] LID of device - * [22:12] Register offset in given reg space - * [23] Register space - * [31:24] Write Data - * output: None + * input: args[0] = write data[31:24] + reg space[23] + + * reg offset[22:12] + device LID[11:8] + + * DIMM address[7:0] */ - {1, 0, HSMP_SET}, + {1, 0, HSMP_SET}, /* * HSMP_SDPS_LIMIT, num_args = 1, response_sz = 1 - * input: args[0] = - * [30:00] SDPS Limit - * [31] Set/Get - * output: args[0] = - * [30:00] SDPS Limit + * input: args[0] = set/get SDPS limit[31] (0 = set, 1 = get) + + * SDPS limit[30:0] + * output: args[0] = SDPS limit[30:0] */ - {1, 1, HSMP_SET_GET}, + {1, 1, HSMP_SET_GET}, - /* + /* * HSMP_PQOS_TRAFFIC_PRIORITY, num_args = 1, response_sz = 1 - * input: args[0] = - * [31:30] Operation - * [27:26] Priority selector - * [21:20] Priority value - * [19:0] Input - * output: args[0] = Supported priorities or Priority val[1:0] + * input: args[0] = op[31:30] + priority sel[27:26] + + * priority val[21:20] + input[19:0] + * output: args[0] = supported priorities or priority val[1:0] */ {1, 1, HSMP_SET_GET}, /* * HSMP_PQOS_FLOATING_BW, num_args = 1, response_sz = 2 - * input: args[0] = - * [31] Operation - * [30:29] Sub-operation - * [28:0] Parameter - * output: args[0] = Discovery bits or Floating/Global memory BW (Gbps) - * output: args[1] = Reserved or - * config (drop adjustment, sampling delay, hysteresis) + * input: args[0] = op[31] + sub-op[30:29] + params[28:0] + * output: args[0] = discovery bits or floating/global memory BW (Gbps) + * output: args[1] = reserved or config (drop adj, sampling delay, + * hysteresis) */ {1, 2, HSMP_SET_GET}, }; From 6714f51b9f7a6522e6b6c660bf7a5a9eabfccd3d Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:37:02 +0530 Subject: [PATCH 15/24] platform/x86/amd/hsmp: Unify response_sz validation to an upper-bound check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As HSMP protocol versions evolve, existing message IDs sometimes gain additional response words on newer firmware. validate_message() currently enforces a strict equality (response_sz == table value) for HSMP_SET and HSMP_GET, so userspace compiled against an earlier descriptor table is rejected with -EINVAL when it asks for fewer response words than the in-kernel table now declares - even though that caller has no interest in the additional words. Only HSMP_SET_GET already used a relaxed upper-bound check. Replace the per-type branching with a single upper-bound check for all message types. Userspace can now request fewer response words than hardware provides, while requests that exceed the descriptor table (and therefore the hardware capability) are still rejected. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-3-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit 9185ad51dfd2bc8bde0c7e7d9f4493d8758d4fab) --- hsmp.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/hsmp.c b/hsmp.c index 6861e31..8cefa4a 100644 --- a/hsmp.c +++ b/hsmp.c @@ -209,20 +209,16 @@ static int validate_message(struct hsmp_message *msg) return -EINVAL; /* - * Some older HSMP SET messages are updated to add GET in the same message. - * In these messages, GET returns the current value and SET also returns - * the successfully set value. To support this GET and SET in same message - * while maintaining backward compatibility for the HSMP users, - * hsmp_msg_desc_table[] indicates only maximum allowed response_sz. + * As the HSMP protocol evolves, newer platforms may define more + * response arguments for existing messages. Use an upper-bound + * check so that older userspace callers requesting fewer response + * words than what the current hsmp_msg_desc_table[] defines are + * still accepted, while rejecting requests that exceed the + * hardware capability. */ - if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_SET_GET) { - if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } else { - /* only HSMP_SET or HSMP_GET messages go through this strict check */ - if (msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) - return -EINVAL; - } + if (msg->response_sz > hsmp_msg_desc_table[msg->msg_id].response_sz) + return -EINVAL; + return 0; } From cc810da7d772c9905643143ac7b713285c8bdbe3 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:38:14 +0530 Subject: [PATCH 16/24] platform/x86/amd/hsmp: Source metric-table size from firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The driver hard-codes the metric-table region size to sizeof(struct hsmp_metric_table). That is correct for HSMP protocol version 6 but mis-sizes the ioremap of the SMU DRAM region on newer platforms: Family 1Ah Model 50h-5Fh exposes a ~13 KB table under protocol version 7, and the table is expected to keep growing on future firmware. The same hard-coded value also forces hsmp_metric_tbl_read() to reject any read that follows the actual firmware layout. Pick up the table size from firmware instead. SMU on Family 1Ah Model 50h and later populates HSMP_GET_METRIC_TABLE_DRAM_ADDR's args[2] with the DRAM region size in bytes; older firmware leaves it 0. Bump the descriptor's response_sz to 3 so the field is read, and store the result in the new per-socket hsmp_socket.metric_tbl_size, which is then used both for the ioremap() of the region and as the expected size in hsmp_metric_tbl_read(). The size is stored per socket rather than per platform because hsmp_get_tbl_dram_base() runs once per socket and each socket maps its own region. A single platform-wide field would let the last socket's size be used to copy out of an earlier socket's smaller mapping. Behaviour on existing protocol-version-6 hardware is unchanged. Reading a third response word is safe there: for this command SMU leaves args[2] as 0 rather than a stale value from an earlier mailbox transaction, so the fallback always applies, yielding the same value as the previous hard-coded one, and both the ioremap and the size check produce the same result as before. Out-of-tree note: upstream bumps DRIVER_VERSION 2.5 -> 2.6; this tree carries its own version, bumped 3.0 -> 3.1 here. Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K Link: https://patch.msgid.link/20260727141542.3370108-4-muralidhara.mk@amd.com Reviewed-by: Ilpo Järvinen Signed-off-by: Ilpo Järvinen (cherry picked from commit 96f1ba765ab55d57ee2a2bf88e05c2ee699c7c5b) --- amd_hsmp.h | 5 +++-- hsmp.c | 17 ++++++++++++++--- hsmp.h | 5 ++++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/amd_hsmp.h b/amd_hsmp.h index 718bf66..e94b801 100644 --- a/amd_hsmp.h +++ b/amd_hsmp.h @@ -367,11 +367,12 @@ static const struct hsmp_msg_desc hsmp_msg_desc_table[] {0, 0, HSMP_GET}, /* - * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 2 + * HSMP_GET_METRIC_TABLE_DRAM_ADDR, num_args = 0, response_sz = 3 * output: args[0] = lower 32 bits of the address * output: args[1] = upper 32 bits of the address + * output: args[2] = DRAM region size in bytes */ - {0, 2, HSMP_GET}, + {0, 3, HSMP_GET}, /* * HSMP_SET_XGMI_PSTATE_RANGE, num_args = 1, response_sz = 0/1 diff --git a/hsmp.c b/hsmp.c index 8cefa4a..4aae2e6 100644 --- a/hsmp.c +++ b/hsmp.c @@ -459,8 +459,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) return -ENOMEM; } - /* Do not support lseek(), also don't allow more than the size of metric table */ - if (size != sizeof(struct hsmp_metric_table)) { + if (size != sock->metric_tbl_size) { dev_err(sock->dev, "Wrong buffer size\n"); return -EINVAL; } @@ -530,6 +529,7 @@ void hsmp_unmap_metric_tbls(struct hsmp_plat_device *pdev) iounmap(sock->metric_tbl_addr); sock->metric_tbl_addr = NULL; } + sock->metric_tbl_size = 0; } } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) @@ -543,6 +543,7 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) struct hsmp_socket *sock = &hsmp_pdev.sock[sock_ind]; struct hsmp_message msg = { 0 }; phys_addr_t dram_addr; + size_t tbl_size; int ret; msg.sock_ind = sock_ind; @@ -574,11 +575,21 @@ int hsmp_get_tbl_dram_base(u16 sock_ind) iounmap(sock->metric_tbl_addr); sock->metric_tbl_addr = NULL; } - sock->metric_tbl_addr = ioremap(dram_addr, sizeof(struct hsmp_metric_table)); + sock->metric_tbl_size = 0; + + /* SMU returns table size from Family 1Ah Model 50h and forward */ + if (msg.args[2]) + tbl_size = msg.args[2]; + else + tbl_size = sizeof(struct hsmp_metric_table); + + sock->metric_tbl_addr = ioremap(dram_addr, tbl_size); if (!sock->metric_tbl_addr) { dev_err(sock->dev, "Failed to ioremap metric table addr\n"); return -ENOMEM; } + sock->metric_tbl_size = tbl_size; + return 0; } #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) diff --git a/hsmp.h b/hsmp.h index 78bd9f5..96f5952 100644 --- a/hsmp.h +++ b/hsmp.h @@ -20,6 +20,7 @@ #include #include #include +#include /* * Helper macros to handle API changes across kernel versions: @@ -51,7 +52,7 @@ #define HSMP_DEVNODE_NAME "hsmp" #define ACPI_HSMP_DEVICE_HID "AMDI0097" -#define DRIVER_VERSION "3.0" +#define DRIVER_VERSION "3.1" struct hsmp_mbaddr_info { u32 base_addr; @@ -65,6 +66,8 @@ struct hsmp_socket { struct bin_attribute hsmp_attr; struct hsmp_mbaddr_info mbinfo; void __iomem *metric_tbl_addr; + /* Size of the region mapped at @metric_tbl_addr, as reported by SMU */ + size_t metric_tbl_size; void __iomem *virt_base_addr; struct semaphore hsmp_sem; /* Serializes HSMP_GET_METRIC_TABLE fill-and-copy for this socket */ From acb27cd7135f23d8a91185758a8438742f4806ff Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:40:59 +0530 Subject: [PATCH 17/24] amd_hsmp: Refresh amd_hsmp.rst from upstream documentation The bundled documentation had drifted a long way behind Documentation/arch/x86/amd_hsmp.rst: it still described struct hsmp_message with a response[] member that no longer exists, listed only Fam19h as supported, pointed at dead PPR/E-SMI links, and was missing everything added upstream since - the metrics_bin binary sysfs file, the per-socket HSMP telemetry sysfs attributes, the expected ACPI device object format and the hwmon power interface. Replace it with the upstream text so the shipped documentation matches the driver in this tree. The following patch adds the telemetry ioctl description on top. Signed-off-by: Muralidhara M K --- amd_hsmp.rst | 147 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 123 insertions(+), 24 deletions(-) diff --git a/amd_hsmp.rst b/amd_hsmp.rst index b77b888..8bb411f 100644 --- a/amd_hsmp.rst +++ b/amd_hsmp.rst @@ -4,23 +4,38 @@ AMD HSMP interface ============================================ -Newer Fam19h EPYC server line of processors from AMD support system -management functionality via HSMP (Host System Management Port). +Newer Fam19h(model 0x00-0x1f, 0x30-0x3f, 0x90-0x9f, 0xa0-0xaf), +Fam1Ah(model 0x00-0x1f) EPYC server line of processors from AMD support +system management functionality via HSMP (Host System Management Port). The Host System Management Port (HSMP) is an interface to provide OS-level software with access to system management functions via a set of mailbox registers. More details on the interface can be found in chapter -"7 Host System Management Port (HSMP)" of the following PPR -https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip +"7 Host System Management Port (HSMP)" of the family/model PPR +Eg: https://docs.amd.com/v/u/en-US/55898_B1_pub_0_50 + + +HSMP interface is supported on EPYC line of server CPUs and MI300A (APU). HSMP device ============================================ -amd_hsmp driver under the drivers/platforms/x86/ creates miscdevice -/dev/hsmp to let user space programs run hsmp mailbox commands. +amd_hsmp driver under drivers/platforms/x86/amd/hsmp/ has separate driver files +for ACPI object based probing, platform device based probing and for the common +code for these two drivers. + +Kconfig option CONFIG_AMD_HSMP_PLAT compiles plat.c and creates amd_hsmp.ko. +Kconfig option CONFIG_AMD_HSMP_ACPI compiles acpi.c and creates hsmp_acpi.ko. +Selecting any of these two configs automatically selects CONFIG_AMD_HSMP. This +compiles common code hsmp.c and creates hsmp_common.ko module. + +Both the ACPI and plat drivers create the miscdevice /dev/hsmp to let +user space programs run hsmp mailbox commands. + +The ACPI object format supported by the driver is defined below. $ ls -al /dev/hsmp crw-r--r-- 1 root root 10, 123 Jan 21 21:41 /dev/hsmp @@ -38,15 +53,99 @@ In-kernel integration: function hsmp_send_message(). * Locking across callers is taken care by the driver. -Features support by the interface include monitor and/or control of -a. boostlimit -b. current power, power limit, max power limit -c. c0 residency -d. prochot status -e. clocks (fclk, mclk and cclk) -f. ddr bandwidth, utilization -g. data fabric P-state +HSMP sysfs interface +==================== + +1. Metrics table binary sysfs + +AMD MI300A MCM provides GET_METRICS_TABLE message to retrieve +most of the system management information from SMU in one go. + +The metrics table is made available as hexadecimal sysfs binary file +under per socket sysfs directory created at +/sys/devices/platform/amd_hsmp/socket%d/metrics_bin + +Note: lseek() is not supported as entire metrics table is read. + +Metrics table definitions will be documented as part of Public PPR. +The same is defined in the amd_hsmp.h header. + +2. HSMP telemetry sysfs files + +Following sysfs files are available at /sys/devices/platform/AMDI0097:0X/. + +* c0_residency_input: Percentage of cores in C0 state. +* prochot_status: Reports 1 if the processor is at thermal threshold value, + 0 otherwise. +* smu_fw_version: SMU firmware version. +* protocol_version: HSMP interface version. +* ddr_max_bw: Theoretical maximum DDR bandwidth in GB/s. +* ddr_utilised_bw_input: Current utilized DDR bandwidth in GB/s. +* ddr_utilised_bw_perc_input(%): Percentage of current utilized DDR bandwidth. +* mclk_input: Memory clock in MHz. +* fclk_input: Fabric clock in MHz. +* clk_fmax: Maximum frequency of socket in MHz. +* clk_fmin: Minimum frequency of socket in MHz. +* cclk_freq_limit_input: Core clock frequency limit per socket in MHz. +* pwr_current_active_freq_limit: Current active frequency limit of socket + in MHz. +* pwr_current_active_freq_limit_source: Source of current active frequency + limit. + +ACPI device object format +========================= +The ACPI object format expected from the amd_hsmp driver +for socket with ID00 is given below:: + + Device(HSMP) + { + Name(_HID, "AMDI0097") + Name(_UID, "ID00") + Name(HSE0, 0x00000001) + Name(RBF0, ResourceTemplate() + { + Memory32Fixed(ReadWrite, 0xxxxxxx, 0x00100000) + }) + Method(_CRS, 0, NotSerialized) + { + Return(RBF0) + } + Method(_STA, 0, NotSerialized) + { + If(LEqual(HSE0, One)) + { + Return(0x0F) + } + Else + { + Return(Zero) + } + } + Name(_DSD, Package(2) + { + Buffer(0x10) + { + 0x9D, 0x61, 0x4D, 0xB7, 0x07, 0x57, 0xBD, 0x48, + 0xA6, 0x9F, 0x4E, 0xA2, 0x87, 0x1F, 0xC2, 0xF6 + }, + Package(3) + { + Package(2) {"MsgIdOffset", 0x00010934}, + Package(2) {"MsgRspOffset", 0x00010980}, + Package(2) {"MsgArgOffset", 0x000109E0} + } + }) + } + +HSMP HWMON interface +==================== +HSMP power sensors are registered with the hwmon interface. A separate hwmon +directory is created for each socket and the following files are generated +within the hwmon directory. +- power1_input (read only) +- power1_cap_max (read only) +- power1_cap (read, write) An example ========== @@ -55,6 +154,7 @@ To access hsmp device from a C program. First, you need to include the headers:: #include + Which defines the supported messages/message IDs. Next thing, open the device file, as follows:: @@ -73,21 +173,20 @@ The following IOCTL is defined: The argument is a pointer to a:: struct hsmp_message { - __u32 msg_id; /* Message ID */ - __u16 num_args; /* Number of arguments in message */ - __u16 response_sz; /* Number of expected response words */ - __u32 args[HSMP_MAX_MSG_LEN]; /* Argument(s) */ - __u32 response[HSMP_MAX_MSG_LEN]; /* Response word(s) */ - __u16 sock_ind; /* socket number */ + __u32 msg_id; /* Message ID */ + __u16 num_args; /* Number of input argument words in message */ + __u16 response_sz; /* Number of expected output/response words */ + __u32 args[HSMP_MAX_MSG_LEN]; /* argument/response buffer */ + __u16 sock_ind; /* socket number */ }; The ioctl would return a non-zero on failure; you can read errno to see what happened. The transaction returns 0 on success. -More details on the interface can be found in chapter -"7 Host System Management Port (HSMP)" of the following PPR -https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip +More details on the interface and message definitions can be found in chapter +"7 Host System Management Port (HSMP)" of the respective family/model PPR +eg: https://docs.amd.com/v/u/en-US/55898_B1_pub_0_50 User space C-APIs are made available by linking against the esmi library, -which is provided by the E-SMS project https://developer.amd.com/e-sms/. +which is provided by the E-SMS project https://www.amd.com/en/developer/e-sms.html. See: https://github.com/amd/esmi_ib_library From faf57b58b9ed695b80a540a14b5798f83df2ef89 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:42:39 +0530 Subject: [PATCH 18/24] platform/x86/amd/hsmp: Add IOCTL_GET_TELEMETRY_DATA for metric table reads The metric table needs to be delivered to userspace as a single atomic snapshot, but the current sysfs metrics_bin path is a file read: userspace can read it in chunks and observe a torn snapshot if an SMU refresh happens between read() calls. The same path is also bounded by PAGE_SIZE, so the ~13 KB table used by HSMP protocol version 7 on Family 1Ah Model 50h-5Fh cannot be returned at all, regardless of how userspace reads it. Rather than extend sysfs to lift both restrictions, expose the metric table through the existing HSMP character device using a new ioctl that always copies the table in one shot. Add struct hsmp_telemetry_data and HSMP_IOCTL_GET_TELEMETRY_DATA to the UAPI header. Under the surrounding #pragma pack(4), placing the __u64 user pointer first gives a tight 16-byte layout that is identical for 32- and 64-bit callers, and the trailing __u16 reserved field is rejected with -EINVAL if non-zero so future kernels can repurpose it without breaking already-deployed userspace. The command is encoded with _IOW because the kernel only reads the request struct; the snapshot travels through the user pointer it carries. The requested size may be anything from one byte up to the size firmware reported for that socket's table. A short request returns the leading bytes of the snapshot, so userspace built against an older table layout keeps working on firmware that grew the table, mirroring the relaxed response_sz rule applied to HSMP messages earlier in this series. A request larger than the firmware table is rejected with -EINVAL rather than short-written, so a caller can never mistake a partial copy for a full one. Dispatch hsmp_ioctl() on the ioctl command: the existing message handler is factored out as hsmp_ioctl_msg() for HSMP_IOCTL_CMD, and HSMP_IOCTL_GET_TELEMETRY_DATA goes to a new hsmp_ioctl_get_telemetry() helper. /dev/hsmp is a singleton character device that outlives an individual socket unbind, so an ioctl issued on an already-open fd can run concurrently with socket teardown. hsmp_sock_rwsem is the driver's contract for that: the data plane takes it for read, and probe and remove take it for write to drain the data plane before freeing the socket array, unmapping the metric tables and destroying the per-socket mutexes. hsmp_ioctl_get_telemetry() takes it for read across the socket lookup, the checks on that socket's metric-table state and the table read itself, so none of that state can be torn down underneath it. Without this the handler would sleep in its kvmalloc() holding no lock at all, and could resume with a freed socket, locking a destroyed mutex and reading from an unmapped iomem region. The lock is dropped before the copy_to_user(), because faulting in the destination can block indefinitely on a userfaultfd-backed buffer and would otherwise leave a socket unbind waiting for the write lock. Since hsmp_metric_tbl_read() reached the mailbox through hsmp_send_message(), which takes hsmp_sock_rwsem itself, calling it with the lock already held would recursively take the read side and can deadlock against a queued writer. Split out hsmp_metric_tbl_read_locked(), which asserts the lock and uses hsmp_send_message_locked(), and leave hsmp_metric_tbl_read() as a wrapper that takes the read lock for the sysfs callers. This also brings the whole fill-and-copy under the rwsem for those callers, where the memcpy_fromio() previously ran outside it, and makes the lock order uniformly hsmp_sock_rwsem -> metric_read_lock -> hsmp_sem. The user-controlled socket index in HSMP_IOCTL_GET_TELEMETRY_DATA is clamped with array_index_nospec() before indexing hsmp_pdev.sock[], mitigating Spectre v1 (CVE-2017-5753). Include linux/nospec.h, which the file relied on getting transitively. Out-of-tree delta: cleanup.h class-based cleanup is not available on the older kernels this module supports, so scoped_guard(rwsem_read) and the __free(kvfree) bounce buffer are open-coded with explicit down_read()/up_read() and kvfree() on a goto unwind path. Upstream commit: 5273183b6362 ("platform/x86/amd/hsmp: Add IOCTL_GET_TELEMETRY_DATA for metric table reads") Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K --- amd_hsmp.h | 45 ++++++++++++++ amd_hsmp.rst | 35 ++++++++++- hsmp.c | 167 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 243 insertions(+), 4 deletions(-) diff --git a/amd_hsmp.h b/amd_hsmp.h index e94b801..e534b96 100644 --- a/amd_hsmp.h +++ b/amd_hsmp.h @@ -614,6 +614,39 @@ struct hsmp_metric_table { __u32 gfxclk_frequency[8]; }; +/** + * struct hsmp_telemetry_data - Request descriptor for HSMP telemetry IOCTL + * @buf: Input. Userspace pointer (encoded as __u64 to keep the layout + * stable between 32-bit and 64-bit callers) to the destination + * buffer that receives the metric table. + * @size: Input. Size in bytes of the buffer pointed to by @buf, and the + * number of bytes copied out on success. Must be non-zero and no + * larger than the metric table size firmware reports for this + * socket; a larger value is rejected with -EINVAL rather than + * short-written. A smaller value returns the leading @size bytes + * of the snapshot. The kernel does not write this field back. + * @sock_ind: Input. Socket index from which the metric table is read. + * @reserved: Reserved for future use. Callers should set this to zero; + * future kernels may begin interpreting the field, so passing + * a non-zero value today is not forwards compatible. + * + * Placing @buf first lets all fields fall on their natural alignment under + * the surrounding #pragma pack(4), so the struct is a tight 16 bytes with + * the same wire layout on 32-bit and 64-bit userspace. + * + * The metric table layout depends on the HSMP protocol version reported by + * firmware, which userspace can read from the protocol_version sysfs + * attribute. Protocol version 6 uses struct hsmp_metric_table, so callers on + * that version pass sizeof(struct hsmp_metric_table). Later version metrics + * table layout is documented in the Public PPR. + */ +struct hsmp_telemetry_data { + __u64 buf; + __u32 size; + __u16 sock_ind; + __u16 reserved; +}; + /* Reset to default packing */ #pragma pack() @@ -623,4 +656,16 @@ int hsmp_send_message(struct hsmp_message *msg); #define HSMP_BASE_IOCTL_NR 0xF8 #define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) +/* + * Fetch the firmware metric (telemetry) table for a given socket via the + * HSMP character device. This avoids the PAGE_SIZE limitation of the + * sysfs binary attribute path for tables larger than one page (such as the + * ~13 KB table used by HSMP protocol version 7). + * + * The direction is _IOW because the kernel only reads the request struct; + * the table itself is written to the buffer that @buf points at. + */ +#define HSMP_IOCTL_GET_TELEMETRY_DATA \ + _IOW(HSMP_BASE_IOCTL_NR, 1, struct hsmp_telemetry_data) + #endif /*_ASM_X86_AMD_HSMP_H_*/ diff --git a/amd_hsmp.rst b/amd_hsmp.rst index 8bb411f..fa1fc24 100644 --- a/amd_hsmp.rst +++ b/amd_hsmp.rst @@ -68,6 +68,13 @@ under per socket sysfs directory created at Note: lseek() is not supported as entire metrics table is read. +The sysfs metrics_bin path supports only HSMP protocol version 6 and, +because it is a file read, can return a torn snapshot if userspace +reads in pieces. The protocol version 7 metric table (~13 KB) also +exceeds PAGE_SIZE, so a read returns ``-EOPNOTSUPP`` there. For +atomic reads on any protocol version, use the +``HSMP_IOCTL_GET_TELEMETRY_DATA`` ioctl on /dev/hsmp (see below). + Metrics table definitions will be documented as part of Public PPR. The same is defined in the amd_hsmp.h header. @@ -167,7 +174,7 @@ Next thing, open the device file, as follows:: exit(1); } -The following IOCTL is defined: +The following IOCTLs are defined: ``ioctl(file, HSMP_IOCTL_CMD, struct hsmp_message *msg)`` The argument is a pointer to a:: @@ -180,6 +187,32 @@ The following IOCTL is defined: __u16 sock_ind; /* socket number */ }; +``ioctl(file, HSMP_IOCTL_GET_TELEMETRY_DATA, struct hsmp_telemetry_data *req)`` + Atomically fetch the firmware metric (telemetry) table for a socket. + The ioctl copies the table in one shot, so unlike the metrics_bin + sysfs path it cannot return a torn snapshot and is not bounded by + PAGE_SIZE. Required for HSMP protocol version 7+ (e.g. Family 1Ah + Model 50h-5Fh, whose table is ~13 KB). Argument:: + + struct hsmp_telemetry_data { + __u64 buf; /* User pointer to destination buffer */ + __u32 size; /* Size of @buf in bytes */ + __u16 sock_ind; /* Socket index */ + __u16 reserved; /* Reserved, must be zero */ + }; + + ``size`` must be non-zero and no larger than the table size firmware + reports for that socket; a larger value is rejected with ``-EINVAL`` + rather than short-written, and a smaller one returns the leading + ``size`` bytes of the snapshot. A non-zero ``reserved`` is also + rejected with ``-EINVAL``. + + The table layout depends on the protocol version, which userspace + reads from the ``protocol_version`` sysfs attribute. On version 6 + the table is ``struct hsmp_metric_table``, so callers pass + ``sizeof(struct hsmp_metric_table)``. Later version metrics table + layout is documented in the Public PPR. + The ioctl would return a non-zero on failure; you can read errno to see what happened. The transaction returns 0 on success. diff --git a/hsmp.c b/hsmp.c index 4aae2e6..748f73a 100644 --- a/hsmp.c +++ b/hsmp.c @@ -20,11 +20,14 @@ #include #include #include +#include #include #include #include #include +#include #include +#include #include "hsmp.h" #include "amd_hsmp.h" /* this will come from linux kernel as UAPI header */ @@ -377,7 +380,7 @@ static bool is_get_msg(struct hsmp_message *msg) return false; } -long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +static long hsmp_ioctl_msg(struct file *fp, unsigned long arg) { int __user *arguser = (int __user *)arg; struct hsmp_message msg = { 0 }; @@ -446,11 +449,156 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) return 0; } -ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size); + +/* + * Fetch the firmware metric (telemetry) table for the requested socket and + * copy it to the userspace buffer described by the request. + * + * The metric table size is variable across HSMP protocol versions and on + * Family 1Ah Model 50h-5Fh exceeds PAGE_SIZE. The request carries the buffer + * size, which may be anything up to the size firmware reported for this + * socket's table. + */ +static long hsmp_ioctl_get_telemetry(struct file *fp, unsigned long arg) +{ + void __user *arguser = (void __user *)arg; + struct hsmp_telemetry_data req; + struct hsmp_socket *sock; + void __user *user_buf; + unsigned int sock_ind; + size_t tbl_size; + void *kbuf = NULL; + int ret; + + /* Telemetry data is read-only; require read access on the fd. */ + if (!(fp->f_mode & FMODE_READ)) + return -EPERM; + + if (copy_from_user(&req, arguser, sizeof(req))) + return -EFAULT; + + /* + * Reserved fields must be zero so future kernels can safely + * repurpose them without breaking already-deployed userspace. + */ + if (req.reserved) + return -EINVAL; + + user_buf = u64_to_user_ptr(req.buf); + + /* + * /dev/hsmp is a singleton character device that outlives an individual + * socket unbind, so an ioctl on an already-open fd can run concurrently + * with socket teardown. Hold hsmp_sock_rwsem for read across the socket + * lookup, the checks on its metric-table state and the read itself: + * probe and remove take the same lock for write, so they cannot free the + * socket array, unmap the table or destroy the per-socket mutex while + * this runs. + * + * The lock is dropped before the copy_to_user() below. Faulting in the + * destination can block indefinitely on a userfaultfd-backed buffer, + * which would leave a socket unbind waiting for the write lock. + */ + down_read(&hsmp_sock_rwsem); + + if (!hsmp_pdev.sock || req.sock_ind >= hsmp_pdev.num_sockets) { + ret = -ENODEV; + goto unlock; + } + + /* + * Sanitize the user-controlled socket index against speculative + * execution. The bounds check above retires the out-of-range + * case with -ENODEV, but a mispredicted branch can still let the + * CPU speculatively use sock_ind as an index into + * hsmp_pdev.sock[] and pull arbitrary kernel memory into the + * cache (Spectre v1, CVE-2017-5753). array_index_nospec() turns + * the bounds check into a data-flow clamp so the speculative + * load is in-range too. + */ + sock_ind = array_index_nospec(req.sock_ind, hsmp_pdev.num_sockets); + sock = &hsmp_pdev.sock[sock_ind]; + if (!sock->metric_tbl_addr) { + ret = -ENODEV; + goto unlock; + } + + tbl_size = sock->metric_tbl_size; + if (!tbl_size) { + ret = -ENODEV; + goto unlock; + } + + /* + * A request shorter than the firmware table is served with the + * leading @size bytes of the snapshot, so userspace built + * against an older table layout keeps working on firmware that + * grew the table. Asking for more than firmware provides is + * rejected rather than short-written, so a caller can never + * mistake a partial copy for a full one. + */ + if (!req.size || req.size > tbl_size) { + ret = -EINVAL; + goto unlock; + } + + /* + * The bounce buffer is overwritten in full by memcpy_fromio() + * inside hsmp_metric_tbl_read_locked(); use kvmalloc() to avoid + * the zeroing cost of kvzalloc() on the ~13 KB allocation done + * on every ioctl call. + */ + kbuf = kvmalloc(tbl_size, GFP_KERNEL); + if (!kbuf) { + ret = -ENOMEM; + goto unlock; + } + + ret = hsmp_metric_tbl_read_locked(sock, kbuf, tbl_size); + +unlock: + up_read(&hsmp_sock_rwsem); + + if (ret < 0) + goto free_kbuf; + + ret = 0; + if (copy_to_user(user_buf, kbuf, req.size)) + ret = -EFAULT; + +free_kbuf: + kvfree(kbuf); + + return ret; +} + +long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + switch (cmd) { + case HSMP_IOCTL_CMD: + return hsmp_ioctl_msg(fp, arg); + case HSMP_IOCTL_GET_TELEMETRY_DATA: + return hsmp_ioctl_get_telemetry(fp, arg); + default: + return -ENOTTY; + } +} + +/* + * Caller must hold hsmp_sock_rwsem. It keeps @sock, its metric-table mapping + * and its metric_read_lock alive: probe and remove take the same lock for + * write while they bring sockets up and tear them down. + */ +static ssize_t hsmp_metric_tbl_read_locked(struct hsmp_socket *sock, char *buf, + size_t size) { struct hsmp_message msg = { 0 }; int ret; + lockdep_assert_held(&hsmp_sock_rwsem); + if (!sock || !buf) return -EINVAL; @@ -475,7 +623,7 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) */ mutex_lock(&sock->metric_read_lock); - ret = hsmp_send_message(&msg); + ret = hsmp_send_message_locked(&msg); if (ret) { mutex_unlock(&sock->metric_read_lock); return ret; @@ -486,6 +634,19 @@ ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) return size; } + +ssize_t hsmp_metric_tbl_read(struct hsmp_socket *sock, char *buf, size_t size) +{ + ssize_t ret; + + down_read(&hsmp_sock_rwsem); + + ret = hsmp_metric_tbl_read_locked(sock, buf, size); + + up_read(&hsmp_sock_rwsem); + + return ret; +} #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 13, 0) EXPORT_SYMBOL_NS_GPL(hsmp_metric_tbl_read, "AMD_HSMP"); #else From 0fee7b6da5d1e23206a3b69ad55c461eb3ae4bb8 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:44:06 +0530 Subject: [PATCH 19/24] platform/x86/amd/hsmp: Enable protocol version 7 metric tables on the ACPI driver The ACPI driver currently prepares the per-socket metric table only on HSMP_PROTO_VER6. With protocol version 7 in use on Family 1Ah Model 50h-5Fh, userspace cannot reach the larger ~13 KB table: hsmp_get_tbl_dram_base() is skipped, sock->metric_tbl_addr stays NULL, and the ioctl added earlier in this series has nothing to read. Widen the proto_ver gate in init_acpi() from '== HSMP_PROTO_VER6' to '>= HSMP_PROTO_VER6' so the DRAM region is mapped and sock->metric_tbl_size is populated on protocol version 7 (and any future compatible version), making the ioctl path functional. hsmp_metric_tbl_acpi_read() now returns -EOPNOTSUPP whenever the running protocol version is not VER6, because the sysfs binary attribute cannot carry a table larger than PAGE_SIZE. Version 7 userspace gets a clear, actionable error and a documented pointer to HSMP_IOCTL_GET_TELEMETRY_DATA; version 6 userspace sees no change. The non-ACPI plat.c path is intentionally left untouched: it covers Family 1Ah Model 0h-Fh hardware fixed at protocol version 6, where the existing metrics_bin remains the supported interface. Out-of-tree delta: both proto_ver gates already tested '>=' in this tree, so only the read handler check and the rationale comments are added here. Upstream commit: aca39607c173 ("platform/x86/amd/hsmp: Enable protocol version 7 metric tables on the ACPI driver") Co-developed-by: Muthusamy Ramalingam Signed-off-by: Muthusamy Ramalingam Signed-off-by: Muralidhara M K --- acpi.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/acpi.c b/acpi.c index 4b1e35b..220d6a6 100644 --- a/acpi.c +++ b/acpi.c @@ -300,6 +300,18 @@ static ssize_t hsmp_metric_tbl_acpi_read(struct file *filp, struct kobject *kobj struct device *dev = container_of(kobj, struct device, kobj); struct hsmp_socket *sock = dev_get_drvdata(dev); + /* + * metrics_bin is a sysfs binary attribute and is capped at PAGE_SIZE. + * It can therefore only carry the protocol version 6 metric table + * (struct hsmp_metric_table). The larger tables defined from protocol + * version 7 onwards do not fit; userspace on those systems must read + * the snapshot through HSMP_IOCTL_GET_TELEMETRY_DATA on /dev/hsmp. + * Surface the unsupported case here as -EOPNOTSUPP rather than + * silently truncating the snapshot. + */ + if (hsmp_pdev->proto_ver != HSMP_PROTO_VER6) + return -EOPNOTSUPP; + return hsmp_metric_tbl_read(sock, buf, count); } @@ -311,6 +323,12 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, struct bin_attribute *battr, int id) #endif { + /* + * Keep metrics_bin visible on protocol version 7 and later as well, + * so that userspace which expects the file to exist gets a clear + * -EOPNOTSUPP from the read handler instead of -ENOENT, and is + * pointed at HSMP_IOCTL_GET_TELEMETRY_DATA as the supported path. + */ if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) return battr->attr.mode; From cfb90b36fcab17c35e2bd8622535a26cfe1388e0 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:47:08 +0530 Subject: [PATCH 20/24] platform/x86/amd/hsmp: Ensure success even if hwmon registration fails Even if hwmon registration fails, HSMP remains accessible through the device file, so the operation should return success. This upstream fix predates the series ported into this tree but was never picked up here, leaving init_acpi() returning the hwmon registration status and failing the whole ACPI probe over a non-fatal error. Upstream commit: de5cec220e4d ("platform/x86/amd/hsmp: Ensure success even if hwmon registration fails") Signed-off-by: Suma Hegde Signed-off-by: Muralidhara M K --- acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/acpi.c b/acpi.c index 220d6a6..6377c93 100644 --- a/acpi.c +++ b/acpi.c @@ -644,7 +644,7 @@ static int init_acpi(struct device *dev) dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]); - return ret; + return 0; } static HSMP_CONST struct bin_attribute hsmp_metric_tbl_attr = { From 756b28938d136aa57c5752f131287046e6276425 Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:47:27 +0530 Subject: [PATCH 21/24] platform/x86/amd/hsmp: Replace dev_err() with dev_info() for non-fatal errors Failure in metric table initialization and hwmon registration are non-fatal errors. Hence replace them with dev_info(). This upstream change predates the series ported into this tree but was never picked up here. Upstream commit: ee1cb9b0e6a8 ("platform/x86/amd/hsmp: Replace dev_err() with dev_info() for non-fatal errors") Signed-off-by: Suma Hegde Signed-off-by: Muralidhara M K --- acpi.c | 4 ++-- plat.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/acpi.c b/acpi.c index 6377c93..e7ba060 100644 --- a/acpi.c +++ b/acpi.c @@ -635,12 +635,12 @@ static int init_acpi(struct device *dev) if (hsmp_pdev->proto_ver >= HSMP_PROTO_VER6) { ret = hsmp_get_tbl_dram_base(sock_ind); if (ret) - dev_err(dev, "Failed to init metric table\n"); + dev_info(dev, "Failed to init metric table\n"); } ret = hsmp_create_sensor(dev, sock_ind); if (ret) - dev_err(dev, "Failed to register HSMP sensors with hwmon\n"); + dev_info(dev, "Failed to register HSMP sensors with hwmon\n"); dev_set_drvdata(dev, &hsmp_pdev->sock[sock_ind]); diff --git a/plat.c b/plat.c index 431d6b8..67260d8 100644 --- a/plat.c +++ b/plat.c @@ -226,13 +226,13 @@ static int init_platform_device(struct device *dev) if (hsmp_pdev->proto_ver == HSMP_PROTO_VER6) { ret = hsmp_get_tbl_dram_base(i); if (ret) - dev_err(dev, "Failed to init metric table\n"); + dev_info(dev, "Failed to init metric table\n"); } /* Register with hwmon interface for reporting power */ ret = hsmp_create_sensor(dev, i); if (ret) - dev_err(dev, "Failed to register HSMP sensors with hwmon\n"); + dev_info(dev, "Failed to register HSMP sensors with hwmon\n"); } return 0; From f70d3f590348753fdca03ba58304aace6ce3700e Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:48:11 +0530 Subject: [PATCH 22/24] amd_hsmp: Bound the detected socket count in the platform driver The platform driver builds a static array of 8 + 1 sysfs attribute groups, one per socket, and indexes it by the socket count firmware reports. Upstream guards that assumption by rejecting a count above MAX_AMD_NUM_NODES with a static_assert() tying the check to the array size, but this tree only rejected a count of zero: on a system reporting more than 8 sockets the extra sockets silently got no sysfs group at all. MAX_AMD_NUM_NODES is not available on all kernels supported here, and this tree already carries MAX_AMD_SOCKETS for exactly this purpose, though nothing referenced it. Use it for both the probe-time bound and the static_assert(), which also stops the macro from being dead. Signed-off-by: Muralidhara M K --- plat.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/plat.c b/plat.c index 67260d8..dfb5591 100644 --- a/plat.c +++ b/plat.c @@ -17,6 +17,7 @@ #endif #include +#include #include #include #include @@ -119,7 +120,12 @@ static umode_t hsmp_is_sock_attr_visible(struct kobject *kobj, * Static array of 8 + 1(for NULL) elements is created below * to create sysfs groups for sockets. * is_bin_visible function is used to show / hide the necessary groups. + * + * Validate the maximum number against MAX_AMD_SOCKETS. If this changes, + * then the attributes and groups below must be adjusted. */ +static_assert(MAX_AMD_SOCKETS == 8); + #define HSMP_BIN_ATTR(index, _list) \ static HSMP_CONST struct bin_attribute attr##index = { \ .attr = { .name = HSMP_METRICS_TABLE_NAME, .mode = 0444}, \ @@ -406,8 +412,8 @@ static int __init hsmp_plt_init(void) #else hsmp_pdev->num_sockets = amd_num_nodes(); #endif - if (!hsmp_pdev->num_sockets) { - pr_err("No CPU sockets detected\n"); + if (!hsmp_pdev->num_sockets || hsmp_pdev->num_sockets > MAX_AMD_SOCKETS) { + pr_err("Wrong number of sockets\n"); return ret; } From 094a4c55a5efb5753fe22ba4a7aaffaa5b1d89ef Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:49:03 +0530 Subject: [PATCH 23/24] amd_hsmp: Clean up include and whitespace warts Housekeeping noticed while diffing this tree against the upstream driver, no functional change: - hsmp.c included linux/acpi.h twice and listed linux/mm.h after linux/mutex.h. - hsmp.h listed linux/mutex.h after linux/pci.h. - plat.c had a space before the tab in the .remove initialiser, a stray blank line inside the attribute-group macro block, and a misaligned line continuation in it. - acpi.c indented the pre-5.10 sprintf() fallback in hsmp_msg_fw_ver_show() with spaces. Signed-off-by: Muralidhara M K --- acpi.c | 8 ++++---- hsmp.c | 3 +-- hsmp.h | 2 +- plat.c | 5 ++--- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/acpi.c b/acpi.c index e7ba060..22aded9 100644 --- a/acpi.c +++ b/acpi.c @@ -448,10 +448,10 @@ static ssize_t hsmp_msg_fw_ver_show(struct device *dev, struct device_attribute FIELD_GET(FW_VER_MINOR_MASK, data), FIELD_GET(FW_VER_DEBUG_MASK, data)); #else - return sprintf(buf, "%lu.%lu.%lu\n", - FIELD_GET(FW_VER_MAJOR_MASK, data), - FIELD_GET(FW_VER_MINOR_MASK, data), - FIELD_GET(FW_VER_DEBUG_MASK, data)); + return sprintf(buf, "%lu.%lu.%lu\n", + FIELD_GET(FW_VER_MAJOR_MASK, data), + FIELD_GET(FW_VER_MINOR_MASK, data), + FIELD_GET(FW_VER_DEBUG_MASK, data)); #endif } diff --git a/hsmp.c b/hsmp.c index 748f73a..6667b68 100644 --- a/hsmp.c +++ b/hsmp.c @@ -19,12 +19,11 @@ #include #include #include -#include #include +#include #include #include #include -#include #include #include #include diff --git a/hsmp.h b/hsmp.h index 96f5952..5930902 100644 --- a/hsmp.h +++ b/hsmp.h @@ -15,8 +15,8 @@ #include #include #include -#include #include +#include #include #include #include diff --git a/plat.c b/plat.c index dfb5591..39646e4 100644 --- a/plat.c +++ b/plat.c @@ -147,12 +147,11 @@ HSMP_BIN_ATTR(5, *sock5_attr_list); HSMP_BIN_ATTR(6, *sock6_attr_list); HSMP_BIN_ATTR(7, *sock7_attr_list); - #define HSMP_BIN_ATTR_GRP(index, _list, _name) \ static HSMP_CONST struct attribute_group sock##index##_attr_grp = { \ HSMP_BIN_ATTRS_FIELD = _list, \ .is_bin_visible = hsmp_is_sock_attr_visible, \ - .name = #_name, \ + .name = #_name, \ } HSMP_BIN_ATTR_GRP(0, sock0_attr_list, socket0); @@ -320,7 +319,7 @@ static int hsmp_pltdrv_remove(struct platform_device *pdev) static struct platform_driver amd_hsmp_driver = { .probe = hsmp_pltdrv_probe, - .remove = hsmp_pltdrv_remove, + .remove = hsmp_pltdrv_remove, .driver = { .name = DRIVER_NAME, .dev_groups = hsmp_groups, From 2ae29d376dcc3e3360f556c8f93cb83a12cc643b Mon Sep 17 00:00:00 2001 From: Muralidhara M K Date: Wed, 12 Aug 2026 17:49:29 +0530 Subject: [PATCH 24/24] amd_hsmp: Fix the pre-5.10 fallback in hsmp_freq_limit_source_show() The sysfs_emit_at() fallback for kernels older than 5.10 passed the running output length as scnprintf()'s buffer size and always wrote at the start of the buffer, rather than writing at buf + len with the remaining space as the limit. On the first matching source len is still 0, so scnprintf() is told the buffer is zero bytes long, writes nothing and returns 0. len therefore never advances and the attribute reports an empty string on every such kernel, no matter how many limit sources are active. Write at buf + len with PAGE_SIZE - len remaining, matching what sysfs_emit_at() does on newer kernels. Signed-off-by: Muralidhara M K --- acpi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/acpi.c b/acpi.c index 22aded9..ba08a0e 100644 --- a/acpi.c +++ b/acpi.c @@ -584,7 +584,8 @@ static ssize_t hsmp_freq_limit_source_show(struct device *dev, struct device_att #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0) len += sysfs_emit_at(buf, len, "%s\n", freqlimit_srcnames[index]); #else - len += scnprintf(buf, len, "%s\n", freqlimit_srcnames[index]); + len += scnprintf(buf + len, PAGE_SIZE - len, "%s\n", + freqlimit_srcnames[index]); #endif src_ind >>= 1; }