[PATCH 6/7] platform/x86/amd/hsmp: Add MSR read IOCTL support

From: Muralidhara M K

Date: Wed Jul 29 2026 - 14:27:13 EST


Add HSMP_IOCTL_MSR_CMD to read a 64-bit MSR through /dev/hsmp, either on
a caller-chosen logical CPU or, with HSMP_MSR_ANY_CPU, on whichever CPU
the ioctl runs on. The read goes through rdmsrq_safe() and
rdmsrq_safe_on_cpu(), so an unimplemented MSR returns an error rather
than taking a #GP.

This lets a tool that is already talking to the HSMP mailbox read the
few core MSRs it needs to interpret what the mailbox reports, without
opening a second interface and without needing the CPU it queries to be
the one it happens to be running on.

Like the SMN read, the ioctl is read-only and requires CAP_SYS_RAWIO.
struct hsmp_msr_message carries a @write flag so write support could be
added later without changing the layout, and a request that sets it is
rejected with -EPERM regardless of privilege or open mode.

@value is placed after the two __u32 fields so it lands on an 8-byte
boundary, giving a tight 20-byte struct with the same wire layout on
32-bit and 64-bit userspace under the surrounding #pragma pack(4). The
value is staged through a u64 local rather than read straight into the
request struct, because that struct is packed to 4 bytes and its __u64
member is therefore not guaranteed to be 8-byte aligned. All three
reserved bytes are rejected when non-zero so a future kernel can
repurpose them without breaking deployed userspace.

A caller-supplied CPU index must refer to an online CPU, and is clamped
with array_index_nospec() before the IPI that carries the read reaches
per-CPU data, mitigating Spectre v1 (CVE-2017-5753). The CPU may go
offline between the check and the read; that races harmlessly, as
rdmsrq_safe_on_cpu() then fails.

Signed-off-by: Muralidhara M K <muralidhara.mk@xxxxxxx>
---
arch/x86/include/uapi/asm/amd_hsmp.h | 39 +++++++++++++++
drivers/platform/x86/amd/hsmp/hsmp.c | 75 ++++++++++++++++++++++++++++
2 files changed, 114 insertions(+)

diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h
index 9c4ad22e47ae..32d269e41620 100644
--- a/arch/x86/include/uapi/asm/amd_hsmp.h
+++ b/arch/x86/include/uapi/asm/amd_hsmp.h
@@ -104,6 +104,38 @@ struct hsmp_smn_message {
__u8 reserved;
};

