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

Muralidhara M K <[email protected]> Wed, 29 Jul 2026 22:10:33 +0530
Newsgroups org.kernel.vger.platform-driver-x86,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
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 <[email protected]>
---
 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