+/*
+ * Sentinel for hsmp_msr_message.core_id meaning "read on the calling CPU"
+ * rather than scheduling the read on a specific logical CPU.
+ */
+#define HSMP_MSR_ANY_CPU ((__u32)-1)
+
+/**
+ * struct hsmp_msr_message - Request descriptor for the HSMP MSR read IOCTL
+ * @msr_address: Input. MSR index to read.
+ * @core_id: Input. Logical CPU to read the MSR on, or HSMP_MSR_ANY_CPU to
+ * read it on whichever CPU the ioctl runs on.
+ * @value: Output. Populated by the kernel with the value read.
+ * @write: Must be zero. This IOCTL is read-only, so a request with
+ * @write set is rejected with -EPERM whatever the caller's
+ * privilege or open mode. The field is kept so that write
+ * support could be added later without changing the layout.
+ * @reserved: Reserved for future use. Callers must set every byte to zero;
+ * a non-zero value is rejected with -EINVAL so future kernels
+ * can repurpose the field without breaking deployed userspace.
+ *
+ * Placing @value after the two __u32 fields lands it on an 8-byte boundary, so
+ * under the surrounding #pragma pack(4) the struct is a tight 20 bytes with
+ * the same wire layout on 32-bit and 64-bit userspace.
+ */
+struct hsmp_msr_message {
+ __u32 msr_address;
+ __u32 core_id;
+ __u64 value;
+ __u8 write;
+ __u8 reserved[3];
+};
+
enum hsmp_msg_type {
HSMP_RSVD = -1,
HSMP_SET = 0,
@@ -698,6 +730,13 @@ struct hsmp_telemetry_data {
*/
#define HSMP_IOCTL_SMN_CMD _IOWR(HSMP_BASE_IOCTL_NR, 2, struct hsmp_smn_message)

+/*
+ * Read a 64-bit MSR on a given logical CPU. Like the SMN read above this
+ * requires CAP_SYS_RAWIO and is encoded _IOWR because the value read is
+ * written back into the request struct.
+ */
+#define HSMP_IOCTL_MSR_CMD _IOWR(HSMP_BASE_IOCTL_NR, 3, struct hsmp_msr_message)
+
/*
* Client HSMP messages supported on the Family 1Ah client platforms:
* Models 80h-87h (Medusa1), Models 88h-8Fh (Olympic Ridge) and
diff --git a/drivers/platform/x86/amd/hsmp/hsmp.c b/drivers/platform/x86/amd/hsmp/hsmp.c
index ec11c5840b87..4467a0eb0224 100644
--- a/drivers/platform/x86/amd/hsmp/hsmp.c
+++ b/drivers/platform/x86/amd/hsmp/hsmp.c
@@ -9,10 +9,12 @@

#include <asm/amd/hsmp.h>
#include <asm/amd/node.h>
+#include <asm/msr.h>

#include <linux/acpi.h>
#include <linux/capability.h>
#include <linux/cleanup.h>
+#include <linux/cpumask.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/io.h>
@@ -21,6 +23,7 @@
#include <linux/rwsem.h>
#include <linux/semaphore.h>
#include <linux/slab.h>
+#include <linux/string.h>
#include <linux/sysfs.h>
#include <linux/uaccess.h>

@@ -497,6 +500,76 @@ static long hsmp_ioctl_smn(struct file *fp, unsigned long arg)
return 0;
}

+/*
+ * Read a 64-bit MSR, either on a caller-chosen logical CPU or on whichever CPU
+ * the ioctl happens to run on.
+ *
+ * Gated on CAP_SYS_RAWIO and read-only on the same terms as the SMN read
+ * above. No socket state is involved, so unlike that path this needs no
+ * serialisation against socket teardown.
+ */
+static long hsmp_ioctl_msr(struct file *fp, unsigned long arg)
+{
+ void __user *arguser = (void __user *)arg;
+ struct hsmp_msr_message msr;
+ u64 val;
+ int ret;
+
+ if (!capable(CAP_SYS_RAWIO))
+ return -EPERM;
+
+ /* The value read travels back in the request struct. */
+ if (!(fp->f_mode & FMODE_READ))
+ return -EPERM;
+
+ if (copy_from_user(&msr, arguser, sizeof(msr)))
+ return -EFAULT;
+
+ if (msr.write)
+ return -EPERM;
+
+ /*
+ * Require the padding to be zero so that it stays available for
+ * future fields. Callers that leave garbage here would otherwise
+ * have it misread as a request once the bytes gain a meaning.
+ */
+ if (memchr_inv(msr.reserved, 0, sizeof(msr.reserved)))
+ return -EINVAL;
+
+ if (msr.core_id == HSMP_MSR_ANY_CPU) {
+ ret = rdmsrq_safe(msr.msr_address, &val);
+ } else {
+ unsigned int cpu;
+
+ if (msr.core_id >= nr_cpu_ids || !cpu_online(msr.core_id))
+ return -EINVAL;
+
+ /*
+ * Sanitize the user-controlled CPU index against speculative
+ * execution, as it reaches per-CPU data through the IPI that
+ * carries the read (Spectre v1, CVE-2017-5753). The CPU may
+ * go offline between the check and the read; that races
+ * harmlessly, as rdmsrq_safe_on_cpu() then fails.
+ */
+ cpu = array_index_nospec(msr.core_id, nr_cpu_ids);
+ ret = rdmsrq_safe_on_cpu(cpu, msr.msr_address, &val);
+ }
+ if (ret)
+ return ret;
+
+ /*
+ * Copy through a u64 local rather than reading straight into
+ * msr.value: the request struct is packed to 4 bytes, so its __u64
+ * member is not guaranteed to be 8-byte aligned.
+ */
+ msr.value = val;
+
+ if (copy_to_user(arguser, &msr, sizeof(msr)))
+ return -EFAULT;
+
+ return 0;
+}
+
static long hsmp_ioctl_msg(struct file *fp, unsigned long arg)
{
int __user *arguser = (int __user *)arg;
@@ -689,6 +762,8 @@ long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
return hsmp_ioctl_get_telemetry(fp, arg);
case HSMP_IOCTL_SMN_CMD:
return hsmp_ioctl_smn(fp, arg);
+ case HSMP_IOCTL_MSR_CMD:
+ return hsmp_ioctl_msr(fp, arg);
default:
return -ENOTTY;
}
--
2.34.1