All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support
@ 2026-09-03 16:05 Alexandru Elisei
  2026-09-03 16:05 ` [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1 Alexandru Elisei
                   ` (28 more replies)
  0 siblings, 29 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:05 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

For a description of the problem, and why using gup doesn't work, see the
previous iteration of the series, version v6 [1].

This series is based on v7.3-rc1.

I've completely changed the approach yet again, but I've kept Reviewed-by tags
for the some of the prerequisite patches. The highlights are:

* The entire memory is effectively pinned at stage 2 before the first VCPU is
run by having userspace use guest_memfd to back VM memory and then doing
KVM_PRE_FAULT_MEMORY on that memory.

This change alone simplifies the code immensly.

* The maximum buffer size does not need to be virtualized since the entire VM
memory is pinned, which means that PMBIDR_EL1 doesn't need to be trapped.
SPE features cannot be completely hidden from the guest, as some of them (most
of them?) add new fields to the records. The end result: PMBIDR_EL1 and
PMSIDR_EL1 don't need to be trapped, so the dependency on FEAT_FGT is dropped.

* It works with the SPE host driver built as a module; KVM requires that the
module is loaded for SPE to be virtualized.

The end result is that KVM support is mostly reduced to the userspace ABI to
configure the feature and context switching the SPE state.


Prerequisites
=============

There are several prerequisites:

* The series depends on KVM_PRE_FAULT_MEMORY being implemented for arm64.
There are two different implementations of that on the mailing list: [2], [3]. I
have chosen to use the series from Jack Thomson [2] (rebased on top of v7.3-rc1
+ the SPE patches) for the simple reason that it doesn't change the way KVM
handles stage 2 faults, and can be cherry-picked *after* the KVM SPE patches, to
allow sashiko to review the SPE patches.

* A patch to explicitely ignore MMU notifiers for guest_memfd-only memslots. The
patch is on Sean's radar to be merged once he finishes fixing a bug in a related
area [4].

I believe that it is possible to have memory unmapped from stage 2 (it involves
automatic NUMA balancing and userspace explicitly setting a mempolicy), but
haven't tested that; the patch codifies that guest_memfd memory should never be
unmapped without userspace requesting it (userspace can unmap the memory from
stage 2 by closing the guest_memfd file descriptor or punching a hole in the
descriptor).

* Patches to add dirty page logging support to guest_memfd-only memslots
(rebased on top of v7.3-rc1 + the SPE patches), as it's impossible today to
change the flags for a guest_memfd-backed memslot. These patches were sent to
the mailing list [5], and as far as I can tell the conclusion was that allowing
dirty page logging is a goal to be pursued for guest_memfd memslots. If we do
decide that dirty page logging for a VM with SPE is useful, I'm planning to
revisit dirty page logging for guest_memfd.

A branch with the prerequisites **after** the SPE series can be found at [6].
The prequisites come after the SPE patches so I can send this series with an
upstream base commit, which allows sashiko to review them.

kvmtool patches are at [7], and kvm-unit-tests are at [8].


How it works
============

1. Userspace sets the KVM_ARM_VCPU_SPE VCPU feature for all VCPUs, configures
the SPE interrupt number (required), assigns a SPE PMU instance to the VM
(required) and then calls KVM_ARM_VCPU_SPE_INIT to finalise configuration. All
of this is done via KVM_SET_DEVICE_ATTR on each VCPU.

Once the VCPU feature is set, KVM_ARM_VCPU_SPE_INIT must be executed before a
VCPU can be run.

2. Userspace uses guest_memfd-backed memslots to represent guest memory, and
pre-maps that memory at stage 2 using KVM_PRE_FAULT_MEMORY.

The order between steps 1 and 2 does not matter.

3. Userspace runs the VCPU(s).

For a VM with SPE, KVM will not check if memslots are backed by guest_memfd,
because KVM cannot know which memslots the guest will use as RAM and which have
a different use, even if they are not read-only. For example, pvtime.

To create a VM with SPE enabled with kvmtool:

# modprobe arm_spe_pmu # If not already loaded or built-in
$ lkvm run --kernel <image> --spe --guest-memfd --name spe-test --params "kpti=off <other_params>"

To test dirty page logging with kvmtool, use the debug interface:

# Create a VM with the name spe-test, as above, then:
$ lkvm debug --name spe-test --enable-dirty-track # to enable dirty page logging
or
$ lkvm debug --name spe-test --disable-dirty-track # to disable dirty page logging

To run the tests from kvm-unit-tests:

$ configure --arch=arm64 --cross-prefix=<cross_compile> --target=kvmtool
$ make clean && make
$ KVMTOOL=<path/to/kvmtool/binary> ./run_tests.sh -g spe


Migration
=========

This is tricky (read: hacky). Relying on KVM fixing up stage 2 faults triggered
SPE attempting to write to memory that is read-only doesn't really work, because
a partial record might be written to the buffer (PMBSR_EL1.DL set). To recover
from this, KVM, or better yet, the VMM, would need to learn to parse the buffer
and rewind PMBPTR_EL1 to the first by after the last complete record.

So what I did instead was to introduce a new hardware exit reason, and have
VCPUs which have the buffer enabled exit to userspace when dirty page logging is
enabled. A VCPU also exits to userspace if the buffer was disabled when dirty
page logging was enabled, and the buffer later becomes enabled, before dirty page
logging is disabled.

When dirty page logging is disabled, the stage 2 permissions are relaxed to
allow writes. The gory details are in patch #27 ("KVM: arm64: Handle dirty page
logging when SPE feature is set").

[1] https://lore.kernel.org/kvmarm/20251114160717.163230-1-alexandru.elisei@arm.com/
[2] https://lore.kernel.org/kvmarm/20260612162354.73378-3-jackabt.amazon@gmail.com/
[3] https://lore.kernel.org/kvmarm/20260825-kvm-arm-prefault-v1-0-befe8947702e@kernel.org/
[4] https://lore.kernel.org/kvmarm/amPCdAswlk24Xilf@google.com/
[5] https://lore.kernel.org/kvmarm/20260702142912.6395-1-alexandru.elisei@arm.com/
[6] https://gitlab.arm.com/linux-arm/linux-ae/-/tree/kvm-spe-v7
[7] https://gitlab.arm.com/linux-arm/kvmtool-ae/-/tree/kvm-spe-v7
[8] https://gitlab.arm.com/linux-arm/kvm-unit-tests-ae/-/tree/kvm-spe-v7

Alexandru Elisei (26):
  arm64/sysreg: Add the nVM field to PMBLIMITR_EL1
  arm64/sysreg: Define MDCR_EL2.E2PB values
  KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option
  perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file
  perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu
  KVM: arm64: Add KVM_CAP_ARM_SPE capability
  KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature
  HACK! KVM: arm64: Disable SPE virtualization if protected KVM is
    enabled
  HACK! KVM: arm64: Enable SPE virtualization only in VHE mode
  HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled
  KVM: arm64: Add SPE VCPU device attribute to set the SPE device
  KVM: arm64: Add SPE VCPU device attribute to initialize SPE
  KVM: arm64: Use PMSVer from the assigned SPE instance
  KVM: arm64: Add SPE system registers to VCPU context
  KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes
  KVM: arm64: config: Use functions from spe.c to test
    FEAT_SPE_{FnE,FDS}
  KVM: arm64: VHE: Context switch SPE state
  KVM: arm64: Allow guest SPE physical timestamps only if kernel allows
    it
  KVM: arm64: Handle SPE maintenance interrupts
  arm64: errata: Disable SPE in KVM
  KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter
  arm64: errata: Don't enable guest buffer if misprogrammed
  KVM: arm64: at: Use callback for reading descriptor
  KVM: arm64: Map memory on a SPE stage 2 fault
  KVM: arm64: Handle dirty page logging when SPE feature is set
  KVM: arm64: Allow the creation of a SPE enabled VM

Sudeep Holla (2):
  KVM: arm64: Add a new VCPU device control group for SPE
  KVM: arm64: Add SPE VCPU device attribute to set the interrupt number

 .../admin-guide/kernel-parameters.txt         |   3 +
 Documentation/arch/arm64/silicon-errata.rst   |  37 +
 Documentation/virt/kvm/api.rst                |  19 +-
 Documentation/virt/kvm/devices/vcpu.rst       |  84 +++
 arch/arm64/Kconfig                            |  55 ++
 arch/arm64/include/asm/kvm_asm.h              |   1 +
 arch/arm64/include/asm/kvm_host.h             |  48 +-
 arch/arm64/include/asm/kvm_hyp.h              |  26 +-
 arch/arm64/include/asm/kvm_mmu.h              |   1 +
 arch/arm64/include/asm/kvm_nested.h           |   7 +
 arch/arm64/include/asm/kvm_pgtable.h          |  19 +
 arch/arm64/include/asm/kvm_pkvm.h             |   1 +
 arch/arm64/include/asm/kvm_spe.h              | 150 ++++
 arch/arm64/include/uapi/asm/kvm.h             |   6 +
 arch/arm64/kernel/cpu_errata.c                |  51 ++
 arch/arm64/kvm/Kconfig                        |  10 +
 arch/arm64/kvm/Makefile                       |   1 +
 arch/arm64/kvm/arm.c                          |  98 ++-
 arch/arm64/kvm/at.c                           |  22 +-
 arch/arm64/kvm/config.c                       |  30 +-
 arch/arm64/kvm/guest.c                        |  12 +
 arch/arm64/kvm/hyp/include/nvhe/mem_protect.h |   2 +-
 arch/arm64/kvm/hyp/nvhe/hyp-main.c            |  34 +-
 arch/arm64/kvm/hyp/nvhe/mem_protect.c         |   7 +-
 arch/arm64/kvm/hyp/pgtable.c                  |   8 +
 arch/arm64/kvm/hyp/vhe/Makefile               |   1 +
 arch/arm64/kvm/hyp/vhe/debug-sr.c             |  18 +
 arch/arm64/kvm/hyp/vhe/spe-sr.c               | 212 ++++++
 arch/arm64/kvm/hyp/vhe/switch.c               |   9 +
 arch/arm64/kvm/mmu.c                          |  72 ++
 arch/arm64/kvm/nested.c                       |   7 +-
 arch/arm64/kvm/pkvm.c                         |  27 +
 arch/arm64/kvm/pmu-emul.c                     |  11 +-
 arch/arm64/kvm/spe.c                          | 647 ++++++++++++++++++
 arch/arm64/kvm/sys_regs.c                     | 103 ++-
 arch/arm64/tools/cpucaps                      |   2 +
 arch/arm64/tools/sysreg                       |  11 +-
 drivers/perf/arm_spe_pmu.c                    |  46 +-
 include/linux/perf/arm_spe_pmu.h              |  66 ++
 include/uapi/linux/kvm.h                      |   1 +
 40 files changed, 1849 insertions(+), 116 deletions(-)
 create mode 100644 arch/arm64/include/asm/kvm_spe.h
 create mode 100644 arch/arm64/kvm/hyp/vhe/spe-sr.c
 create mode 100644 arch/arm64/kvm/spe.c
 create mode 100644 include/linux/perf/arm_spe_pmu.h


base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
-- 
2.43.0


^ permalink raw reply	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
@ 2026-09-03 16:05 ` Alexandru Elisei
  2026-09-03 16:14   ` sashiko-bot
  2026-10-15 13:23   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:05 ` [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values Alexandru Elisei
                   ` (27 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:05 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Define PMBLIMITR_EL1.nVM, to be used by KVM.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/tools/sysreg | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index 94bf065c8ac7..a11a968eb707 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -3106,7 +3106,9 @@ EndSysreg
 
 Sysreg	PMBLIMITR_EL1	3	0	9	10	0
 Field	63:12	LIMIT
-Res0	11:6
+Res0	11:8
+Field	7	nVM
+Res0	6
 Field	5	PMFZ
 Res0	4:3
 Enum	2:1	FM
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
  2026-09-03 16:05 ` [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1 Alexandru Elisei
@ 2026-09-03 16:05 ` Alexandru Elisei
  2026-09-03 16:12   ` sashiko-bot
  2026-10-15 13:52   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:05 ` [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option Alexandru Elisei
                   ` (26 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:05 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

KVM will make use of the different values for MDCR_EL2.E2PB, document them.

Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/tools/sysreg | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
index a11a968eb707..4485736a4f49 100644
--- a/arch/arm64/tools/sysreg
+++ b/arch/arm64/tools/sysreg
@@ -4045,7 +4045,12 @@ Field	17	HPMD
 Res0	16
 Field	15	EnSPM
 Field	14	TPMS
-Field	13:12	E2PB
+UnsignedEnum	13:12 E2PB
+	0b00	EL2
+	0b01	RESERVED
+	0b10	EL1_TRAP
+	0b11	EL1
+EndEnum
 Field	11	TDRA
 Field	10	TDOSA
 Field	9	TDA
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
  2026-09-03 16:05 ` [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1 Alexandru Elisei
  2026-09-03 16:05 ` [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values Alexandru Elisei
@ 2026-09-03 16:05 ` Alexandru Elisei
  2026-09-03 16:13   ` sashiko-bot
  2026-10-15 14:15   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:05 ` [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file Alexandru Elisei
                   ` (25 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:05 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Add a new configuration option that will be used for KVM SPE emulation.
CONFIG_KVM_ARM_SPE depends on the SPE driver being loaded because:

1. The SPE driver maintains a cpumask of physical CPUs that support SPE,
   and that will be used by KVM to emulate SPE on heterogeneous systems.

2. KVM will rely on the SPE driver enabling the SPE interrupt at the GIC
   level.

The option will become user selectable after SPE support in KVM is
implemented.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/kvm/Kconfig | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 449154f9a485..fece4c02b97c 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -103,5 +103,14 @@ config PKVM_STACKTRACE
 	  If unsure, or not using protected nVHE (pKVM), say N.
 
 endif # NVHE_EL2_DEBUG
+
+config KVM_ARM_SPE
+	bool
+	depends on KVM && ARM_SPE_PMU
+	default n
+	help
+	  Adds support for Statistical Profiling Extension (SPE) in virtual
+	  machines.
+
 endif # KVM
 endif # VIRTUALIZATION
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (2 preceding siblings ...)
  2026-09-03 16:05 ` [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option Alexandru Elisei
@ 2026-09-03 16:05 ` Alexandru Elisei
  2026-09-03 16:11   ` sashiko-bot
  2026-10-15 14:40   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu Alexandru Elisei
                   ` (24 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:05 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

KVM will soon want to make use of struct arm_spe_pmu, move it to a separate
header where it will be easily accessible.

Cc: Will Deacon <will@kernel.org>
Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 drivers/perf/arm_spe_pmu.c       | 33 +--------------------
 include/linux/perf/arm_spe_pmu.h | 50 ++++++++++++++++++++++++++++++++
 2 files changed, 51 insertions(+), 32 deletions(-)
 create mode 100644 include/linux/perf/arm_spe_pmu.h

diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
index b70f3fedf028..79c571a649d5 100644
--- a/drivers/perf/arm_spe_pmu.c
+++ b/drivers/perf/arm_spe_pmu.c
@@ -28,6 +28,7 @@
 #include <linux/of.h>
 #include <linux/perf_event.h>
 #include <linux/perf/arm_pmu.h>
+#include <linux/perf/arm_spe_pmu.h>
 #include <linux/platform_device.h>
 #include <linux/printk.h>
 #include <linux/slab.h>
@@ -67,38 +68,6 @@ struct arm_spe_pmu_buf {
 	void					*base;
 };
 
-struct arm_spe_pmu {
-	struct pmu				pmu;
-	struct platform_device			*pdev;
-	cpumask_t				supported_cpus;
-	struct hlist_node			hotplug_node;
-
-	int					irq; /* PPI */
-	u16					pmsver;
-	u16					min_period;
-	u16					counter_sz;
-
-#define SPE_PMU_FEAT_FILT_EVT			(1UL << 0)
-#define SPE_PMU_FEAT_FILT_TYP			(1UL << 1)
-#define SPE_PMU_FEAT_FILT_LAT			(1UL << 2)
-#define SPE_PMU_FEAT_ARCH_INST			(1UL << 3)
-#define SPE_PMU_FEAT_LDS			(1UL << 4)
-#define SPE_PMU_FEAT_ERND			(1UL << 5)
-#define SPE_PMU_FEAT_INV_FILT_EVT		(1UL << 6)
-#define SPE_PMU_FEAT_DISCARD			(1UL << 7)
-#define SPE_PMU_FEAT_EFT			(1UL << 8)
-#define SPE_PMU_FEAT_FDS			(1UL << 9)
-#define SPE_PMU_FEAT_DEV_PROBED			(1UL << 63)
-	u64					features;
-
-	u64					pmsevfr_res0;
-	u16					max_record_sz;
-	u16					align;
-	struct perf_output_handle __percpu	*handle;
-};
-
-#define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
-
 /* Convert a free-running index from perf into an SPE buffer offset */
 #define PERF_IDX2OFF(idx, buf) \
 	((idx) % ((unsigned long)(buf)->nr_pages << PAGE_SHIFT))
diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
new file mode 100644
index 000000000000..af86fd184050
--- /dev/null
+++ b/include/linux/perf/arm_spe_pmu.h
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Split from driver/perf/arm_spe_pmu.c
+ *
+ * Copyright (C) 2016 ARM Limited
+ */
+
+#ifndef __PERF_ARM_SPE_PMU_H__
+#define __PERF_ARM_SPE_PMU_H__
+
+#include <linux/cpumask.h>
+#include <linux/container_of.h>
+#include <linux/list.h>
+#include <linux/perf_event.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+struct arm_spe_pmu {
+	struct pmu				pmu;
+	struct platform_device			*pdev;
+	cpumask_t				supported_cpus;
+	struct hlist_node			hotplug_node;
+
+	int					irq; /* PPI */
+	u16					pmsver;
+	u16					min_period;
+	u16					counter_sz;
+
+#define SPE_PMU_FEAT_FILT_EVT			(1UL << 0)
+#define SPE_PMU_FEAT_FILT_TYP			(1UL << 1)
+#define SPE_PMU_FEAT_FILT_LAT			(1UL << 2)
+#define SPE_PMU_FEAT_ARCH_INST			(1UL << 3)
+#define SPE_PMU_FEAT_LDS			(1UL << 4)
+#define SPE_PMU_FEAT_ERND			(1UL << 5)
+#define SPE_PMU_FEAT_INV_FILT_EVT		(1UL << 6)
+#define SPE_PMU_FEAT_DISCARD			(1UL << 7)
+#define SPE_PMU_FEAT_EFT			(1UL << 8)
+#define SPE_PMU_FEAT_FDS			(1UL << 9)
+#define SPE_PMU_FEAT_DEV_PROBED			(1UL << 63)
+	u64					features;
+
+	u64					pmsevfr_res0;
+	u16					max_record_sz;
+	u16					align;
+	struct perf_output_handle __percpu	*handle;
+};
+
+#define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
+
+#endif /* __PERF_ARM_SPE_PMU_H__ */
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (3 preceding siblings ...)
  2026-09-03 16:05 ` [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:11   ` sashiko-bot
  2026-10-15 15:12   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability Alexandru Elisei
                   ` (23 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 drivers/perf/arm_spe_pmu.c       | 3 +++
 include/linux/perf/arm_spe_pmu.h | 2 ++
 2 files changed, 5 insertions(+)

diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
index 79c571a649d5..42ba0924bd02 100644
--- a/drivers/perf/arm_spe_pmu.c
+++ b/drivers/perf/arm_spe_pmu.c
@@ -1095,6 +1095,7 @@ static void __arm_spe_pmu_dev_probe(void *info)
 			"profiling buffer owned by higher exception level\n");
 		return;
 	}
+	spe_pmu->pmbidr_el1 = reg;
 
 	/* Minimum alignment. If it's out-of-range, then fail the probe */
 	fld = FIELD_GET(PMBIDR_EL1_ALIGN, reg);
@@ -1107,6 +1108,8 @@ static void __arm_spe_pmu_dev_probe(void *info)
 
 	/* It's now safe to read PMSIDR and figure out what we've got */
 	reg = read_sysreg_s(SYS_PMSIDR_EL1);
+	spe_pmu->pmsidr_el1 = reg;
+
 	if (FIELD_GET(PMSIDR_EL1_FE, reg))
 		spe_pmu->features |= SPE_PMU_FEAT_FILT_EVT;
 
diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
index af86fd184050..97b14e236fcd 100644
--- a/include/linux/perf/arm_spe_pmu.h
+++ b/include/linux/perf/arm_spe_pmu.h
@@ -21,6 +21,8 @@ struct arm_spe_pmu {
 	cpumask_t				supported_cpus;
 	struct hlist_node			hotplug_node;
 
+	u64					pmbidr_el1;
+	u64					pmsidr_el1;
 	int					irq; /* PPI */
 	u16					pmsver;
 	u16					min_period;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (4 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:15   ` sashiko-bot
  2026-10-15 16:51   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature Alexandru Elisei
                   ` (22 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Add the SPE capability that will be used by userspace to test if KVM
supports SPE virtualization.

The SPE driver supports heterogenous systems. Keep track of all the
available Statistical Profiling Units (SPUs) in the system, because KVM
will require the user to associate a VM with exactly one SPU.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/api.rst   | 10 ++++++
 arch/arm64/include/asm/kvm_spe.h | 18 ++++++++++
 arch/arm64/kvm/Makefile          |  1 +
 arch/arm64/kvm/arm.c             |  4 +++
 arch/arm64/kvm/spe.c             | 56 ++++++++++++++++++++++++++++++++
 drivers/perf/arm_spe_pmu.c       |  4 +++
 include/linux/perf/arm_spe_pmu.h | 12 +++++++
 include/uapi/linux/kvm.h         |  1 +
 8 files changed, 106 insertions(+)
 create mode 100644 arch/arm64/include/asm/kvm_spe.h
 create mode 100644 arch/arm64/kvm/spe.c

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index e0430cc750c9..1ed5c389c7a4 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -9600,6 +9600,16 @@ take care to differentiate between these cases.
 The presence of this capability indicates that the nested KVM guest can
 start in ESA mode.
 
+8.48 KVM_CAP_ARM_SPE
+--------------------
+
+:Capability: KVM_CAP_ARM_SPE
+:Architectures: arm64
+:Type: vm
+
+This capability indicates that Statistical Profiling Extension (SPE)
+virtualization is available in KVM.
+
 9. Known KVM API problems
 =========================
 
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
new file mode 100644
index 000000000000..f05e6d3f5705
--- /dev/null
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021 - ARM Ltd
+ */
+
+#ifndef __ARM64_KVM_SPE_H__
+#define __ARM64_KVM_SPE_H__
+
+#ifdef CONFIG_KVM_ARM_SPE
+bool kvm_supports_spe(void);
+#else
+static __always_inline bool kvm_supports_spe(void)
+{
+	return false;
+}
+#endif /* CONFIG_KVM_ARM_SPE */
+
+#endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
index 59612d2f277c..9ee271f713cd 100644
--- a/arch/arm64/kvm/Makefile
+++ b/arch/arm64/kvm/Makefile
@@ -29,6 +29,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
 kvm-$(CONFIG_HW_PERF_EVENTS)  += pmu-emul.o pmu.o
 kvm-$(CONFIG_ARM64_PTR_AUTH)  += pauth.o
 kvm-$(CONFIG_PTDUMP_STAGE2_DEBUGFS) += ptdump.o
+kvm-$(CONFIG_KVM_ARM_SPE)     += spe.o
 
 kvm-$(CONFIG_NVHE_EL2_TRACING) += hyp_trace.o
 
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 8b080804bc90..eca1184ef9dd 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -42,6 +42,7 @@
 #include <asm/kvm_nested.h>
 #include <asm/kvm_pkvm.h>
 #include <asm/kvm_ptrauth.h>
+#include <asm/kvm_spe.h>
 #include <asm/sections.h>
 #include <asm/stacktrace/nvhe.h>
 
@@ -468,6 +469,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
 	case KVM_CAP_ARM_PMU_V3_STRICT:
 		r = kvm_supports_guest_pmuv3();
 		break;
+	case KVM_CAP_ARM_SPE:
+		r = kvm_supports_spe();
+		break;
 	case KVM_CAP_ARM_INJECT_SERROR_ESR:
 		r = cpus_have_final_cap(ARM64_HAS_RAS_EXTN);
 		break;
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
new file mode 100644
index 000000000000..646806a60d66
--- /dev/null
+++ b/arch/arm64/kvm/spe.c
@@ -0,0 +1,56 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 - ARM Ltd
+ */
+
+#include <linux/cpumask.h>
+#include <linux/kvm_host.h>
+#include <linux/perf/arm_spe_pmu.h>
+
+#include <asm/kvm_spe.h>
+#include <asm/sysreg.h>
+
+static LIST_HEAD(spe_pmus);
+static DEFINE_MUTEX(spe_pmus_lock);
+
+struct spe_pmu_entry {
+	struct list_head link;
+	struct arm_spe_pmu *spe_pmu;
+};
+
+void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
+{
+	struct spe_pmu_entry *entry;
+
+	guard(mutex)(&spe_pmus_lock);
+
+	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return;
+
+	entry->spe_pmu = spe_pmu;
+	list_add_tail(&entry->link, &spe_pmus);
+}
+EXPORT_SYMBOL_FOR_MODULES(kvm_spe_add_instance, "arm_spe_pmu");
+
+void kvm_spe_remove_instance(struct arm_spe_pmu *spe_pmu)
+{
+	struct spe_pmu_entry *entry, *tmp;
+
+	guard(mutex)(&spe_pmus_lock);
+
+	list_for_each_entry_safe(entry, tmp, &spe_pmus, link) {
+		if (entry->spe_pmu == spe_pmu) {
+			list_del(&entry->link);
+			kfree(entry);
+			break;
+		}
+	}
+}
+EXPORT_SYMBOL_FOR_MODULES(kvm_spe_remove_instance, "arm_spe_pmu");
+
+bool kvm_supports_spe(void)
+{
+	guard(mutex)(&spe_pmus_lock);
+	return !list_empty(&spe_pmus);
+}
diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
index 42ba0924bd02..f24b122a087c 100644
--- a/drivers/perf/arm_spe_pmu.c
+++ b/drivers/perf/arm_spe_pmu.c
@@ -1370,6 +1370,8 @@ static int arm_spe_pmu_device_probe(struct platform_device *pdev)
 	if (ret)
 		goto out_teardown_dev;
 
+	kvm_spe_add_instance(spe_pmu);
+
 	return 0;
 
 out_teardown_dev:
@@ -1383,6 +1385,8 @@ static void arm_spe_pmu_device_remove(struct platform_device *pdev)
 {
 	struct arm_spe_pmu *spe_pmu = platform_get_drvdata(pdev);
 
+	kvm_spe_remove_instance(spe_pmu);
+
 	arm_spe_pmu_perf_destroy(spe_pmu);
 	arm_spe_pmu_dev_teardown(spe_pmu);
 	free_percpu(spe_pmu->handle);
diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
index 97b14e236fcd..bdb703558a87 100644
--- a/include/linux/perf/arm_spe_pmu.h
+++ b/include/linux/perf/arm_spe_pmu.h
@@ -49,4 +49,16 @@ struct arm_spe_pmu {
 
 #define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
 
+#ifdef CONFIG_KVM_ARM_SPE
+void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu);
+void kvm_spe_remove_instance(struct arm_spe_pmu *spe_pmu);
+#else
+static inline void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
+{
+}
+static inline void kvm_spe_remove_instance(struct arm_spe_pmu *spe_pmu)
+{
+}
+#endif /* CONFIG_KVM_ARM_SPE */
+
 #endif /* __PERF_ARM_SPE_PMU_H__ */
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index ac2d77d14963..1565a609890b 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -999,6 +999,7 @@ struct kvm_enable_cap {
 #define KVM_CAP_S390_HPAGE_2G 249
 #define KVM_CAP_PPC_COMPAT_CAPS 250
 #define KVM_CAP_ARM_PMU_V3_STRICT 251
+#define KVM_CAP_ARM_SPE 252
 
 struct kvm_irq_routing_irqchip {
 	__u32 irqchip;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (5 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:21   ` sashiko-bot
  2026-10-16 14:23   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled Alexandru Elisei
                   ` (21 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Add a new VCPU feature that enables SPE virtualization when set by
userspace. KVM_VCPU_MAX_FEATURE will be bumped and the feature will
become selectable by userspace once SPE support in KVM is implemented.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/api.rst    | 2 ++
 arch/arm64/include/asm/kvm_spe.h  | 5 +++++
 arch/arm64/include/uapi/asm/kvm.h | 1 +
 arch/arm64/kvm/arm.c              | 7 +++++++
 4 files changed, 15 insertions(+)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 1ed5c389c7a4..654736fac78c 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -3591,6 +3591,8 @@ Possible features:
 	  Depends on KVM_CAP_ARM_EL2_E2H0.
 	  KVM_ARM_VCPU_HAS_EL2 must also be set.
 
+        - KVM_ARM_VCPU_SPE: Enable SPE for the CPU. Depends on KVM_CAP_ARM_SPE.
+
 4.83 KVM_ARM_PREFERRED_TARGET
 -----------------------------
 
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index f05e6d3f5705..e452da82666d 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -8,11 +8,16 @@
 
 #ifdef CONFIG_KVM_ARM_SPE
 bool kvm_supports_spe(void);
+
+#define vcpu_has_spe(vcpu)					\
+	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
 #else
 static __always_inline bool kvm_supports_spe(void)
 {
 	return false;
 }
+
+#define vcpu_has_spe(vcpu)	false
 #endif /* CONFIG_KVM_ARM_SPE */
 
 #endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 019e5e3d892e..f7f751c00e6f 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -107,6 +107,7 @@ struct kvm_regs {
 #define KVM_ARM_VCPU_HAS_EL2		7 /* Support nested virtualization */
 #define KVM_ARM_VCPU_HAS_EL2_E2H0	8 /* Limit NV support to E2H RES0 */
 #define KVM_ARM_VCPU_PMU_V3_STRICT	9 /* No default PMU creation */
+#define KVM_ARM_VCPU_SPE		10 /* Support SPE in guest */
 
 struct kvm_vcpu_init {
 	__u32 target;
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index eca1184ef9dd..b9cee4cf293d 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1604,6 +1604,9 @@ static unsigned long system_supported_vcpu_features(void)
 	if (!cpus_have_final_cap(ARM64_HAS_NESTED_VIRT))
 		clear_bit(KVM_ARM_VCPU_HAS_EL2, &features);
 
+	if (!kvm_supports_spe())
+		clear_bit(KVM_ARM_VCPU_SPE, &features);
+
 	return features;
 }
 
@@ -1648,6 +1651,10 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,
 	if (test_bit(KVM_ARM_VCPU_HAS_EL2, &features))
 		return -EINVAL;
 
+	/* SPE is incompatible with AArch32 */
+	if (test_bit(KVM_ARM_VCPU_SPE, &features))
+		return -EINVAL;
+
 	return 0;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (6 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:21   ` sashiko-bot
  2026-10-16 14:30   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 09/28] HACK! KVM: arm64: Enable SPE virtualization only in VHE mode Alexandru Elisei
                   ` (20 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

For RFC only.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/kvm/spe.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 646806a60d66..97160d1fae81 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -22,6 +22,10 @@ void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
 {
 	struct spe_pmu_entry *entry;
 
+	/* TODO: pKVM support */
+	if (is_protected_kvm_enabled())
+		return;
+
 	guard(mutex)(&spe_pmus_lock);
 
 	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 09/28] HACK! KVM: arm64: Enable SPE virtualization only in VHE mode
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (7 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:15   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 10/28] HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled Alexandru Elisei
                   ` (19 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

For RFC only.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/kvm/spe.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 97160d1fae81..00908aa2576b 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -22,8 +22,8 @@ void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
 {
 	struct spe_pmu_entry *entry;
 
-	/* TODO: pKVM support */
-	if (is_protected_kvm_enabled())
+	/* TODO: pKVM and nVHE support */
+	if (is_protected_kvm_enabled() || !has_vhe())
 		return;
 
 	guard(mutex)(&spe_pmus_lock);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 10/28] HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (8 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 09/28] HACK! KVM: arm64: Enable SPE virtualization only in VHE mode Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:20   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE Alexandru Elisei
                   ` (18 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

For RFC only.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/kvm/arm.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index b9cee4cf293d..763b102dc51f 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1640,6 +1640,11 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,
 	    !test_bit(KVM_ARM_VCPU_PMU_V3, &features))
 		return -EINVAL;
 
+	/* TODO: NV support */
+	if (test_bit(KVM_ARM_VCPU_SPE, &features) &&
+	    test_bit(KVM_ARM_VCPU_HAS_EL2, &features))
+		return -EINVAL;
+
 	if (!test_bit(KVM_ARM_VCPU_EL1_32BIT, &features))
 		return 0;
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (9 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 10/28] HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:22   ` sashiko-bot
  2026-10-16 14:44   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number Alexandru Elisei
                   ` (17 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark
  Cc: Sudeep Holla

From: Sudeep Holla <sudeep.holla@arm.com>

Add a new VCPU device control group to control various aspects of KVM's SPE
emulation. Functionality will be added in later patches.

[ Alexandru E: Major rework ]

Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/devices/vcpu.rst |  5 +++++
 arch/arm64/include/asm/kvm_spe.h        | 17 +++++++++++++++++
 arch/arm64/include/uapi/asm/kvm.h       |  1 +
 arch/arm64/kvm/guest.c                  | 10 ++++++++++
 arch/arm64/kvm/spe.c                    | 15 +++++++++++++++
 5 files changed, 48 insertions(+)

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index deb5c51bc00c..e0a7fa35dfd2 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -302,3 +302,8 @@ From the destination VMM process:
 
 7. Write the KVM_VCPU_TSC_OFFSET attribute for every vCPU with the
    respective value derived in the previous step.
+
+5. GROUP: KVM_ARM_VCPU_SPE_CTRL
+===============================
+
+:Architectures: ARM64
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index e452da82666d..1327064d0984 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -11,6 +11,10 @@ bool kvm_supports_spe(void);
 
 #define vcpu_has_spe(vcpu)					\
 	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
+
+int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 #else
 static __always_inline bool kvm_supports_spe(void)
 {
@@ -18,6 +22,19 @@ static __always_inline bool kvm_supports_spe(void)
 }
 
 #define vcpu_has_spe(vcpu)	false
+
+static inline int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
+{
+	return -ENXIO;
+}
+static inline int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
+{
+	return -ENXIO;
+}
+static inline int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
+{
+	return -ENXIO;
+}
 #endif /* CONFIG_KVM_ARM_SPE */
 
 #endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index f7f751c00e6f..e8a68e34be63 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -446,6 +446,7 @@ enum {
 #define   KVM_ARM_VCPU_TIMER_IRQ_HPTIMER	3
 #define KVM_ARM_VCPU_PVTIME_CTRL	2
 #define   KVM_ARM_VCPU_PVTIME_IPA	0
+#define KVM_ARM_VCPU_SPE_CTRL		3
 
 /* KVM_IRQ_LINE irq field index values */
 #define KVM_ARM_IRQ_VCPU2_SHIFT		28
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index b01d6622b872..56c5f937fcdb 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -25,6 +25,7 @@
 #include <asm/kvm.h>
 #include <asm/kvm_emulate.h>
 #include <asm/kvm_nested.h>
+#include <asm/kvm_spe.h>
 #include <asm/sigcontext.h>
 
 #include "trace.h"
@@ -918,6 +919,9 @@ int kvm_arm_vcpu_arch_set_attr(struct kvm_vcpu *vcpu,
 	case KVM_ARM_VCPU_PVTIME_CTRL:
 		ret = kvm_arm_pvtime_set_attr(vcpu, attr);
 		break;
+	case KVM_ARM_VCPU_SPE_CTRL:
+		ret = kvm_spe_set_attr(vcpu, attr);
+		break;
 	default:
 		ret = -ENXIO;
 		break;
@@ -941,6 +945,9 @@ int kvm_arm_vcpu_arch_get_attr(struct kvm_vcpu *vcpu,
 	case KVM_ARM_VCPU_PVTIME_CTRL:
 		ret = kvm_arm_pvtime_get_attr(vcpu, attr);
 		break;
+	case KVM_ARM_VCPU_SPE_CTRL:
+		ret = kvm_spe_get_attr(vcpu, attr);
+		break;
 	default:
 		ret = -ENXIO;
 		break;
@@ -964,6 +971,9 @@ int kvm_arm_vcpu_arch_has_attr(struct kvm_vcpu *vcpu,
 	case KVM_ARM_VCPU_PVTIME_CTRL:
 		ret = kvm_arm_pvtime_has_attr(vcpu, attr);
 		break;
+	case KVM_ARM_VCPU_SPE_CTRL:
+		ret = kvm_spe_has_attr(vcpu, attr);
+		break;
 	default:
 		ret = -ENXIO;
 		break;
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 00908aa2576b..fe45ad4c703a 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -58,3 +58,18 @@ bool kvm_supports_spe(void)
 	guard(mutex)(&spe_pmus_lock);
 	return !list_empty(&spe_pmus);
 }
+
+int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
+{
+	return -ENXIO;
+}
+
+int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
+{
+	return -ENXIO;
+}
+
+int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
+{
+	return -ENXIO;
+}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (10 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:27   ` sashiko-bot
                     ` (2 more replies)
  2026-09-03 16:06 ` [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device Alexandru Elisei
                   ` (16 subsequent siblings)
  28 siblings, 3 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark
  Cc: Sudeep Holla

From: Sudeep Holla <sudeep.holla@arm.com>

Add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_IRQ) to allow the user to set
the interrupt number for the buffer management interrupt.

[ Alexandru E: Split from "KVM: arm64: Add a new VCPU device control group
               for SPE" ]

Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/devices/vcpu.rst | 22 +++++++
 arch/arm64/include/asm/kvm_host.h       |  2 +
 arch/arm64/include/asm/kvm_spe.h        | 11 ++++
 arch/arm64/include/uapi/asm/kvm.h       |  1 +
 arch/arm64/kvm/guest.c                  |  2 +
 arch/arm64/kvm/spe.c                    | 82 +++++++++++++++++++++++++
 6 files changed, 120 insertions(+)

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index e0a7fa35dfd2..d30b2f7fec9a 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -307,3 +307,25 @@ From the destination VMM process:
 ===============================
 
 :Architectures: ARM64
+
+5.1 ATTRIBUTE: KVM_ARM_VCPU_SPE_IRQ
+-----------------------------------
+
+:Parameters: in kvm_device_attr.addr the address for the Profiling Buffer
+             management interrupt number as a pointer to an int
+
+Returns:
+
+	 =======  ==========================================================
+	 -EFAULT  Error accessing the buffer management interrupt number
+	 -EINVAL  Invalid interrupt number or not using an in-kernel irqchip
+	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set
+	 -ENXIO   SPE not supported or not properly configured
+	 =======  ==========================================================
+
+Required.
+
+Specifies the Profiling Buffer management interrupt number. The interrupt number
+must be a PPI and the interrupt number must be the same for each VCPU. Arm
+recommends 21 as the interrupt number. SPE virtualization requires an in-kernel
+vGIC implementation.
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 27fe0cd5b2d7..e7332bd82bf3 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -27,6 +27,7 @@
 #include <asm/fpsimd.h>
 #include <asm/kvm.h>
 #include <asm/kvm_asm.h>
+#include <asm/kvm_spe.h>
 #include <asm/vncr_mapping.h>
 
 #define __KVM_HAVE_ARCH_INTC_INITIALIZED
@@ -918,6 +919,7 @@ struct kvm_vcpu_arch {
 	struct vgic_cpu vgic_cpu;
 	struct arch_timer_cpu timer_cpu;
 	struct kvm_pmu pmu;
+	struct kvm_vcpu_spe vcpu_spe;
 
 	/* vcpu power state */
 	struct kvm_mp_state mp_state;
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 1327064d0984..8ded2d821bff 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -6,7 +6,15 @@
 #ifndef __ARM64_KVM_SPE_H__
 #define __ARM64_KVM_SPE_H__
 
+struct kvm;
+struct kvm_vcpu;
+struct kvm_device_attr;
+
 #ifdef CONFIG_KVM_ARM_SPE
+struct kvm_vcpu_spe {
+	int irq_num;		/* Buffer management interrupt number */
+};
+
 bool kvm_supports_spe(void);
 
 #define vcpu_has_spe(vcpu)					\
@@ -16,6 +24,9 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 #else
+struct kvm_vcpu_spe {
+};
+
 static __always_inline bool kvm_supports_spe(void)
 {
 	return false;
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index e8a68e34be63..b9e5019181eb 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -447,6 +447,7 @@ enum {
 #define KVM_ARM_VCPU_PVTIME_CTRL	2
 #define   KVM_ARM_VCPU_PVTIME_IPA	0
 #define KVM_ARM_VCPU_SPE_CTRL		3
+#define   KVM_ARM_VCPU_SPE_IRQ		0
 
 /* KVM_IRQ_LINE irq field index values */
 #define KVM_ARM_IRQ_VCPU2_SHIFT		28
diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
index 56c5f937fcdb..f953b235b458 100644
--- a/arch/arm64/kvm/guest.c
+++ b/arch/arm64/kvm/guest.c
@@ -920,7 +920,9 @@ int kvm_arm_vcpu_arch_set_attr(struct kvm_vcpu *vcpu,
 		ret = kvm_arm_pvtime_set_attr(vcpu, attr);
 		break;
 	case KVM_ARM_VCPU_SPE_CTRL:
+		mutex_lock(&vcpu->kvm->arch.config_lock);
 		ret = kvm_spe_set_attr(vcpu, attr);
+		mutex_unlock(&vcpu->kvm->arch.config_lock);
 		break;
 	default:
 		ret = -ENXIO;
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index fe45ad4c703a..dd5f80318ba4 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -59,17 +59,99 @@ bool kvm_supports_spe(void)
 	return !list_empty(&spe_pmus);
 }
 
+static bool kvm_spe_irq_is_valid(struct kvm *kvm, int irq)
+{
+	struct kvm_vcpu *vcpu;
+	struct kvm_vcpu_spe *vcpu_spe;
+	unsigned long i;
+
+	kvm_for_each_vcpu(i, vcpu, kvm) {
+		vcpu_spe = &vcpu->arch.vcpu_spe;
+
+		if (!vcpu_spe->irq_num)
+			continue;
+
+		if (vcpu_spe->irq_num != irq)
+			return false;
+	}
+
+	return true;
+}
+
 int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
+	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
+	struct kvm *kvm = vcpu->kvm;
+
+	lockdep_assert_held(&kvm->arch.config_lock);
+
+	if (!vcpu_has_spe(vcpu))
+		return -ENODEV;
+
+	switch (attr->attr) {
+	case KVM_ARM_VCPU_SPE_IRQ: {
+		int __user *uaddr = (int __user *)(long)attr->addr;
+		int irq;
+
+		if (!irqchip_in_kernel(kvm))
+			return -EINVAL;
+
+		if (get_user(irq, uaddr))
+			return -EFAULT;
+
+		if (!irq_is_ppi(kvm, irq))
+			return -EINVAL;
+
+		if (!kvm_spe_irq_is_valid(kvm, irq))
+			return -EINVAL;
+
+		vcpu_spe->irq_num = irq;
+		return 0;
+	}
+	}
+
 	return -ENXIO;
 }
 
 int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
+	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
+	struct kvm *kvm = vcpu->kvm;
+
+	if (!vcpu_has_spe(vcpu))
+		return -ENODEV;
+
+	switch (attr->attr) {
+	case KVM_ARM_VCPU_SPE_IRQ: {
+		int __user *uaddr = (int __user *)(long)attr->addr;
+		int irq;
+
+		if (!irqchip_in_kernel(kvm))
+			return -EINVAL;
+
+		if (!vcpu_spe->irq_num)
+			return -ENXIO;
+
+		irq = vcpu_spe->irq_num;
+		if (put_user(irq, uaddr))
+			return -EFAULT;
+
+		return 0;
+	}
+	}
+
 	return -ENXIO;
 }
 
 int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
+	if (!vcpu_has_spe(vcpu))
+		return -ENODEV;
+
+	switch(attr->attr) {
+	case KVM_ARM_VCPU_SPE_IRQ:
+		return 0;
+	}
+
 	return -ENXIO;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (11 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:39   ` sashiko-bot
  2026-10-28 13:11   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE Alexandru Elisei
                   ` (15 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

To support systems where there is more than one SPE instance, or where not
all the PEs have SPE, add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_ID) for
userspace to set the SPE instance it wants the virtual machine to use.
Similar to the PMU, it is entirely up to userspace to make sure the VCPUs
are run only on the physical CPUs which share this SPE instance.

If the ioctl is called for multiple VCPUs, userspace must use the same
SPE identifier for each of the VCPUs.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/devices/vcpu.rst | 32 +++++++++
 arch/arm64/include/asm/kvm_host.h       |  4 ++
 arch/arm64/include/asm/kvm_spe.h        | 19 ++++++
 arch/arm64/include/uapi/asm/kvm.h       |  1 +
 arch/arm64/kvm/arm.c                    | 43 ++++++++++++
 arch/arm64/kvm/pmu-emul.c               | 11 ++-
 arch/arm64/kvm/spe.c                    | 90 +++++++++++++++++++++++++
 7 files changed, 198 insertions(+), 2 deletions(-)

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index d30b2f7fec9a..d60dd1daa67f 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -329,3 +329,35 @@ Specifies the Profiling Buffer management interrupt number. The interrupt number
 must be a PPI and the interrupt number must be the same for each VCPU. Arm
 recommends 21 as the interrupt number. SPE virtualization requires an in-kernel
 vGIC implementation.
+
+5.2 ATTRIBUTE: KVM_ARM_VCPU_SPE_ID
+------------------------------------------
+
+:Parameters: in kvm_device_attr.addr the address to an int representing the SPE
+             identifier.
+
+:Returns:
+
+	 =======  =========================================================
+	 -EBUSY   Virtual machine has already run
+	 -EFAULT  Error accessing the SPE identifier
+	 -EINVAL  A different SPE instance already assigned, or no physical
+                  CPUs found
+	 -ENXIO   SPE not supported or not properly configured, or the SPE
+                  module not loaded
+	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set
+	 =======  =========================================================
+
+Required.
+
+Request that the VCPU uses the specified SPE instance. The SPE identifier can be
+read from the 'type' file for the desired SPE instance under /sys/devices (or,
+equivalent, /sys/bus/event_source). Must be set for at least one VCPU, in which
+case all the other VCPUs will use the same SPE instance. Once a SPE instance has
+been set, attempting to set a different one will result in an error.
+
+Similar to KVM_ARM_VCPU_PMU_V3_CTRL(KVM_ARM_VCPU_PMU_SET_PMU), userspace is
+responsible for making sure that the VCPU is run only on physical CPUs which
+share the specified SPE instance. If the VCPU has a PMU assigned, and there are
+no physical CPUs which share the same SPE and PMU instance, an error is
+returned.
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index e7332bd82bf3..10fa223bdbda 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -382,6 +382,7 @@ struct kvm_arch {
 	 */
 	unsigned long *pmu_filter;
 	struct arm_pmu *arm_pmu;
+	struct kvm_spe kvm_spe;
 
 	cpumask_var_t supported_cpus;
 
@@ -1499,6 +1500,9 @@ void kvm_vcpu_put_vhe(struct kvm_vcpu *vcpu);
 int __init kvm_set_ipa_limit(void);
 u32 kvm_get_pa_bits(struct kvm *kvm);
 
+int kvm_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus,
+			      cpumask_t *spe_cpus);
+
 #define __KVM_HAVE_ARCH_VM_ALLOC
 struct kvm *kvm_arch_alloc_vm(void);
 
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 8ded2d821bff..b8afb419c851 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -11,6 +11,11 @@ struct kvm_vcpu;
 struct kvm_device_attr;
 
 #ifdef CONFIG_KVM_ARM_SPE
+struct arm_spe_pmu;
+struct kvm_spe {
+	struct arm_spe_pmu *spe_pmu;
+};
+
 struct kvm_vcpu_spe {
 	int irq_num;		/* Buffer management interrupt number */
 };
@@ -20,10 +25,17 @@ bool kvm_supports_spe(void);
 #define vcpu_has_spe(vcpu)					\
 	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
 
+void kvm_spe_destroy_vm(struct kvm *kvm);
+
 int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
+
+bool kvm_spe_pmu_set(struct kvm *kvm);
 #else
+struct kvm_spe {
+};
+
 struct kvm_vcpu_spe {
 };
 
@@ -34,6 +46,9 @@ static __always_inline bool kvm_supports_spe(void)
 
 #define vcpu_has_spe(vcpu)	false
 
+static inline void kvm_spe_destroy_vm(struct kvm *kvm)
+{
+}
 static inline int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
 	return -ENXIO;
@@ -46,6 +61,10 @@ static inline int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr
 {
 	return -ENXIO;
 }
+static inline bool kvm_spe_pmu_set(struct kvm *kvm)
+{
+	return false;
+}
 #endif /* CONFIG_KVM_ARM_SPE */
 
 #endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index b9e5019181eb..8ffa3e89d38c 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -448,6 +448,7 @@ enum {
 #define   KVM_ARM_VCPU_PVTIME_IPA	0
 #define KVM_ARM_VCPU_SPE_CTRL		3
 #define   KVM_ARM_VCPU_SPE_IRQ		0
+#define   KVM_ARM_VCPU_SPE_ID		1
 
 /* KVM_IRQ_LINE irq field index values */
 #define KVM_ARM_IRQ_VCPU2_SHIFT		28
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 763b102dc51f..132df44c3088 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -336,6 +336,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
 	kfree(kvm->arch.sysreg_masks);
 	kvm_destroy_vcpus(kvm);
 
+	kvm_spe_destroy_vm(kvm);
+
 	kvm_unshare_hyp(kvm, kvm + 1);
 
 	kvm_arm_teardown_hypercalls(kvm);
@@ -612,6 +614,47 @@ void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu)
 
 }
 
+int kvm_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus,
+			      cpumask_t *spe_cpus)
+{
+	cpumask_t *supported_cpus;
+	cpumask_t *cpus;
+	int ret;
+
+	if (!zalloc_cpumask_var(&supported_cpus, GFP_KERNEL_ACCOUNT))
+		return -ENOMEM;
+
+	cpumask_copy(supported_cpus, kvm->arch.supported_cpus);
+
+	lockdep_assert_held(&kvm->arch.config_lock);
+
+	ret = -EINVAL;
+	/* Cannot update for SPE and PMU at the same time. */
+	if (WARN_ON_ONCE(pmu_cpus && spe_cpus))
+		goto out;
+
+	if (pmu_cpus)
+		cpus = pmu_cpus;
+	else
+		cpus = spe_cpus;
+
+	if ((spe_cpus && kvm->arch.arm_pmu) ||
+	    (pmu_cpus && kvm_spe_pmu_set(kvm)))
+		cpumask_and(supported_cpus, cpus, supported_cpus);
+	else
+		cpumask_copy(supported_cpus, cpus);
+
+	if (cpumask_empty(supported_cpus))
+		goto out;
+
+	cpumask_copy(kvm->arch.supported_cpus, supported_cpus);
+	ret = 0;
+out:
+	free_cpumask_var(supported_cpus);
+
+	return ret;
+}
+
 static void vcpu_set_pauth_traps(struct kvm_vcpu *vcpu)
 {
 	if (vcpu_has_ptrauth(vcpu) && !is_protected_kvm_enabled()) {
diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
index 5b1af7e2176f..59930e1bf357 100644
--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ -1085,6 +1085,11 @@ int kvm_arm_set_default_pmu(struct kvm *kvm)
 	return 0;
 }
 
+static int kvm_pmu_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus)
+{
+	return kvm_update_supported_cpus(kvm, pmu_cpus, NULL);
+}
+
 static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
 {
 	struct kvm *kvm = vcpu->kvm;
@@ -1104,8 +1109,11 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
 				break;
 			}
 
+			ret = kvm_pmu_update_supported_cpus(kvm, &arm_pmu->supported_cpus);
+			if (ret)
+				break;
 			kvm_arm_set_pmu(kvm, arm_pmu);
-			cpumask_copy(kvm->arch.supported_cpus, &arm_pmu->supported_cpus);
+
 
 			/*
 			 * Since a specific PMU is explicitly selected,
@@ -1117,7 +1125,6 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
 				kvm->arch.pmmir_slots =
 					FIELD_GET(ARMV8_PMU_SLOTS,
 						  arm_pmu->reg_pmmir);
-			ret = 0;
 			break;
 		}
 	}
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index dd5f80318ba4..87fcdbbcbd67 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -59,6 +59,71 @@ bool kvm_supports_spe(void)
 	return !list_empty(&spe_pmus);
 }
 
+void kvm_spe_destroy_vm(struct kvm *kvm)
+{
+	struct arm_spe_pmu *spe_pmu;
+
+	guard(mutex)(&spe_pmus_lock);
+
+	spe_pmu = kvm->arch.kvm_spe.spe_pmu;
+	if (!spe_pmu)
+		return;
+
+	module_put(spe_pmu->pmu.module);
+}
+
+bool kvm_spe_pmu_set(struct kvm *kvm)
+{
+	return !!kvm->arch.kvm_spe.spe_pmu;
+}
+
+static int kvm_spe_update_supported_cpus(struct kvm *kvm, cpumask_t *spe_cpus)
+{
+	return kvm_update_supported_cpus(kvm, NULL, spe_cpus);
+}
+
+static int kvm_spe_set_spe_id(struct kvm_vcpu *vcpu, int spe_id)
+{
+	struct kvm *kvm = vcpu->kvm;
+	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
+	struct arm_spe_pmu *existing_spe_pmu, *spe_pmu = NULL;
+	struct spe_pmu_entry *entry;
+
+	if (kvm_vm_has_ran_once(kvm))
+		return -EBUSY;
+
+	guard(mutex)(&spe_pmus_lock);
+
+	list_for_each_entry(entry, &spe_pmus, link) {
+		if (entry->spe_pmu->pmu.type == spe_id) {
+			spe_pmu = entry->spe_pmu;
+			break;
+		}
+	}
+
+	if (!spe_pmu)
+		return -ENXIO;
+
+	existing_spe_pmu = kvm_spe->spe_pmu;
+	if (existing_spe_pmu) {
+		if (existing_spe_pmu != spe_pmu)
+			return -EINVAL;
+		return 0;
+	}
+
+	if (!try_module_get(spe_pmu->pmu.module))
+		return -ENXIO;
+
+	if (kvm_spe_update_supported_cpus(kvm, &spe_pmu->supported_cpus)) {
+		module_put(spe_pmu->pmu.module);
+		return -EINVAL;
+	}
+
+	kvm_spe->spe_pmu = spe_pmu;
+
+	return 0;
+}
+
 static bool kvm_spe_irq_is_valid(struct kvm *kvm, int irq)
 {
 	struct kvm_vcpu *vcpu;
@@ -108,6 +173,15 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 		vcpu_spe->irq_num = irq;
 		return 0;
 	}
+	case KVM_ARM_VCPU_SPE_ID: {
+		int __user *uaddr = (int __user *)(long)attr->addr;
+		int spe_id;
+
+		if (get_user(spe_id, uaddr))
+			return -EFAULT;
+
+		return kvm_spe_set_spe_id(vcpu, spe_id);
+	}
 	}
 
 	return -ENXIO;
@@ -117,6 +191,7 @@ int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
 	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
 	struct kvm *kvm = vcpu->kvm;
+	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
 
 	if (!vcpu_has_spe(vcpu))
 		return -ENODEV;
@@ -138,6 +213,20 @@ int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 
 		return 0;
 	}
+	case KVM_ARM_VCPU_SPE_ID: {
+		struct arm_spe_pmu *spe_pmu = kvm_spe->spe_pmu;
+		int __user *uaddr = (int __user *)(long)attr->addr;
+		int spe_id;
+
+		if (!spe_pmu)
+			return -ENXIO;
+
+		spe_id = spe_pmu->pmu.type;
+		if (put_user(spe_id, uaddr))
+			return -EFAULT;
+
+		return 0;
+	}
 	}
 
 	return -ENXIO;
@@ -150,6 +239,7 @@ int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 
 	switch(attr->attr) {
 	case KVM_ARM_VCPU_SPE_IRQ:
+	case KVM_ARM_VCPU_SPE_ID:
 		return 0;
 	}
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (12 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:28   ` sashiko-bot
                     ` (2 more replies)
  2026-09-03 16:06 ` [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance Alexandru Elisei
                   ` (14 subsequent siblings)
  28 siblings, 3 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark
  Cc: Sudeep Holla

Add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_INIT) VCPU ioctl to initialize
SPE. Initialization must be done exactly once for each VCPU.

[ Alexandru E: Split from "KVM: arm64: Add a new VCPU device control group
	   for SPE" ]

Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/devices/vcpu.rst | 28 ++++++++++++++++++++---
 arch/arm64/include/asm/kvm_spe.h        |  6 +++++
 arch/arm64/include/uapi/asm/kvm.h       |  1 +
 arch/arm64/kvm/arm.c                    |  6 +++++
 arch/arm64/kvm/spe.c                    | 30 +++++++++++++++++++++++++
 5 files changed, 68 insertions(+), 3 deletions(-)

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index d60dd1daa67f..e2eac48b9d28 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -317,6 +317,7 @@ From the destination VMM process:
 Returns:
 
 	 =======  ==========================================================
+	 -EBUSY   SPE already initialized
 	 -EFAULT  Error accessing the buffer management interrupt number
 	 -EINVAL  Invalid interrupt number or not using an in-kernel irqchip
 	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set
@@ -338,15 +339,15 @@ vGIC implementation.
 
 :Returns:
 
-	 =======  =========================================================
-	 -EBUSY   Virtual machine has already run
+	 =======  ===========================================================
+	 -EBUSY   Virtual machine has already run, or SPE already initialized
 	 -EFAULT  Error accessing the SPE identifier
 	 -EINVAL  A different SPE instance already assigned, or no physical
                   CPUs found
 	 -ENXIO   SPE not supported or not properly configured, or the SPE
                   module not loaded
 	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set
-	 =======  =========================================================
+	 =======  ===========================================================
 
 Required.
 
@@ -361,3 +362,24 @@ responsible for making sure that the VCPU is run only on physical CPUs which
 share the specified SPE instance. If the VCPU has a PMU assigned, and there are
 no physical CPUs which share the same SPE and PMU instance, an error is
 returned.
+
+5.3 ATTRIBUTE: KVM_ARM_VCPU_SPE_INIT
+-----------------------------------
+
+:Parameters: no additional parameter in kvm_device_attr.addr
+
+Returns:
+
+	 =======  ============================================
+	 -EBUSY   SPE already initialized for this VCPU
+	 -ENXIO   SPE not supported or not properly configured
+	 =======  ============================================
+
+Required.
+
+Request initialization of the Statistical Profiling Extension for this VCPU.
+Must be done last, after SPE has been fully configured for the VCPU, and after
+the in-kernel irqchip has been initialized.
+
+KVM will refuse to run the VCPU and KVM_RUN will return an error if the SPE
+feature has been set for the VCPU, but SPE hasn't been initialized.
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index b8afb419c851..a6999709b298 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -18,6 +18,7 @@ struct kvm_spe {
 
 struct kvm_vcpu_spe {
 	int irq_num;		/* Buffer management interrupt number */
+	bool initialized;	/* SPE initialized for the VCPU */
 };
 
 bool kvm_supports_spe(void);
@@ -25,6 +26,7 @@ bool kvm_supports_spe(void);
 #define vcpu_has_spe(vcpu)					\
 	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
 
+int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu);
 void kvm_spe_destroy_vm(struct kvm *kvm);
 
 int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
@@ -46,6 +48,10 @@ static __always_inline bool kvm_supports_spe(void)
 
 #define vcpu_has_spe(vcpu)	false
 
+static inline int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
+{
+	return 0;
+}
 static inline void kvm_spe_destroy_vm(struct kvm *kvm)
 {
 }
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index 8ffa3e89d38c..c695aae484d3 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -449,6 +449,7 @@ enum {
 #define KVM_ARM_VCPU_SPE_CTRL		3
 #define   KVM_ARM_VCPU_SPE_IRQ		0
 #define   KVM_ARM_VCPU_SPE_ID		1
+#define   KVM_ARM_VCPU_SPE_INIT		2
 
 /* KVM_IRQ_LINE irq field index values */
 #define KVM_ARM_IRQ_VCPU2_SHIFT		28
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 132df44c3088..c9a026c61ce7 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1026,6 +1026,12 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
 			return ret;
 	}
 
+	if (vcpu_has_spe(vcpu)) {
+		ret = kvm_spe_vcpu_first_run_init(vcpu);
+		if (ret)
+			return ret;
+	}
+
 	ret = vgic_v5_finalize_ppi_state(kvm);
 	if (ret)
 		return ret;
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 87fcdbbcbd67..fa87d224c8af 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -59,6 +59,16 @@ bool kvm_supports_spe(void)
 	return !list_empty(&spe_pmus);
 }
 
+int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
+
+	if (!vcpu_spe->initialized)
+		return -EINVAL;
+
+	return 0;
+}
+
 void kvm_spe_destroy_vm(struct kvm *kvm)
 {
 	struct arm_spe_pmu *spe_pmu;
@@ -147,12 +157,16 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
 	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
 	struct kvm *kvm = vcpu->kvm;
+	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
 
 	lockdep_assert_held(&kvm->arch.config_lock);
 
 	if (!vcpu_has_spe(vcpu))
 		return -ENODEV;
 
+	if (vcpu_spe->initialized)
+		return -EBUSY;
+
 	switch (attr->attr) {
 	case KVM_ARM_VCPU_SPE_IRQ: {
 		int __user *uaddr = (int __user *)(long)attr->addr;
@@ -182,6 +196,21 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 
 		return kvm_spe_set_spe_id(vcpu, spe_id);
 	}
+	case KVM_ARM_VCPU_SPE_INIT:
+		if (!vcpu_spe->irq_num)
+			return -ENXIO;
+
+		if (!kvm_spe->spe_pmu)
+			return -ENXIO;
+
+		if (!vgic_initialized(kvm))
+			return -ENXIO;
+
+		if (kvm_vgic_set_owner(vcpu, vcpu_spe->irq_num, vcpu_spe))
+			return -ENXIO;
+
+		vcpu_spe->initialized = true;
+		return 0;
 	}
 
 	return -ENXIO;
@@ -240,6 +269,7 @@ int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 	switch(attr->attr) {
 	case KVM_ARM_VCPU_SPE_IRQ:
 	case KVM_ARM_VCPU_SPE_ID:
+	case KVM_ARM_VCPU_SPE_INIT:
 		return 0;
 	}
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (13 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:41   ` sashiko-bot
  2026-10-28 16:41   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context Alexandru Elisei
                   ` (13 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

KVM allows SPE to be virtualized on heterogeneous system, where hardware
can implement multiple SPE instances for different CPUs, or where SPE
might even be missing on some of the physical CPUs.

KVM resets the VCPU ID registers following a KVM_ARM_VCPU_INIT ioctl.
Using the value for ID_AA64DFR0_EL1.PMSVer on the physical CPU where the
ioctl is executing is unreliable, since userspace might want to later
assign a different SPE instance, with a different PMSVer value. Worse yet,
it is be possible that the VCPU reset is being performed on a physical CPU
without SPE. Be consistent and use PMSVer for the SPE instance assigned to
the VM to initialize ID_AA64DFR0_EL1.PMSVer.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_spe.h | 10 ++++++++++
 arch/arm64/kvm/spe.c             | 24 ++++++++++++++++++++++++
 arch/arm64/kvm/sys_regs.c        |  8 +++++++-
 3 files changed, 41 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index a6999709b298..4bae3e6b05c8 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -29,11 +29,14 @@ bool kvm_supports_spe(void);
 int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu);
 void kvm_spe_destroy_vm(struct kvm *kvm);
 
+void kvm_spe_finalize_idregs(struct kvm *kvm);
+
 int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 
 bool kvm_spe_pmu_set(struct kvm *kvm);
+u8 kvm_spe_get_pmsver(struct kvm *kvm);
 #else
 struct kvm_spe {
 };
@@ -55,6 +58,9 @@ static inline int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
 static inline void kvm_spe_destroy_vm(struct kvm *kvm)
 {
 }
+static inline void kvm_spe_finalize_idregs(struct kvm *kvm)
+{
+}
 static inline int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
 	return -ENXIO;
@@ -71,6 +77,10 @@ static inline bool kvm_spe_pmu_set(struct kvm *kvm)
 {
 	return false;
 }
+static inline u8 kvm_spe_get_pmsver(struct kvm *kvm)
+{
+	return 0;
+}
 #endif /* CONFIG_KVM_ARM_SPE */
 
 #endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index fa87d224c8af..4426d614a9e0 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -87,6 +87,30 @@ bool kvm_spe_pmu_set(struct kvm *kvm)
 	return !!kvm->arch.kvm_spe.spe_pmu;
 }
 
+void kvm_spe_finalize_idregs(struct kvm *kvm)
+{
+	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
+	u64 reg;
+
+	if (!spe_pmu)
+		return;
+
+	reg = kvm_read_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1);
+	reg &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
+	reg |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer, spe_pmu->pmsver);
+	kvm_set_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1, reg);
+}
+
+u8 kvm_spe_get_pmsver(struct kvm *kvm)
+{
+	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
+
+	if (!spe_pmu)
+		return 0;
+
+	return spe_pmu->pmsver;
+}
+
 static int kvm_spe_update_supported_cpus(struct kvm *kvm, cpumask_t *spe_cpus)
 {
 	return kvm_update_supported_cpus(kvm, NULL, spe_cpus);
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 44aae52c473d..a6bb6884b965 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -2220,8 +2220,11 @@ static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
 		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMUVer,
 				      kvm_arm_pmu_get_pmuver_limit());
 
-	/* Hide SPE from guests */
+	/* Only initialize the SPE version if the vCPU has the feature. */
 	val &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
+	if (vcpu_has_spe(vcpu))
+		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer,
+				      kvm_spe_get_pmsver(vcpu->kvm));
 
 	/* Hide BRBE from guests */
 	val &= ~ID_AA64DFR0_EL1_BRBE_MASK;
@@ -5928,6 +5931,9 @@ int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu)
 		kvm_vgic_finalize_idregs(kvm);
 	}
 
+	if (vcpu_has_spe(vcpu))
+		kvm_spe_finalize_idregs(kvm);
+
 	return 0;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (14 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:32   ` sashiko-bot
  2026-10-29 15:42   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes Alexandru Elisei
                   ` (12 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Add the SPE registers to the VCPU context. Guest writes to the service bit,
which change the state of the SPE maintenance interrupt, will be handled in
a subsequent patch.

Only the buffer registers are trapped, because KVM will need to keep track
of the buffer enabled state for dirty page logging, errata handling and to
sanitise writes to PMBLIMITR_EL1 based on the features exposed to the VM.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_host.h | 13 +++++++
 arch/arm64/include/asm/kvm_spe.h  | 11 ++++++
 arch/arm64/kvm/spe.c              | 29 ++++++++++++++++
 arch/arm64/kvm/sys_regs.c         | 56 ++++++++++++++++++++++++-------
 4 files changed, 96 insertions(+), 13 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 10fa223bdbda..b92b19229c36 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -483,6 +483,19 @@ enum vcpu_sysreg {
 	PMOVSSET_EL0,	/* Overflow Flag Status Set Register */
 	PMUSERENR_EL0,	/* User Enable Register */
 
+	/* SPE registers */
+	PMSCR_EL1,
+	PMSNEVFR_EL1,
+	PMSICR_EL1,
+	PMSIRR_EL1,
+	PMSFCR_EL1,
+	PMSEVFR_EL1,
+	PMSLATFR_EL1,
+	PMBLIMITR_EL1,
+	PMBPTR_EL1,
+	PMBSR_EL1,
+	PMSDSFR_EL1,
+
 	/* Pointer Authentication Registers in a strict increasing order. */
 	APIAKEYLO_EL1,
 	APIAKEYHI_EL1,
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 4bae3e6b05c8..56dc9e660bef 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -37,6 +37,9 @@ int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 
 bool kvm_spe_pmu_set(struct kvm *kvm);
 u8 kvm_spe_get_pmsver(struct kvm *kvm);
+
+bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val);
+u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg);
 #else
 struct kvm_spe {
 };
@@ -81,6 +84,14 @@ static inline u8 kvm_spe_get_pmsver(struct kvm *kvm)
 {
 	return 0;
 }
+static inline bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
+{
+	return true;
+}
+static inline u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
+{
+	return 0;
+}
 #endif /* CONFIG_KVM_ARM_SPE */
 
 #endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 4426d614a9e0..6a00a44c73ed 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -7,6 +7,7 @@
 #include <linux/kvm_host.h>
 #include <linux/perf/arm_spe_pmu.h>
 
+#include <asm/kvm_emulate.h>
 #include <asm/kvm_spe.h>
 #include <asm/sysreg.h>
 
@@ -82,6 +83,34 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
 	module_put(spe_pmu->pmu.module);
 }
 
+bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
+{
+	switch (reg) {
+	case PMBLIMITR_EL1:
+	case PMBSR_EL1:
+	case PMBPTR_EL1:
+		__vcpu_assign_sys_reg(vcpu, reg, val);
+		break;
+	default:
+		WARN_ON_ONCE("unexpected trap");
+	}
+
+	return true;
+}
+
+u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
+{
+	switch (reg) {
+	case PMBLIMITR_EL1:
+	case PMBSR_EL1:
+	case PMBPTR_EL1:
+		return __vcpu_sys_reg(vcpu, reg);
+	default:
+		WARN_ON_ONCE("unexpected trap");
+		return 0;
+	}
+}
+
 bool kvm_spe_pmu_set(struct kvm *kvm)
 {
 	return !!kvm->arch.kvm_spe.spe_pmu;
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index a6bb6884b965..89b01b6ac783 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1554,6 +1554,28 @@ static int set_pmcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
 	return 0;
 }
 
+static unsigned int spe_visibility(const struct kvm_vcpu *vcpu,
+				   const struct sys_reg_desc *r)
+{
+	if (vcpu_has_spe(vcpu))
+		return 0;
+
+	return REG_HIDDEN;
+}
+
+static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
+			   const struct sys_reg_desc *r)
+{
+	u64 val = p->regval;
+	int reg = r->reg;
+
+	if (p->is_write)
+		return kvm_spe_write_sysreg(vcpu, reg, val);
+
+	p->regval = kvm_spe_read_sysreg(vcpu, reg);
+	return true;
+}
+
 /* Silly macro to expand the DBG{BCR,BVR,WVR,WCR}n_EL1 registers in one go */
 #define DBG_BCR_BVR_WCR_WVR_EL1(n)					\
 	{ SYS_DESC(SYS_DBGBVRn_EL1(n)),					\
@@ -1586,6 +1608,14 @@ static int set_pmcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
 	  .reset = reset_pmevtyper,					\
 	  .access = access_pmu_evtyper, .reg = (PMEVTYPER0_EL0 + n), }
 
+#define SPE_SYS_REG(name)						\
+	SYS_DESC(SYS_##name), .reg = name, .access = access_spe_reg,	\
+	.reset = reset_val, .val = 0, .visibility = spe_visibility
+
+#define SPE_UNTRAPPED_REG(name)						\
+	SYS_DESC(SYS_##name), .reg = name, .access = undef_access,	\
+	.reset = reset_val, .val = 0, .visibility = spe_visibility
+
 /* Macro to expand the AMU counter and type registers*/
 #define AMU_AMEVCNTR0_EL0(n) { SYS_DESC(SYS_AMEVCNTR0_EL0(n)), undef_access }
 #define AMU_AMEVTYPER0_EL0(n) { SYS_DESC(SYS_AMEVTYPER0_EL0(n)), undef_access }
@@ -3525,19 +3555,19 @@ static const struct sys_reg_desc sys_reg_descs[] = {
 	{ SYS_DESC(SYS_FAR_EL1), access_vm_reg, reset_unknown, FAR_EL1 },
 	{ SYS_DESC(SYS_PAR_EL1), NULL, reset_unknown, PAR_EL1 },
 
-	{ SYS_DESC(SYS_PMSCR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSNEVFR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSICR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSIRR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSFCR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSEVFR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSLATFR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSIDR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMBLIMITR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMBPTR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMBSR_EL1), undef_access },
-	{ SYS_DESC(SYS_PMSDSFR_EL1), undef_access },
-	/* PMBIDR_EL1 is not trapped */
+	{ SPE_UNTRAPPED_REG(PMSCR_EL1) },
+	{ SPE_UNTRAPPED_REG(PMSNEVFR_EL1) },
+	{ SPE_UNTRAPPED_REG(PMSICR_EL1) },
+	{ SPE_UNTRAPPED_REG(PMSIRR_EL1) },
+	{ SPE_UNTRAPPED_REG(PMSFCR_EL1) },
+	{ SPE_UNTRAPPED_REG(PMSEVFR_EL1) },
+	{ SPE_UNTRAPPED_REG(PMSLATFR_EL1) },
+	{ SYS_DESC(SYS_PMSIDR_EL1), .access = undef_access },
+	{ SPE_SYS_REG(PMBLIMITR_EL1) },
+	{ SPE_SYS_REG(PMBPTR_EL1) },
+	{ SPE_SYS_REG(PMBSR_EL1) },
+	{ SPE_UNTRAPPED_REG(PMSDSFR_EL1) },
+	/* PMBIDR_EL1 is not trapped and read-only */
 
 	{ PMU_SYS_REG(PMINTENSET_EL1),
 	  .access = access_pminten, .reg = PMINTENSET_EL1,
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (15 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:37   ` sashiko-bot
  2026-10-29 17:23   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS} Alexandru Elisei
                   ` (11 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

PMBLIMITR_EL1 is special, because it has feature enable fields. If those
features are hidden from a guest, ignore them when the guest or the VMM
attempt to set them.

The least significant bits of the buffer limit address are RES0 based on
the smallest granule implemented. Make sure they are treated as zero even
if the smallest granule exposed to the VM is larger than what the hardware
implements.

Note that it is up to the user to write to the SPE buffer registers only
after the VM features have been configured in the VM id registers.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/devices/vcpu.rst |  3 ++
 arch/arm64/include/asm/kvm_spe.h        |  7 +++++
 arch/arm64/kvm/spe.c                    | 40 +++++++++++++++++++++++--
 arch/arm64/kvm/sys_regs.c               | 38 +++++++++++++++++++++--
 4 files changed, 83 insertions(+), 5 deletions(-)

diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
index e2eac48b9d28..eb352760869c 100644
--- a/Documentation/virt/kvm/devices/vcpu.rst
+++ b/Documentation/virt/kvm/devices/vcpu.rst
@@ -383,3 +383,6 @@ the in-kernel irqchip has been initialized.
 
 KVM will refuse to run the VCPU and KVM_RUN will return an error if the SPE
 feature has been set for the VCPU, but SPE hasn't been initialized.
+
+Attempting to access any of the SPE registers until SPE has been initialized on
+the VCPU will fail with -EBUSY.
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 56dc9e660bef..3d8e9720daca 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -14,6 +14,7 @@ struct kvm_device_attr;
 struct arm_spe_pmu;
 struct kvm_spe {
 	struct arm_spe_pmu *spe_pmu;
+	u64 pmblimitr_el1_res0_mask;
 };
 
 struct kvm_vcpu_spe {
@@ -26,6 +27,8 @@ bool kvm_supports_spe(void);
 #define vcpu_has_spe(vcpu)					\
 	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
 
+bool kvm_vcpu_spe_initialized(struct kvm_vcpu *vcpu);
+
 int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu);
 void kvm_spe_destroy_vm(struct kvm *kvm);
 
@@ -54,6 +57,10 @@ static __always_inline bool kvm_supports_spe(void)
 
 #define vcpu_has_spe(vcpu)	false
 
+static inline bool kvm_vcpu_spe_initialized(struct kvm_vcpu *vcpu)
+{
+	return false;
+}
 static inline int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
 {
 	return 0;
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 6a00a44c73ed..8c173a1f48bd 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -60,13 +60,43 @@ bool kvm_supports_spe(void)
 	return !list_empty(&spe_pmus);
 }
 
-int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
+bool kvm_vcpu_spe_initialized(struct kvm_vcpu *vcpu)
 {
 	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
 
-	if (!vcpu_spe->initialized)
+	return vcpu_spe->initialized;
+}
+
+static void kvm_spe_compute_pmblimitr_el1_res0_mask(struct kvm *kvm)
+{
+	u64 res0_mask = GENMASK_ULL(11, 8) | BIT(6) | GENMASK_ULL(4, 3);
+
+	if (!kvm_has_feat(kvm, ID_AA64DFR2_EL1, SPE_nVM, IMP))
+		res0_mask |= PMBLIMITR_EL1_nVM_MASK;
+
+	if (!kvm_has_feat_enum(kvm, ID_AA64MMFR0_EL1, TGRAN4, NI))
+		goto set_mask;
+
+	if (kvm_has_feat(kvm, ID_AA64MMFR0_EL1, TGRAN16, IMP))
+		res0_mask |= GENMASK_ULL(13, 12);
+	else
+		res0_mask |= GENMASK_ULL(15, 12);
+
+set_mask:
+	WRITE_ONCE(kvm->arch.kvm_spe.pmblimitr_el1_res0_mask, res0_mask);
+}
+
+int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
+{
+	struct kvm *kvm = vcpu->kvm;
+	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
+
+	if (!kvm_vcpu_spe_initialized(vcpu))
 		return -EINVAL;
 
+	if (!kvm_spe->pmblimitr_el1_res0_mask)
+		kvm_spe_compute_pmblimitr_el1_res0_mask(kvm);
+
 	return 0;
 }
 
@@ -85,8 +115,12 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
 
 bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 {
+	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
+
 	switch (reg) {
 	case PMBLIMITR_EL1:
+		val &= ~kvm_spe->pmblimitr_el1_res0_mask;
+		fallthrough;
 	case PMBSR_EL1:
 	case PMBPTR_EL1:
 		__vcpu_assign_sys_reg(vcpu, reg, val);
@@ -217,7 +251,7 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 	if (!vcpu_has_spe(vcpu))
 		return -ENODEV;
 
-	if (vcpu_spe->initialized)
+	if (kvm_vcpu_spe_initialized(vcpu))
 		return -EBUSY;
 
 	switch (attr->attr) {
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 89b01b6ac783..4c4bf88c9aa5 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1563,6 +1563,38 @@ static unsigned int spe_visibility(const struct kvm_vcpu *vcpu,
 	return REG_HIDDEN;
 }
 
+static int set_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+			       u64 val)
+{
+	int reg = r->reg;
+
+	if (!kvm_vcpu_spe_initialized(vcpu))
+		return -EBUSY;
+
+	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)
+		kvm_spe_write_sysreg(vcpu, reg, val);
+	else
+		__vcpu_assign_sys_reg(vcpu, reg, val);
+
+	return 0;
+}
+
+static int get_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
+			       u64 *val)
+{
+	int reg = r->reg;
+
+	if (!kvm_vcpu_spe_initialized(vcpu))
+		return -EBUSY;
+
+	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)
+		*val = kvm_spe_read_sysreg(vcpu, reg);
+	else
+		*val = __vcpu_sys_reg(vcpu, reg);
+
+	return 0;
+}
+
 static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 			   const struct sys_reg_desc *r)
 {
@@ -1610,11 +1642,13 @@ static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 
 #define SPE_SYS_REG(name)						\
 	SYS_DESC(SYS_##name), .reg = name, .access = access_spe_reg,	\
-	.reset = reset_val, .val = 0, .visibility = spe_visibility
+	.reset = reset_val, .val = 0, .visibility = spe_visibility,	\
+	.set_user = set_user_spe_sysreg, .get_user = get_user_spe_sysreg
 
 #define SPE_UNTRAPPED_REG(name)						\
 	SYS_DESC(SYS_##name), .reg = name, .access = undef_access,	\
-	.reset = reset_val, .val = 0, .visibility = spe_visibility
+	.reset = reset_val, .val = 0, .visibility = spe_visibility,	\
+	.set_user = set_user_spe_sysreg, .get_user = get_user_spe_sysreg
 
 /* Macro to expand the AMU counter and type registers*/
 #define AMU_AMEVCNTR0_EL0(n) { SYS_DESC(SYS_AMEVCNTR0_EL0(n)), undef_access }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS}
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (16 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:40   ` sashiko-bot
  2026-10-29 17:47   ` Lorenzo Stoakes (ARM)
  2026-09-03 16:06 ` [RFC PATCH v7 19/28] KVM: arm64: VHE: Context switch SPE state Alexandru Elisei
                   ` (10 subsequent siblings)
  28 siblings, 2 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

KVM's FGU mechanism will trap the registers introduced by FEAT_SPE_FnE and
FEAT_SPE_FDS if the feature is not present for the VM. Move the functions
that check for the presence of these features out of config.c and into
spe.c, since that's where the bulk of SPE virtualization lies.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_spe.h | 11 +++++++++++
 arch/arm64/kvm/config.c          | 30 +++++-------------------------
 arch/arm64/kvm/spe.c             | 22 ++++++++++++++++++++++
 3 files changed, 38 insertions(+), 25 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 3d8e9720daca..618051dcf59f 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -43,6 +43,9 @@ u8 kvm_spe_get_pmsver(struct kvm *kvm);
 
 bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val);
 u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg);
+
+bool kvm_spe_has_feat_spe_fne(struct kvm *kvm);
+bool kvm_spe_has_feat_spe_fds(struct kvm *kvm);
 #else
 struct kvm_spe {
 };
@@ -99,6 +102,14 @@ static inline u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
 {
 	return 0;
 }
+static inline bool kvm_spe_has_feat_spe_fne(struct kvm *kvm)
+{
+	return false;
+}
+static inline bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
+{
+	return false;
+}
 #endif /* CONFIG_KVM_ARM_SPE */
 
 #endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
index 1053676551af..78e414ed736d 100644
--- a/arch/arm64/kvm/config.c
+++ b/arch/arm64/kvm/config.c
@@ -7,6 +7,7 @@
 #include <linux/kvm_host.h>
 #include <asm/kvm_emulate.h>
 #include <asm/kvm_nested.h>
+#include <asm/kvm_spe.h>
 #include <asm/sysreg.h>
 
 /*
@@ -159,7 +160,6 @@ struct reg_feat_map_desc {
 #define FEAT_GICv3		ID_AA64PFR0_EL1, GIC, IMP
 #define FEAT_LOR		ID_AA64MMFR1_EL1, LO, IMP
 #define FEAT_SPEv1p2		ID_AA64DFR0_EL1, PMSVer, V1P2
-#define FEAT_SPEv1p4		ID_AA64DFR0_EL1, PMSVer, V1P4
 #define FEAT_SPEv1p5		ID_AA64DFR0_EL1, PMSVer, V1P5
 #define FEAT_ATS1A		ID_AA64ISAR2_EL1, ATS1A, IMP
 #define FEAT_SPECRES2		ID_AA64ISAR1_EL1, SPECRES, COSP_RCTX
@@ -292,26 +292,6 @@ static bool feat_sme_smps(struct kvm *kvm)
 		(read_sysreg_s(SYS_SMIDR_EL1) & SMIDR_EL1_SMPS));
 }
 
-static bool feat_spe_fds(struct kvm *kvm)
-{
-	/*
-	 * Revisit this if KVM ever supports SPE -- this really should
-	 * look at the guest's view of PMSIDR_EL1.
-	 */
-	return (kvm_has_feat(kvm, FEAT_SPEv1p4) &&
-		(read_sysreg_s(SYS_PMSIDR_EL1) & PMSIDR_EL1_FDS));
-}
-
-static bool feat_spe_fne(struct kvm *kvm)
-{
-	/*
-	 * Revisit this if KVM ever supports SPE -- this really should
-	 * look at the guest's view of PMSIDR_EL1.
-	 */
-	return (kvm_has_feat(kvm, FEAT_SPEv1p2) &&
-		(read_sysreg_s(SYS_PMSIDR_EL1) & PMSIDR_EL1_FnE));
-}
-
 static bool feat_trbe_mpam(struct kvm *kvm)
 {
 	/*
@@ -547,7 +527,7 @@ static const struct reg_bits_to_feat_map hdfgrtr_feat_map[] = {
 		   HDFGRTR_EL2_PMBPTR_EL1	|
 		   HDFGRTR_EL2_PMBLIMITR_EL1,
 		   FEAT_SPE),
-	NEEDS_FEAT(HDFGRTR_EL2_nPMSNEVFR_EL1, feat_spe_fne),
+	NEEDS_FEAT(HDFGRTR_EL2_nPMSNEVFR_EL1, kvm_spe_has_feat_spe_fne),
 	NEEDS_FEAT(HDFGRTR_EL2_nBRBDATA		|
 		   HDFGRTR_EL2_nBRBCTL		|
 		   HDFGRTR_EL2_nBRBIDR,
@@ -615,7 +595,7 @@ static const struct reg_bits_to_feat_map hdfgwtr_feat_map[] = {
 		   HDFGWTR_EL2_PMBPTR_EL1	|
 		   HDFGWTR_EL2_PMBLIMITR_EL1,
 		   FEAT_SPE),
-	NEEDS_FEAT(HDFGWTR_EL2_nPMSNEVFR_EL1, feat_spe_fne),
+	NEEDS_FEAT(HDFGWTR_EL2_nPMSNEVFR_EL1, kvm_spe_has_feat_spe_fne),
 	NEEDS_FEAT(HDFGWTR_EL2_nBRBDATA		|
 		   HDFGWTR_EL2_nBRBCTL,
 		   FEAT_BRBE),
@@ -851,7 +831,7 @@ static const struct reg_bits_to_feat_map hdfgrtr2_feat_map[] = {
 		   HDFGRTR2_EL2_nPMSSDATA,
 		   FEAT_PMUv3_SS),
 	NEEDS_FEAT(HDFGRTR2_EL2_nPMIAR_EL1, FEAT_SEBEP),
-	NEEDS_FEAT(HDFGRTR2_EL2_nPMSDSFR_EL1, feat_spe_fds),
+	NEEDS_FEAT(HDFGRTR2_EL2_nPMSDSFR_EL1, kvm_spe_has_feat_spe_fds),
 	NEEDS_FEAT(HDFGRTR2_EL2_nPMBMAR_EL1, FEAT_SPE_nVM),
 	NEEDS_FEAT(HDFGRTR2_EL2_nSPMACCESSR_EL1	|
 		   HDFGRTR2_EL2_nSPMCNTEN	|
@@ -884,7 +864,7 @@ static const struct reg_bits_to_feat_map hdfgwtr2_feat_map[] = {
 		   feat_pmuv3p9),
 	NEEDS_FEAT(HDFGWTR2_EL2_nPMSSCR_EL1, FEAT_PMUv3_SS),
 	NEEDS_FEAT(HDFGWTR2_EL2_nPMIAR_EL1, FEAT_SEBEP),
-	NEEDS_FEAT(HDFGWTR2_EL2_nPMSDSFR_EL1, feat_spe_fds),
+	NEEDS_FEAT(HDFGWTR2_EL2_nPMSDSFR_EL1, kvm_spe_has_feat_spe_fds),
 	NEEDS_FEAT(HDFGWTR2_EL2_nPMBMAR_EL1, FEAT_SPE_nVM),
 	NEEDS_FEAT(HDFGWTR2_EL2_nSPMACCESSR_EL1	|
 		   HDFGWTR2_EL2_nSPMCNTEN	|
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 8c173a1f48bd..3b285b45332b 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -113,6 +113,28 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
 	module_put(spe_pmu->pmu.module);
 }
 
+bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
+{
+	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
+
+	if (!spe_pmu)
+		return false;
+
+	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P4) &&
+	       FIELD_GET(PMSIDR_EL1_FDS, spe_pmu->pmsidr_el1);
+}
+
+bool kvm_spe_has_feat_spe_fne(struct kvm *kvm)
+{
+	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
+
+	if (!spe_pmu)
+		return false;
+
+	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P2) &&
+	       FIELD_GET(PMSIDR_EL1_FnE, spe_pmu->pmsidr_el1);
+}
+
 bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 {
 	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 19/28] KVM: arm64: VHE: Context switch SPE state
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (17 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS} Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:43   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 20/28] KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it Alexandru Elisei
                   ` (9 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Save and restore the SPE register state when a VCPU is run. The SPE
resources are shared between the guest and the host (the resources are not
partitioned) so KVM has to save the host state, change the owning
translation regime, and then restore the guest state before entering a
guest. The sequence is performed in reverse when exiting a guest.

Somewhat unexpectedly, the owning regime is not modified in
kvm_arm_setup_mdcr_el2(), where MDCR_EL2 is written in the VHE case. That's
because after kvm_arm_setup_mdcr_el2() there is a window where the VCPU
runs with interrupts enabled, and during this window perf on the host might
install a new event on the physical CPU via an IPI. If the owning regime
were modified in kvm_arm_setup_mdcr_el2(), the buffer would use the guest
EL1 translation tables to write to memory, leading at best to buffer
management events due to faults, at worst to random memory corruption.

Note that now the host now stops profiling KVM in
__kvm_spe_save_host_state_vhe(), as opposed to when the guest starts
executing with PMSCR_EL1.{E1SPE,E0SPE} = {0,0} following the ERET to EL1.

Buffer management event interrupts will be handled later.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_host.h |   1 +
 arch/arm64/include/asm/kvm_hyp.h  |  26 +++-
 arch/arm64/include/asm/kvm_spe.h  |   4 +
 arch/arm64/kvm/arm.c              |   4 +-
 arch/arm64/kvm/hyp/vhe/Makefile   |   1 +
 arch/arm64/kvm/hyp/vhe/debug-sr.c |  18 +++
 arch/arm64/kvm/hyp/vhe/spe-sr.c   | 209 ++++++++++++++++++++++++++++++
 arch/arm64/kvm/hyp/vhe/switch.c   |   9 ++
 arch/arm64/kvm/spe.c              |   9 ++
 9 files changed, 279 insertions(+), 2 deletions(-)
 create mode 100644 arch/arm64/kvm/hyp/vhe/spe-sr.c

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index b92b19229c36..81abfb705aeb 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -796,6 +796,7 @@ struct kvm_host_data {
 		struct kvm_guest_debug_arch regs;
 		/* Statistical profiling extension */
 		u64 pmscr_el1;
+		u64 pmscr_el2;
 		u64 pmblimitr_el1;
 		/* Self-hosted trace */
 		u64 trfcr_el1;
diff --git a/arch/arm64/include/asm/kvm_hyp.h b/arch/arm64/include/asm/kvm_hyp.h
index 4974492744cc..a98c0d445260 100644
--- a/arch/arm64/include/asm/kvm_hyp.h
+++ b/arch/arm64/include/asm/kvm_hyp.h
@@ -119,7 +119,31 @@ void __debug_switch_to_host(struct kvm_vcpu *vcpu);
 #ifdef __KVM_NVHE_HYPERVISOR__
 void __debug_save_host_buffers_nvhe(struct kvm_vcpu *vcpu);
 void __debug_restore_host_buffers_nvhe(struct kvm_vcpu *vcpu);
-#endif
+#else
+#ifdef CONFIG_KVM_ARM_SPE
+void __kvm_spe_save_host_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *host_ctxt);
+void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *guest_ctxt);
+void __kvm_spe_save_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *guest_ctxt);
+void __kvm_spe_restore_host_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *host_ctxt);
+#else
+static inline void __kvm_spe_save_host_state_vhe(struct kvm_vcpu *vcpu,
+						 struct kvm_cpu_context *host_ctxt)
+{
+}
+static inline void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu,
+						     struct kvm_cpu_context *guest_ctxt)
+{
+}
+static inline void __kvm_spe_save_guest_state_vhe(struct kvm_vcpu *vcpu,
+						  struct kvm_cpu_context *guest_ctxt)
+{
+}
+static inline void __kvm_spe_restore_host_state_vhe(struct kvm_vcpu *vcpu,
+						    struct kvm_cpu_context *host_ctxt)
+{
+}
+#endif /* CONFIG_KVM_ARM_SPE */
+#endif /* __KVM_NVHE_HYPERVISOR__ */
 
 u64 __guest_enter(struct kvm_vcpu *vcpu);
 
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 618051dcf59f..ad0ad6ace93c 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -30,6 +30,7 @@ bool kvm_supports_spe(void);
 bool kvm_vcpu_spe_initialized(struct kvm_vcpu *vcpu);
 
 int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu);
+void kvm_vcpu_put_spe_vhe(struct kvm_vcpu *vcpu);
 void kvm_spe_destroy_vm(struct kvm *kvm);
 
 void kvm_spe_finalize_idregs(struct kvm *kvm);
@@ -68,6 +69,9 @@ static inline int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
 {
 	return 0;
 }
+static inline void kvm_vcpu_put_spe_vhe(struct kvm_vcpu *vcpu)
+{
+}
 static inline void kvm_spe_destroy_vm(struct kvm *kvm)
 {
 }
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index c9a026c61ce7..5992efc4f94b 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -804,8 +804,10 @@ void kvm_arch_vcpu_put(struct kvm_vcpu *vcpu)
 
 	kvm_vcpu_put_debug(vcpu);
 	kvm_arch_vcpu_put_fp(vcpu);
-	if (has_vhe())
+	if (has_vhe()) {
+		kvm_vcpu_put_spe_vhe(vcpu);
 		kvm_vcpu_put_vhe(vcpu);
+	}
 	kvm_timer_vcpu_put(vcpu);
 	kvm_vgic_put(vcpu);
 	kvm_vcpu_pmu_restore_host(vcpu);
diff --git a/arch/arm64/kvm/hyp/vhe/Makefile b/arch/arm64/kvm/hyp/vhe/Makefile
index d6b3475145c0..e757d7a24212 100644
--- a/arch/arm64/kvm/hyp/vhe/Makefile
+++ b/arch/arm64/kvm/hyp/vhe/Makefile
@@ -11,3 +11,4 @@ CFLAGS_switch.o += -Wno-override-init
 obj-y := timer-sr.o sysreg-sr.o debug-sr.o switch.o tlb.o
 obj-y += ../vgic-v3-sr.o ../aarch32.o ../vgic-v2-cpuif-proxy.o ../entry.o \
 	 ../hyp-entry.o ../exception.o ../vgic-v5-sr.o
+obj-$(CONFIG_KVM_ARM_SPE) += spe-sr.o
diff --git a/arch/arm64/kvm/hyp/vhe/debug-sr.c b/arch/arm64/kvm/hyp/vhe/debug-sr.c
index 0100339b09e0..7b03f2611259 100644
--- a/arch/arm64/kvm/hyp/vhe/debug-sr.c
+++ b/arch/arm64/kvm/hyp/vhe/debug-sr.c
@@ -13,9 +13,27 @@
 void __debug_switch_to_guest(struct kvm_vcpu *vcpu)
 {
 	__debug_switch_to_guest_common(vcpu);
+	if (vcpu_has_spe(vcpu)) {
+		u64 mdcr_el2 = vcpu->arch.mdcr_el2;
+
+		mdcr_el2 &= ~MDCR_EL2_E2PB;
+		/* Set buffer owner to EL1 and trap the buffer registers. */
+		mdcr_el2 |= FIELD_PREP(MDCR_EL2_E2PB, MDCR_EL2_E2PB_EL1_TRAP);
+
+		/* Do not trap sampling control registers. */
+		mdcr_el2 &= ~MDCR_EL2_TPMS;
+		write_sysreg(mdcr_el2, mdcr_el2);
+		/* Synchronise MDCR_EL2 and HCR_EL2 writes. */
+		isb();
+	}
 }
 
 void __debug_switch_to_host(struct kvm_vcpu *vcpu)
 {
+	if (vcpu_has_spe(vcpu)) {
+		write_sysreg(*host_data_ptr(host_debug_state.mdcr_el2), mdcr_el2);
+		/* Synchronise MDCR_EL2 and HCR_EL2 writes. */
+		isb();
+	}
 	__debug_switch_to_host_common(vcpu);
 }
diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
new file mode 100644
index 000000000000..978620ad99fa
--- /dev/null
+++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021 - ARM Ltd
+ */
+
+#include <linux/kvm_host.h>
+
+#include <asm/kvm_hyp.h>
+#include <asm/kprobes.h>
+#include <asm/kvm_spe.h>
+
+static void __kvm_spe_save_sampling_regs(struct kvm *kvm, struct kvm_cpu_context *ctxt)
+{
+	/*
+	 * This is dependent on the VM features, and not the hardware features,
+	 * even for the host, because (a) guest accesses to PMSNEVFR_EL1 and
+	 * PMSDSFR_EL1 result in an undefined exception in the guest and (b)
+	 * they don't affect the guest unless the guest explicitly enables them,
+	 * and a well-behaved guest shouldn't enable them when the corresponding
+	 * feature is not advertised.
+	 */
+	if (kvm_spe_has_feat_spe_fne(kvm))
+		ctxt_sys_reg(ctxt, PMSNEVFR_EL1) = read_sysreg_s(SYS_PMSNEVFR_EL1);
+	ctxt_sys_reg(ctxt, PMSICR_EL1) = read_sysreg_s(SYS_PMSICR_EL1);
+	ctxt_sys_reg(ctxt, PMSIRR_EL1) = read_sysreg_s(SYS_PMSIRR_EL1);
+	ctxt_sys_reg(ctxt, PMSFCR_EL1) = read_sysreg_s(SYS_PMSFCR_EL1);
+	ctxt_sys_reg(ctxt, PMSEVFR_EL1) = read_sysreg_s(SYS_PMSEVFR_EL1);
+	ctxt_sys_reg(ctxt, PMSLATFR_EL1) = read_sysreg_s(SYS_PMSLATFR_EL1);
+	if (kvm_spe_has_feat_spe_fds(kvm))
+		ctxt_sys_reg(ctxt, PMSDSFR_EL1) = read_sysreg_s(SYS_PMSDSFR_EL1);
+}
+
+static void __kvm_spe_restore_sampling_regs(struct kvm *kvm, struct kvm_cpu_context *ctxt)
+{
+	if (kvm_spe_has_feat_spe_fne(kvm))
+		write_sysreg_s(ctxt_sys_reg(ctxt, PMSNEVFR_EL1), SYS_PMSNEVFR_EL1);
+	write_sysreg_s(ctxt_sys_reg(ctxt, PMSICR_EL1), SYS_PMSICR_EL1);
+	write_sysreg_s(ctxt_sys_reg(ctxt, PMSIRR_EL1), SYS_PMSIRR_EL1);
+	write_sysreg_s(ctxt_sys_reg(ctxt, PMSFCR_EL1), SYS_PMSFCR_EL1);
+	write_sysreg_s(ctxt_sys_reg(ctxt, PMSEVFR_EL1), SYS_PMSEVFR_EL1);
+	write_sysreg_s(ctxt_sys_reg(ctxt, PMSLATFR_EL1), SYS_PMSLATFR_EL1);
+	if (kvm_spe_has_feat_spe_fds(kvm))
+		write_sysreg_s(ctxt_sys_reg(ctxt, PMSDSFR_EL1), SYS_PMSDSFR_EL1);
+}
+
+/*
+ * Before
+ *  - PMSCR_EL2.E2SPE = 0 or 1
+ *  - PMBLIMITR_EL1.E = 0 or 1
+ *  - PMBSR_EL1.S = 0 or 1
+ *
+ * After:
+ *  - PMSCR_EL2.E2SPE = 0
+ *  - PMBLIMITR_EL1.E = 0
+ *  - PMBSR_EL1.S = 0
+ */
+void __kvm_spe_save_host_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *host_ctxt)
+{
+	u64 pmblimitr, pmscr_el2, pmbsr;
+
+	pmscr_el2 = read_sysreg_el2(SYS_PMSCR);
+	if (FIELD_GET(PMSCR_EL1_E1SPE, pmscr_el2)) {
+		write_sysreg_el2(0, SYS_PMSCR);
+		isb();
+	}
+
+	pmblimitr = read_sysreg_s(SYS_PMBLIMITR_EL1);
+	if (FIELD_GET(PMBLIMITR_EL1_E, pmblimitr)) {
+		psb_csync();
+		dsb(nsh);
+		write_sysreg_s(0, SYS_PMBLIMITR_EL1);
+		/* Update PMBPTR_EL1 and PMBSR_EL1. */
+		isb();
+	}
+
+	pmbsr = read_sysreg_s(SYS_PMBSR_EL1);
+	if (FIELD_GET(PMBSR_EL1_S, pmbsr)) {
+		write_sysreg_s(0, SYS_PMBSR_EL1);
+		isb();
+	}
+
+	__kvm_spe_save_sampling_regs(vcpu->kvm, host_ctxt);
+
+	ctxt_sys_reg(host_ctxt, PMBLIMITR_EL1) = pmblimitr;
+	ctxt_sys_reg(host_ctxt, PMBPTR_EL1) = read_sysreg_s(SYS_PMBPTR_EL1);
+	ctxt_sys_reg(host_ctxt, PMBSR_EL1) = pmbsr;
+
+	*host_data_ptr(host_debug_state.pmscr_el2) = pmscr_el2;
+}
+NOKPROBE_SYMBOL(__kvm_spe_save_host_state_vhe);
+
+static bool __kvm_spe_profiling_buffer_enabled(u64 pmblimitr, u64 pmbsr)
+{
+	return FIELD_GET(PMBLIMITR_EL1_E, pmblimitr) && !FIELD_GET(PMBSR_EL1_S, pmbsr);
+}
+
+/*
+ * Before:
+ *  - PMSCR_EL2.E2PE = 0
+ *  - PMBLIMITR_EL1.E = 0
+ *  - PMBSR_EL1.S = 0
+ *
+ * After:
+ *  - PMSCR_EL2.E2SPE = 0
+ *  - PMBLIMITR_EL1.E = 0 or 1
+ *  - PMBSR_EL1.S = 0
+ */
+void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *guest_ctxt)
+{
+	u64 pmblimitr, pmbsr;
+
+	pmbsr = ctxt_sys_reg(guest_ctxt, PMBSR_EL1);
+	pmblimitr = ctxt_sys_reg(guest_ctxt, PMBLIMITR_EL1);
+	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
+		write_sysreg_s(ctxt_sys_reg(guest_ctxt, PMBPTR_EL1), SYS_PMBPTR_EL1);
+		isb();
+		write_sysreg_s(pmblimitr, SYS_PMBLIMITR_EL1);
+		isb();
+		/* A buffer management event preserves fields in PMBSR_EL1 */
+		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
+	}
+
+	__kvm_spe_restore_sampling_regs(vcpu->kvm, guest_ctxt);
+
+	write_sysreg_el1(ctxt_sys_reg(guest_ctxt, PMSCR_EL1), SYS_PMSCR);
+	write_sysreg_el2(0, SYS_PMSCR);
+}
+NOKPROBE_SYMBOL(__kvm_spe_restore_guest_state_vhe);
+
+/*
+ * Before:
+ *  - PMSCR_EL2.E2SPE = 0
+ *  - PMBLIMITR_EL1.E = 0 or 1
+ *  - PMBSR_EL1.S = 0 or 1
+ *
+ * After:
+ *  - PMSCR_EL2.E2SPE = 0
+ *  - PMBLIMITR_EL1.E = 0
+ *  - PMBSR_EL1.S = 0
+ */
+void __kvm_spe_save_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *guest_ctxt)
+{
+	u64 pmblimitr, pmbsr;
+
+	pmblimitr = ctxt_sys_reg(guest_ctxt, PMBLIMITR_EL1);
+	pmbsr = ctxt_sys_reg(guest_ctxt, PMBSR_EL1);
+
+	/*
+	 * Update PMBPTR_EL1 and PMBSR_EL1 only if profiling was enabled
+	 * when the guest state was resumed.
+	 */
+	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
+		psb_csync();
+		dsb(nsh);
+		write_sysreg_s(0, SYS_PMBLIMITR_EL1);
+		/* Advance PMBPTR_EL1. */
+		isb();
+
+		/* Hardware updates to PMBSR_EL1 are not handled, yet. */
+		ctxt_sys_reg(guest_ctxt, PMBPTR_EL1) = read_sysreg_s(SYS_PMBPTR_EL1);
+	}
+
+	__kvm_spe_save_sampling_regs(vcpu->kvm, guest_ctxt);
+
+	ctxt_sys_reg(guest_ctxt, PMSCR_EL1) = read_sysreg_el1(SYS_PMSCR);
+}
+NOKPROBE_SYMBOL(__kvm_spe_save_guest_state_vhe);
+
+/*
+ * Before:
+ *  - PMSCR_EL2.E2SPE = 0
+ *  - PMBLIMITR_EL1.E = 0
+ *  - PMBSR_EL1.S = 0
+ *
+ * After:
+ *  - PMSCR_EL2.E2SPE = 0 or 1
+ *  - PMBLIMITR_EL1.E = 0 or 1
+ *  - PMBSR_EL1.S = 0 or 1
+ */
+void __kvm_spe_restore_host_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *host_ctxt)
+{
+	u64 pmbsr, pmblimitr;
+
+	/* Synchronise MDCR_EL2.{E2PB,TPMS} write. */
+	isb();
+
+	pmbsr = ctxt_sys_reg(host_ctxt, PMBSR_EL1);
+	pmblimitr = ctxt_sys_reg(host_ctxt, PMBLIMITR_EL1);
+
+	write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
+	write_sysreg_s(ctxt_sys_reg(host_ctxt, PMBPTR_EL1), SYS_PMBPTR_EL1);
+	/*
+	 * If the enable bit is set:
+	 *  - If PMBSR_EL1.S = 0, synchronise the write to PMBPTR_EL1.
+	 *  - If PMBSR_EL1.S = 1, synchronise the write to PMBSR_EL1, which
+	 *  disables the buffer.
+	 */
+	if (FIELD_GET(PMBLIMITR_EL1_E, pmblimitr))
+		isb();
+
+	write_sysreg_s(pmblimitr, SYS_PMBLIMITR_EL1);
+	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr))
+		isb();
+
+	__kvm_spe_restore_sampling_regs(vcpu->kvm, host_ctxt);
+
+	write_sysreg_el2(*host_data_ptr(host_debug_state.pmscr_el2), SYS_PMSCR);
+}
+NOKPROBE_SYMBOL(__kvm_spe_restore_host_state_vhe);
diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c
index 7875911c0506..a6817a10ae3b 100644
--- a/arch/arm64/kvm/hyp/vhe/switch.c
+++ b/arch/arm64/kvm/hyp/vhe/switch.c
@@ -613,6 +613,8 @@ static int __kvm_vcpu_run_vhe(struct kvm_vcpu *vcpu)
 	fpsimd_lazy_switch_to_guest(vcpu);
 
 	sysreg_save_host_state_vhe(host_ctxt);
+	if (vcpu_has_spe(vcpu))
+		__kvm_spe_save_host_state_vhe(vcpu, host_ctxt);
 
 	/*
 	 * Note that ARM erratum 1165522 requires us to configure both stage 1
@@ -625,7 +627,10 @@ static int __kvm_vcpu_run_vhe(struct kvm_vcpu *vcpu)
 	__kvm_adjust_pc(vcpu);
 
 	sysreg_restore_guest_state_vhe(guest_ctxt);
+
 	__debug_switch_to_guest(vcpu);
+	if (vcpu_has_spe(vcpu))
+		__kvm_spe_restore_guest_state_vhe(vcpu, guest_ctxt);
 
 	do {
 		/* Jump in the fire! */
@@ -635,12 +640,16 @@ static int __kvm_vcpu_run_vhe(struct kvm_vcpu *vcpu)
 	} while (fixup_guest_exit(vcpu, &exit_code));
 
 	sysreg_save_guest_state_vhe(guest_ctxt);
+	if (vcpu_has_spe(vcpu))
+		__kvm_spe_save_guest_state_vhe(vcpu, guest_ctxt);
 
 	__deactivate_traps(vcpu);
 
 	sysreg_restore_host_state_vhe(host_ctxt);
 
 	__debug_switch_to_host(vcpu);
+	if (vcpu_has_spe(vcpu))
+		__kvm_spe_restore_host_state_vhe(vcpu, host_ctxt);
 
 	/*
 	 * Ensure that all system register writes above have taken effect
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 3b285b45332b..af1e19cb8e9a 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -100,6 +100,15 @@ int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
 	return 0;
 }
 
+void kvm_vcpu_put_spe_vhe(struct kvm_vcpu *vcpu)
+{
+	if (!vcpu_has_spe(vcpu) || unlikely(vcpu_on_unsupported_cpu(vcpu)))
+		return;
+
+	/* See kvm_debug_init_vhe() */
+	write_sysreg_el1(0, SYS_PMSCR);
+}
+
 void kvm_spe_destroy_vm(struct kvm *kvm)
 {
 	struct arm_spe_pmu *spe_pmu;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 20/28] KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (18 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 19/28] KVM: arm64: VHE: Context switch SPE state Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:48   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 21/28] KVM: arm64: Handle SPE maintenance interrupts Alexandru Elisei
                   ` (8 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

The SPE driver allows userspace to use physical timestamps for records only
if the kernel allows it, do the same for a virtual machine.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_spe.h | 1 +
 arch/arm64/kvm/hyp/vhe/spe-sr.c  | 5 +++--
 arch/arm64/kvm/spe.c             | 8 ++++++++
 3 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index ad0ad6ace93c..8a55ea0de4e5 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -15,6 +15,7 @@ struct arm_spe_pmu;
 struct kvm_spe {
 	struct arm_spe_pmu *spe_pmu;
 	u64 pmblimitr_el1_res0_mask;
+	u64 guest_pmscr_el2;
 };
 
 struct kvm_vcpu_spe {
diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
index 978620ad99fa..3c32a1338cc8 100644
--- a/arch/arm64/kvm/hyp/vhe/spe-sr.c
+++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c
@@ -107,6 +107,7 @@ static bool __kvm_spe_profiling_buffer_enabled(u64 pmblimitr, u64 pmbsr)
  */
 void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *guest_ctxt)
 {
+	struct kvm *kvm = vcpu->kvm;
 	u64 pmblimitr, pmbsr;
 
 	pmbsr = ctxt_sys_reg(guest_ctxt, PMBSR_EL1);
@@ -120,10 +121,10 @@ void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_con
 		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
 	}
 
-	__kvm_spe_restore_sampling_regs(vcpu->kvm, guest_ctxt);
+	__kvm_spe_restore_sampling_regs(kvm, guest_ctxt);
 
 	write_sysreg_el1(ctxt_sys_reg(guest_ctxt, PMSCR_EL1), SYS_PMSCR);
-	write_sysreg_el2(0, SYS_PMSCR);
+	write_sysreg_el2(kvm->arch.kvm_spe.guest_pmscr_el2, SYS_PMSCR);
 }
 NOKPROBE_SYMBOL(__kvm_spe_restore_guest_state_vhe);
 
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index af1e19cb8e9a..68b751bdb384 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -3,9 +3,11 @@
  * Copyright (C) 2021 - ARM Ltd
  */
 
+#include <linux/capability.h>
 #include <linux/cpumask.h>
 #include <linux/kvm_host.h>
 #include <linux/perf/arm_spe_pmu.h>
+#include <linux/perf_event.h>
 
 #include <asm/kvm_emulate.h>
 #include <asm/kvm_spe.h>
@@ -97,6 +99,12 @@ int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
 	if (!kvm_spe->pmblimitr_el1_res0_mask)
 		kvm_spe_compute_pmblimitr_el1_res0_mask(kvm);
 
+	if (kvm_vm_has_ran_once(kvm))
+		return 0;
+
+	if (perf_allow_kernel())
+		kvm_spe->guest_pmscr_el2 = PMSCR_EL2_PCT_PHYS;
+
 	return 0;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 21/28] KVM: arm64: Handle SPE maintenance interrupts
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (19 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 20/28] KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:58   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 22/28] arm64: errata: Disable SPE in KVM Alexandru Elisei
                   ` (7 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Inject the maintenance interrupt when the guest writes 1 to PMBSR_EL1.S,
and deassert the interrupt when the service bit is cleared.

Re-inject all maintenance interrupts raised by the SPE hardware while
the guest was running.

Save the value of the hardware PMBSR_EL1 register in a separate
variable, instead of updating the VCPU sysreg directly. That's to allow
kvm_spe_sync_hwstate() to discern when the service bit was set by the
hardware, and not by the guest.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_spe.h | 13 ++++++++++
 arch/arm64/kvm/arm.c             |  2 ++
 arch/arm64/kvm/hyp/vhe/spe-sr.c  |  4 +--
 arch/arm64/kvm/spe.c             | 43 ++++++++++++++++++++++++++++++++
 arch/arm64/kvm/sys_regs.c        |  7 +++++-
 5 files changed, 66 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 8a55ea0de4e5..385b1157e578 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -19,8 +19,10 @@ struct kvm_spe {
 };
 
 struct kvm_vcpu_spe {
+	u64 hw_pmbsr_el1;	/* Updated on hardware management event */
 	int irq_num;		/* Buffer management interrupt number */
 	bool initialized;	/* SPE initialized for the VCPU */
+	bool irq_level;		/* Virtual buffer management interrupt level */
 };
 
 bool kvm_supports_spe(void);
@@ -43,11 +45,15 @@ int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 bool kvm_spe_pmu_set(struct kvm *kvm);
 u8 kvm_spe_get_pmsver(struct kvm *kvm);
 
+u64 kvm_spe_reset_sysreg(struct kvm_vcpu *vcpu, int reg);
+
 bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val);
 u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg);
 
 bool kvm_spe_has_feat_spe_fne(struct kvm *kvm);
 bool kvm_spe_has_feat_spe_fds(struct kvm *kvm);
+
+void kvm_spe_sync_hwstate(struct kvm_vcpu *vcpu);
 #else
 struct kvm_spe {
 };
@@ -99,6 +105,10 @@ static inline u8 kvm_spe_get_pmsver(struct kvm *kvm)
 {
 	return 0;
 }
+static inline u64 kvm_spe_reset_sysreg(struct kvm_vcpu *vcpu, int reg)
+{
+	return 0;
+}
 static inline bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 {
 	return true;
@@ -115,6 +125,9 @@ static inline bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
 {
 	return false;
 }
+static inline void kvm_spe_sync_hwstate(struct kvm_vcpu *vcpu)
+{
+}
 #endif /* CONFIG_KVM_ARM_SPE */
 
 #endif /* __ARM64_KVM_SPE_H__ */
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 5992efc4f94b..689fa175f5f4 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1421,6 +1421,8 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
 		if (kvm_vcpu_has_pmu(vcpu))
 			kvm_pmu_sync_hwstate(vcpu);
 
+		kvm_spe_sync_hwstate(vcpu);
+
 		/*
 		 * Sync the vgic state before syncing the timer state because
 		 * the timer code needs to know if the virtual timer
diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
index 3c32a1338cc8..df35025db19a 100644
--- a/arch/arm64/kvm/hyp/vhe/spe-sr.c
+++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c
@@ -154,10 +154,10 @@ void __kvm_spe_save_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_contex
 		psb_csync();
 		dsb(nsh);
 		write_sysreg_s(0, SYS_PMBLIMITR_EL1);
-		/* Advance PMBPTR_EL1. */
+		/* Advance PMBPTR_EL1 and PMBSR_EL1. */
 		isb();
 
-		/* Hardware updates to PMBSR_EL1 are not handled, yet. */
+		vcpu->arch.vcpu_spe.hw_pmbsr_el1 = read_sysreg_s(SYS_PMBSR_EL1);
 		ctxt_sys_reg(guest_ctxt, PMBPTR_EL1) = read_sysreg_s(SYS_PMBPTR_EL1);
 	}
 
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 68b751bdb384..89ebeef1c223 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -21,6 +21,8 @@ struct spe_pmu_entry {
 	struct arm_spe_pmu *spe_pmu;
 };
 
+static void kvm_spe_update_irq_level(struct kvm_vcpu *vcpu, bool level);
+
 void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
 {
 	struct spe_pmu_entry *entry;
@@ -152,6 +154,14 @@ bool kvm_spe_has_feat_spe_fne(struct kvm *kvm)
 	       FIELD_GET(PMSIDR_EL1_FnE, spe_pmu->pmsidr_el1);
 }
 
+u64 kvm_spe_reset_sysreg(struct kvm_vcpu *vcpu, int reg)
+{
+	if (reg == PMBSR_EL1 && kvm_vcpu_spe_initialized(vcpu))
+		kvm_spe_update_irq_level(vcpu, false);
+
+	return 0;
+}
+
 bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 {
 	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
@@ -168,6 +178,11 @@ bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 		WARN_ON_ONCE("unexpected trap");
 	}
 
+	if (reg == PMBSR_EL1) {
+		val = __vcpu_sys_reg(vcpu, PMBSR_EL1);
+		kvm_spe_update_irq_level(vcpu, FIELD_GET(PMBSR_EL1_S, val));
+	}
+
 	return true;
 }
 
@@ -184,6 +199,34 @@ u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
 	}
 }
 
+static void kvm_spe_update_irq_level(struct kvm_vcpu *vcpu, bool level)
+{
+	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
+	int ret;
+
+	if (vcpu_spe->irq_level == level)
+		return;
+
+	ret = kvm_vgic_inject_irq(vcpu->kvm, vcpu, vcpu_spe->irq_num, level, vcpu_spe);
+	if (!WARN_ON_ONCE(ret))
+		vcpu_spe->irq_level = level;
+}
+
+void kvm_spe_sync_hwstate(struct kvm_vcpu *vcpu)
+{
+	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
+
+	if (!vcpu_has_spe(vcpu))
+		return;
+
+	if (!FIELD_GET(PMBSR_EL1_S, vcpu_spe->hw_pmbsr_el1))
+		return;
+
+	__vcpu_assign_sys_reg(vcpu, PMBSR_EL1, vcpu_spe->hw_pmbsr_el1);
+	vcpu_spe->hw_pmbsr_el1 = 0;
+	kvm_spe_update_irq_level(vcpu, true);
+}
+
 bool kvm_spe_pmu_set(struct kvm *kvm)
 {
 	return !!kvm->arch.kvm_spe.spe_pmu;
diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
index 4c4bf88c9aa5..ebe1c933f8d5 100644
--- a/arch/arm64/kvm/sys_regs.c
+++ b/arch/arm64/kvm/sys_regs.c
@@ -1595,6 +1595,11 @@ static int get_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc
 	return 0;
 }
 
+static u64 reset_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
+{
+	return kvm_spe_reset_sysreg(vcpu, r->reg);
+}
+
 static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 			   const struct sys_reg_desc *r)
 {
@@ -1642,7 +1647,7 @@ static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
 
 #define SPE_SYS_REG(name)						\
 	SYS_DESC(SYS_##name), .reg = name, .access = access_spe_reg,	\
-	.reset = reset_val, .val = 0, .visibility = spe_visibility,	\
+	.reset = reset_spe_sysreg, .visibility = spe_visibility,	\
 	.set_user = set_user_spe_sysreg, .get_user = get_user_spe_sysreg
 
 #define SPE_UNTRAPPED_REG(name)						\
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 22/28] arm64: errata: Disable SPE in KVM
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (20 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 21/28] KVM: arm64: Handle SPE maintenance interrupts Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:50   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 23/28] KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter Alexandru Elisei
                   ` (6 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

When stage 1 is enabled and buffer virtual address VA is mapped as
read-only, and hardware dirty bit management is disabled at stage 1 and
stage 2, if stage 2 is enabled, SPE might write to address VA instead of
generating a fault.

This doesn't affect the SPE driver when running on baremetal, since the
memory is mapped at stage 1 with write permissions.

KVM does not use hardware dirty bit management and this erratum allows a
guest to write to read-only memory as long as that address is mapped at
stage 2.

Since KVM allows userspace to use SPE on heterogenous systems, where there
might be different SPE instances, tie the workaround to a particular SPE
instance and do not allow userspace to use that instance when it is
affected.

Several parts are affected:

* ARM Neoverse-N1 (MP050), SDEN v35, erratum 3023823
  https://support.arm.com/documentation/SDEN-885747/35-0
* ARM Neoverse-N2 (MP128), SDEN v21, erratum 3031178
  https://support.arm.com/documentation/SDEN-1982442/21-0
* ARM Neoverse-V1 (MP076), SDEN v23, erratum 3028884
  https://support.arm.com/documentation/SDEN-1401781/23-0
* ARM Neoverse-V2 (MP158), SDEN v13, erratum 3031173
  https://support.arm.com/documentation/SDEN-2332927/13-0
* ARM Cortex-A78 (MP102), SDEN v25, erratum 3031174
  https://support.arm.com/documentation/SDEN-1401784/25-0
* ARM Cortex-A78C:
    * MP138: SDEN v20, erratum 3031176
      https://support.arm.com/documentation/SDEN-1707916/20-0
    * MP154: SDEN v14, erratum 3031177
      https://support.arm.com/documentation/SDEN-2004089/14-0
* ARM Cortex-A78AE (MP105), SDEN v22, erratum 3031175
  https://support.arm.com/documentation/SDEN-1707912/22-0
* ARM Cortex-X1 (MP077), SDEN v25, erratum 3031174
  https://support.arm.com/documentation/SDEN-1401782/25-0
* ARM Cortex-X1C (MP136), SDEN v20, erratum 3031176
  https://support.arm.com/documentation/SDEN-1707914/20-0
* ARM Cortex-X3 (MP141), SDEN v18, erratum 3022726
  https://support.arm.com/documentation/SDEN-2055130/18-0
* ARM Cortex-X4 (MP161), SDEN v13, erratum 3022725
  https://support.arm.com/documentation/109148/13-0

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/arch/arm64/silicon-errata.rst | 23 ++++++++++++++++
 arch/arm64/Kconfig                          | 30 +++++++++++++++++++++
 arch/arm64/kernel/cpu_errata.c              | 25 +++++++++++++++++
 arch/arm64/kvm/Kconfig                      |  1 +
 arch/arm64/kvm/spe.c                        |  3 +++
 arch/arm64/tools/cpucaps                    |  1 +
 drivers/perf/arm_spe_pmu.c                  |  3 +++
 include/linux/perf/arm_spe_pmu.h            |  1 +
 8 files changed, 87 insertions(+)

diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index ac3248b9f2f3..a29f8fbc16a8 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -149,13 +149,20 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78      | #4193791        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-A78      | #3031174        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78AE    | #4193793        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-A78AE    | #3031175        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78C     | #3324346,       | ARM64_ERRATUM_3194386       |
 |                |                 | #3324347        |                             |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78C     | #4193794        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-A78C     | #3031176,       | ARM64_ERRATUM_3023823       |
+|                |                 | #3031177        |                             |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A710     | #2119858        | ARM64_ERRATUM_2119858       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A710     | #2054223        | ARM64_ERRATUM_2054223       |
@@ -180,10 +187,14 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X1       | #4193791        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-X1       | #3031174        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X1C      | #3324346        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X1C      | #4193792        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-X1C      | #3031176        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X2       | #2119858        | ARM64_ERRATUM_2119858       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X2       | #2224489        | ARM64_ERRATUM_2224489       |
@@ -196,10 +207,14 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X3       | #4193786        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-X3       | #3022726        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X4       | #3194386        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X4       | #4118414        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-X4       | #3022725        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X925     | #3324334        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X925     | #4193781        | ARM64_ERRATUM_4118414       |
@@ -216,6 +231,8 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N1     | #4193800        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Neoverse-N1     | #3023823        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N2     | #2139208        | ARM64_ERRATUM_2139208       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N2     | #2067961        | ARM64_ERRATUM_2067961       |
@@ -226,6 +243,8 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N2     | #4193789        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Neoverse-N2     | #3031178        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N3     | #3456111        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V1     | #1619801        | N/A                         |
@@ -234,10 +253,14 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V1     | #4193790        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Neoverse-V1     | #3028884        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V2     | #3324336        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V2     | #4193787        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Neoverse-V2     | #3031173        | ARM64_ERRATUM_3023823       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V3     | #3312417        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V3     | #4193784        | ARM64_ERRATUM_4118414       |
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index b5a51b0ef944..ca6759d16d27 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1464,6 +1464,36 @@ config SOCIONEXT_SYNQUACER_PREITS
 
 	  If unsure, say Y.
 
+config ARM64_ERRATUM_3023823
+	bool "SPE might write to pages which lack write permission at Stage-1 or Stage-2"
+	depends on KVM_ARM_SPE
+	default y
+	help
+	  This option adds a workaround for the following errata:
+
+	  * ARM Neoverse-N1 erratum 3023823
+	  * ARM Neoverse-N2 erratum 3031178
+	  * ARM Neoverse-V1 erratum 3028884
+	  * ARM Neoverse-V2 erratum 3031173
+	  * ARM Cortex-A78 erratum 3031174
+	  * ARM Cortex-A78C errata 3031176 and 3031177
+	  * ARM Cortex-A78AE erratum 3031175
+	  * ARM Cortex-X1 erratum 3031174
+	  * ARM Cortex-X1C erratum 3031176
+	  * ARM Cortex-X3 erratum 3022726
+	  * ARM Cortex-X4 erratum 3022725
+
+	  When stage 1 is enabled, and stage 2 is either enabled or disabled,
+	  and buffer virtual address VA is mapped as read-only, and hardware
+	  dirty bit management is disabled at stage 1, and stage 2 if stage 2
+	  is enabled, SPE might write to address VA instead of generating a
+	  fault.
+
+	  The work around is to disable SPE for a virtual machine. The host
+	  driver is unaffected.
+
+	  If unsure, say Y.
+
 endmenu # "ARM errata workarounds via the alternatives framework"
 
 choice
diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
index 5db8f0619e4b..ab0558fbd5b2 100644
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
@@ -630,6 +630,24 @@ static void cpu_enable_sme_dvmsync(const struct arm64_cpu_capabilities *__unused
 }
 #endif
 
+#ifdef CONFIG_ARM64_ERRATUM_3023823
+static const struct midr_range erratum_3023823_list[] = {
+	MIDR_ALL_VERSIONS(MIDR_NEOVERSE_N1),
+	MIDR_ALL_VERSIONS(MIDR_NEOVERSE_N2),
+	MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V1),
+	MIDR_ALL_VERSIONS(MIDR_NEOVERSE_V2),
+	MIDR_ALL_VERSIONS(MIDR_CORTEX_A78),
+	MIDR_ALL_VERSIONS(MIDR_CORTEX_A78C),
+	MIDR_ALL_VERSIONS(MIDR_CORTEX_A78AE),
+	MIDR_ALL_VERSIONS(MIDR_CORTEX_X1),
+	MIDR_ALL_VERSIONS(MIDR_CORTEX_X1C),
+	MIDR_ALL_VERSIONS(MIDR_CORTEX_X3),
+	/* Cortex-X4 r0p0 to r0p1 */
+	MIDR_REV_RANGE(MIDR_CORTEX_X4, 0, 0, 1),
+	{},
+};
+#endif
+
 #ifdef CONFIG_AMPERE_ERRATUM_AC03_CPU_38
 static const struct midr_range erratum_ac03_cpu_38_list[] = {
 	MIDR_ALL_VERSIONS(MIDR_AMPERE1),
@@ -987,6 +1005,13 @@ const struct arm64_cpu_capabilities arm64_errata[] = {
 		MIDR_FIXED(MIDR_CPU_VAR_REV(1, 2), BIT(0)),
 	},
 #endif
+#ifdef CONFIG_ARM64_ERRATUM_3023823
+	{
+		.desc = "SPE might write to read-only pages",
+		.capability = ARM64_WORKAROUND_3023823,
+		ERRATA_MIDR_RANGE_LIST(erratum_3023823_list),
+	},
+#endif
 #ifdef CONFIG_ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD
 	{
 		.desc = "ARM errata 2966298, 3117295",
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index fece4c02b97c..2bc8e34c538d 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -104,6 +104,7 @@ config PKVM_STACKTRACE
 
 endif # NVHE_EL2_DEBUG
 
+
 config KVM_ARM_SPE
 	bool
 	depends on KVM && ARM_SPE_PMU
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 89ebeef1c223..ce68789d184b 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -290,6 +290,9 @@ static int kvm_spe_set_spe_id(struct kvm_vcpu *vcpu, int spe_id)
 		return 0;
 	}
 
+	if (spe_pmu->has_workaround_3023823)
+		return -EINVAL;
+
 	if (!try_module_get(spe_pmu->pmu.module))
 		return -ENXIO;
 
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 2775ba3359cf..5f997851b6d7 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -132,3 +132,4 @@ WORKAROUND_REPEAT_TLBI_SYNC
 WORKAROUND_SPECULATIVE_AT
 WORKAROUND_SPECULATIVE_SSBS
 WORKAROUND_SPECULATIVE_UNPRIV_LOAD
+WORKAROUND_3023823
diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
index f24b122a087c..928ea94f2ed6 100644
--- a/drivers/perf/arm_spe_pmu.c
+++ b/drivers/perf/arm_spe_pmu.c
@@ -1198,6 +1198,9 @@ static void __arm_spe_pmu_dev_probe(void *info)
 	write_sysreg_s(U64_MAX, SYS_PMSEVFR_EL1);
 	spe_pmu->pmsevfr_res0 = ~read_sysreg_s(SYS_PMSEVFR_EL1);
 
+	if (this_cpu_has_cap(ARM64_WORKAROUND_3023823))
+		spe_pmu->has_workaround_3023823 = true;
+
 	dev_info(dev,
 		 "probed SPEv1.%d for CPUs %*pbl [max_record_sz %u, align %u, features 0x%llx]\n",
 		 spe_pmu->pmsver - 1, cpumask_pr_args(&spe_pmu->supported_cpus),
diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
index bdb703558a87..edf58c51fb94 100644
--- a/include/linux/perf/arm_spe_pmu.h
+++ b/include/linux/perf/arm_spe_pmu.h
@@ -45,6 +45,7 @@ struct arm_spe_pmu {
 	u16					max_record_sz;
 	u16					align;
 	struct perf_output_handle __percpu	*handle;
+	bool					has_workaround_3023823;
 };
 
 #define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 23/28] KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (21 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 22/28] arm64: errata: Disable SPE in KVM Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:46   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 24/28] arm64: errata: Don't enable guest buffer if misprogrammed Alexandru Elisei
                   ` (5 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Add a new kernel command line parameter, kvm-arm.ignore_spe_errata with the
effect of ignoring any errata that might inhibit userspace from creating a
SPE enabled virtual machine.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/admin-guide/kernel-parameters.txt |  3 +++
 arch/arm64/kvm/spe.c                            | 10 +++++++++-
 2 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 68647ff4bdd2..2d91ce8e8c4b 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -3299,6 +3299,9 @@ Kernel parameters
 
 			notrap: clear WFI instruction trap
 
+	kvm-arm.ignore_spe_errata=
+			[KVM,ARM] Ignore SPE errata and expose SPE to guests.
+
 	kvm_cma_resv_ratio=n [PPC,EARLY]
 			Reserves given percentage from system memory area for
 			contiguous memory allocation for KVM hash pagetable
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index ce68789d184b..3e82edc8be85 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -21,8 +21,16 @@ struct spe_pmu_entry {
 	struct arm_spe_pmu *spe_pmu;
 };
 
+static bool ignore_spe_errata;
+
 static void kvm_spe_update_irq_level(struct kvm_vcpu *vcpu, bool level);
 
+static int __init kvm_ignore_spe_errata(char *arg)
+{
+	return kstrtobool(arg, &ignore_spe_errata);
+}
+early_param("kvm-arm.ignore_spe_errata", kvm_ignore_spe_errata);
+
 void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
 {
 	struct spe_pmu_entry *entry;
@@ -290,7 +298,7 @@ static int kvm_spe_set_spe_id(struct kvm_vcpu *vcpu, int spe_id)
 		return 0;
 	}
 
-	if (spe_pmu->has_workaround_3023823)
+	if (spe_pmu->has_workaround_3023823 && !ignore_spe_errata)
 		return -EINVAL;
 
 	if (!try_module_get(spe_pmu->pmu.module))
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 24/28] arm64: errata: Don't enable guest buffer if misprogrammed
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (22 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 23/28] KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 17:00   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 25/28] KVM: arm64: at: Use callback for reading descriptor Alexandru Elisei
                   ` (4 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

When PMBPTR_EL1 is programmed to be equal to or greater than
PMBLIMITR_EL1.Limit, SPE is enabled and PMBSR_EL1.S is zero, the CPU might
deadlock.

This doesn't affect correctly written software, like the SPE driver, but a
buggy KVM guest might trigger the erratum. The workaround for KVM is not to
enable the hardware buffer, and, as a consequence, profiling. According
to ARM DDI0487M.c, rule R_PGGHS, this is one of the acceptable
behaviours when the buffer is misprogrammed.

Several parts are affected:

* ARM Neoverse-N1 (MP050), SDEN v35, erratum 1978083
  https://support.arm.com/documentation/SDEN-885747/35-0
* ARM Neoverse-V1 (MP076), SDEN v23, erratum 1978082
  https://support.arm.com/documentation/SDEN-1401781/23-0
* ARM Cortex-X1 (MP077), SDEN v25, erratum 2004055
  https://support.arm.com/documentation/SDEN-1401782/25-0
* ARM Cortex-X1C (MP136), SDEN v20, erratum 2004056
  https://support.arm.com/documentation/SDEN-1707914/20-0
* ARM Cortex-A78 (MP102), SDEN v25, erratum 2004055
  https://support.arm.com/documentation/SDEN-1401784/25-0
* ARM Cortex-A78AE (MP105), SDEN v22, erratum 2004059
  https://support.arm.com/documentation/SDEN-1707912/22-0
* ARM Cortex-A78C (MP138), SDEN v20, erratum 2004056
  https://support.arm.com/documentation/SDEN-1707916/20-0

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/arch/arm64/silicon-errata.rst | 14 +++++++++++
 arch/arm64/Kconfig                          | 25 ++++++++++++++++++++
 arch/arm64/include/asm/kvm_spe.h            |  1 +
 arch/arm64/kernel/cpu_errata.c              | 26 +++++++++++++++++++++
 arch/arm64/kvm/hyp/vhe/spe-sr.c             |  6 +++--
 arch/arm64/kvm/spe.c                        | 22 +++++++++++++++++
 arch/arm64/tools/cpucaps                    |  1 +
 drivers/perf/arm_spe_pmu.c                  |  3 +++
 include/linux/perf/arm_spe_pmu.h            |  1 +
 9 files changed, 97 insertions(+), 2 deletions(-)

diff --git a/Documentation/arch/arm64/silicon-errata.rst b/Documentation/arch/arm64/silicon-errata.rst
index a29f8fbc16a8..a5477cae7d70 100644
--- a/Documentation/arch/arm64/silicon-errata.rst
+++ b/Documentation/arch/arm64/silicon-errata.rst
@@ -151,10 +151,14 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78      | #3031174        | ARM64_ERRATUM_3023823       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-A78      | #2004055        | ARM64_ERRATUM_2004055       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78AE    | #4193793        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78AE    | #3031175        | ARM64_ERRATUM_3023823       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-A78AE    | #2004059        | ARM64_ERRATUM_2004055       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A78C     | #3324346,       | ARM64_ERRATUM_3194386       |
 |                |                 | #3324347        |                             |
 +----------------+-----------------+-----------------+-----------------------------+
@@ -163,6 +167,8 @@ stable kernels.
 | ARM            | Cortex-A78C     | #3031176,       | ARM64_ERRATUM_3023823       |
 |                |                 | #3031177        |                             |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-A78C     | #2004056        | ARM64_ERRATUM_2004055       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A710     | #2119858        | ARM64_ERRATUM_2119858       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-A710     | #2054223        | ARM64_ERRATUM_2054223       |
@@ -189,12 +195,16 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X1       | #3031174        | ARM64_ERRATUM_3023823       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-X1       | #2004055        | ARM64_ERRATUM_2004055       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X1C      | #3324346        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X1C      | #4193792        | ARM64_ERRATUM_4118414       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X1C      | #3031176        | ARM64_ERRATUM_3023823       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Cortex-X1C      | #2004056        | ARM64_ERRATUM_2004055       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X2       | #2119858        | ARM64_ERRATUM_2119858       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Cortex-X2       | #2224489        | ARM64_ERRATUM_2224489       |
@@ -233,6 +243,8 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N1     | #3023823        | ARM64_ERRATUM_3023823       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Neoverse-N1     | #1978083        | ARM64_ERRATUM_2004055       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N2     | #2139208        | ARM64_ERRATUM_2139208       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-N2     | #2067961        | ARM64_ERRATUM_2067961       |
@@ -255,6 +267,8 @@ stable kernels.
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V1     | #3028884        | ARM64_ERRATUM_3023823       |
 +----------------+-----------------+-----------------+-----------------------------+
+| ARM            | Neoverse-V1     | #1978082        | ARM64_ERRATUM_2004055       |
++----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V2     | #3324336        | ARM64_ERRATUM_3194386       |
 +----------------+-----------------+-----------------+-----------------------------+
 | ARM            | Neoverse-V2     | #4193787        | ARM64_ERRATUM_4118414       |
diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index ca6759d16d27..d9b27ee90ab5 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1494,6 +1494,31 @@ config ARM64_ERRATUM_3023823
 
 	  If unsure, say Y.
 
+config ARM64_ERRATUM_2004055
+	bool "Incorrect programming of PMBPTR_EL1 might result in a deadlock"
+	depends on KVM_ARM_SPE
+	default y
+	help
+	  This option adds a workaround for the following errata:
+
+	  * ARM Neoverse-N1 erratum 1978083
+	  * ARM Neoverse-V1 erratum 1978082
+	  * ARM Cortex-X1 erratum 2004055
+	  * ARM Cortex-X1C erratum 2004056
+	  * ARM Cortex-A78 erratum 2004055
+	  * ARM Cortex-A78AE erratum 2004059
+	  * ARM Cortex-A78C errata 2004056
+
+	  When PMBPTR_EL1 is programmed to be equal to or greater than
+	  PMBLIMITR_EL1.Limit, SPE is enabled and PMBSR_EL1.S is zero, the CPU
+	  might deadlock.
+
+	  The work around is for KVM to behave as if profiling is disabled when
+	  the buffer is enabled, PMSBSR_EL1.S is zero and
+	  PMBPTR_EL1.PTR >= PMBLIMITR_EL1.Limit.
+
+	  If unsure, say Y.
+
 endmenu # "ARM errata workarounds via the alternatives framework"
 
 choice
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 385b1157e578..88f8b1bb6864 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -23,6 +23,7 @@ struct kvm_vcpu_spe {
 	int irq_num;		/* Buffer management interrupt number */
 	bool initialized;	/* SPE initialized for the VCPU */
 	bool irq_level;		/* Virtual buffer management interrupt level */
+	bool buffer_misprogrammed;
 };
 
 bool kvm_supports_spe(void);
diff --git a/arch/arm64/kernel/cpu_errata.c b/arch/arm64/kernel/cpu_errata.c
index ab0558fbd5b2..0b9620893763 100644
--- a/arch/arm64/kernel/cpu_errata.c
+++ b/arch/arm64/kernel/cpu_errata.c
@@ -648,6 +648,25 @@ static const struct midr_range erratum_3023823_list[] = {
 };
 #endif
 
+#ifdef CONFIG_ARM64_ERRATUM_2004055
+static const struct midr_range erratum_2004055_list[] = {
+	MIDR_ALL_VERSIONS(MIDR_NEOVERSE_N1),
+	/* Neoverse-V1 r0p0 to r1p1 */
+	MIDR_RANGE(MIDR_NEOVERSE_V1, 0, 0, 1, 1),
+	/* Cortex-X1 r0p0 to r1p1 */
+	MIDR_RANGE(MIDR_CORTEX_X1, 0, 0, 1, 1),
+	/* Cortex-X1 r0p0 */
+	MIDR_REV(MIDR_CORTEX_X1C, 0, 0),
+	/* Cortex-A78 r0p0 to r1p1 */
+	MIDR_RANGE(MIDR_CORTEX_A78, 0, 0, 1, 1),
+	/* Cortex-A78 r0p0 to r1p1 */
+	MIDR_RANGE(MIDR_CORTEX_A78AE, 0, 0, 0, 1),
+	/* Cortex-A78C r0p0 */
+	MIDR_REV(MIDR_CORTEX_A78C, 0, 0),
+	{},
+};
+#endif
+
 #ifdef CONFIG_AMPERE_ERRATUM_AC03_CPU_38
 static const struct midr_range erratum_ac03_cpu_38_list[] = {
 	MIDR_ALL_VERSIONS(MIDR_AMPERE1),
@@ -1012,6 +1031,13 @@ const struct arm64_cpu_capabilities arm64_errata[] = {
 		ERRATA_MIDR_RANGE_LIST(erratum_3023823_list),
 	},
 #endif
+#ifdef CONFIG_ARM64_ERRATUM_2004055
+	{
+		.desc = "Incorrect programming of PMBPTR_EL1 leads to deadlock",
+		.capability = ARM64_WORKAROUND_2004055,
+		ERRATA_MIDR_RANGE_LIST(erratum_2004055_list),
+	},
+#endif
 #ifdef CONFIG_ARM64_WORKAROUND_SPECULATIVE_UNPRIV_LOAD
 	{
 		.desc = "ARM errata 2966298, 3117295",
diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
index df35025db19a..ec991f4cc348 100644
--- a/arch/arm64/kvm/hyp/vhe/spe-sr.c
+++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c
@@ -112,7 +112,8 @@ void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_con
 
 	pmbsr = ctxt_sys_reg(guest_ctxt, PMBSR_EL1);
 	pmblimitr = ctxt_sys_reg(guest_ctxt, PMBLIMITR_EL1);
-	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
+	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr) &&
+	    !vcpu->arch.vcpu_spe.buffer_misprogrammed) {
 		write_sysreg_s(ctxt_sys_reg(guest_ctxt, PMBPTR_EL1), SYS_PMBPTR_EL1);
 		isb();
 		write_sysreg_s(pmblimitr, SYS_PMBLIMITR_EL1);
@@ -150,7 +151,8 @@ void __kvm_spe_save_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_contex
 	 * Update PMBPTR_EL1 and PMBSR_EL1 only if profiling was enabled
 	 * when the guest state was resumed.
 	 */
-	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
+	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr) &&
+	    !vcpu->arch.vcpu_spe.buffer_misprogrammed) {
 		psb_csync();
 		dsb(nsh);
 		write_sysreg_s(0, SYS_PMBLIMITR_EL1);
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index 3e82edc8be85..ef138562f939 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -170,6 +170,25 @@ u64 kvm_spe_reset_sysreg(struct kvm_vcpu *vcpu, int reg)
 	return 0;
 }
 
+static void kvm_spe_apply_workaround_2004055(struct kvm_vcpu *vcpu)
+{
+	u64 pmblimitr_el1 = __vcpu_sys_reg(vcpu, PMBLIMITR_EL1);
+	u64 pmbsr_el1 = __vcpu_sys_reg(vcpu, PMBSR_EL1);
+	u64 ptr, limit;
+
+	if (FIELD_GET(PMBSR_EL1_S, pmbsr_el1) ||
+	    !FIELD_GET(PMBLIMITR_EL1_E, pmblimitr_el1))
+		return;
+
+	ptr = FIELD_GET(PMBPTR_EL1_PTR, __vcpu_sys_reg(vcpu, PMBPTR_EL1));
+	limit = FIELD_GET(PMBLIMITR_EL1_LIMIT, pmblimitr_el1) << 12;
+
+	if (ptr >= limit)
+		vcpu->arch.vcpu_spe.buffer_misprogrammed = true;
+	else
+		vcpu->arch.vcpu_spe.buffer_misprogrammed = false;
+}
+
 bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 {
 	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
@@ -186,6 +205,9 @@ bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 		WARN_ON_ONCE("unexpected trap");
 	}
 
+	if (unlikely(kvm_spe->spe_pmu->has_workaround_2004055))
+		kvm_spe_apply_workaround_2004055(vcpu);
+
 	if (reg == PMBSR_EL1) {
 		val = __vcpu_sys_reg(vcpu, PMBSR_EL1);
 		kvm_spe_update_irq_level(vcpu, FIELD_GET(PMBSR_EL1_S, val));
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 5f997851b6d7..e001a6ea5564 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -133,3 +133,4 @@ WORKAROUND_SPECULATIVE_AT
 WORKAROUND_SPECULATIVE_SSBS
 WORKAROUND_SPECULATIVE_UNPRIV_LOAD
 WORKAROUND_3023823
+WORKAROUND_2004055
diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
index 928ea94f2ed6..bca8cd3bca16 100644
--- a/drivers/perf/arm_spe_pmu.c
+++ b/drivers/perf/arm_spe_pmu.c
@@ -1201,6 +1201,9 @@ static void __arm_spe_pmu_dev_probe(void *info)
 	if (this_cpu_has_cap(ARM64_WORKAROUND_3023823))
 		spe_pmu->has_workaround_3023823 = true;
 
+	if (this_cpu_has_cap(ARM64_WORKAROUND_2004055))
+		spe_pmu->has_workaround_2004055 = true;
+
 	dev_info(dev,
 		 "probed SPEv1.%d for CPUs %*pbl [max_record_sz %u, align %u, features 0x%llx]\n",
 		 spe_pmu->pmsver - 1, cpumask_pr_args(&spe_pmu->supported_cpus),
diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
index edf58c51fb94..d5c510ebf785 100644
--- a/include/linux/perf/arm_spe_pmu.h
+++ b/include/linux/perf/arm_spe_pmu.h
@@ -46,6 +46,7 @@ struct arm_spe_pmu {
 	u16					align;
 	struct perf_output_handle __percpu	*handle;
 	bool					has_workaround_3023823;
+	bool					has_workaround_2004055;
 };
 
 #define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 25/28] KVM: arm64: at: Use callback for reading descriptor
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (23 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 24/28] arm64: errata: Don't enable guest buffer if misprogrammed Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:51   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 26/28] KVM: arm64: Map memory on a SPE stage 2 fault Alexandru Elisei
                   ` (3 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark
  Cc: James Morse

Allow callers of __kvm_translate_va() to use a custom function for reading
the translation table descriptor from guest memory. This will be useful for
handling stage 2 faults reported by SPE, to map the stage 1 translation
tables at stage 2.

Suggested-by: James Morse <james.morse@arm.com>
Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_nested.h |  7 +++++++
 arch/arm64/kvm/at.c                 | 22 ++++++++++++++--------
 arch/arm64/kvm/nested.c             |  7 ++++---
 3 files changed, 25 insertions(+), 11 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_nested.h b/arch/arm64/include/asm/kvm_nested.h
index 1ed708335809..d812137dcd9e 100644
--- a/arch/arm64/include/asm/kvm_nested.h
+++ b/arch/arm64/include/asm/kvm_nested.h
@@ -350,8 +350,15 @@ struct s1_walk_filter {
 	void	*priv;
 };
 
+typedef int (*read_desc_fn)(struct kvm_vcpu *, u64, u64 *,
+			    struct s1_walk_info *);
+
+int kvm_vcpu_read_s1_desc(struct kvm_vcpu *vcpu, u64 pa, u64 *desc,
+			  struct s1_walk_info *wi);
+
 struct s1_walk_info {
 	struct s1_walk_filter	*filter;
+	read_desc_fn		read_s1_desc;
 	u64	     		baddr;
 	enum trans_regime	regime;
 	unsigned int		max_oa_bits;
diff --git a/arch/arm64/kvm/at.c b/arch/arm64/kvm/at.c
index 0926426b8798..6e77beced96c 100644
--- a/arch/arm64/kvm/at.c
+++ b/arch/arm64/kvm/at.c
@@ -224,6 +224,9 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
 	unsigned int stride, x;
 	bool va55, tbi, lva, upper_range;
 
+	if (!wi->read_s1_desc)
+		return -EINVAL;
+
 	va55 = va & BIT(55);
 	upper_range = va55 && wi->regime != TR_EL2;
 
@@ -427,8 +430,8 @@ static int setup_s1_walk(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
 	return -EFAULT;
 }
 
-static int kvm_read_s1_desc(struct kvm_vcpu *vcpu, u64 pa, u64 *desc,
-			    struct s1_walk_info *wi)
+int kvm_vcpu_read_s1_desc(struct kvm_vcpu *vcpu, u64 pa, u64 *desc,
+			  struct s1_walk_info *wi)
 {
 	u64 val;
 	int r;
@@ -513,7 +516,7 @@ static int walk_s1(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
 				return ret;
 		}
 
-		ret = kvm_read_s1_desc(vcpu, ipa, &desc, wi);
+		ret = wi->read_s1_desc(vcpu, ipa, &desc, wi);
 		if (ret) {
 			fail_s1_walk(wr, ESR_ELx_FSC_SEA_TTW(level), false);
 			return ret;
@@ -1324,6 +1327,7 @@ static int handle_at_slow(struct kvm_vcpu *vcpu, u32 op, u64 vaddr, u64 *par)
 	bool perm_fail = false;
 	int ret, idx;
 
+	wi.read_s1_desc = kvm_vcpu_read_s1_desc;
 	wi.regime = compute_translation_regime(vcpu, op);
 	wi.as_el0 = (op == OP_AT_S1E0R || op == OP_AT_S1E0W);
 	wi.pan = (op == OP_AT_S1E1RP || op == OP_AT_S1E1WP) &&
@@ -1657,9 +1661,10 @@ int __kvm_at_s12(struct kvm_vcpu *vcpu, u32 op, u64 vaddr)
 }
 
 /*
- * Translate a VA for a given EL in a given translation regime, with
- * or without PAN. This requires wi->{regime, as_el0, pan} to be
- * set. The rest of the wi and wr should be 0-initialised.
+ * Translate a VA for a given EL in a given translation regime, with or without
+ * PAN. This requires wi->{regime, as_el0, pan, read_s1_desc} to be set.
+ * Setting wi->filter is optional. The rest of the wi and wr should be
+ * 0-initialised.
  */
 int __kvm_translate_va(struct kvm_vcpu *vcpu, struct s1_walk_info *wi,
 		       struct s1_walk_result *wr, u64 va)
@@ -1716,8 +1721,9 @@ int __kvm_find_s1_desc_level(struct kvm_vcpu *vcpu, u64 va, u64 ipa, int *level)
 			.fn	= match_s1_desc,
 			.priv	= &dm,
 		},
-		.as_el0	= false,
-		.pan	= false,
+		.read_s1_desc	= kvm_vcpu_read_s1_desc,
+		.as_el0		= false,
+		.pan		= false,
 	};
 	struct s1_walk_result wr = {};
 	int ret;
diff --git a/arch/arm64/kvm/nested.c b/arch/arm64/kvm/nested.c
index 17123f0b6dab..e3e71628f8c5 100644
--- a/arch/arm64/kvm/nested.c
+++ b/arch/arm64/kvm/nested.c
@@ -1435,9 +1435,10 @@ static int kvm_translate_vncr(struct kvm_vcpu *vcpu, bool *is_gmem)
 			invalidate_vncr(vcpu->kvm, vt);
 
 		vt->wi = (struct s1_walk_info) {
-			.regime	= TR_EL20,
-			.as_el0	= false,
-			.pan	= false,
+			.read_s1_desc	= kvm_vcpu_read_s1_desc,
+			.regime		= TR_EL20,
+			.as_el0		= false,
+			.pan		= false,
 		};
 		vt->wr = (struct s1_walk_result){};
 	}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 26/28] KVM: arm64: Map memory on a SPE stage 2 fault
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (24 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 25/28] KVM: arm64: at: Use callback for reading descriptor Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 17:08   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 27/28] KVM: arm64: Handle dirty page logging when SPE feature is set Alexandru Elisei
                   ` (2 subsequent siblings)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Make sure SPE in a guest can make progress even if something goes terribly
wrong in KVM by mapping the memory on stage 2 fault SPE buffer management
event.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_host.h |  2 +
 arch/arm64/kvm/arm.c              |  4 +-
 arch/arm64/kvm/mmu.c              | 53 ++++++++++++++++++
 arch/arm64/kvm/spe.c              | 93 ++++++++++++++++++++++++++++++-
 4 files changed, 148 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 81abfb705aeb..31be131c8486 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -1464,6 +1464,8 @@ void kvm_debug_handle_oslar(struct kvm_vcpu *vcpu, u64 val);
 #define kvm_guest_owns_debug_regs(vcpu)		\
 	((vcpu)->arch.debug_owner == VCPU_DEBUG_GUEST_OWNED)
 
+int kvm_map_gpa(struct kvm_vcpu *vcpu, u64 gpa, u8 fsc, bool s1ptw);
+
 int kvm_arm_vcpu_arch_set_attr(struct kvm_vcpu *vcpu,
 			       struct kvm_device_attr *attr);
 int kvm_arm_vcpu_arch_get_attr(struct kvm_vcpu *vcpu,
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 689fa175f5f4..7c7d0616db53 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1421,8 +1421,6 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
 		if (kvm_vcpu_has_pmu(vcpu))
 			kvm_pmu_sync_hwstate(vcpu);
 
-		kvm_spe_sync_hwstate(vcpu);
-
 		/*
 		 * Sync the vgic state before syncing the timer state because
 		 * the timer code needs to know if the virtual timer
@@ -1472,6 +1470,8 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu)
 
 		preempt_enable();
 
+		kvm_spe_sync_hwstate(vcpu);
+
 		/*
 		 * The ARMv8 architecture doesn't give the hypervisor
 		 * a mechanism to prevent a guest from dropping to AArch32 EL0
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index 9ba86450fe4a..d7e7365c5c5a 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -2796,3 +2796,56 @@ void kvm_toggle_cache(struct kvm_vcpu *vcpu, bool was_enabled)
 
 	trace_kvm_toggle_cache(*vcpu_pc(vcpu), was_enabled, now_enabled);
 }
+
+int kvm_map_gpa(struct kvm_vcpu *vcpu, u64 gpa, u8 fsc, bool s1ptw)
+{
+	struct kvm_vcpu_fault_info *fault_info = &vcpu->arch.fault;
+	struct kvm_vcpu_fault_info fault_backup = *fault_info;
+	struct kvm_s2_fault_desc s2fd = {
+		.vcpu		= vcpu,
+		.fault_ipa	= gpa,
+	};
+	struct kvm_memory_slot *memslot;
+	struct kvm *kvm = vcpu->kvm;
+	gfn_t gfn;
+	hva_t hva;
+	int ret;
+
+	lockdep_assert(srcu_read_lock_held(&kvm->srcu));
+
+	if (WARN_ON_ONCE(is_protected_kvm_enabled() || vcpu_has_nv(vcpu)))
+		return -EOPNOTSUPP;
+
+	if (esr_fsc_is_access_flag_fault(fsc)) {
+		handle_access_fault(vcpu, gpa);
+		return 0;
+	}
+
+	gfn = gpa_to_gfn(gpa);
+	memslot = gfn_to_memslot(kvm, gfn);
+	if (!memslot)
+		return -ENOENT;
+	hva = gfn_to_hva_memslot(memslot, gfn);
+	if (kvm_is_error_hva(hva))
+		return -EFAULT;
+
+	s2fd.memslot = memslot;
+	s2fd.hva = hva;
+
+	fault_info->esr_el2 = FIELD_PREP(ESR_ELx_EC_MASK, ESR_ELx_EC_DABT_LOW) |
+			      ESR_ELx_IL | FIELD_PREP(ESR_ELx_FSC, fsc);
+	if (s1ptw)
+		fault_info->esr_el2 |= ESR_ELx_S1PTW;
+	else
+		fault_info->esr_el2 |= ESR_ELx_WNR;
+	fault_info->hpfar_el2 = FIELD_PREP(HPFAR_EL2_FIPA, gpa >> 12);
+
+	if (kvm_slot_has_gmem(memslot))
+		ret = gmem_abort(&s2fd);
+	else
+		ret = user_mem_abort(&s2fd);
+
+	*fault_info = fault_backup;
+
+	return ret;
+}
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index ef138562f939..e5f3d0dea7c5 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -10,6 +10,7 @@
 #include <linux/perf_event.h>
 
 #include <asm/kvm_emulate.h>
+#include <asm/kvm_nested.h>
 #include <asm/kvm_spe.h>
 #include <asm/sysreg.h>
 
@@ -242,17 +243,105 @@ static void kvm_spe_update_irq_level(struct kvm_vcpu *vcpu, bool level)
 		vcpu_spe->irq_level = level;
 }
 
+static bool kvm_spe_should_handle_fault(struct kvm_vcpu *vcpu)
+{
+	u64 pmbsr = vcpu->arch.vcpu_spe.hw_pmbsr_el1;
+	u64 pmbsr_ec = FIELD_GET(PMBSR_EL1_EC, pmbsr);
+
+	if (pmbsr_ec != PMBSR_EL1_EC_FAULT_S2)
+		return false;
+
+	if (FIELD_GET(PMBSR_EL1_EA, pmbsr))
+		return false;
+
+	if (esr_fsc_is_translation_fault(pmbsr) ||
+	    esr_fsc_is_permission_fault(pmbsr) ||
+	    esr_fsc_is_access_flag_fault(pmbsr))
+		return true;
+
+	return false;
+}
+
+static int kvm_spe_read_s1_desc(struct kvm_vcpu *vcpu, u64 gpa, u64 *desc,
+				struct s1_walk_info *wi)
+{
+	u64 pmbsr = vcpu->arch.vcpu_spe.hw_pmbsr_el1;
+	int ret;
+
+	ret = kvm_vcpu_read_s1_desc(vcpu, gpa, desc, wi);
+	if (ret)
+		return ret;
+
+	/* TODO: this should be a hardware capability check */
+	if (kvm_has_feat(vcpu->kvm, ID_AA64DFR2_EL1, SPE_EXC, IMP) &&
+	    !(FIELD_GET(ESR_ELx_S1PTW, pmbsr)))
+		return 0;
+
+	/*
+	 * No choice but to assume that the s2 fault happened on a s1 table
+	 * walk.
+	 */
+	return kvm_map_gpa(vcpu, gpa, FIELD_GET(ESR_ELx_FSC, pmbsr), true);
+}
+
+static int kvm_handle_spe_fault(struct kvm_vcpu *vcpu)
+{
+	struct s1_walk_info wi = {
+		.read_s1_desc	= kvm_spe_read_s1_desc,
+		.regime		= TR_EL10,
+		.as_el0		= false,
+		.pan		= false,
+	};
+	struct s1_walk_result wr = {};
+	u64 pmbptr = __vcpu_sys_reg(vcpu, PMBPTR_EL1);
+	u64 pmbsr = vcpu->arch.vcpu_spe.hw_pmbsr_el1;
+	int ret;
+
+	guard(srcu)(&vcpu->kvm->srcu);
+
+	ret = __kvm_translate_va(vcpu, &wi, &wr, pmbptr);
+	if (ret == -EAGAIN)
+		return 0;
+	if (ret)
+		return ret;
+
+	/* Stage 2 fault on a table walk was handled during the translation. */
+	/* TODO: this should be a hardware capability check */
+	if (kvm_has_feat(vcpu->kvm, ID_AA64DFR2_EL1, SPE_EXC, IMP) &&
+	    FIELD_GET(ESR_ELx_S1PTW, pmbsr))
+		return 0;
+
+	return kvm_map_gpa(vcpu, wr.pa, FIELD_GET(ESR_ELx_FSC, pmbsr), false);
+}
+
 void kvm_spe_sync_hwstate(struct kvm_vcpu *vcpu)
 {
 	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
+	u64 hw_pmbsr_el1;
+	int ret;
 
 	if (!vcpu_has_spe(vcpu))
 		return;
 
-	if (!FIELD_GET(PMBSR_EL1_S, vcpu_spe->hw_pmbsr_el1))
+	hw_pmbsr_el1 = vcpu_spe->hw_pmbsr_el1;
+	if (!FIELD_GET(PMBSR_EL1_S, hw_pmbsr_el1))
 		return;
 
-	__vcpu_assign_sys_reg(vcpu, PMBSR_EL1, vcpu_spe->hw_pmbsr_el1);
+	if (kvm_spe_should_handle_fault(vcpu)) {
+		pr_debug("SPE stage 2 fault PMBSR_EL1=0x%llx PMBPTR_EL1=0x%llx",
+			 hw_pmbsr_el1, __vcpu_sys_reg(vcpu, PMBPTR_EL1));
+		ret = kvm_handle_spe_fault(vcpu);
+		if (ret)
+			goto reinject;
+		/* Let the guest know about the partial record. */
+		if (FIELD_GET(PMBSR_EL1_DL, hw_pmbsr_el1))
+			goto reinject;
+		vcpu_spe->hw_pmbsr_el1 = 0;
+		return;
+	}
+
+reinject:
+	__vcpu_assign_sys_reg(vcpu, PMBSR_EL1, hw_pmbsr_el1);
 	vcpu_spe->hw_pmbsr_el1 = 0;
 	kvm_spe_update_irq_level(vcpu, true);
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 27/28] KVM: arm64: Handle dirty page logging when SPE feature is set
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (25 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 26/28] KVM: arm64: Map memory on a SPE stage 2 fault Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 17:06   ` sashiko-bot
  2026-09-03 16:06 ` [RFC PATCH v7 28/28] KVM: arm64: Allow the creation of a SPE enabled VM Alexandru Elisei
  2026-10-15 10:25 ` [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Dirty page logging requires that memory is marked as read-only at stage 2,
to track when a guest writes to memory.

For SPE, this will cause a stage 2 fault when a record is written to
memory, and a profiling blackout window. Avoid it by refusing to run a VCPU
with the buffer enabled when at least one memslot is logging dirty pages.

Though not prohibited by KVM, to avoid the aforementioned blackout window,
when the VCPU exits with KVM_EXIT_FAIL_ENTRY and
hardware_entry_failure_reason set to KVM_EXIT_FAIL_ENTRY_SPE_DIRTY_LOGGING,
userspace should not run that VCPU until dirty page logging is disabled.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 Documentation/virt/kvm/api.rst                |  7 +-
 arch/arm64/include/asm/kvm_asm.h              |  1 +
 arch/arm64/include/asm/kvm_host.h             | 24 +++---
 arch/arm64/include/asm/kvm_mmu.h              |  1 +
 arch/arm64/include/asm/kvm_pgtable.h          | 19 +++++
 arch/arm64/include/asm/kvm_pkvm.h             |  1 +
 arch/arm64/include/asm/kvm_spe.h              | 16 ++++
 arch/arm64/include/uapi/asm/kvm.h             |  1 +
 arch/arm64/kvm/arm.c                          | 27 ++++++-
 arch/arm64/kvm/hyp/include/nvhe/mem_protect.h |  2 +-
 arch/arm64/kvm/hyp/nvhe/hyp-main.c            | 34 +++++---
 arch/arm64/kvm/hyp/nvhe/mem_protect.c         |  7 +-
 arch/arm64/kvm/hyp/pgtable.c                  |  8 ++
 arch/arm64/kvm/mmu.c                          | 19 +++++
 arch/arm64/kvm/pkvm.c                         | 27 +++++++
 arch/arm64/kvm/spe.c                          | 79 +++++++++++++++++++
 16 files changed, 246 insertions(+), 27 deletions(-)

diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index 654736fac78c..00e85e79e145 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -1422,7 +1422,12 @@ pages in the host.
 The flags field supports two flags: KVM_MEM_LOG_DIRTY_PAGES and
 KVM_MEM_READONLY.  The former can be set to instruct KVM to keep track of
 writes to memory within the slot.  See KVM_GET_DIRTY_LOG ioctl to know how to
-use it.  The latter can be set, if KVM_CAP_READONLY_MEM capability allows it,
+use it. For arm64, setting and then clearing KVM_MEM_LOG_DIRTY_PAGES before
+creating a VCPU with the KVM_ARM_VCPU_SPE feature can lead to profiling
+blackout windows in the guest, caused by SPE encountering stage 2 faults when
+attempting to write to memory.
+
+KVM_MEM_READONLY can be set if KVM_CAP_READONLY_MEM capability allows it,
 to make a new slot read-only.  In this case, writes to this memory will be
 posted to userspace as KVM_EXIT_MMIO exits.
 
diff --git a/arch/arm64/include/asm/kvm_asm.h b/arch/arm64/include/asm/kvm_asm.h
index e5b92ac09e69..6251cbc6b105 100644
--- a/arch/arm64/include/asm/kvm_asm.h
+++ b/arch/arm64/include/asm/kvm_asm.h
@@ -100,6 +100,7 @@ enum __kvm_host_smccc_func {
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_unshare_guest,
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_relax_perms_guest,
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_wrprotect_guest,
+	__KVM_HOST_SMCCC_FUNC___pkvm_host_write_enable_guest,
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_test_clear_young_guest,
 	__KVM_HOST_SMCCC_FUNC___pkvm_host_mkyoung_guest,
 	__KVM_HOST_SMCCC_FUNC___pkvm_reserve_vm,
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index 31be131c8486..ff77f6ac3adf 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -45,17 +45,19 @@
 
 #define KVM_REQ_SLEEP \
 	KVM_ARCH_REQ_FLAGS(0, KVM_REQUEST_WAIT | KVM_REQUEST_NO_WAKEUP)
-#define KVM_REQ_IRQ_PENDING		KVM_ARCH_REQ(1)
-#define KVM_REQ_VCPU_RESET		KVM_ARCH_REQ(2)
-#define KVM_REQ_RECORD_STEAL		KVM_ARCH_REQ(3)
-#define KVM_REQ_RELOAD_GICv4		KVM_ARCH_REQ(4)
-#define KVM_REQ_RELOAD_PMU		KVM_ARCH_REQ(5)
-#define KVM_REQ_SUSPEND			KVM_ARCH_REQ(6)
-#define KVM_REQ_RESYNC_PMU_EL0		KVM_ARCH_REQ(7)
-#define KVM_REQ_NESTED_S2_UNMAP		KVM_ARCH_REQ(8)
-#define KVM_REQ_GUEST_HYP_IRQ_PENDING	KVM_ARCH_REQ(9)
-#define KVM_REQ_MAP_L1_VNCR_EL2		KVM_ARCH_REQ(10)
-#define KVM_REQ_VGIC_PROCESS_UPDATE	KVM_ARCH_REQ(11)
+#define KVM_REQ_IRQ_PENDING			KVM_ARCH_REQ(1)
+#define KVM_REQ_VCPU_RESET			KVM_ARCH_REQ(2)
+#define KVM_REQ_RECORD_STEAL			KVM_ARCH_REQ(3)
+#define KVM_REQ_RELOAD_GICv4			KVM_ARCH_REQ(4)
+#define KVM_REQ_RELOAD_PMU			KVM_ARCH_REQ(5)
+#define KVM_REQ_SUSPEND				KVM_ARCH_REQ(6)
+#define KVM_REQ_RESYNC_PMU_EL0			KVM_ARCH_REQ(7)
+#define KVM_REQ_NESTED_S2_UNMAP			KVM_ARCH_REQ(8)
+#define KVM_REQ_GUEST_HYP_IRQ_PENDING		KVM_ARCH_REQ(9)
+#define KVM_REQ_MAP_L1_VNCR_EL2			KVM_ARCH_REQ(10)
+#define KVM_REQ_VGIC_PROCESS_UPDATE		KVM_ARCH_REQ(11)
+#define KVM_REQ_SPE_HANDLE_DIRTY_LOGGING	KVM_ARCH_REQ(12)
+#define KVM_REQ_SPE_EXIT_DIRTY_LOGGING		KVM_ARCH_REQ(13)
 
 #define KVM_DIRTY_LOG_MANUAL_CAPS   (KVM_DIRTY_LOG_MANUAL_PROTECT_ENABLE | \
 				     KVM_DIRTY_LOG_INITIALLY_SET)
diff --git a/arch/arm64/include/asm/kvm_mmu.h b/arch/arm64/include/asm/kvm_mmu.h
index 6eae7e7e2a68..5509e3e64abc 100644
--- a/arch/arm64/include/asm/kvm_mmu.h
+++ b/arch/arm64/include/asm/kvm_mmu.h
@@ -173,6 +173,7 @@ void kvm_stage2_unmap_range(struct kvm_s2_mmu *mmu, phys_addr_t start,
 			    u64 size, bool may_block);
 void kvm_stage2_flush_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, phys_addr_t end);
 void kvm_stage2_wp_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, phys_addr_t end);
+void kvm_stage2_write_enable_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, phys_addr_t end);
 
 void stage2_unmap_vm(struct kvm *kvm);
 int kvm_init_stage2_mmu(struct kvm *kvm, struct kvm_s2_mmu *mmu, unsigned long type);
diff --git a/arch/arm64/include/asm/kvm_pgtable.h b/arch/arm64/include/asm/kvm_pgtable.h
index 41a8687938eb..53c8dd62dcd5 100644
--- a/arch/arm64/include/asm/kvm_pgtable.h
+++ b/arch/arm64/include/asm/kvm_pgtable.h
@@ -735,6 +735,25 @@ int kvm_pgtable_stage2_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size);
  */
 int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size);
 
+/**
+ * kvm_pgtable_stage2_write_enable() - Grant write permission to the stage-2
+ * 				       address range without TLB invalidation.
+ * @pgt:	Page-table structure initialised by kvm_pgtable_stage2_init*().
+ * @addr:	Intermediate physical address from which to write-enable.
+ * @size:	Size of the range.
+ *
+ * The offset of @addr within a page is ignored and @size is rounded-up to
+ * the next page boundary.
+ *
+ * Note that it is the caller's responsibility to invalidate the TLB after
+ * calling this function to ensure that the updated permissions are visible
+ * to the CPUs.
+ *
+ * Return: 0 on success, negative error code on failure.
+ */
+int kvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr,
+				    u64 size);
+
 /**
  * kvm_pgtable_stage2_mkyoung() - Set the access flag in a page-table entry.
  * @pgt:	Page-table structure initialised by kvm_pgtable_stage2_init*().
diff --git a/arch/arm64/include/asm/kvm_pkvm.h b/arch/arm64/include/asm/kvm_pkvm.h
index beea00e693a0..a1aa03d13937 100644
--- a/arch/arm64/include/asm/kvm_pkvm.h
+++ b/arch/arm64/include/asm/kvm_pkvm.h
@@ -215,6 +215,7 @@ int pkvm_pgtable_stage2_map(struct kvm_pgtable *pgt, u64 addr, u64 size, u64 phy
 			    enum kvm_pgtable_walk_flags flags);
 int pkvm_pgtable_stage2_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size);
 int pkvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size);
+int pkvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr, u64 size);
 int pkvm_pgtable_stage2_flush(struct kvm_pgtable *pgt, u64 addr, u64 size);
 bool pkvm_pgtable_stage2_test_clear_young(struct kvm_pgtable *pgt, u64 addr, u64 size, bool mkold);
 int pkvm_pgtable_stage2_relax_perms(struct kvm_pgtable *pgt, u64 addr, enum kvm_pgtable_prot prot,
diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
index 88f8b1bb6864..d710f64e00be 100644
--- a/arch/arm64/include/asm/kvm_spe.h
+++ b/arch/arm64/include/asm/kvm_spe.h
@@ -9,6 +9,7 @@
 struct kvm;
 struct kvm_vcpu;
 struct kvm_device_attr;
+struct kvm_memory_slot;
 
 #ifdef CONFIG_KVM_ARM_SPE
 struct arm_spe_pmu;
@@ -39,6 +40,10 @@ void kvm_spe_destroy_vm(struct kvm *kvm);
 
 void kvm_spe_finalize_idregs(struct kvm *kvm);
 
+void kvm_spe_add_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot);
+void kvm_spe_remove_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot);
+void kvm_spe_handle_dirty_logging_request(struct kvm_vcpu *vcpu);
+
 int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
 int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
@@ -86,6 +91,17 @@ static inline void kvm_spe_destroy_vm(struct kvm *kvm)
 static inline void kvm_spe_finalize_idregs(struct kvm *kvm)
 {
 }
+static inline void kvm_spe_add_dirty_logging(struct kvm *kvm,
+					     const struct kvm_memory_slot *slot)
+{
+}
+static inline void kvm_spe_remove_dirty_logging(struct kvm *kvm,
+						const struct kvm_memory_slot *slot)
+{
+}
+static inline void kvm_spe_handle_dirty_logging_request(struct kvm_vcpu *vcpu)
+{
+}
 static inline int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
 {
 	return -ENXIO;
diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
index c695aae484d3..a86fa4f9d9e4 100644
--- a/arch/arm64/include/uapi/asm/kvm.h
+++ b/arch/arm64/include/uapi/asm/kvm.h
@@ -511,6 +511,7 @@ enum {
 
 /* run->fail_entry.hardware_entry_failure_reason codes. */
 #define KVM_EXIT_FAIL_ENTRY_CPU_UNSUPPORTED	(1ULL << 0)
+#define KVM_EXIT_FAIL_ENTRY_SPE_DIRTY_LOGGING	(1ULL << 1)
 
 enum kvm_smccc_filter_action {
 	KVM_SMCCC_FILTER_HANDLE = 0,
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
index 7c7d0616db53..73f321b3ed2d 100644
--- a/arch/arm64/kvm/arm.c
+++ b/arch/arm64/kvm/arm.c
@@ -1172,6 +1172,15 @@ static int kvm_vcpu_suspend(struct kvm_vcpu *vcpu)
 	return 1;
 }
 
+static void kvm_vcpu_prepare_exit_fail_entry(struct kvm_vcpu *vcpu, u64 reason)
+{
+	struct kvm_run *run = vcpu->run;
+
+	run->exit_reason = KVM_EXIT_FAIL_ENTRY;
+	run->fail_entry.hardware_entry_failure_reason = reason;
+	run->fail_entry.cpu = smp_processor_id();
+}
+
 /**
  * check_vcpu_requests - check and handle pending vCPU requests
  * @vcpu:	the VCPU pointer
@@ -1223,6 +1232,20 @@ static int check_vcpu_requests(struct kvm_vcpu *vcpu)
 		if (kvm_check_request(KVM_REQ_SUSPEND, vcpu))
 			return kvm_vcpu_suspend(vcpu);
 
+		if (kvm_check_request(KVM_REQ_SPE_HANDLE_DIRTY_LOGGING, vcpu))
+			kvm_spe_handle_dirty_logging_request(vcpu);
+
+		/*
+		 * The request is consumed on purpose, to allow userspace to
+		 * resume the VCPU at the expense of profiling blackout windows
+		 * if it so wishes.
+		 */
+		if (kvm_check_request(KVM_REQ_SPE_EXIT_DIRTY_LOGGING, vcpu)) {
+			kvm_vcpu_prepare_exit_fail_entry(vcpu,
+					KVM_EXIT_FAIL_ENTRY_SPE_DIRTY_LOGGING);
+			return 0;
+		}
+
 		if (kvm_dirty_ring_check_request(vcpu))
 			return 0;
 
@@ -1285,9 +1308,7 @@ static bool kvm_vcpu_exit_request(struct kvm_vcpu *vcpu, int *ret)
 	}
 
 	if (unlikely(vcpu_on_unsupported_cpu(vcpu))) {
-		run->exit_reason = KVM_EXIT_FAIL_ENTRY;
-		run->fail_entry.hardware_entry_failure_reason = KVM_EXIT_FAIL_ENTRY_CPU_UNSUPPORTED;
-		run->fail_entry.cpu = smp_processor_id();
+		kvm_vcpu_prepare_exit_fail_entry(vcpu, KVM_EXIT_FAIL_ENTRY_CPU_UNSUPPORTED);
 		*ret = 0;
 		return true;
 	}
diff --git a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
index 29935c7da1de..dadb56d62820 100644
--- a/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
+++ b/arch/arm64/kvm/hyp/include/nvhe/mem_protect.h
@@ -47,7 +47,7 @@ int __pkvm_host_share_guest(u64 pfn, u64 gfn, u64 nr_pages, struct pkvm_hyp_vcpu
 			    enum kvm_pgtable_prot prot);
 int __pkvm_host_unshare_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *hyp_vm);
 int __pkvm_host_relax_perms_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu, enum kvm_pgtable_prot prot);
-int __pkvm_host_wrprotect_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *hyp_vm);
+int __pkvm_host_toggle_wp_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *hyp_vm, bool wp);
 int __pkvm_host_test_clear_young_guest(u64 gfn, u64 nr_pages, bool mkold, struct pkvm_hyp_vm *vm);
 int __pkvm_host_mkyoung_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu);
 
diff --git a/arch/arm64/kvm/hyp/nvhe/hyp-main.c b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
index 9a3b92e626ad..34c442f1529b 100644
--- a/arch/arm64/kvm/hyp/nvhe/hyp-main.c
+++ b/arch/arm64/kvm/hyp/nvhe/hyp-main.c
@@ -487,22 +487,37 @@ static void handle___pkvm_host_relax_perms_guest(struct kvm_cpu_context *host_ct
 	cpu_reg(host_ctxt, 1) = ret;
 }
 
-static void handle___pkvm_host_wrprotect_guest(struct kvm_cpu_context *host_ctxt)
+static int host_toggle_wp_guest(pkvm_handle_t handle, u64 gfn, u64 nr_pages, bool wp)
 {
-	DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
-	DECLARE_REG(u64, gfn, host_ctxt, 2);
-	DECLARE_REG(u64, nr_pages, host_ctxt, 3);
 	struct pkvm_hyp_vm *hyp_vm;
-	int ret = -EINVAL;
+	int ret;
 
 	hyp_vm = get_np_pkvm_hyp_vm(handle);
 	if (!hyp_vm)
-		goto out;
+		return -EINVAL;
 
-	ret = __pkvm_host_wrprotect_guest(gfn, nr_pages, hyp_vm);
+	ret = __pkvm_host_toggle_wp_guest(gfn, nr_pages, hyp_vm, wp);
 	put_pkvm_hyp_vm(hyp_vm);
-out:
-	cpu_reg(host_ctxt, 1) = ret;
+
+	return ret;
+}
+
+static void handle___pkvm_host_wrprotect_guest(struct kvm_cpu_context *host_ctxt)
+{
+	DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
+	DECLARE_REG(u64, gfn, host_ctxt, 2);
+	DECLARE_REG(u64, nr_pages, host_ctxt, 3);
+
+	cpu_reg(host_ctxt, 1) = host_toggle_wp_guest(handle, gfn, nr_pages, true);
+}
+
+static void handle___pkvm_host_write_enable_guest(struct kvm_cpu_context *host_ctxt)
+{
+	DECLARE_REG(pkvm_handle_t, handle, host_ctxt, 1);
+	DECLARE_REG(u64, gfn, host_ctxt, 2);
+	DECLARE_REG(u64, nr_pages, host_ctxt, 3);
+
+	cpu_reg(host_ctxt, 1) = host_toggle_wp_guest(handle, gfn, nr_pages, false);
 }
 
 static void handle___pkvm_host_test_clear_young_guest(struct kvm_cpu_context *host_ctxt)
@@ -942,6 +957,7 @@ static const hcall_t host_hcall[] = {
 	HANDLE_FUNC(__pkvm_host_unshare_guest),
 	HANDLE_FUNC(__pkvm_host_relax_perms_guest),
 	HANDLE_FUNC(__pkvm_host_wrprotect_guest),
+	HANDLE_FUNC(__pkvm_host_write_enable_guest),
 	HANDLE_FUNC(__pkvm_host_test_clear_young_guest),
 	HANDLE_FUNC(__pkvm_host_mkyoung_guest),
 	HANDLE_FUNC(__pkvm_reserve_vm),
diff --git a/arch/arm64/kvm/hyp/nvhe/mem_protect.c b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
index 39aa8911f62c..8e5405280c9d 100644
--- a/arch/arm64/kvm/hyp/nvhe/mem_protect.c
+++ b/arch/arm64/kvm/hyp/nvhe/mem_protect.c
@@ -1650,7 +1650,7 @@ int __pkvm_host_relax_perms_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu, enum kvm_
 	return ret;
 }
 
-int __pkvm_host_wrprotect_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *vm)
+int __pkvm_host_toggle_wp_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *vm, bool wp)
 {
 	u64 size, ipa = hyp_pfn_to_phys(gfn);
 	int ret;
@@ -1664,7 +1664,10 @@ int __pkvm_host_wrprotect_guest(u64 gfn, u64 nr_pages, struct pkvm_hyp_vm *vm)
 
 	assert_host_shared_guest(vm, ipa, size);
 	guest_lock_component(vm);
-	ret = kvm_pgtable_stage2_wrprotect(&vm->pgt, ipa, size);
+	if (wp)
+		ret = kvm_pgtable_stage2_wrprotect(&vm->pgt, ipa, size);
+	else
+		ret = kvm_pgtable_stage2_write_enable(&vm->pgt, ipa, size);
 	guest_unlock_component(vm);
 
 	return ret;
diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
index b74dd5ce1efd..07e43f607eae 100644
--- a/arch/arm64/kvm/hyp/pgtable.c
+++ b/arch/arm64/kvm/hyp/pgtable.c
@@ -1293,6 +1293,14 @@ int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size)
 					KVM_PGTABLE_WALK_IGNORE_EAGAIN);
 }
 
+int kvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr, u64 size)
+{
+	return stage2_update_leaf_attrs(pgt, addr, size,
+					KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, 0,
+					NULL, NULL,
+					KVM_PGTABLE_WALK_IGNORE_EAGAIN);
+}
+
 void kvm_pgtable_stage2_mkyoung(struct kvm_pgtable *pgt, u64 addr,
 				enum kvm_pgtable_walk_flags flags)
 {
diff --git a/arch/arm64/kvm/mmu.c b/arch/arm64/kvm/mmu.c
index d7e7365c5c5a..e1dfc7d5db3e 100644
--- a/arch/arm64/kvm/mmu.c
+++ b/arch/arm64/kvm/mmu.c
@@ -1240,6 +1240,17 @@ int kvm_phys_addr_ioremap(struct kvm *kvm, phys_addr_t guest_ipa,
 	return ret;
 }
 
+/**
+ * kvm_stage2_write_enable_range() - grant write permision for the memory range
+ * @mmu:        The KVM stage-2 MMU pointer
+ * @addr:	Start address of range
+ * @end:	End address of range
+ */
+void kvm_stage2_write_enable_range(struct kvm_s2_mmu *mmu, phys_addr_t addr, phys_addr_t end)
+{
+	stage2_apply_range_resched(mmu, addr, end, KVM_PGT_FN(kvm_pgtable_stage2_write_enable));
+}
+
 /**
  * kvm_stage2_wp_range() - write protect stage2 memory region range
  * @mmu:        The KVM stage-2 MMU pointer
@@ -2578,6 +2589,14 @@ void kvm_arch_commit_memory_region(struct kvm *kvm,
 				   enum kvm_mr_change change)
 {
 	bool log_dirty_pages = new && new->flags & KVM_MEM_LOG_DIRTY_PAGES;
+	bool had_dirty_logging = old && old->flags & KVM_MEM_LOG_DIRTY_PAGES;
+
+	if (change != KVM_MR_DELETE && had_dirty_logging != log_dirty_pages) {
+		if (log_dirty_pages)
+			kvm_spe_add_dirty_logging(kvm, new);
+		else
+			kvm_spe_remove_dirty_logging(kvm, new);
+	}
 
 	/*
 	 * At this point memslot has been committed and there is an
diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
index 8e4c6e4bec12..5ba2fa8010cc 100644
--- a/arch/arm64/kvm/pkvm.c
+++ b/arch/arm64/kvm/pkvm.c
@@ -488,6 +488,33 @@ int pkvm_pgtable_stage2_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size)
 	return __pkvm_pgtable_stage2_unshare(pgt, addr, addr + size);
 }
 
+int pkvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr, u64 size)
+{
+	struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
+	pkvm_handle_t handle = kvm->arch.pkvm.handle;
+	struct pkvm_mapping *mapping;
+	u64 gfn, end_gfn, nr_pages;
+	int ret = 0;
+
+	if (WARN_ON(kvm_vm_is_protected(kvm)))
+		return -EPERM;
+
+	end_gfn = (addr + size) >> PAGE_SHIFT;
+
+	lockdep_assert_held(&kvm->mmu_lock);
+	for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) {
+		gfn = max(addr >> PAGE_SHIFT, mapping->gfn);
+		nr_pages = min_t(u64, end_gfn - gfn, mapping->nr_pages);
+		ret = kvm_call_hyp_nvhe(__pkvm_host_write_enable_guest, handle, gfn,
+					nr_pages);
+		if (WARN_ON(ret))
+			break;
+	}
+
+	return ret;
+}
+
+
 int pkvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size)
 {
 	struct kvm *kvm = kvm_s2_mmu_to_kvm(pgt->mmu);
diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
index e5f3d0dea7c5..f3310465f40e 100644
--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ -10,6 +10,7 @@
 #include <linux/perf_event.h>
 
 #include <asm/kvm_emulate.h>
+#include <asm/kvm_mmu.h>
 #include <asm/kvm_nested.h>
 #include <asm/kvm_spe.h>
 #include <asm/sysreg.h>
@@ -141,6 +142,81 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
 	module_put(spe_pmu->pmu.module);
 }
 
+void kvm_spe_add_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot)
+{
+	struct kvm_vcpu *vcpu;
+	unsigned long i;
+
+	lockdep_assert_held(&kvm->slots_lock);
+
+	/*
+	 * No point in handling a regular memslot, because they cannot guarantee
+	 * that memory remains mapped at stage 2.
+	 *
+	 * Memory described by a read-only memslot is always read-only, so SPE
+	 * cannot write to it anyway.
+	 */
+	if (!kvm_slot_has_gmem(slot) || (slot->flags & KVM_MEM_READONLY))
+		return;
+
+	kvm_for_each_vcpu(i, vcpu, kvm) {
+		if (!vcpu_has_spe(vcpu))
+			continue;
+		kvm_make_request(KVM_REQ_SPE_HANDLE_DIRTY_LOGGING, vcpu);
+		if (vcpu_has_run_once(vcpu))
+			__kvm_vcpu_kick(vcpu, true);
+	}
+}
+
+void kvm_spe_remove_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot)
+{
+	phys_addr_t start, end;
+	struct kvm_vcpu *vcpu;
+	unsigned long i;
+
+	lockdep_assert_held(&kvm->slots_lock);
+
+	if (!kvm_slot_has_gmem(slot) || (slot->flags & KVM_MEM_READONLY))
+		return;
+
+	kvm_for_each_vcpu(i, vcpu, kvm) {
+		/*
+		 * Write-enabling an entire memslot can be expensive, do it only
+		 * if the VM has SPE.
+		 */
+		if (vcpu_has_spe(vcpu)) {
+			start = slot->base_gfn << PAGE_SHIFT;
+			end = (slot->base_gfn + slot->npages) << PAGE_SHIFT;
+
+			write_lock(&kvm->mmu_lock);
+			kvm_stage2_write_enable_range(&kvm->arch.mmu, start, end);
+			write_unlock(&kvm->mmu_lock);
+			kvm_flush_remote_tlbs_memslot(kvm, slot);
+			return;
+		}
+	}
+}
+
+static bool kvm_spe_should_exit_dirty_logging(struct kvm_vcpu *vcpu)
+{
+	struct kvm *kvm = vcpu->kvm;
+	u64 pmblimitr, pmbsr;
+
+	if (!atomic_read(&kvm->nr_memslots_dirty_logging))
+		return false;
+
+	pmblimitr = __vcpu_sys_reg(vcpu, PMBLIMITR_EL1);
+	pmbsr = __vcpu_sys_reg(vcpu, PMBSR_EL1);
+
+	return FIELD_GET(PMBLIMITR_EL1_E, pmblimitr) && !FIELD_GET(PMBSR_EL1_S, pmbsr);
+}
+
+void kvm_spe_handle_dirty_logging_request(struct kvm_vcpu *vcpu)
+{
+	if (kvm_spe_should_exit_dirty_logging(vcpu))
+		kvm_make_request(KVM_REQ_SPE_EXIT_DIRTY_LOGGING, vcpu);
+}
+
 bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
 {
 	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
@@ -214,6 +290,9 @@ bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
 		kvm_spe_update_irq_level(vcpu, FIELD_GET(PMBSR_EL1_S, val));
 	}
 
+	if (kvm_spe_should_exit_dirty_logging(vcpu))
+		kvm_make_request(KVM_REQ_SPE_EXIT_DIRTY_LOGGING, vcpu);
+
 	return true;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* [RFC PATCH v7 28/28] KVM: arm64: Allow the creation of a SPE enabled VM
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (26 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 27/28] KVM: arm64: Handle dirty page logging when SPE feature is set Alexandru Elisei
@ 2026-09-03 16:06 ` Alexandru Elisei
  2026-09-03 16:59   ` sashiko-bot
  2026-10-15 10:25 ` [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-03 16:06 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Everything is in place, allow userspace to enable SPE for a virtual
machine.

Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
---
 arch/arm64/include/asm/kvm_host.h | 2 +-
 arch/arm64/kvm/Kconfig            | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
index ff77f6ac3adf..32b30ffdcf77 100644
--- a/arch/arm64/include/asm/kvm_host.h
+++ b/arch/arm64/include/asm/kvm_host.h
@@ -40,7 +40,7 @@
 
 #define KVM_MAX_VCPUS VGIC_V3_MAX_CPUS
 
-#define KVM_VCPU_MAX_FEATURES 10
+#define KVM_VCPU_MAX_FEATURES 11
 #define KVM_VCPU_VALID_FEATURES	(BIT(KVM_VCPU_MAX_FEATURES) - 1)
 
 #define KVM_REQ_SLEEP \
diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
index 2bc8e34c538d..a285b18eb1cc 100644
--- a/arch/arm64/kvm/Kconfig
+++ b/arch/arm64/kvm/Kconfig
@@ -106,9 +106,9 @@ endif # NVHE_EL2_DEBUG
 
 
 config KVM_ARM_SPE
-	bool
+	bool "Support SPE in guest"
 	depends on KVM && ARM_SPE_PMU
-	default n
+	default y
 	help
 	  Adds support for Statistical Profiling Extension (SPE) in virtual
 	  machines.
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu
  2026-09-03 16:06 ` [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu Alexandru Elisei
@ 2026-09-03 16:11   ` sashiko-bot
  2026-10-15 15:12   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:11 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: linux-perf-users, Marc Zyngier, kvmarm, Oliver Upton

> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=5


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file
  2026-09-03 16:05 ` [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file Alexandru Elisei
@ 2026-09-03 16:11   ` sashiko-bot
  2026-10-15 14:40   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:11 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, linux-perf-users, Oliver Upton, Marc Zyngier

> KVM will soon want to make use of struct arm_spe_pmu, move it to a separate
> header where it will be easily accessible.
> 
> Cc: Will Deacon <will@kernel.org>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=4


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values
  2026-09-03 16:05 ` [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values Alexandru Elisei
@ 2026-09-03 16:12   ` sashiko-bot
  2026-10-15 13:52   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:12 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Marc Zyngier, linux-perf-users, Oliver Upton, kvmarm

> KVM will make use of the different values for MDCR_EL2.E2PB, document them.
> 
> Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=2


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option
  2026-09-03 16:05 ` [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option Alexandru Elisei
@ 2026-09-03 16:13   ` sashiko-bot
  2026-10-15 14:15   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:13 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Oliver Upton, kvmarm, Marc Zyngier, linux-perf-users

> Add a new configuration option that will be used for KVM SPE emulation.
> CONFIG_KVM_ARM_SPE depends on the SPE driver being loaded because:
> 
> 1. The SPE driver maintains a cpumask of physical CPUs that support SPE,
>    and that will be used by KVM to emulate SPE on heterogeneous systems.
> 
> 2. KVM will rely on the SPE driver enabling the SPE interrupt at the GIC
>    level.
> 
> The option will become user selectable after SPE support in KVM is
> implemented.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=3


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1
  2026-09-03 16:05 ` [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1 Alexandru Elisei
@ 2026-09-03 16:14   ` sashiko-bot
  2026-10-15 13:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:14 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Oliver Upton, kvmarm, linux-perf-users, Marc Zyngier

> Define PMBLIMITR_EL1.nVM, to be used by KVM.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=1


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability
  2026-09-03 16:06 ` [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability Alexandru Elisei
@ 2026-09-03 16:15   ` sashiko-bot
  2026-10-15 16:51   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:15 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Marc Zyngier, linux-perf-users, kvmarm, Oliver Upton

> Add the SPE capability that will be used by userspace to test if KVM
> supports SPE virtualization.
> 
> The SPE driver supports heterogenous systems. Keep track of all the
> available Statistical Profiling Units (SPUs) in the system, because KVM
> will require the user to associate a VM with exactly one SPU.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=6


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 09/28] HACK! KVM: arm64: Enable SPE virtualization only in VHE mode
  2026-09-03 16:06 ` [RFC PATCH v7 09/28] HACK! KVM: arm64: Enable SPE virtualization only in VHE mode Alexandru Elisei
@ 2026-09-03 16:15   ` sashiko-bot
  0 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:15 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Marc Zyngier, linux-perf-users, kvmarm, Oliver Upton

> For RFC only.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=9


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 10/28] HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled
  2026-09-03 16:06 ` [RFC PATCH v7 10/28] HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled Alexandru Elisei
@ 2026-09-03 16:20   ` sashiko-bot
  0 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:20 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, Oliver Upton, Marc Zyngier, linux-perf-users

> For RFC only.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=10


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature
  2026-09-03 16:06 ` [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature Alexandru Elisei
@ 2026-09-03 16:21   ` sashiko-bot
  2026-10-16 14:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:21 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, linux-perf-users, Marc Zyngier, Oliver Upton

> Add a new VCPU feature that enables SPE virtualization when set by
> userspace. KVM_VCPU_MAX_FEATURE will be bumped and the feature will
> become selectable by userspace once SPE support in KVM is implemented.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=7


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled
  2026-09-03 16:06 ` [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled Alexandru Elisei
@ 2026-09-03 16:21   ` sashiko-bot
  2026-10-16 14:30   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:21 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Marc Zyngier, linux-perf-users, kvmarm, Oliver Upton

> For RFC only.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=8


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE
  2026-09-03 16:06 ` [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE Alexandru Elisei
@ 2026-09-03 16:22   ` sashiko-bot
  2026-10-16 14:44   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:22 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, Oliver Upton, Marc Zyngier, linux-perf-users

> From: Sudeep Holla <sudeep.holla@arm.com>
> 
> Add a new VCPU device control group to control various aspects of KVM's SPE
> emulation. Functionality will be added in later patches.
> 
> [ Alexandru E: Major rework ]
> 
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=11


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number
  2026-09-03 16:06 ` [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number Alexandru Elisei
@ 2026-09-03 16:27   ` sashiko-bot
  2026-10-20 17:20   ` Lorenzo Stoakes (ARM)
  2026-10-28 16:52   ` Suzuki K Poulose
  2 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:27 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: linux-perf-users, kvmarm, Oliver Upton, Marc Zyngier

> From: Sudeep Holla <sudeep.holla@arm.com>
> 
> Add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_IRQ) to allow the user to set
> the interrupt number for the buffer management interrupt.
> 
> [ Alexandru E: Split from "KVM: arm64: Add a new VCPU device control group
>                for SPE" ]
> 
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=12


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE
  2026-09-03 16:06 ` [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE Alexandru Elisei
@ 2026-09-03 16:28   ` sashiko-bot
  2026-10-28 13:45   ` Lorenzo Stoakes (ARM)
  2026-10-29 18:03   ` Lorenzo Stoakes (ARM)
  2 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:28 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, linux-perf-users, Marc Zyngier, Oliver Upton

> Add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_INIT) VCPU ioctl to initialize
> SPE. Initialization must be done exactly once for each VCPU.
> 
> [ Alexandru E: Split from "KVM: arm64: Add a new VCPU device control group
> 	   for SPE" ]
> 
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=14


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context
  2026-09-03 16:06 ` [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context Alexandru Elisei
@ 2026-09-03 16:32   ` sashiko-bot
  2026-09-04 10:28     ` Alexandru Elisei
  2026-10-29 15:42   ` Lorenzo Stoakes (ARM)
  1 sibling, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:32 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Marc Zyngier, kvmarm, Oliver Upton, linux-perf-users

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] WARN_ON_ONCE is incorrectly called with a string literal instead of a boolean condition.
--

commit aa1092f112d707ae630c6edfaa06e1cf58da7524
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: Add SPE system registers to VCPU context

This commit adds SPE registers to the VCPU context. It only traps the buffer
registers, allowing KVM to track the buffer enabled state for dirty page
logging and to sanitise writes to PMBLIMITR_EL1 based on exposed VM features.

[ ... ]

> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index 4426d614a9e0b..6a00a44c73edf 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c

[ ... ]

> @@ -82,6 +83,34 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
>  	module_put(spe_pmu->pmu.module);
>  }
>  
> +bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
> +{
> +	switch (reg) {
> +	case PMBLIMITR_EL1:
> +	case PMBSR_EL1:
> +	case PMBPTR_EL1:
> +		__vcpu_assign_sys_reg(vcpu, reg, val);
> +		break;
> +	default:
> +		WARN_ON_ONCE("unexpected trap");

[Severity: Low]
Does this macro call miss a boolean condition? The WARN_ON_ONCE macro
expects a boolean expression, but it is being passed a string literal here.
While this functionally evaluates to true because the string decays into a
non-null pointer, could this be considered an API misuse? Perhaps this
should use WARN_ONCE(1, "unexpected trap") or WARN_ON_ONCE(1) instead when
an unexpected trapped SPE register access falls through to the default case
in kvm_spe_write_sysreg().

> +	}
> +
> +	return true;
> +}
> +
> +u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
> +{
> +	switch (reg) {
> +	case PMBLIMITR_EL1:
> +	case PMBSR_EL1:
> +	case PMBPTR_EL1:
> +		return __vcpu_sys_reg(vcpu, reg);
> +	default:
> +		WARN_ON_ONCE("unexpected trap");

[Severity: Low]
Similarly, should this use WARN_ONCE(1, "unexpected trap") instead of
passing a string literal directly into WARN_ON_ONCE when an unexpected
trapped read falls through to the default case in kvm_spe_read_sysreg()?

> +		return 0;
> +	}
> +}

[ ... ]

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=16

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes
  2026-09-03 16:06 ` [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes Alexandru Elisei
@ 2026-09-03 16:37   ` sashiko-bot
  2026-09-04 10:41     ` Alexandru Elisei
  2026-10-29 17:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:37 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Oliver Upton, Marc Zyngier, kvmarm, linux-perf-users

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Critical] get_user_spe_sysreg() and set_user_spe_sysreg() return -EBUSY before SPE initialization, violating the KVM_GET_REG_LIST contract.
- [High] Userspace can bypass the PMBLIMITR_EL1 RES0 mask by writing to the register before the first KVM_RUN.
- [Low] Missing READ_ONCE() when accessing pmblimitr_el1_res0_mask causes a data race.
--

commit 1270412c2611a30b1f16a0aa9858eb290c7f9768
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes

This commit introduces a RES0 mask for PMBLIMITR_EL1 to hide unsupported
granule sizes from the guest. It computes this mask during the vCPU's
first run and applies it when userspace or the guest writes to the register.

> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index 6a00a44c73edf..8c173a1f48bd4 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
[ ... ]
> @@ -60,13 +60,43 @@ bool kvm_supports_spe(void)
[ ... ]
>  int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
>  {
> +	struct kvm *kvm = vcpu->kvm;
> +	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
>  
> -	if (!vcpu_spe->initialized)
> +	if (!kvm_vcpu_spe_initialized(vcpu))
>  		return -EINVAL;
>  
> +	if (!kvm_spe->pmblimitr_el1_res0_mask)

[Severity: Low]
Does this plain read of pmblimitr_el1_res0_mask need a READ_ONCE() to
prevent compiler-induced load tearing?

The mask is updated locklessly using WRITE_ONCE() inside
kvm_spe_compute_pmblimitr_el1_res0_mask(). Without a READ_ONCE() here,
concurrent vCPU executions could trigger a data race.

> +		kvm_spe_compute_pmblimitr_el1_res0_mask(kvm);
>  
>  	return 0;
>  }
> @@ -85,8 +115,12 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
>  
>  bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
>  {
> +	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
> +
>  	switch (reg) {
>  	case PMBLIMITR_EL1:
> +		val &= ~kvm_spe->pmblimitr_el1_res0_mask;

[Severity: High]
Can userspace bypass this RES0 sanitization?

If the VMM calls KVM_SET_ONE_REG for PMBLIMITR_EL1 before running the vCPU
for the first time, pmblimitr_el1_res0_mask will still be 0.

Because the mask isn't computed until the first run, the bitwise NOT evaluates
to all 1s (val &= ~0), which leaves the user-provided value unmodified and
potentially exposes the host hardware to unsupported granule configurations.

[Severity: Low]
Does this mask read also need a READ_ONCE() to safely pair with the
WRITE_ONCE() update, similar to the check in kvm_spe_vcpu_first_run_init()?

> +		fallthrough;
>  	case PMBSR_EL1:
>  	case PMBPTR_EL1:
>  		__vcpu_assign_sys_reg(vcpu, reg, val);

> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index 89b01b6ac7830..4c4bf88c9aa53 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
[ ... ]
> @@ -1563,6 +1563,38 @@ static unsigned int spe_visibility(const struct kvm_vcpu *vcpu,
>  	return REG_HIDDEN;
>  }
>  
> +static int set_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
> +			       u64 val)
> +{
> +	int reg = r->reg;
> +
> +	if (!kvm_vcpu_spe_initialized(vcpu))
> +		return -EBUSY;

[Severity: Critical]
Does returning -EBUSY here break the KVM_GET_REG_LIST ABI contract?

spe_visibility() currently exposes SPE registers based purely on
vcpu_has_spe(). Because of this, KVM_GET_REG_LIST will advertise the SPE
registers immediately after a vCPU is created with the SPE feature.

If a VMM queries the register list and then calls KVM_GET_ONE_REG or
KVM_SET_ONE_REG before KVM_ARM_VCPU_SPE_INIT is called, it will unexpectedly
hit this -EBUSY path and fail. The KVM API requires that any register
returned by KVM_GET_REG_LIST must be successfully readable via
KVM_GET_ONE_REG.

> +
> +	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)
> +		kvm_spe_write_sysreg(vcpu, reg, val);
> +	else
> +		__vcpu_assign_sys_reg(vcpu, reg, val);
> +
> +	return 0;
> +}
> +
> +static int get_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
> +			       u64 *val)
> +{
> +	int reg = r->reg;
> +
> +	if (!kvm_vcpu_spe_initialized(vcpu))
> +		return -EBUSY;
> +
> +	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)
> +		*val = kvm_spe_read_sysreg(vcpu, reg);
> +	else
> +		*val = __vcpu_sys_reg(vcpu, reg);
> +
> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=17

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device
  2026-09-03 16:06 ` [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device Alexandru Elisei
@ 2026-09-03 16:39   ` sashiko-bot
  2026-09-04  9:32     ` Alexandru Elisei
  2026-10-28 13:11   ` Lorenzo Stoakes (ARM)
  1 sibling, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:39 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Marc Zyngier, Oliver Upton, linux-perf-users, kvmarm

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Incorrect declaration of `supported_cpus` causes stack corruption and NULL pointer dereferences when `CONFIG_CPUMASK_OFFSTACK=n`.
- [Medium] Changing the PMU identifier after SPE is configured incorrectly accumulates CPU mask restrictions, potentially rejecting valid reassignments.
--

commit b2f754414c4648e5b2996f0b8f1a6f27ec6f0cf7
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: Add SPE VCPU device attribute to set the SPE device

This commit introduces a new KVM device attribute to assign an SPE
instance to a virtual machine. It adds support for tracking physical
CPUs that share the assigned SPE and PMU instances, ensuring VCPUs
only run on appropriate hardware.

> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 763b102dc51fa..132df44c30887 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c

[ ... ]

> @@ -612,6 +614,47 @@ void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu)
>  
>  }
>  
> +int kvm_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus,
> +			      cpumask_t *spe_cpus)
> +{
> +	cpumask_t *supported_cpus;
> +	cpumask_t *cpus;
> +	int ret;
> +
> +	if (!zalloc_cpumask_var(&supported_cpus, GFP_KERNEL_ACCOUNT))
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: Critical]
Does this cause stack memory corruption and a NULL pointer dereference
when CONFIG_CPUMASK_OFFSTACK=n?

In kvm_update_supported_cpus(), supported_cpus is declared as a simple pointer
rather than cpumask_var_t. When CONFIG_CPUMASK_OFFSTACK is disabled,
cpumask_var_t is defined as a 1-element array. Passing &supported_cpus to
zalloc_cpumask_var() will zero out NR_CPUS bits starting at the stack address
of the pointer itself, overwriting the pointer and potentially adjacent
stack variables.

The subsequent cpumask_copy() then dereferences a NULL pointer. Could we
use cpumask_var_t for the declaration here?

> +		return -ENOMEM;
> +
> +	cpumask_copy(supported_cpus, kvm->arch.supported_cpus);
> +
> +	lockdep_assert_held(&kvm->arch.config_lock);
> +
> +	ret = -EINVAL;
> +	/* Cannot update for SPE and PMU at the same time. */
> +	if (WARN_ON_ONCE(pmu_cpus && spe_cpus))
> +		goto out;
> +
> +	if (pmu_cpus)
> +		cpus = pmu_cpus;
> +	else
> +		cpus = spe_cpus;
> +
> +	if ((spe_cpus && kvm->arch.arm_pmu) ||
> +	    (pmu_cpus && kvm_spe_pmu_set(kvm)))
> +		cpumask_and(supported_cpus, cpus, supported_cpus);
                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
[Severity: Medium]
Could this incorrectly accumulate CPU mask restrictions and reject valid
reassignments?

If userspace configures a VM by first setting a PMU, then setting the SPE ID,
and later changes the PMU to a different instance (which is permitted before
the VM runs), this logic intersects the new PMU mask with the
already-intersected kvm->arch.supported_cpus mask.

Since the current mask already includes the first PMU's restrictions, the new
mask accumulates both PMUs' restrictions instead of replacing the old PMU's
mask. If the two PMUs have disjoint CPU masks, the intersection becomes empty
and the operation fails with -EINVAL. 

> +	else
> +		cpumask_copy(supported_cpus, cpus);
> +
> +	if (cpumask_empty(supported_cpus))
> +		goto out;
> +
> +	cpumask_copy(kvm->arch.supported_cpus, supported_cpus);
> +	ret = 0;
> +out:
> +	free_cpumask_var(supported_cpus);
> +
> +	return ret;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=13

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS}
  2026-09-03 16:06 ` [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS} Alexandru Elisei
@ 2026-09-03 16:40   ` sashiko-bot
  2026-10-29 17:47   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:40 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: linux-perf-users, kvmarm, Oliver Upton, Marc Zyngier

> KVM's FGU mechanism will trap the registers introduced by FEAT_SPE_FnE and
> FEAT_SPE_FDS if the feature is not present for the VM. Move the functions
> that check for the presence of these features out of config.c and into
> spe.c, since that's where the bulk of SPE virtualization lies.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=18


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance
  2026-09-03 16:06 ` [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance Alexandru Elisei
@ 2026-09-03 16:41   ` sashiko-bot
  2026-09-04 10:26     ` Alexandru Elisei
  2026-10-28 16:41   ` Lorenzo Stoakes (ARM)
  1 sibling, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:41 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, linux-perf-users, Marc Zyngier, Oliver Upton

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] kvm_spe_finalize_idregs() unconditionally overwrites the user-provided ID_AA64DFR0_EL1.PMSVer with the host's physical version on the first KVM_RUN, breaking live migration.
- [High] sanitise_id_aa64dfr0_el1() evaluates PMSVer dynamically based on the assigned SPE PMU, returning 0 if called before the PMU is assigned, which breaks KVM_SET_ONE_REG validation.
- [High] kvm_spe_finalize_idregs() is skipped if a vCPU without SPE runs first in a heterogeneous VM, permanently leaving PMSVer as 0 and hiding SPE from the guest.
--

commit 45e0320e2eaeb926a33b978420aa5a133c7a42c6
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: Use PMSVer from the assigned SPE instance

This commit updates KVM to initialize the VM's ID_AA64DFR0_EL1.PMSVer using
the SPE PMU instance assigned to the VM rather than the physical CPU executing
the vCPU init ioctl. This is intended to handle heterogeneous systems where
SPE instances may have different versions or be entirely absent on some CPUs.

> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index fa87d224c8af8..4426d614a9e0b 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
[ ... ]
> @@ -87,6 +87,30 @@ bool kvm_spe_pmu_set(struct kvm *kvm)
>  	return !!kvm->arch.kvm_spe.spe_pmu;
>  }
>  
> +void kvm_spe_finalize_idregs(struct kvm *kvm)
> +{
> +	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
> +	u64 reg;
> +
> +	if (!spe_pmu)
> +		return;
> +
> +	reg = kvm_read_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1);
> +	reg &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
> +	reg |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer, spe_pmu->pmsver);
> +	kvm_set_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1, reg);
> +}

[Severity: High]
Does this unconditionally overwrite the user-provided ID_AA64DFR0_EL1.PMSVer
value on the first KVM_RUN?

If a VMM restores ID_AA64DFR0_EL1 during live migration (for example, when
migrating from an older host to a newer one), kvm_spe_finalize_idregs() will
silently clobber the userspace-configured value and force it back to the host
hardware's version. Could this violate the KVM ABI and break live migration
sequences?

[ ... ]
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index 44aae52c473d7..a6bb6884b965a 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -2220,8 +2220,11 @@ static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
>  		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMUVer,
>  				      kvm_arm_pmu_get_pmuver_limit());
>  
> -	/* Hide SPE from guests */
>  	val &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
> +	if (vcpu_has_spe(vcpu))
> +		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer,
> +				      kvm_spe_get_pmsver(vcpu->kvm));

[Severity: High]
Can this dynamic evaluation break KVM_SET_ONE_REG validation during VM setup?

VMMs like QEMU typically restore VM ID registers via KVM_SET_ONE_REG
immediately after KVM_ARM_VCPU_INIT, before configuring device attributes.
Because sanitise_id_aa64dfr0_el1() evaluates PMSVer based on the assigned SPE
PMU, kvm_spe_get_pmsver() returns 0 until the SPE PMU is assigned.

This strict ordering dependency will cause KVM_SET_ONE_REG to reject valid,
non-zero userspace values with -EINVAL, which breaks standard live migration
sequences.

[ ... ]
> @@ -5928,6 +5931,9 @@ int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu)
>  		kvm_vgic_finalize_idregs(kvm);
>  	}
>  
> +	if (vcpu_has_spe(vcpu))
> +		kvm_spe_finalize_idregs(kvm);
> +
>  	return 0;
>  }

[Severity: High]
Can this leave PMSVer permanently set to 0 in a heterogeneous VM if a vCPU
without SPE runs first?

kvm_finalize_sys_regs() executes on the first KVM_RUN for a given VM. It uses
a per-vCPU feature check (vcpu_has_spe(vcpu)) to guard the VM-wide ID register
initialization in kvm_spe_finalize_idregs(kvm).

If the first scheduled vCPU happens to lack SPE, vcpu_has_spe(vcpu) evaluates
to false and kvm_spe_finalize_idregs(kvm) is skipped. The VM is then marked
with KVM_ARCH_FLAG_HAS_RAN_ONCE, meaning SPE initialization is skipped forever.
This would permanently hide SPE from all vCPUs in the VM.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=15

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 19/28] KVM: arm64: VHE: Context switch SPE state
  2026-09-03 16:06 ` [RFC PATCH v7 19/28] KVM: arm64: VHE: Context switch SPE state Alexandru Elisei
@ 2026-09-03 16:43   ` sashiko-bot
  2026-09-04 11:35     ` Alexandru Elisei
  0 siblings, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:43 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, Marc Zyngier, linux-perf-users, Oliver Upton

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] Writing to PMBSR_EL1 after enabling the SPE profiling buffer via PMBLIMITR_EL1 is CONSTRAINED UNPREDICTABLE and can corrupt hardware events.
- [High] Host SPE sampling control registers (e.g., PMSNEVFR_EL1, PMSDSFR_EL1) can be permanently corrupted by a malicious guest.
--

commit 32125a6226fde42baaf9001e75434e4177067f24
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: VHE: Context switch SPE state

Save and restore the SPE register state when a VCPU is run. The SPE
resources are shared between the guest and the host (the resources are not
partitioned) so KVM has to save the host state, change the owning
translation regime, and then restore the guest state before entering a
guest.

> diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
> new file mode 100644
> index 0000000000000..978620ad99fa5
> --- /dev/null
> +++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c
> @@ -0,0 +1,209 @@
[ ... ]
> +static void __kvm_spe_save_sampling_regs(struct kvm *kvm, struct kvm_cpu_context *ctxt)
> +{
> +	/*
> +	 * This is dependent on the VM features, and not the hardware features,
> +	 * even for the host, because (a) guest accesses to PMSNEVFR_EL1 and
> +	 * PMSDSFR_EL1 result in an undefined exception in the guest and (b)
> +	 * they don't affect the guest unless the guest explicitly enables them,
> +	 * and a well-behaved guest shouldn't enable them when the corresponding
> +	 * feature is not advertised.
> +	 */
> +	if (kvm_spe_has_feat_spe_fne(kvm))
> +		ctxt_sys_reg(ctxt, PMSNEVFR_EL1) = read_sysreg_s(SYS_PMSNEVFR_EL1);

[Severity: High]
Does this check of the guest's advertised features (kvm_spe_has_feat_spe_fne)
leave the host's sampling control registers vulnerable to corruption?

If the host physically supports FEAT_SPE_FNE but the KVM VM does not
advertise it, KVM will skip saving and restoring the host's PMSNEVFR_EL1
register. Because MDCR_EL2.TPMS = 0 is cleared on guest entry, these
registers will not trap to EL2.

A malicious guest could execute an msr instruction to PMSNEVFR_EL1 directly,
overwriting the hardware register. When the guest exits, KVM skips restoring
the host's value in __kvm_spe_restore_sampling_regs(), allowing the guest's
malicious configuration to persist in the host execution context.

Could this be checking the host's physical capabilities instead of the VM's
advertised features when saving and restoring host state?

[ ... ]
> +void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *guest_ctxt)
> +{
> +	u64 pmblimitr, pmbsr;
> +
> +	pmbsr = ctxt_sys_reg(guest_ctxt, PMBSR_EL1);
> +	pmblimitr = ctxt_sys_reg(guest_ctxt, PMBLIMITR_EL1);
> +	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
> +		write_sysreg_s(ctxt_sys_reg(guest_ctxt, PMBPTR_EL1), SYS_PMBPTR_EL1);
> +		isb();
> +		write_sysreg_s(pmblimitr, SYS_PMBLIMITR_EL1);
> +		isb();
> +		/* A buffer management event preserves fields in PMBSR_EL1 */
> +		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);

[Severity: High]
Is it safe to write to PMBSR_EL1 after enabling the SPE profiling buffer via
PMBLIMITR_EL1?

According to the ARM Architecture Reference Manual, when the profiling buffer
is enabled, a direct write to PMBSR_EL1 is constrained unpredictable. This
can corrupt hardware events or lead to unknown exceptions.

In __kvm_spe_restore_host_state_vhe(), the PMBSR_EL1 register is correctly
restored before PMBLIMITR_EL1. Should the guest restoration follow the same
sequence to avoid unpredictable behavior?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=19

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 23/28] KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter
  2026-09-03 16:06 ` [RFC PATCH v7 23/28] KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter Alexandru Elisei
@ 2026-09-03 16:46   ` sashiko-bot
  0 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:46 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: linux-perf-users, kvmarm, Oliver Upton, Marc Zyngier

> Add a new kernel command line parameter, kvm-arm.ignore_spe_errata with the
> effect of ignoring any errata that might inhibit userspace from creating a
> SPE enabled virtual machine.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=23


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 20/28] KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it
  2026-09-03 16:06 ` [RFC PATCH v7 20/28] KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it Alexandru Elisei
@ 2026-09-03 16:48   ` sashiko-bot
  2026-09-04 13:45     ` Alexandru Elisei
  0 siblings, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:48 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, Marc Zyngier, linux-perf-users, Oliver Upton

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Inverted logic for `perf_allow_kernel()` grants physical timestamps to unprivileged users and denies them to privileged users.
- [High] Capability check `perf_allow_kernel()` is evaluated at `KVM_RUN` time (TOCTOU) instead of VM/VCPU configuration time.
- [Medium] Silently-on guest-visible feature (SPE physical timestamps) breaks live migration and violates subsystem guidelines.
- [Medium] Concurrent first `KVM_RUN` invocations on multiple vCPUs cause a data race on the VM-wide `kvm_spe->guest_pmscr_el2`.
--

commit 8640bfaf0b943972e8c97350b961b39b15830f1b
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it

The SPE driver allows userspace to use physical timestamps for records only
if the kernel allows it, do the same for a virtual machine.

[ ... ]

> diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
> index 978620ad99fa5..3c32a1338cc8d 100644
> --- a/arch/arm64/kvm/hyp/vhe/spe-sr.c
> +++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c

[ ... ]

> @@ -120,10 +121,10 @@ void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_con
>  		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
>  	}
>  
> -	__kvm_spe_restore_sampling_regs(vcpu->kvm, guest_ctxt);
> +	__kvm_spe_restore_sampling_regs(kvm, guest_ctxt);
>  
>  	write_sysreg_el1(ctxt_sys_reg(guest_ctxt, PMSCR_EL1), SYS_PMSCR);
> -	write_sysreg_el2(0, SYS_PMSCR);
> +	write_sysreg_el2(kvm->arch.kvm_spe.guest_pmscr_el2, SYS_PMSCR);

[Severity: Medium]
Can this read of kvm->arch.kvm_spe.guest_pmscr_el2 race with a concurrent
write from another vCPU?

If multiple vCPUs are starting their first KVM_RUN, one vCPU might complete
kvm_arch_vcpu_run_pid_change(), proceed to enter the guest, and execute
__kvm_spe_restore_guest_state_vhe(), reading guest_pmscr_el2.

Because kvm_spe_vcpu_first_run_init() performs unlocked writes to this
shared VM-wide variable, another vCPU could be modifying it at the same
time. Should this use READ_ONCE() and WRITE_ONCE() to prevent LKMM
violations and KCSAN data race splats?

>  }
>  NOKPROBE_SYMBOL(__kvm_spe_restore_guest_state_vhe);
>  
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index af1e19cb8e9ae..68b751bdb384b 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c

[ ... ]

> @@ -97,6 +99,12 @@ int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
>  	if (!kvm_spe->pmblimitr_el1_res0_mask)
>  		kvm_spe_compute_pmblimitr_el1_res0_mask(kvm);
>  
> +	if (kvm_vm_has_ran_once(kvm))
> +		return 0;
> +
> +	if (perf_allow_kernel())
> +		kvm_spe->guest_pmscr_el2 = PMSCR_EL2_PCT_PHYS;

[Severity: High]
Does this logic invert the intended permission check?

The perf_allow_kernel() function returns 0 on success (access granted) and
a negative error code (like -EACCES) on failure. By evaluating to true when
access is denied, it seems we might be granting physical timestamps to
unprivileged users and incorrectly denying them to privileged users.

[Severity: High]
Is kvm_spe_vcpu_first_run_init() the right place to check these
capabilities?

Checking perf_allow_kernel() here means it is evaluated during the KVM_RUN
ioctl, rather than during VM configuration. VMMs typically configure VMs
with full privileges but drop privileges before executing KVM_RUN for
sandboxing. Checking at KVM_RUN time will incorrectly deny physical
timestamps to properly sandboxed VMMs.

Furthermore, an unprivileged process could configure a VM and pass the vCPU
file descriptor to a privileged process, improperly gaining access to
physical timestamps using the privileged thread's capabilities.

[Severity: Medium]
Could silently enabling SPE physical timestamps based on the host kernel
break live migration?

If a VM is migrated from a host where perf_allow_kernel() grants access to
one where it denies it, the guest would silently downgrade to virtual
timestamps, causing data corruption in the profiling trace.

The KVM guidelines require that new behavior the guest can observe must be
off by default and explicitly enabled through standard capability
negotiation (like a KVM_CAP_* or ID register flag). Should this require an
explicit opt-in from userspace instead?

[Severity: Medium]
Can this write to kvm_spe->guest_pmscr_el2 happen concurrently across
multiple vCPUs?

The kvm_vm_has_ran_once(kvm) flag is not set until the end of
kvm_arch_vcpu_run_pid_change(), which happens after
kvm_spe_vcpu_first_run_init() completes. If multiple vCPUs start their
first KVM_RUN concurrently, they will all see kvm_vm_has_ran_once(kvm) as
false and write to this shared variable without synchronization.

> +
>  	return 0;
>  }
>

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=20

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 22/28] arm64: errata: Disable SPE in KVM
  2026-09-03 16:06 ` [RFC PATCH v7 22/28] arm64: errata: Disable SPE in KVM Alexandru Elisei
@ 2026-09-03 16:50   ` sashiko-bot
  0 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:50 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, linux-perf-users, Oliver Upton, Marc Zyngier

> When stage 1 is enabled and buffer virtual address VA is mapped as
> read-only, and hardware dirty bit management is disabled at stage 1 and
> stage 2, if stage 2 is enabled, SPE might write to address VA instead of
> generating a fault.
> 
> This doesn't affect the SPE driver when running on baremetal, since the
> memory is mapped at stage 1 with write permissions.
> 
> KVM does not use hardware dirty bit management and this erratum allows a
> guest to write to read-only memory as long as that address is mapped at
> stage 2.
> 
> Since KVM allows userspace to use SPE on heterogenous systems, where there
> might be different SPE instances, tie the workaround to a particular SPE
> instance and do not allow userspace to use that instance when it is
> [ ... ]
>   https://support.arm.com/documentation/SDEN-2055130/18-0
> * ARM Cortex-X4 (MP161), SDEN v13, erratum 3022725
>   https://support.arm.com/documentation/109148/13-0
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=22


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 25/28] KVM: arm64: at: Use callback for reading descriptor
  2026-09-03 16:06 ` [RFC PATCH v7 25/28] KVM: arm64: at: Use callback for reading descriptor Alexandru Elisei
@ 2026-09-03 16:51   ` sashiko-bot
  0 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:51 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: linux-perf-users, Oliver Upton, kvmarm, Marc Zyngier

> Allow callers of __kvm_translate_va() to use a custom function for reading
> the translation table descriptor from guest memory. This will be useful for
> handling stage 2 faults reported by SPE, to map the stage 1 translation
> tables at stage 2.
> 
> Suggested-by: James Morse <james.morse@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=25


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 21/28] KVM: arm64: Handle SPE maintenance interrupts
  2026-09-03 16:06 ` [RFC PATCH v7 21/28] KVM: arm64: Handle SPE maintenance interrupts Alexandru Elisei
@ 2026-09-03 16:58   ` sashiko-bot
  2026-09-04 14:04     ` Alexandru Elisei
  0 siblings, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:58 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, Marc Zyngier, Oliver Upton, linux-perf-users

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Medium] SPE system registers are not cleared during VCPU reset, leaking stale state across warm resets.

Pre-existing issues:
- [High] Hardware profiling buffer is enabled before `PMBSR_EL1` is restored, creating a race condition that clobbers hardware events.
--

commit 0111e834cf6f2849f99044fcfd2c2f6ef3480c83
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: Handle SPE maintenance interrupts

This commit introduces handling for SPE maintenance interrupts in KVM. It
injects the interrupt when the guest sets the PMBSR_EL1.S bit and re-injects
hardware-raised maintenance interrupts.

> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index 4c4bf88c9aa53..ebe1c933f8d55 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -1595,6 +1595,11 @@ static int get_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc
>  	return 0;
>  }
>  
> +static u64 reset_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
> +{
> +	return kvm_spe_reset_sysreg(vcpu, r->reg);
> +}
> +

[Severity: Medium]
Does this change leak stale SPE register state across warm resets?

Previously, SPE registers used reset_val as their reset callback, which
actively updated the vCPU sysreg array via __vcpu_assign_sys_reg().

Here, the new reset_spe_sysreg() callback returns 0 but misses a call to
__vcpu_assign_sys_reg() to clear the VCPU's system register memory.
Because the KVM sysreg reset caller ignores the return value, the values in
vcpu->arch.ctxt.sys_regs remain untouched.

If a VM goes through a warm reset (e.g., via PSCI CPU_OFF / CPU_ON) while
profiling is active, the new OS environment inherits a stale profiling state.
Should kvm_spe_reset_sysreg() explicitly clear the register state?

>  static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
>  			   const struct sys_reg_desc *r)
>  {
> @@ -1642,7 +1647,7 @@ static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
>  
>  #define SPE_SYS_REG(name)						\
>  	SYS_DESC(SYS_##name), .reg = name, .access = access_spe_reg,	\
> -	.reset = reset_val, .val = 0, .visibility = spe_visibility,	\
> +	.reset = reset_spe_sysreg, .visibility = spe_visibility,	\
>  	.set_user = set_user_spe_sysreg, .get_user = get_user_spe_sysreg

[Severity: High]
This is a pre-existing issue, but does __kvm_spe_restore_guest_state_vhe()
clobber hardware events by enabling the profiling buffer before restoring
PMBSR_EL1?

In arch/arm64/kvm/hyp/vhe/spe-sr.c:__kvm_spe_restore_guest_state_vhe():

	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
		write_sysreg_s(ctxt_sys_reg(guest_ctxt, PMBPTR_EL1), SYS_PMBPTR_EL1);
		isb();
		write_sysreg_s(pmblimitr, SYS_PMBLIMITR_EL1);
		isb();
		/* A buffer management event preserves fields in PMBSR_EL1 */
		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
	}

Because PMBLIMITR_EL1 is restored (with the E bit set) and an ISB executed
before PMBSR_EL1 is restored, hardware can autonomously trigger a buffer
management event right after the ISB if the buffer is near full.

KVM's subsequent software write of the guest's PMBSR_EL1 (which has S = 0)
overwrites the hardware register, wiping the event and clearing the
interrupt.

This violates the ARM Architecture Reference Manual D10.1.5, which forbids
software from writing to PMBSR_EL1 while PMBLIMITR_EL1.E == 1.
Should PMBSR_EL1 be restored before PMBLIMITR_EL1 to prevent this race
condition?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=21

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 28/28] KVM: arm64: Allow the creation of a SPE enabled VM
  2026-09-03 16:06 ` [RFC PATCH v7 28/28] KVM: arm64: Allow the creation of a SPE enabled VM Alexandru Elisei
@ 2026-09-03 16:59   ` sashiko-bot
  2026-09-04 14:09     ` Alexandru Elisei
  0 siblings, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 16:59 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Oliver Upton, kvmarm, Marc Zyngier, linux-perf-users

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The Kconfig dependency for `KVM_ARM_SPE` restricts it to 'n' when the `ARM_SPE_PMU` driver is built as a module, preventing users from enabling SPE virtualization.
--

commit 9bb804f4572dd230ad3b5db909bd2001b3ee3186
Author: Alexandru Elisei <alexandru.elisei@arm.com>

KVM: arm64: Allow the creation of a SPE enabled VM

This commit introduces the Kconfig option KVM_ARM_SPE to allow userspace
to enable the Statistical Profiling Extension (SPE) for virtual machines.

[ ... ]

> diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
> --- a/arch/arm64/kvm/Kconfig
> +++ b/arch/arm64/kvm/Kconfig
> @@ -106,9 +106,9 @@ endif # NVHE_EL2_DEBUG
>  
>  
>  config KVM_ARM_SPE
> -	bool
> +	bool "Support SPE in guest"
>  	depends on KVM && ARM_SPE_PMU

[Severity: Medium]
Does this Kconfig dependency unintentionally disable KVM_ARM_SPE when the
host driver ARM_SPE_PMU is built as a module?

Because KVM_ARM_SPE is a boolean, if ARM_SPE_PMU is configured as a
loadable module (=m), the Kconfig dependency evaluates to m. For a boolean
config, this restricts its maximum value to n, effectively disabling the
feature in the build configuration.

Since the implementation in arch/arm64/kvm/spe.c supports being built
against a modular driver (using try_module_get()), should this dependency
be relaxed or handled differently so the feature can still be selected?

> -	default n
> +	default y
>  	help
>  	  Adds support for Statistical Profiling Extension (SPE) in virtual
>  	  machines.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=28

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 24/28] arm64: errata: Don't enable guest buffer if misprogrammed
  2026-09-03 16:06 ` [RFC PATCH v7 24/28] arm64: errata: Don't enable guest buffer if misprogrammed Alexandru Elisei
@ 2026-09-03 17:00   ` sashiko-bot
  0 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 17:00 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: kvmarm, linux-perf-users, Marc Zyngier, Oliver Upton

> When PMBPTR_EL1 is programmed to be equal to or greater than
> PMBLIMITR_EL1.Limit, SPE is enabled and PMBSR_EL1.S is zero, the CPU might
> deadlock.
> 
> This doesn't affect correctly written software, like the SPE driver, but a
> buggy KVM guest might trigger the erratum. The workaround for KVM is not to
> enable the hardware buffer, and, as a consequence, profiling. According
> to ARM DDI0487M.c, rule R_PGGHS, this is one of the acceptable
> behaviours when the buffer is misprogrammed.
> 
> Several parts are affected:
> 
> * ARM Neoverse-N1 (MP050), SDEN v35, erratum 1978083
>   https://support.arm.com/documentation/SDEN-885747/35-0
> * ARM Neoverse-V1 (MP076), SDEN v23, erratum 1978082
>   https://support.arm.com/documentation/SDEN-1401781/23-0
> * ARM Cortex-X1 (MP077), SDEN v25, erratum 2004055
>   https://support.arm.com/documentation/SDEN-1401782/25-0
> * ARM Cortex-X1C (MP136), SDEN v20, erratum 2004056
>   https://support.arm.com/documentation/SDEN-1707914/20-0
> * ARM Cortex-A78 (MP102), SDEN v25, erratum 2004055
>   https://support.arm.com/documentation/SDEN-1401784/25-0
> * ARM Cortex-A78AE (MP105), SDEN v22, erratum 2004059
>   https://support.arm.com/documentation/SDEN-1707912/22-0
> * ARM Cortex-A78C (MP138), SDEN v20, erratum 2004056
>   https://support.arm.com/documentation/SDEN-1707916/20-0
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=24


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 27/28] KVM: arm64: Handle dirty page logging when SPE feature is set
  2026-09-03 16:06 ` [RFC PATCH v7 27/28] KVM: arm64: Handle dirty page logging when SPE feature is set Alexandru Elisei
@ 2026-09-03 17:06   ` sashiko-bot
  2026-09-04 14:41     ` Alexandru Elisei
  0 siblings, 1 reply; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 17:06 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: Marc Zyngier, kvmarm, linux-perf-users, Oliver Upton

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Memory Protection Bypass / Privilege Escalation via Unconditional Stage-2 Write-Enable
- [High] Logic Error and EL2 Hypercall Argument Corruption due to flawed `nr_pages` calculation
- [High] Logical Inconsistency / State Desynchronization causing missed VCPU kicks and unexpected guest exits
- [Medium] Kernel preemption bug via `smp_processor_id()` in a preemptible context
--

commit 45c89e608abbd3e8f9c8b944b7e7d74478d5db12
Author: Alexandru Elisei <alexandru.elisei@arm.com>
Subject: KVM: arm64: Handle dirty page logging when SPE feature is set

This commit aims to avoid profiling blackout windows by preventing VCPU
execution when the SPE buffer is enabled and dirty page logging is active.
It introduces dirty logging synchronization constraints and automatic guest
exits for the KVM/ARM64 SPE implementation.

> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 7c7d0616db53a..73f321b3ed2dc 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -1172,6 +1172,15 @@ static int kvm_vcpu_suspend(struct kvm_vcpu *vcpu)
>  	return 1;
>  }
>  
> +static void kvm_vcpu_prepare_exit_fail_entry(struct kvm_vcpu *vcpu, u64 reason)
> +{
> +	struct kvm_run *run = vcpu->run;
> +
> +	run->exit_reason = KVM_EXIT_FAIL_ENTRY;
> +	run->fail_entry.hardware_entry_failure_reason = reason;
> +	run->fail_entry.cpu = smp_processor_id();

[Severity: Medium]
Is it safe to use smp_processor_id() here? This function is newly invoked by
check_vcpu_requests() when handling KVM_REQ_SPE_EXIT_DIRTY_LOGGING. Because
check_vcpu_requests() executes in the KVM run loop before preempt_disable()
is called, the context remains preemptible. This will trigger a kernel
warning if CONFIG_DEBUG_PREEMPT is enabled, and records an unreliable CPU ID
since the thread can migrate immediately afterward.

[ ... ]
> diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
> index b74dd5ce1efd3..07e43f607eae7 100644
> --- a/arch/arm64/kvm/hyp/pgtable.c
> +++ b/arch/arm64/kvm/hyp/pgtable.c
> @@ -1293,6 +1293,14 @@ int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size)
>  					KVM_PGTABLE_WALK_IGNORE_EAGAIN);
>  }
>  
> +int kvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr, u64 size)
> +{
> +	return stage2_update_leaf_attrs(pgt, addr, size,
> +					KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, 0,
> +					NULL, NULL,
> +					KVM_PGTABLE_WALK_IGNORE_EAGAIN);
> +}

[Severity: High]
Can this lead to a memory protection bypass? By unconditionally adding the
KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W bit to all valid leaf PTEs, this doesn't
appear to validate if the underlying host memory mapping actually permits
writes. If a guest reads unallocated shared memory while dirty logging is on,
KVM maps the host's zero page as read-only at stage 2. Once dirty logging is
disabled, this blindly grants write access to it.

[ ... ]
> diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
> index 8e4c6e4bec123..5ba2fa8010cc9 100644
> --- a/arch/arm64/kvm/pkvm.c
> +++ b/arch/arm64/kvm/pkvm.c
> @@ -488,6 +488,33 @@ int pkvm_pgtable_stage2_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size)
>  	return __pkvm_pgtable_stage2_unshare(pgt, addr, addr + size);
>  }
>  
> +int pkvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr, u64 size)
> +{
[ ... ]
> +	lockdep_assert_held(&kvm->mmu_lock);
> +	for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) {
> +		gfn = max(addr >> PAGE_SHIFT, mapping->gfn);
> +		nr_pages = min_t(u64, end_gfn - gfn, mapping->nr_pages);

[Severity: High]
Does this calculation span past the end of the mapping? If the requested
address starts in the middle of an existing mapping, gfn is offset from
mapping->gfn. Because the offset isn't subtracted from mapping->nr_pages,
the EL1 host passes an out-of-bounds nr_pages to the EL2 hypervisor. This
could cause the hypercall to fail or inadvertently write-enable pages beyond
the intended mapping.

> +		ret = kvm_call_hyp_nvhe(__pkvm_host_write_enable_guest, handle, gfn,
> +					nr_pages);
> +		if (WARN_ON(ret))
> +			break;
> +	}
> +
> +	return ret;
> +}

[ ... ]
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index e5f3d0dea7c54..f3310465f40eb 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
[ ... ]
> +void kvm_spe_add_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot)
> +{
> +	struct kvm_vcpu *vcpu;
> +	unsigned long i;
> +
> +	lockdep_assert_held(&kvm->slots_lock);
> +
> +	/*
> +	 * No point in handling a regular memslot, because they cannot guarantee
> +	 * that memory remains mapped at stage 2.
> +	 *
> +	 * Memory described by a read-only memslot is always read-only, so SPE
> +	 * cannot write to it anyway.
> +	 */
> +	if (!kvm_slot_has_gmem(slot) || (slot->flags & KVM_MEM_READONLY))
> +		return;

[Severity: High]
Does this cause unexpected guest exits and missed VCPU kicks for regular
memslots? Here in kvm_spe_add_dirty_logging(), regular memslots are skipped,
so running VCPUs aren't kicked when dirty logging starts.

> +
> +	kvm_for_each_vcpu(i, vcpu, kvm) {
> +		if (!vcpu_has_spe(vcpu))
> +			continue;
> +		kvm_make_request(KVM_REQ_SPE_HANDLE_DIRTY_LOGGING, vcpu);
> +		if (vcpu_has_run_once(vcpu))
> +			__kvm_vcpu_kick(vcpu, true);
> +	}
> +}
> +
> +void kvm_spe_remove_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot)
> +{
> +	phys_addr_t start, end;
> +	struct kvm_vcpu *vcpu;
> +	unsigned long i;
> +
> +	lockdep_assert_held(&kvm->slots_lock);
> +
> +	if (!kvm_slot_has_gmem(slot) || (slot->flags & KVM_MEM_READONLY))
> +		return;
> +
> +	kvm_for_each_vcpu(i, vcpu, kvm) {
> +		/*
> +		 * Write-enabling an entire memslot can be expensive, do it only
> +		 * if the VM has SPE.
> +		 */
> +		if (vcpu_has_spe(vcpu)) {
> +			start = slot->base_gfn << PAGE_SHIFT;
> +			end = (slot->base_gfn + slot->npages) << PAGE_SHIFT;
> +
> +			write_lock(&kvm->mmu_lock);
> +			kvm_stage2_write_enable_range(&kvm->arch.mmu, start, end);

[Severity: High]
Does this blindly write-enable the entire shared stage-2 memslot? Combined
with the unconditional KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W addition in
kvm_pgtable_stage2_write_enable(), this allows a malicious guest to gain write
access to read-only host memory (like the global shared zero page) if it
was previously mapped as read-only.

> +			write_unlock(&kvm->mmu_lock);
> +			kvm_flush_remote_tlbs_memslot(kvm, slot);
> +			return;
> +		}
> +	}
> +}
> +
> +static bool kvm_spe_should_exit_dirty_logging(struct kvm_vcpu *vcpu)
> +{
> +	struct kvm *kvm = vcpu->kvm;
> +	u64 pmblimitr, pmbsr;
> +
> +	if (!atomic_read(&kvm->nr_memslots_dirty_logging))
> +		return false;

[Severity: High]
Because regular memslots were skipped in kvm_spe_add_dirty_logging(), they
weren't protected. However, this check relies on the global
nr_memslots_dirty_logging counter, which increments for all memslots.
Could this mismatch cause unexpected guest exits when a VM enables standard
dirty logging (e.g., VGA framebuffers) while utilizing SPE?

> +
> +	pmblimitr = __vcpu_sys_reg(vcpu, PMBLIMITR_EL1);
> +	pmbsr = __vcpu_sys_reg(vcpu, PMBSR_EL1);
> +
> +	return FIELD_GET(PMBLIMITR_EL1_E, pmblimitr) && !FIELD_GET(PMBSR_EL1_S, pmbsr);
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=27

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 26/28] KVM: arm64: Map memory on a SPE stage 2 fault
  2026-09-03 16:06 ` [RFC PATCH v7 26/28] KVM: arm64: Map memory on a SPE stage 2 fault Alexandru Elisei
@ 2026-09-03 17:08   ` sashiko-bot
  0 siblings, 0 replies; 87+ messages in thread
From: sashiko-bot @ 2026-09-03 17:08 UTC (permalink / raw)
  To: Alexandru Elisei; +Cc: linux-perf-users, Marc Zyngier, Oliver Upton, kvmarm

> Make sure SPE in a guest can make progress even if something goes terribly
> wrong in KVM by mapping the memory on stage 2 fault SPE buffer management
> event.
> 
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Sashiko has reviewed this patch and found no issues. It looks great!

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=26


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device
  2026-09-03 16:39   ` sashiko-bot
@ 2026-09-04  9:32     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04  9:32 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Marc Zyngier, Oliver Upton, linux-perf-users, kvmarm

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:39:44PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [Critical] Incorrect declaration of `supported_cpus` causes stack corruption and NULL pointer dereferences when `CONFIG_CPUMASK_OFFSTACK=n`.
> - [Medium] Changing the PMU identifier after SPE is configured incorrectly accumulates CPU mask restrictions, potentially rejecting valid reassignments.
> --
> 
> commit b2f754414c4648e5b2996f0b8f1a6f27ec6f0cf7
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: Add SPE VCPU device attribute to set the SPE device
> 
> This commit introduces a new KVM device attribute to assign an SPE
> instance to a virtual machine. It adds support for tracking physical
> CPUs that share the assigned SPE and PMU instances, ensuring VCPUs
> only run on appropriate hardware.
> 
> > diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> > index 763b102dc51fa..132df44c30887 100644
> > --- a/arch/arm64/kvm/arm.c
> > +++ b/arch/arm64/kvm/arm.c
> 
> [ ... ]
> 
> > @@ -612,6 +614,47 @@ void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu)
> >  
> >  }
> >  
> > +int kvm_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus,
> > +			      cpumask_t *spe_cpus)
> > +{
> > +	cpumask_t *supported_cpus;
> > +	cpumask_t *cpus;
> > +	int ret;
> > +
> > +	if (!zalloc_cpumask_var(&supported_cpus, GFP_KERNEL_ACCOUNT))
>         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> [Severity: Critical]
> Does this cause stack memory corruption and a NULL pointer dereference
> when CONFIG_CPUMASK_OFFSTACK=n?
> 
> In kvm_update_supported_cpus(), supported_cpus is declared as a simple pointer
> rather than cpumask_var_t. When CONFIG_CPUMASK_OFFSTACK is disabled,
> cpumask_var_t is defined as a 1-element array. Passing &supported_cpus to
> zalloc_cpumask_var() will zero out NR_CPUS bits starting at the stack address
> of the pointer itself, overwriting the pointer and potentially adjacent
> stack variables.
> 
> The subsequent cpumask_copy() then dereferences a NULL pointer. Could we
> use cpumask_var_t for the declaration here?

This indeed look like a bug, I'll fix it, thanks for spotting it.

> 
> > +		return -ENOMEM;
> > +
> > +	cpumask_copy(supported_cpus, kvm->arch.supported_cpus);
> > +
> > +	lockdep_assert_held(&kvm->arch.config_lock);
> > +
> > +	ret = -EINVAL;
> > +	/* Cannot update for SPE and PMU at the same time. */
> > +	if (WARN_ON_ONCE(pmu_cpus && spe_cpus))
> > +		goto out;
> > +
> > +	if (pmu_cpus)
> > +		cpus = pmu_cpus;
> > +	else
> > +		cpus = spe_cpus;
> > +
> > +	if ((spe_cpus && kvm->arch.arm_pmu) ||
> > +	    (pmu_cpus && kvm_spe_pmu_set(kvm)))
> > +		cpumask_and(supported_cpus, cpus, supported_cpus);
>                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> [Severity: Medium]
> Could this incorrectly accumulate CPU mask restrictions and reject valid
> reassignments?
> 
> If userspace configures a VM by first setting a PMU, then setting the SPE ID,
> and later changes the PMU to a different instance (which is permitted before
> the VM runs), this logic intersects the new PMU mask with the
> already-intersected kvm->arch.supported_cpus mask.
> 
> Since the current mask already includes the first PMU's restrictions, the new
> mask accumulates both PMUs' restrictions instead of replacing the old PMU's
> mask. If the two PMUs have disjoint CPU masks, the intersection becomes empty
> and the operation fails with -EINVAL. 

My mistake here, I was under the impression that you can assign a PMU *exactly*
once to a VM, but looking at the code it turns out I was wrong and you can
assign different PMUs (where each assignment overwrites the previous one), as
long as you haven't set a filter. I'll fix it in the next iteration.

This begs the question: should the same behaviour be allowed with SPE? Because
right now trying to set a different SPE PMU is rejected by KVM.

Thanks,
Alex

> 
> > +	else
> > +		cpumask_copy(supported_cpus, cpus);
> > +
> > +	if (cpumask_empty(supported_cpus))
> > +		goto out;
> > +
> > +	cpumask_copy(kvm->arch.supported_cpus, supported_cpus);
> > +	ret = 0;
> > +out:
> > +	free_cpumask_var(supported_cpus);
> > +
> > +	return ret;
> > +}
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=13

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance
  2026-09-03 16:41   ` sashiko-bot
@ 2026-09-04 10:26     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 10:26 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvmarm, linux-perf-users, Marc Zyngier, Oliver Upton

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:41:26PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [High] kvm_spe_finalize_idregs() unconditionally overwrites the user-provided ID_AA64DFR0_EL1.PMSVer with the host's physical version on the first KVM_RUN, breaking live migration.
> - [High] sanitise_id_aa64dfr0_el1() evaluates PMSVer dynamically based on the assigned SPE PMU, returning 0 if called before the PMU is assigned, which breaks KVM_SET_ONE_REG validation.
> - [High] kvm_spe_finalize_idregs() is skipped if a vCPU without SPE runs first in a heterogeneous VM, permanently leaving PMSVer as 0 and hiding SPE from the guest.
> --
> 
> commit 45e0320e2eaeb926a33b978420aa5a133c7a42c6
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: Use PMSVer from the assigned SPE instance
> 
> This commit updates KVM to initialize the VM's ID_AA64DFR0_EL1.PMSVer using
> the SPE PMU instance assigned to the VM rather than the physical CPU executing
> the vCPU init ioctl. This is intended to handle heterogeneous systems where
> SPE instances may have different versions or be entirely absent on some CPUs.
> 
> > diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> > index fa87d224c8af8..4426d614a9e0b 100644
> > --- a/arch/arm64/kvm/spe.c
> > +++ b/arch/arm64/kvm/spe.c
> [ ... ]
> > @@ -87,6 +87,30 @@ bool kvm_spe_pmu_set(struct kvm *kvm)
> >  	return !!kvm->arch.kvm_spe.spe_pmu;
> >  }
> >  
> > +void kvm_spe_finalize_idregs(struct kvm *kvm)
> > +{
> > +	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
> > +	u64 reg;
> > +
> > +	if (!spe_pmu)
> > +		return;
> > +
> > +	reg = kvm_read_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1);
> > +	reg &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
> > +	reg |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer, spe_pmu->pmsver);
> > +	kvm_set_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1, reg);
> > +}
> 
> [Severity: High]
> Does this unconditionally overwrite the user-provided ID_AA64DFR0_EL1.PMSVer
> value on the first KVM_RUN?

It doesn't look to me like ID_AA64DFR0_EL1.PMSVer is user writable:

static const struct sys_reg_desc sys_reg_descs[] = {
[..]
       ID_FILTERED(ID_AA64DFR0_EL1, id_aa64dfr0_el1,
                    ID_AA64DFR0_EL1_DoubleLock_MASK |
                    ID_AA64DFR0_EL1_WRPs_MASK |
                    ID_AA64DFR0_EL1_PMUVer_MASK |
                    ID_AA64DFR0_EL1_DebugVer_MASK),

unless I'm badly misunderstanding what the masks in the ID_FILTERED() macro do.

> 
> If a VMM restores ID_AA64DFR0_EL1 during live migration (for example, when
> migrating from an older host to a newer one), kvm_spe_finalize_idregs() will
> silently clobber the userspace-configured value and force it back to the host
> hardware's version. Could this violate the KVM ABI and break live migration
> sequences?
> 
> [ ... ]
> > diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> > index 44aae52c473d7..a6bb6884b965a 100644
> > --- a/arch/arm64/kvm/sys_regs.c
> > +++ b/arch/arm64/kvm/sys_regs.c
> > @@ -2220,8 +2220,11 @@ static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
> >  		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMUVer,
> >  				      kvm_arm_pmu_get_pmuver_limit());
> >  
> > -	/* Hide SPE from guests */
> >  	val &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
> > +	if (vcpu_has_spe(vcpu))
> > +		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer,
> > +				      kvm_spe_get_pmsver(vcpu->kvm));
> 
> [Severity: High]
> Can this dynamic evaluation break KVM_SET_ONE_REG validation during VM setup?

What is "KVM_SET_ONE_REG validation"?

> 
> VMMs like QEMU typically restore VM ID registers via KVM_SET_ONE_REG
> immediately after KVM_ARM_VCPU_INIT, before configuring device attributes.
> Because sanitise_id_aa64dfr0_el1() evaluates PMSVer based on the assigned SPE
> PMU, kvm_spe_get_pmsver() returns 0 until the SPE PMU is assigned.
> 
> This strict ordering dependency will cause KVM_SET_ONE_REG to reject valid,
> non-zero userspace values with -EINVAL, which breaks standard live migration
> sequences.

I don't think ID_AA64DFR0_EL1.PMSVer is user-writable.

> 
> [ ... ]
> > @@ -5928,6 +5931,9 @@ int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu)
> >  		kvm_vgic_finalize_idregs(kvm);
> >  	}
> >  
> > +	if (vcpu_has_spe(vcpu))
> > +		kvm_spe_finalize_idregs(kvm);
> > +
> >  	return 0;
> >  }
> 
> [Severity: High]
> Can this leave PMSVer permanently set to 0 in a heterogeneous VM if a vCPU
> without SPE runs first?

Indeed. My understanding from how the PMU VCPU feature is handled is that even
though KVM doesn't forbid running a VCPU without a PMU when other VCPUs do have
one, this kind of setup is not supported in KVM. This is what I was trying to
implement here.

Should I tighten the check and explicitly forbid running a VCPU without a SPE
PMU?

Thanks,
Alex

> 
> kvm_finalize_sys_regs() executes on the first KVM_RUN for a given VM. It uses
> a per-vCPU feature check (vcpu_has_spe(vcpu)) to guard the VM-wide ID register
> initialization in kvm_spe_finalize_idregs(kvm).
> 
> If the first scheduled vCPU happens to lack SPE, vcpu_has_spe(vcpu) evaluates
> to false and kvm_spe_finalize_idregs(kvm) is skipped. The VM is then marked
> with KVM_ARCH_FLAG_HAS_RAN_ONCE, meaning SPE initialization is skipped forever.
> This would permanently hide SPE from all vCPUs in the VM.
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=15

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context
  2026-09-03 16:32   ` sashiko-bot
@ 2026-09-04 10:28     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 10:28 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Marc Zyngier, kvmarm, Oliver Upton, linux-perf-users

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:32:49PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Low] WARN_ON_ONCE is incorrectly called with a string literal instead of a boolean condition.
> --
> 
> commit aa1092f112d707ae630c6edfaa06e1cf58da7524
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: Add SPE system registers to VCPU context
> 
> This commit adds SPE registers to the VCPU context. It only traps the buffer
> registers, allowing KVM to track the buffer enabled state for dirty page
> logging and to sanitise writes to PMBLIMITR_EL1 based on exposed VM features.
> 
> [ ... ]
> 
> > diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> > index 4426d614a9e0b..6a00a44c73edf 100644
> > --- a/arch/arm64/kvm/spe.c
> > +++ b/arch/arm64/kvm/spe.c
> 
> [ ... ]
> 
> > @@ -82,6 +83,34 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
> >  	module_put(spe_pmu->pmu.module);
> >  }
> >  
> > +bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
> > +{
> > +	switch (reg) {
> > +	case PMBLIMITR_EL1:
> > +	case PMBSR_EL1:
> > +	case PMBPTR_EL1:
> > +		__vcpu_assign_sys_reg(vcpu, reg, val);
> > +		break;
> > +	default:
> > +		WARN_ON_ONCE("unexpected trap");
> 
> [Severity: Low]
> Does this macro call miss a boolean condition? The WARN_ON_ONCE macro
> expects a boolean expression, but it is being passed a string literal here.
> While this functionally evaluates to true because the string decays into a
> non-null pointer, could this be considered an API misuse? Perhaps this
> should use WARN_ONCE(1, "unexpected trap") or WARN_ON_ONCE(1) instead when
> an unexpected trapped SPE register access falls through to the default case
> in kvm_spe_write_sysreg().
> 
> > +	}
> > +
> > +	return true;
> > +}
> > +
> > +u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
> > +{
> > +	switch (reg) {
> > +	case PMBLIMITR_EL1:
> > +	case PMBSR_EL1:
> > +	case PMBPTR_EL1:
> > +		return __vcpu_sys_reg(vcpu, reg);
> > +	default:
> > +		WARN_ON_ONCE("unexpected trap");
> 
> [Severity: Low]
> Similarly, should this use WARN_ONCE(1, "unexpected trap") instead of
> passing a string literal directly into WARN_ON_ONCE when an unexpected
> trapped read falls through to the default case in kvm_spe_read_sysreg()?

Yeah, that's right, I'll fix both instances of the misuse of the macro.

Thanks,
Alex

> 
> > +		return 0;
> > +	}
> > +}
> 
> [ ... ]
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=16

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes
  2026-09-03 16:37   ` sashiko-bot
@ 2026-09-04 10:41     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 10:41 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Oliver Upton, Marc Zyngier, kvmarm, linux-perf-users

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:37:57PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
> - [Critical] get_user_spe_sysreg() and set_user_spe_sysreg() return -EBUSY before SPE initialization, violating the KVM_GET_REG_LIST contract.
> - [High] Userspace can bypass the PMBLIMITR_EL1 RES0 mask by writing to the register before the first KVM_RUN.
> - [Low] Missing READ_ONCE() when accessing pmblimitr_el1_res0_mask causes a data race.
> --
> 
> commit 1270412c2611a30b1f16a0aa9858eb290c7f9768
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes
> 
> This commit introduces a RES0 mask for PMBLIMITR_EL1 to hide unsupported
> granule sizes from the guest. It computes this mask during the vCPU's
> first run and applies it when userspace or the guest writes to the register.
> 
> > diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> > index 6a00a44c73edf..8c173a1f48bd4 100644
> > --- a/arch/arm64/kvm/spe.c
> > +++ b/arch/arm64/kvm/spe.c
> [ ... ]
> > @@ -60,13 +60,43 @@ bool kvm_supports_spe(void)
> [ ... ]
> >  int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
> >  {
> > +	struct kvm *kvm = vcpu->kvm;
> > +	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
> >  
> > -	if (!vcpu_spe->initialized)
> > +	if (!kvm_vcpu_spe_initialized(vcpu))
> >  		return -EINVAL;
> >  
> > +	if (!kvm_spe->pmblimitr_el1_res0_mask)
> 
> [Severity: Low]
> Does this plain read of pmblimitr_el1_res0_mask need a READ_ONCE() to
> prevent compiler-induced load tearing?
> 
> The mask is updated locklessly using WRITE_ONCE() inside
> kvm_spe_compute_pmblimitr_el1_res0_mask(). Without a READ_ONCE() here,
> concurrent vCPU executions could trigger a data race.

Ok.

> 
> > +		kvm_spe_compute_pmblimitr_el1_res0_mask(kvm);
> >  
> >  	return 0;
> >  }
> > @@ -85,8 +115,12 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
> >  
> >  bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
> >  {
> > +	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
> > +
> >  	switch (reg) {
> >  	case PMBLIMITR_EL1:
> > +		val &= ~kvm_spe->pmblimitr_el1_res0_mask;
> 
> [Severity: High]
> Can userspace bypass this RES0 sanitization?
> 
> If the VMM calls KVM_SET_ONE_REG for PMBLIMITR_EL1 before running the vCPU
> for the first time, pmblimitr_el1_res0_mask will still be 0.
> 
> Because the mask isn't computed until the first run, the bitwise NOT evaluates
> to all 1s (val &= ~0), which leaves the user-provided value unmodified and
> potentially exposes the host hardware to unsupported granule configurations.

That's correct, I assumed here that userspace will be aware of the
constraints on PMBLIMITR_EL1 due to missing architectural features and write the
correct value.

Is that placing a too high a burden on userspace? If it is, I can recompute
PMBLIMITR_EL1 when the VCPU is first run.

> 
> [Severity: Low]
> Does this mask read also need a READ_ONCE() to safely pair with the
> WRITE_ONCE() update, similar to the check in kvm_spe_vcpu_first_run_init()?

Ok.

> 
> > +		fallthrough;
> >  	case PMBSR_EL1:
> >  	case PMBPTR_EL1:
> >  		__vcpu_assign_sys_reg(vcpu, reg, val);
> 
> > diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> > index 89b01b6ac7830..4c4bf88c9aa53 100644
> > --- a/arch/arm64/kvm/sys_regs.c
> > +++ b/arch/arm64/kvm/sys_regs.c
> [ ... ]
> > @@ -1563,6 +1563,38 @@ static unsigned int spe_visibility(const struct kvm_vcpu *vcpu,
> >  	return REG_HIDDEN;
> >  }
> >  
> > +static int set_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
> > +			       u64 val)
> > +{
> > +	int reg = r->reg;
> > +
> > +	if (!kvm_vcpu_spe_initialized(vcpu))
> > +		return -EBUSY;
> 
> [Severity: Critical]
> Does returning -EBUSY here break the KVM_GET_REG_LIST ABI contract?
> 
> spe_visibility() currently exposes SPE registers based purely on
> vcpu_has_spe(). Because of this, KVM_GET_REG_LIST will advertise the SPE
> registers immediately after a vCPU is created with the SPE feature.
> 
> If a VMM queries the register list and then calls KVM_GET_ONE_REG or
> KVM_SET_ONE_REG before KVM_ARM_VCPU_SPE_INIT is called, it will unexpectedly
> hit this -EBUSY path and fail. The KVM API requires that any register
> returned by KVM_GET_REG_LIST must be successfully readable via
> KVM_GET_ONE_REG.

I can't help but notice an inconsistency: here you say that any register
returned by KVM_GET_REG_LIST must be successfully *readable*, yet above you're
saying that a it is a *write* that breaks the KVM_GET_REG_LIST ABI contract.

Regardless, I think you're right. I only put the check there so I can update the
interrupt level on a PMBSR_EL1 write (implemented later in the series). What I
can do is try to update the interrupt level when the VCPU is first run,
alongside applying the res0 mask to PMBLIMITR_EL1.

Thanks,
Alex

> 
> > +
> > +	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)
> > +		kvm_spe_write_sysreg(vcpu, reg, val);
> > +	else
> > +		__vcpu_assign_sys_reg(vcpu, reg, val);
> > +
> > +	return 0;
> > +}
> > +
> > +static int get_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
> > +			       u64 *val)
> > +{
> > +	int reg = r->reg;
> > +
> > +	if (!kvm_vcpu_spe_initialized(vcpu))
> > +		return -EBUSY;
> > +
> > +	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)
> > +		*val = kvm_spe_read_sysreg(vcpu, reg);
> > +	else
> > +		*val = __vcpu_sys_reg(vcpu, reg);
> > +
> > +	return 0;
> > +}
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=17

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 19/28] KVM: arm64: VHE: Context switch SPE state
  2026-09-03 16:43   ` sashiko-bot
@ 2026-09-04 11:35     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 11:35 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvmarm, Marc Zyngier, linux-perf-users, Oliver Upton

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:43:13PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> - [High] Writing to PMBSR_EL1 after enabling the SPE profiling buffer via PMBLIMITR_EL1 is CONSTRAINED UNPREDICTABLE and can corrupt hardware events.
> - [High] Host SPE sampling control registers (e.g., PMSNEVFR_EL1, PMSDSFR_EL1) can be permanently corrupted by a malicious guest.
> --
> 
> commit 32125a6226fde42baaf9001e75434e4177067f24
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: VHE: Context switch SPE state
> 
> Save and restore the SPE register state when a VCPU is run. The SPE
> resources are shared between the guest and the host (the resources are not
> partitioned) so KVM has to save the host state, change the owning
> translation regime, and then restore the guest state before entering a
> guest.
> 
> > diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
> > new file mode 100644
> > index 0000000000000..978620ad99fa5
> > --- /dev/null
> > +++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c
> > @@ -0,0 +1,209 @@
> [ ... ]
> > +static void __kvm_spe_save_sampling_regs(struct kvm *kvm, struct kvm_cpu_context *ctxt)
> > +{
> > +	/*
> > +	 * This is dependent on the VM features, and not the hardware features,
> > +	 * even for the host, because (a) guest accesses to PMSNEVFR_EL1 and
> > +	 * PMSDSFR_EL1 result in an undefined exception in the guest and (b)
> > +	 * they don't affect the guest unless the guest explicitly enables them,
> > +	 * and a well-behaved guest shouldn't enable them when the corresponding
> > +	 * feature is not advertised.
> > +	 */
> > +	if (kvm_spe_has_feat_spe_fne(kvm))
> > +		ctxt_sys_reg(ctxt, PMSNEVFR_EL1) = read_sysreg_s(SYS_PMSNEVFR_EL1);
> 
> [Severity: High]
> Does this check of the guest's advertised features (kvm_spe_has_feat_spe_fne)
> leave the host's sampling control registers vulnerable to corruption?
> 
> If the host physically supports FEAT_SPE_FNE but the KVM VM does not
> advertise it, KVM will skip saving and restoring the host's PMSNEVFR_EL1
> register. Because MDCR_EL2.TPMS = 0 is cleared on guest entry, these
> registers will not trap to EL2.
> 
> A malicious guest could execute an msr instruction to PMSNEVFR_EL1 directly,
> overwriting the hardware register. When the guest exits, KVM skips restoring
> the host's value in __kvm_spe_restore_sampling_regs(), allowing the guest's
> malicious configuration to persist in the host execution context.
> 
> Could this be checking the host's physical capabilities instead of the VM's
> advertised features when saving and restoring host state?

Yeah, that's a good point, this is a leftover from the previous iteration where
SPE support in KVM was dependant on FEAT_FGT, which would trap accesses to those
registers if FEAT_SPE_FnE and/or FEAT_SPE_FDS were not exposed to the VM.

If FEAT_FGT is implemented, nothing changes. But if that's not present, I see
two way to handle it:

1. Save/restore the registers on each VM entry/exit and let the guest access
them as it wishes. This would go against the architecture, which says that
accesses are undefined if the corresponding feature is not implemented. In
practice, this might not be a problem if all hardware that implements
FEAT_SPE_FnE and FEAT_SPE_FDS also implements FEAT_FGT.

2. Trap *all* buffer control registers with MDCR_EL2.TPMS and inject an undefined
instruction exception.

Thoughts?

> 
> [ ... ]
> > +void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_context *guest_ctxt)
> > +{
> > +	u64 pmblimitr, pmbsr;
> > +
> > +	pmbsr = ctxt_sys_reg(guest_ctxt, PMBSR_EL1);
> > +	pmblimitr = ctxt_sys_reg(guest_ctxt, PMBLIMITR_EL1);
> > +	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
> > +		write_sysreg_s(ctxt_sys_reg(guest_ctxt, PMBPTR_EL1), SYS_PMBPTR_EL1);
> > +		isb();
> > +		write_sysreg_s(pmblimitr, SYS_PMBLIMITR_EL1);
> > +		isb();
> > +		/* A buffer management event preserves fields in PMBSR_EL1 */
> > +		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
> 
> [Severity: High]
> Is it safe to write to PMBSR_EL1 after enabling the SPE profiling buffer via
> PMBLIMITR_EL1?

I haven't found any evidence to the contrary.

> 
> According to the ARM Architecture Reference Manual, when the profiling buffer
> is enabled, a direct write to PMBSR_EL1 is constrained unpredictable. This
> can corrupt hardware events or lead to unknown exceptions.

I haven't found anything in the Arm ARM saying that, and I don't see why writing
to PMBSR_EL1 would be a problem at all after the correct sequence for disabling
the buffer has been followed in __kvm_spe_save_host_state_vhe().

This is also how the SPE driver programs the buffer in the interrupt handler, if
there is indeed an issue the SPE driver would also have to be fixed.

Thanks,
Alex

> 
> In __kvm_spe_restore_host_state_vhe(), the PMBSR_EL1 register is correctly
> restored before PMBLIMITR_EL1. Should the guest restoration follow the same
> sequence to avoid unpredictable behavior?
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=19

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 20/28] KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it
  2026-09-03 16:48   ` sashiko-bot
@ 2026-09-04 13:45     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 13:45 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvmarm, Marc Zyngier, linux-perf-users, Oliver Upton

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:48:58PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] Inverted logic for `perf_allow_kernel()` grants physical timestamps to unprivileged users and denies them to privileged users.
> - [High] Capability check `perf_allow_kernel()` is evaluated at `KVM_RUN` time (TOCTOU) instead of VM/VCPU configuration time.
> - [Medium] Silently-on guest-visible feature (SPE physical timestamps) breaks live migration and violates subsystem guidelines.
> - [Medium] Concurrent first `KVM_RUN` invocations on multiple vCPUs cause a data race on the VM-wide `kvm_spe->guest_pmscr_el2`.
> --
> 
> commit 8640bfaf0b943972e8c97350b961b39b15830f1b
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it
> 
> The SPE driver allows userspace to use physical timestamps for records only
> if the kernel allows it, do the same for a virtual machine.
> 
> [ ... ]
> 
> > diff --git a/arch/arm64/kvm/hyp/vhe/spe-sr.c b/arch/arm64/kvm/hyp/vhe/spe-sr.c
> > index 978620ad99fa5..3c32a1338cc8d 100644
> > --- a/arch/arm64/kvm/hyp/vhe/spe-sr.c
> > +++ b/arch/arm64/kvm/hyp/vhe/spe-sr.c
> 
> [ ... ]
> 
> > @@ -120,10 +121,10 @@ void __kvm_spe_restore_guest_state_vhe(struct kvm_vcpu *vcpu, struct kvm_cpu_con
> >  		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
> >  	}
> >  
> > -	__kvm_spe_restore_sampling_regs(vcpu->kvm, guest_ctxt);
> > +	__kvm_spe_restore_sampling_regs(kvm, guest_ctxt);
> >  
> >  	write_sysreg_el1(ctxt_sys_reg(guest_ctxt, PMSCR_EL1), SYS_PMSCR);
> > -	write_sysreg_el2(0, SYS_PMSCR);
> > +	write_sysreg_el2(kvm->arch.kvm_spe.guest_pmscr_el2, SYS_PMSCR);
> 
> [Severity: Medium]
> Can this read of kvm->arch.kvm_spe.guest_pmscr_el2 race with a concurrent
> write from another vCPU?

Yes, it can.

> 
> If multiple vCPUs are starting their first KVM_RUN, one vCPU might complete
> kvm_arch_vcpu_run_pid_change(), proceed to enter the guest, and execute
> __kvm_spe_restore_guest_state_vhe(), reading guest_pmscr_el2.
> 
> Because kvm_spe_vcpu_first_run_init() performs unlocked writes to this
> shared VM-wide variable, another vCPU could be modifying it at the same
> time. Should this use READ_ONCE() and WRITE_ONCE() to prevent LKMM
> violations and KCSAN data race splats?

See below.

> 
> >  }
> >  NOKPROBE_SYMBOL(__kvm_spe_restore_guest_state_vhe);
> >  
> > diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> > index af1e19cb8e9ae..68b751bdb384b 100644
> > --- a/arch/arm64/kvm/spe.c
> > +++ b/arch/arm64/kvm/spe.c
> 
> [ ... ]
> 
> > @@ -97,6 +99,12 @@ int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
> >  	if (!kvm_spe->pmblimitr_el1_res0_mask)
> >  		kvm_spe_compute_pmblimitr_el1_res0_mask(kvm);
> >  
> > +	if (kvm_vm_has_ran_once(kvm))
> > +		return 0;
> > +
> > +	if (perf_allow_kernel())
> > +		kvm_spe->guest_pmscr_el2 = PMSCR_EL2_PCT_PHYS;
> 
> [Severity: High]
> Does this logic invert the intended permission check?
> 
> The perf_allow_kernel() function returns 0 on success (access granted) and
> a negative error code (like -EACCES) on failure. By evaluating to true when
> access is denied, it seems we might be granting physical timestamps to
> unprivileged users and incorrectly denying them to privileged users.

That's correct, I assumed from the name that perf_allow_kernel() simply performs
a permission check and returns true if perf is allowed to profile the kernel.

I'll be changing it to:

	if (!perf_allow_kernel())
		kvm_spe->guest_pmscr_el2 = PMSCR_EL2_PCT_PHYS;

(I remember fixing it locally, I guess I got confused about the name a second
time).

> 
> [Severity: High]
> Is kvm_spe_vcpu_first_run_init() the right place to check these
> capabilities?
> 
> Checking perf_allow_kernel() here means it is evaluated during the KVM_RUN
> ioctl, rather than during VM configuration. VMMs typically configure VMs
> with full privileges but drop privileges before executing KVM_RUN for
> sandboxing. Checking at KVM_RUN time will incorrectly deny physical
> timestamps to properly sandboxed VMMs.
> 
> Furthermore, an unprivileged process could configure a VM and pass the vCPU
> file descriptor to a privileged process, improperly gaining access to
> physical timestamps using the privileged thread's capabilities.

Sure, I can set the VM-wide PMSCR_EL2 value the first time the SPE PMU instance
is assigned. Or maybe when KVM_ARM_VCPU_INIT is first called. I'll think about
what is the best approach.

> 
> [Severity: Medium]
> Could silently enabling SPE physical timestamps based on the host kernel
> break live migration?
> 
> If a VM is migrated from a host where perf_allow_kernel() grants access to
> one where it denies it, the guest would silently downgrade to virtual
> timestamps, causing data corruption in the profiling trace.
> 
> The KVM guidelines require that new behavior the guest can observe must be
> off by default and explicitly enabled through standard capability
> negotiation (like a KVM_CAP_* or ID register flag). Should this require an
> explicit opt-in from userspace instead?

I think a better approach would be to update the documentation to let the
system administrator know that the VM configuration depends on perf being
allowed to profile the kernel, and it should be the same on the source and
destination in the case of migration.

> 
> [Severity: Medium]
> Can this write to kvm_spe->guest_pmscr_el2 happen concurrently across
> multiple vCPUs?

It can, should be fixed with the above approach.

Thanks,
Alex

> 
> The kvm_vm_has_ran_once(kvm) flag is not set until the end of
> kvm_arch_vcpu_run_pid_change(), which happens after
> kvm_spe_vcpu_first_run_init() completes. If multiple vCPUs start their
> first KVM_RUN concurrently, they will all see kvm_vm_has_ran_once(kvm) as
> false and write to this shared variable without synchronization.
> 
> > +
> >  	return 0;
> >  }
> >
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=20

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 21/28] KVM: arm64: Handle SPE maintenance interrupts
  2026-09-03 16:58   ` sashiko-bot
@ 2026-09-04 14:04     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 14:04 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: kvmarm, Marc Zyngier, Oliver Upton, linux-perf-users

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:58:23PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
> 
> New issues:
> - [Medium] SPE system registers are not cleared during VCPU reset, leaking stale state across warm resets.
> 
> Pre-existing issues:
> - [High] Hardware profiling buffer is enabled before `PMBSR_EL1` is restored, creating a race condition that clobbers hardware events.
> --
> 
> commit 0111e834cf6f2849f99044fcfd2c2f6ef3480c83
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: Handle SPE maintenance interrupts
> 
> This commit introduces handling for SPE maintenance interrupts in KVM. It
> injects the interrupt when the guest sets the PMBSR_EL1.S bit and re-injects
> hardware-raised maintenance interrupts.
> 
> > diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> > index 4c4bf88c9aa53..ebe1c933f8d55 100644
> > --- a/arch/arm64/kvm/sys_regs.c
> > +++ b/arch/arm64/kvm/sys_regs.c
> > @@ -1595,6 +1595,11 @@ static int get_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc
> >  	return 0;
> >  }
> >  
> > +static u64 reset_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r)
> > +{
> > +	return kvm_spe_reset_sysreg(vcpu, r->reg);
> > +}
> > +
> 
> [Severity: Medium]
> Does this change leak stale SPE register state across warm resets?
> 
> Previously, SPE registers used reset_val as their reset callback, which
> actively updated the vCPU sysreg array via __vcpu_assign_sys_reg().

Most of them still do.

> 
> Here, the new reset_spe_sysreg() callback returns 0 but misses a call to
> __vcpu_assign_sys_reg() to clear the VCPU's system register memory.
> Because the KVM sysreg reset caller ignores the return value, the values in
> vcpu->arch.ctxt.sys_regs remain untouched.
> 
> If a VM goes through a warm reset (e.g., via PSCI CPU_OFF / CPU_ON) while
> profiling is active, the new OS environment inherits a stale profiling state.
> Should kvm_spe_reset_sysreg() explicitly clear the register state?

Yeah, somehow got confused by the fact that the ->reset callbacks both assign
the value and return the assigned value and I incorrectly chose to return the
value without assigning it first.

> 
> >  static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> >  			   const struct sys_reg_desc *r)
> >  {
> > @@ -1642,7 +1647,7 @@ static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> >  
> >  #define SPE_SYS_REG(name)						\
> >  	SYS_DESC(SYS_##name), .reg = name, .access = access_spe_reg,	\
> > -	.reset = reset_val, .val = 0, .visibility = spe_visibility,	\
> > +	.reset = reset_spe_sysreg, .visibility = spe_visibility,	\
> >  	.set_user = set_user_spe_sysreg, .get_user = get_user_spe_sysreg
> 
> [Severity: High]
> This is a pre-existing issue, but does __kvm_spe_restore_guest_state_vhe()
> clobber hardware events by enabling the profiling buffer before restoring
> PMBSR_EL1?
> 
> In arch/arm64/kvm/hyp/vhe/spe-sr.c:__kvm_spe_restore_guest_state_vhe():
> 
> 	if (__kvm_spe_profiling_buffer_enabled(pmblimitr, pmbsr)) {
> 		write_sysreg_s(ctxt_sys_reg(guest_ctxt, PMBPTR_EL1), SYS_PMBPTR_EL1);
> 		isb();
> 		write_sysreg_s(pmblimitr, SYS_PMBLIMITR_EL1);
> 		isb();
> 		/* A buffer management event preserves fields in PMBSR_EL1 */
> 		write_sysreg_s(pmbsr, SYS_PMBSR_EL1);
> 	}
> 
> Because PMBLIMITR_EL1 is restored (with the E bit set) and an ISB executed
> before PMBSR_EL1 is restored, hardware can autonomously trigger a buffer
> management event right after the ISB if the buffer is near full.

I don't think so. Sampling at EL2 has been disabled when the host state was
saved, and the buffer was drained immediately after. All of this was executed in
__kvm_spe_save_host_state_vhe(). There are no records here to be written back to
memory.

> 
> KVM's subsequent software write of the guest's PMBSR_EL1 (which has S = 0)
> overwrites the hardware register, wiping the event and clearing the
> interrupt.
> 
> This violates the ARM Architecture Reference Manual D10.1.5, which forbids

Please also state the version of the architecture reference manual, because in
M.c, D10.1.5 does not exist, and chapter D10 is about MTE, not SPE.

> software from writing to PMBSR_EL1 while PMBLIMITR_EL1.E == 1.
> Should PMBSR_EL1 be restored before PMBLIMITR_EL1 to prevent this race
> condition?

I believe there was a discussion about something like that, but that was never
merged into the Arm ARM.

Thanks,
Alex

> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=21

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 28/28] KVM: arm64: Allow the creation of a SPE enabled VM
  2026-09-03 16:59   ` sashiko-bot
@ 2026-09-04 14:09     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 14:09 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Oliver Upton, kvmarm, Marc Zyngier, linux-perf-users

Hi Sashiko,

On Thu, Sep 03, 2026 at 04:59:29PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
> - [Medium] The Kconfig dependency for `KVM_ARM_SPE` restricts it to 'n' when the `ARM_SPE_PMU` driver is built as a module, preventing users from enabling SPE virtualization.
> --
> 
> commit 9bb804f4572dd230ad3b5db909bd2001b3ee3186
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> 
> KVM: arm64: Allow the creation of a SPE enabled VM
> 
> This commit introduces the Kconfig option KVM_ARM_SPE to allow userspace
> to enable the Statistical Profiling Extension (SPE) for virtual machines.
> 
> [ ... ]
> 
> > diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
> > --- a/arch/arm64/kvm/Kconfig
> > +++ b/arch/arm64/kvm/Kconfig
> > @@ -106,9 +106,9 @@ endif # NVHE_EL2_DEBUG
> >  
> >  
> >  config KVM_ARM_SPE
> > -	bool
> > +	bool "Support SPE in guest"
> >  	depends on KVM && ARM_SPE_PMU
> 
> [Severity: Medium]
> Does this Kconfig dependency unintentionally disable KVM_ARM_SPE when the
> host driver ARM_SPE_PMU is built as a module?

That contradicts my experience. Setting CONFIG_ARM_SPE_PMU=m makes
CONFIG_KVM_ARM_SPE selectable, and building Linux with CONFIG_ARM_SPE_PMU=m and
CONFIG_KVM_ARM_SPE=y enables the SPE KVM capability when the SPE module is
loaded, and disables the capability when the module is removed.

Thanks,
Alex

> 
> Because KVM_ARM_SPE is a boolean, if ARM_SPE_PMU is configured as a
> loadable module (=m), the Kconfig dependency evaluates to m. For a boolean
> config, this restricts its maximum value to n, effectively disabling the
> feature in the build configuration.
> 
> Since the implementation in arch/arm64/kvm/spe.c supports being built
> against a modular driver (using try_module_get()), should this dependency
> be relaxed or handled differently so the feature can still be selected?
> 
> > -	default n
> > +	default y
> >  	help
> >  	  Adds support for Statistical Profiling Extension (SPE) in virtual
> >  	  machines.
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=28

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 27/28] KVM: arm64: Handle dirty page logging when SPE feature is set
  2026-09-03 17:06   ` sashiko-bot
@ 2026-09-04 14:41     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-09-04 14:41 UTC (permalink / raw)
  To: sashiko-reviews; +Cc: Marc Zyngier, kvmarm, linux-perf-users, Oliver Upton

Hi Sashiko,

On Thu, Sep 03, 2026 at 05:06:12PM +0000, sashiko-bot@kernel.org wrote:
> Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
> - [High] Memory Protection Bypass / Privilege Escalation via Unconditional Stage-2 Write-Enable
> - [High] Logic Error and EL2 Hypercall Argument Corruption due to flawed `nr_pages` calculation
> - [High] Logical Inconsistency / State Desynchronization causing missed VCPU kicks and unexpected guest exits
> - [Medium] Kernel preemption bug via `smp_processor_id()` in a preemptible context
> --
> 
> commit 45c89e608abbd3e8f9c8b944b7e7d74478d5db12
> Author: Alexandru Elisei <alexandru.elisei@arm.com>
> Subject: KVM: arm64: Handle dirty page logging when SPE feature is set
> 
> This commit aims to avoid profiling blackout windows by preventing VCPU
> execution when the SPE buffer is enabled and dirty page logging is active.
> It introduces dirty logging synchronization constraints and automatic guest
> exits for the KVM/ARM64 SPE implementation.
> 
> > diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> > index 7c7d0616db53a..73f321b3ed2dc 100644
> > --- a/arch/arm64/kvm/arm.c
> > +++ b/arch/arm64/kvm/arm.c
> > @@ -1172,6 +1172,15 @@ static int kvm_vcpu_suspend(struct kvm_vcpu *vcpu)
> >  	return 1;
> >  }
> >  
> > +static void kvm_vcpu_prepare_exit_fail_entry(struct kvm_vcpu *vcpu, u64 reason)
> > +{
> > +	struct kvm_run *run = vcpu->run;
> > +
> > +	run->exit_reason = KVM_EXIT_FAIL_ENTRY;
> > +	run->fail_entry.hardware_entry_failure_reason = reason;
> > +	run->fail_entry.cpu = smp_processor_id();
> 
> [Severity: Medium]
> Is it safe to use smp_processor_id() here? This function is newly invoked by
> check_vcpu_requests() when handling KVM_REQ_SPE_EXIT_DIRTY_LOGGING. Because
> check_vcpu_requests() executes in the KVM run loop before preempt_disable()
> is called, the context remains preemptible. This will trigger a kernel
> warning if CONFIG_DEBUG_PREEMPT is enabled, and records an unreliable CPU ID
> since the thread can migrate immediately afterward.

That's true. For the SPE case, it doesn't matter, because the failure reason is
dirty page logging being enabled for a memslot. For the unsupported CPU case it
might matter, but in that case it's called with preemption disabled so the cpu
ID is stable. I can call smp_processor_id() with preemption disabled, or have
the caller supply a cpu id and an argument.

I'll also update my testing configuration, to include CONFIG_DEBUG_PREEMPT. I
think at some point I disabled the debug options because it was making Linux too
slow and forgot to re-enable all of them.

> 
> [ ... ]
> > diff --git a/arch/arm64/kvm/hyp/pgtable.c b/arch/arm64/kvm/hyp/pgtable.c
> > index b74dd5ce1efd3..07e43f607eae7 100644
> > --- a/arch/arm64/kvm/hyp/pgtable.c
> > +++ b/arch/arm64/kvm/hyp/pgtable.c
> > @@ -1293,6 +1293,14 @@ int kvm_pgtable_stage2_wrprotect(struct kvm_pgtable *pgt, u64 addr, u64 size)
> >  					KVM_PGTABLE_WALK_IGNORE_EAGAIN);
> >  }
> >  
> > +int kvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr, u64 size)
> > +{
> > +	return stage2_update_leaf_attrs(pgt, addr, size,
> > +					KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W, 0,
> > +					NULL, NULL,
> > +					KVM_PGTABLE_WALK_IGNORE_EAGAIN);
> > +}
> 
> [Severity: High]
> Can this lead to a memory protection bypass? By unconditionally adding the
> KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W bit to all valid leaf PTEs, this doesn't
> appear to validate if the underlying host memory mapping actually permits
> writes. If a guest reads unallocated shared memory while dirty logging is on,
> KVM maps the host's zero page as read-only at stage 2. Once dirty logging is
> disabled, this blindly grants write access to it.

This is called from kvm_arch_commit_memory_region() ->
kvm_spe_remove_dirty_logging() only if the memslot is backed by guest_memfd and
the memslot is not read-only. guest_memfd is a memory provider independent of
the host mapping, and guest_memfd backed memory is always mapped as writable at
stage 2.

Also worth point out the check that kvm_remove_dirty_logging() performs:

        if (!kvm_slot_has_gmem(slot) || (slot->flags & KVM_MEM_READONLY))
                return;

The read-only check is done for consistency with gmem_abort(), because KVM
rejects the KVM_MEM_READONLY | KVM_MEM_GUEST_MEMFD combination of flags for a
memslot.

> 
> [ ... ]
> > diff --git a/arch/arm64/kvm/pkvm.c b/arch/arm64/kvm/pkvm.c
> > index 8e4c6e4bec123..5ba2fa8010cc9 100644
> > --- a/arch/arm64/kvm/pkvm.c
> > +++ b/arch/arm64/kvm/pkvm.c
> > @@ -488,6 +488,33 @@ int pkvm_pgtable_stage2_unmap(struct kvm_pgtable *pgt, u64 addr, u64 size)
> >  	return __pkvm_pgtable_stage2_unshare(pgt, addr, addr + size);
> >  }
> >  
> > +int pkvm_pgtable_stage2_write_enable(struct kvm_pgtable *pgt, u64 addr, u64 size)
> > +{
> [ ... ]
> > +	lockdep_assert_held(&kvm->mmu_lock);
> > +	for_each_mapping_in_range_safe(pgt, addr, addr + size, mapping) {
> > +		gfn = max(addr >> PAGE_SHIFT, mapping->gfn);
> > +		nr_pages = min_t(u64, end_gfn - gfn, mapping->nr_pages);
> 
> [Severity: High]
> Does this calculation span past the end of the mapping? If the requested
> address starts in the middle of an existing mapping, gfn is offset from
> mapping->gfn. Because the offset isn't subtracted from mapping->nr_pages,
> the EL1 host passes an out-of-bounds nr_pages to the EL2 hypervisor. This
> could cause the hypercall to fail or inadvertently write-enable pages beyond
> the intended mapping.

Did I get the math wrong? Yes, I did.

> 
> > +		ret = kvm_call_hyp_nvhe(__pkvm_host_write_enable_guest, handle, gfn,
> > +					nr_pages);
> > +		if (WARN_ON(ret))
> > +			break;
> > +	}
> > +
> > +	return ret;
> > +}
> 
> [ ... ]
> > diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> > index e5f3d0dea7c54..f3310465f40eb 100644
> > --- a/arch/arm64/kvm/spe.c
> > +++ b/arch/arm64/kvm/spe.c
> [ ... ]
> > +void kvm_spe_add_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot)
> > +{
> > +	struct kvm_vcpu *vcpu;
> > +	unsigned long i;
> > +
> > +	lockdep_assert_held(&kvm->slots_lock);
> > +
> > +	/*
> > +	 * No point in handling a regular memslot, because they cannot guarantee
> > +	 * that memory remains mapped at stage 2.
> > +	 *
> > +	 * Memory described by a read-only memslot is always read-only, so SPE
> > +	 * cannot write to it anyway.
> > +	 */
> > +	if (!kvm_slot_has_gmem(slot) || (slot->flags & KVM_MEM_READONLY))
> > +		return;
> 
> [Severity: High]
> Does this cause unexpected guest exits and missed VCPU kicks for regular
> memslots? Here in kvm_spe_add_dirty_logging(), regular memslots are skipped,
> so running VCPUs aren't kicked when dirty logging starts.

The comment says it all.

> 
> > +
> > +	kvm_for_each_vcpu(i, vcpu, kvm) {
> > +		if (!vcpu_has_spe(vcpu))
> > +			continue;
> > +		kvm_make_request(KVM_REQ_SPE_HANDLE_DIRTY_LOGGING, vcpu);
> > +		if (vcpu_has_run_once(vcpu))
> > +			__kvm_vcpu_kick(vcpu, true);
> > +	}
> > +}
> > +
> > +void kvm_spe_remove_dirty_logging(struct kvm *kvm, const struct kvm_memory_slot *slot)
> > +{
> > +	phys_addr_t start, end;
> > +	struct kvm_vcpu *vcpu;
> > +	unsigned long i;
> > +
> > +	lockdep_assert_held(&kvm->slots_lock);
> > +
> > +	if (!kvm_slot_has_gmem(slot) || (slot->flags & KVM_MEM_READONLY))
> > +		return;
> > +
> > +	kvm_for_each_vcpu(i, vcpu, kvm) {
> > +		/*
> > +		 * Write-enabling an entire memslot can be expensive, do it only
> > +		 * if the VM has SPE.
> > +		 */
> > +		if (vcpu_has_spe(vcpu)) {
> > +			start = slot->base_gfn << PAGE_SHIFT;
> > +			end = (slot->base_gfn + slot->npages) << PAGE_SHIFT;
> > +
> > +			write_lock(&kvm->mmu_lock);
> > +			kvm_stage2_write_enable_range(&kvm->arch.mmu, start, end);
> 
> [Severity: High]
> Does this blindly write-enable the entire shared stage-2 memslot? Combined
> with the unconditional KVM_PTE_LEAF_ATTR_LO_S2_S2AP_W addition in
> kvm_pgtable_stage2_write_enable(), this allows a malicious guest to gain write
> access to read-only host memory (like the global shared zero page) if it
> was previously mapped as read-only.

See above.

> 
> > +			write_unlock(&kvm->mmu_lock);
> > +			kvm_flush_remote_tlbs_memslot(kvm, slot);
> > +			return;
> > +		}
> > +	}
> > +}
> > +
> > +static bool kvm_spe_should_exit_dirty_logging(struct kvm_vcpu *vcpu)
> > +{
> > +	struct kvm *kvm = vcpu->kvm;
> > +	u64 pmblimitr, pmbsr;
> > +
> > +	if (!atomic_read(&kvm->nr_memslots_dirty_logging))
> > +		return false;
> 
> [Severity: High]
> Because regular memslots were skipped in kvm_spe_add_dirty_logging(), they
> weren't protected. However, this check relies on the global
> nr_memslots_dirty_logging counter, which increments for all memslots.
> Could this mismatch cause unexpected guest exits when a VM enables standard
> dirty logging (e.g., VGA framebuffers) while utilizing SPE?

Yes, it can. On the other hand, why would anyone enable dirty page logging for a
memslot if they are not planning to do migration, which requires all memslots to
have dirty page logging enabled?

I would rather know first if dirty page logging for SPE enabled VMs is actually
worth enabling before thinking of such edge cases.

Thanks,
Alex

> 
> > +
> > +	pmblimitr = __vcpu_sys_reg(vcpu, PMBLIMITR_EL1);
> > +	pmbsr = __vcpu_sys_reg(vcpu, PMBSR_EL1);
> > +
> > +	return FIELD_GET(PMBLIMITR_EL1_E, pmblimitr) && !FIELD_GET(PMBSR_EL1_S, pmbsr);
> > +}
> 
> -- 
> Sashiko AI review · https://sashiko.dev/#/patchset/20260903160623.315525-1-alexandru.elisei@arm.com?part=27

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support
  2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
                   ` (27 preceding siblings ...)
  2026-09-03 16:06 ` [RFC PATCH v7 28/28] KVM: arm64: Allow the creation of a SPE enabled VM Alexandru Elisei
@ 2026-10-15 10:25 ` Alexandru Elisei
  2026-10-15 12:50   ` Lorenzo Stoakes (ARM)
  28 siblings, 1 reply; 87+ messages in thread
From: Alexandru Elisei @ 2026-10-15 10:25 UTC (permalink / raw)
  To: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Hi Oliver, Marc,

Gentle ping regarding this series. Is there something you want me to do to make
reviewing easier? Do you want me to rebase it on top of v7.4-rc1 when it comes
out? Something else?

Thanks,
Alex

On Thu, Sep 03, 2026 at 05:05:55PM +0100, Alexandru Elisei wrote:
> For a description of the problem, and why using gup doesn't work, see the
> previous iteration of the series, version v6 [1].
> 
> This series is based on v7.3-rc1.
> 
> I've completely changed the approach yet again, but I've kept Reviewed-by tags
> for the some of the prerequisite patches. The highlights are:
> 
> * The entire memory is effectively pinned at stage 2 before the first VCPU is
> run by having userspace use guest_memfd to back VM memory and then doing
> KVM_PRE_FAULT_MEMORY on that memory.
> 
> This change alone simplifies the code immensly.
> 
> * The maximum buffer size does not need to be virtualized since the entire VM
> memory is pinned, which means that PMBIDR_EL1 doesn't need to be trapped.
> SPE features cannot be completely hidden from the guest, as some of them (most
> of them?) add new fields to the records. The end result: PMBIDR_EL1 and
> PMSIDR_EL1 don't need to be trapped, so the dependency on FEAT_FGT is dropped.
> 
> * It works with the SPE host driver built as a module; KVM requires that the
> module is loaded for SPE to be virtualized.
> 
> The end result is that KVM support is mostly reduced to the userspace ABI to
> configure the feature and context switching the SPE state.
> 
> 
> Prerequisites
> =============
> 
> There are several prerequisites:
> 
> * The series depends on KVM_PRE_FAULT_MEMORY being implemented for arm64.
> There are two different implementations of that on the mailing list: [2], [3]. I
> have chosen to use the series from Jack Thomson [2] (rebased on top of v7.3-rc1
> + the SPE patches) for the simple reason that it doesn't change the way KVM
> handles stage 2 faults, and can be cherry-picked *after* the KVM SPE patches, to
> allow sashiko to review the SPE patches.
> 
> * A patch to explicitely ignore MMU notifiers for guest_memfd-only memslots. The
> patch is on Sean's radar to be merged once he finishes fixing a bug in a related
> area [4].
> 
> I believe that it is possible to have memory unmapped from stage 2 (it involves
> automatic NUMA balancing and userspace explicitly setting a mempolicy), but
> haven't tested that; the patch codifies that guest_memfd memory should never be
> unmapped without userspace requesting it (userspace can unmap the memory from
> stage 2 by closing the guest_memfd file descriptor or punching a hole in the
> descriptor).
> 
> * Patches to add dirty page logging support to guest_memfd-only memslots
> (rebased on top of v7.3-rc1 + the SPE patches), as it's impossible today to
> change the flags for a guest_memfd-backed memslot. These patches were sent to
> the mailing list [5], and as far as I can tell the conclusion was that allowing
> dirty page logging is a goal to be pursued for guest_memfd memslots. If we do
> decide that dirty page logging for a VM with SPE is useful, I'm planning to
> revisit dirty page logging for guest_memfd.
> 
> A branch with the prerequisites **after** the SPE series can be found at [6].
> The prequisites come after the SPE patches so I can send this series with an
> upstream base commit, which allows sashiko to review them.
> 
> kvmtool patches are at [7], and kvm-unit-tests are at [8].
> 
> 
> How it works
> ============
> 
> 1. Userspace sets the KVM_ARM_VCPU_SPE VCPU feature for all VCPUs, configures
> the SPE interrupt number (required), assigns a SPE PMU instance to the VM
> (required) and then calls KVM_ARM_VCPU_SPE_INIT to finalise configuration. All
> of this is done via KVM_SET_DEVICE_ATTR on each VCPU.
> 
> Once the VCPU feature is set, KVM_ARM_VCPU_SPE_INIT must be executed before a
> VCPU can be run.
> 
> 2. Userspace uses guest_memfd-backed memslots to represent guest memory, and
> pre-maps that memory at stage 2 using KVM_PRE_FAULT_MEMORY.
> 
> The order between steps 1 and 2 does not matter.
> 
> 3. Userspace runs the VCPU(s).
> 
> For a VM with SPE, KVM will not check if memslots are backed by guest_memfd,
> because KVM cannot know which memslots the guest will use as RAM and which have
> a different use, even if they are not read-only. For example, pvtime.
> 
> To create a VM with SPE enabled with kvmtool:
> 
> # modprobe arm_spe_pmu # If not already loaded or built-in
> $ lkvm run --kernel <image> --spe --guest-memfd --name spe-test --params "kpti=off <other_params>"
> 
> To test dirty page logging with kvmtool, use the debug interface:
> 
> # Create a VM with the name spe-test, as above, then:
> $ lkvm debug --name spe-test --enable-dirty-track # to enable dirty page logging
> or
> $ lkvm debug --name spe-test --disable-dirty-track # to disable dirty page logging
> 
> To run the tests from kvm-unit-tests:
> 
> $ configure --arch=arm64 --cross-prefix=<cross_compile> --target=kvmtool
> $ make clean && make
> $ KVMTOOL=<path/to/kvmtool/binary> ./run_tests.sh -g spe
> 
> 
> Migration
> =========
> 
> This is tricky (read: hacky). Relying on KVM fixing up stage 2 faults triggered
> SPE attempting to write to memory that is read-only doesn't really work, because
> a partial record might be written to the buffer (PMBSR_EL1.DL set). To recover
> from this, KVM, or better yet, the VMM, would need to learn to parse the buffer
> and rewind PMBPTR_EL1 to the first by after the last complete record.
> 
> So what I did instead was to introduce a new hardware exit reason, and have
> VCPUs which have the buffer enabled exit to userspace when dirty page logging is
> enabled. A VCPU also exits to userspace if the buffer was disabled when dirty
> page logging was enabled, and the buffer later becomes enabled, before dirty page
> logging is disabled.
> 
> When dirty page logging is disabled, the stage 2 permissions are relaxed to
> allow writes. The gory details are in patch #27 ("KVM: arm64: Handle dirty page
> logging when SPE feature is set").
> 
> [1] https://lore.kernel.org/kvmarm/20251114160717.163230-1-alexandru.elisei@arm.com/
> [2] https://lore.kernel.org/kvmarm/20260612162354.73378-3-jackabt.amazon@gmail.com/
> [3] https://lore.kernel.org/kvmarm/20260825-kvm-arm-prefault-v1-0-befe8947702e@kernel.org/
> [4] https://lore.kernel.org/kvmarm/amPCdAswlk24Xilf@google.com/
> [5] https://lore.kernel.org/kvmarm/20260702142912.6395-1-alexandru.elisei@arm.com/
> [6] https://gitlab.arm.com/linux-arm/linux-ae/-/tree/kvm-spe-v7
> [7] https://gitlab.arm.com/linux-arm/kvmtool-ae/-/tree/kvm-spe-v7
> [8] https://gitlab.arm.com/linux-arm/kvm-unit-tests-ae/-/tree/kvm-spe-v7
> 
> Alexandru Elisei (26):
>   arm64/sysreg: Add the nVM field to PMBLIMITR_EL1
>   arm64/sysreg: Define MDCR_EL2.E2PB values
>   KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option
>   perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file
>   perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu
>   KVM: arm64: Add KVM_CAP_ARM_SPE capability
>   KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature
>   HACK! KVM: arm64: Disable SPE virtualization if protected KVM is
>     enabled
>   HACK! KVM: arm64: Enable SPE virtualization only in VHE mode
>   HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled
>   KVM: arm64: Add SPE VCPU device attribute to set the SPE device
>   KVM: arm64: Add SPE VCPU device attribute to initialize SPE
>   KVM: arm64: Use PMSVer from the assigned SPE instance
>   KVM: arm64: Add SPE system registers to VCPU context
>   KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes
>   KVM: arm64: config: Use functions from spe.c to test
>     FEAT_SPE_{FnE,FDS}
>   KVM: arm64: VHE: Context switch SPE state
>   KVM: arm64: Allow guest SPE physical timestamps only if kernel allows
>     it
>   KVM: arm64: Handle SPE maintenance interrupts
>   arm64: errata: Disable SPE in KVM
>   KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter
>   arm64: errata: Don't enable guest buffer if misprogrammed
>   KVM: arm64: at: Use callback for reading descriptor
>   KVM: arm64: Map memory on a SPE stage 2 fault
>   KVM: arm64: Handle dirty page logging when SPE feature is set
>   KVM: arm64: Allow the creation of a SPE enabled VM
> 
> Sudeep Holla (2):
>   KVM: arm64: Add a new VCPU device control group for SPE
>   KVM: arm64: Add SPE VCPU device attribute to set the interrupt number
> 
>  .../admin-guide/kernel-parameters.txt         |   3 +
>  Documentation/arch/arm64/silicon-errata.rst   |  37 +
>  Documentation/virt/kvm/api.rst                |  19 +-
>  Documentation/virt/kvm/devices/vcpu.rst       |  84 +++
>  arch/arm64/Kconfig                            |  55 ++
>  arch/arm64/include/asm/kvm_asm.h              |   1 +
>  arch/arm64/include/asm/kvm_host.h             |  48 +-
>  arch/arm64/include/asm/kvm_hyp.h              |  26 +-
>  arch/arm64/include/asm/kvm_mmu.h              |   1 +
>  arch/arm64/include/asm/kvm_nested.h           |   7 +
>  arch/arm64/include/asm/kvm_pgtable.h          |  19 +
>  arch/arm64/include/asm/kvm_pkvm.h             |   1 +
>  arch/arm64/include/asm/kvm_spe.h              | 150 ++++
>  arch/arm64/include/uapi/asm/kvm.h             |   6 +
>  arch/arm64/kernel/cpu_errata.c                |  51 ++
>  arch/arm64/kvm/Kconfig                        |  10 +
>  arch/arm64/kvm/Makefile                       |   1 +
>  arch/arm64/kvm/arm.c                          |  98 ++-
>  arch/arm64/kvm/at.c                           |  22 +-
>  arch/arm64/kvm/config.c                       |  30 +-
>  arch/arm64/kvm/guest.c                        |  12 +
>  arch/arm64/kvm/hyp/include/nvhe/mem_protect.h |   2 +-
>  arch/arm64/kvm/hyp/nvhe/hyp-main.c            |  34 +-
>  arch/arm64/kvm/hyp/nvhe/mem_protect.c         |   7 +-
>  arch/arm64/kvm/hyp/pgtable.c                  |   8 +
>  arch/arm64/kvm/hyp/vhe/Makefile               |   1 +
>  arch/arm64/kvm/hyp/vhe/debug-sr.c             |  18 +
>  arch/arm64/kvm/hyp/vhe/spe-sr.c               | 212 ++++++
>  arch/arm64/kvm/hyp/vhe/switch.c               |   9 +
>  arch/arm64/kvm/mmu.c                          |  72 ++
>  arch/arm64/kvm/nested.c                       |   7 +-
>  arch/arm64/kvm/pkvm.c                         |  27 +
>  arch/arm64/kvm/pmu-emul.c                     |  11 +-
>  arch/arm64/kvm/spe.c                          | 647 ++++++++++++++++++
>  arch/arm64/kvm/sys_regs.c                     | 103 ++-
>  arch/arm64/tools/cpucaps                      |   2 +
>  arch/arm64/tools/sysreg                       |  11 +-
>  drivers/perf/arm_spe_pmu.c                    |  46 +-
>  include/linux/perf/arm_spe_pmu.h              |  66 ++
>  include/uapi/linux/kvm.h                      |   1 +
>  40 files changed, 1849 insertions(+), 116 deletions(-)
>  create mode 100644 arch/arm64/include/asm/kvm_spe.h
>  create mode 100644 arch/arm64/kvm/hyp/vhe/spe-sr.c
>  create mode 100644 arch/arm64/kvm/spe.c
>  create mode 100644 include/linux/perf/arm_spe_pmu.h
> 
> 
> base-commit: cee9395acd8043be0644b25c34bfa86623f2b935
> -- 
> 2.43.0
> 
> 

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support
  2026-10-15 10:25 ` [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
@ 2026-10-15 12:50   ` Lorenzo Stoakes (ARM)
  2026-10-15 13:22     ` Alexandru Elisei
  0 siblings, 1 reply; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 12:50 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Oct 15, 2026 at 11:25:25AM +0100, Alexandru Elisei wrote:
> Hi Oliver, Marc,
>
> Gentle ping regarding this series. Is there something you want me to do to make
> reviewing easier? Do you want me to rebase it on top of v7.4-rc1 when it comes
> out? Something else?
>
> Thanks,
> Alex

Just to say that I'm planning to go through this series myself and can hopefully
help with the review workload here! :)

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support
  2026-10-15 12:50   ` Lorenzo Stoakes (ARM)
@ 2026-10-15 13:22     ` Alexandru Elisei
  0 siblings, 0 replies; 87+ messages in thread
From: Alexandru Elisei @ 2026-10-15 13:22 UTC (permalink / raw)
  To: Lorenzo Stoakes (ARM)
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

Hi Lorenzo,

On Thu, Oct 15, 2026 at 01:50:09PM +0100, Lorenzo Stoakes (ARM) wrote:
> On Thu, Oct 15, 2026 at 11:25:25AM +0100, Alexandru Elisei wrote:
> > Hi Oliver, Marc,
> >
> > Gentle ping regarding this series. Is there something you want me to do to make
> > reviewing easier? Do you want me to rebase it on top of v7.4-rc1 when it comes
> > out? Something else?
> >
> > Thanks,
> > Alex
> 
> Just to say that I'm planning to go through this series myself and can hopefully
> help with the review workload here! :)

Sounds great, thanks!

Alex


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1
  2026-09-03 16:05 ` [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1 Alexandru Elisei
  2026-09-03 16:14   ` sashiko-bot
@ 2026-10-15 13:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 13:23 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:05:56PM +0100, Alexandru Elisei wrote:
> Define PMBLIMITR_EL1.nVM, to be used by KVM.

I see that is defined in [0].

>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

LGTM, matches the hardware spec and in line with other entries in the file,
so:

Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

> ---
>  arch/arm64/tools/sysreg | 4 +++-
>  1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
> index 94bf065c8ac7..a11a968eb707 100644
> --- a/arch/arm64/tools/sysreg
> +++ b/arch/arm64/tools/sysreg
> @@ -3106,7 +3106,9 @@ EndSysreg
>
>  Sysreg	PMBLIMITR_EL1	3	0	9	10	0
>  Field	63:12	LIMIT
> -Res0	11:6
> +Res0	11:8

I can see from the diagram in the field descriptions section of [0] that 11->8
is res0...

> +Field	7	nVM

...and nVM is defined at bit 7...

> +Res0	6

...and res0 is at 6.

>  Field	5	PMFZ
>  Res0	4:3
>  Enum	2:1	FM

I also confirmed the existing fields are all correct too.

> --
> 2.43.0
>
>

--
Cheers, Lorenzo

[0]:https://support.arm.com/documentation/ddi0601/2026-09/AArch64-Registers/PMBLIMITR-EL1--Profiling-Buffer-Limit-Address-Register?lang=en

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values
  2026-09-03 16:05 ` [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values Alexandru Elisei
  2026-09-03 16:12   ` sashiko-bot
@ 2026-10-15 13:52   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 13:52 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:05:57PM +0100, Alexandru Elisei wrote:
> KVM will make use of the different values for MDCR_EL2.E2PB, document them.

As with 1/28, using [0] to check the definition of the register.

>
> Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Looks correct, a couple small nits below, with those addressed:

Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

> ---
>  arch/arm64/tools/sysreg | 7 ++++++-
>  1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/tools/sysreg b/arch/arm64/tools/sysreg
> index a11a968eb707..4485736a4f49 100644
> --- a/arch/arm64/tools/sysreg
> +++ b/arch/arm64/tools/sysreg
> @@ -4045,7 +4045,12 @@ Field	17	HPMD
>  Res0	16
>  Field	15	EnSPM
>  Field	14	TPMS
> -Field	13:12	E2PB
> +UnsignedEnum	13:12 E2PB

MEGA NIT: Shouldn't there be a tab rather than a space after '13:12'? :)

> +	0b00	EL2

So broadly - this means EL2 owns the profiling buffer right?

This seems to align with the document (which is very verbose, obviously :):

"If EL2 is implemented and enabled in the Profiling Buffer owning Security
state, then the Profiling Buffer owning Exception level is EL2. Otherwise,
the Profiling Buffer owning Exception level is EL1 and, Profiling is
prohibited at EL2."

"If EL2 is implemented and enabled in the current Security state, then
accesses to Profiling Buffer System registers at EL1 are trapped to EL2,
unless the access generates a higher priority exception."

> +	0b01	RESERVED

NIT: Hmm, it seems to be the convention to just omit entries that are
missing from the definition in the docs? So is this needed?

I see a couple named ones, e.g. in CTR_EL0.L1Ip:

	# This was named as VPIPT in the ARM but now documented as reserved
	0b00	RESERVED_VPIPT
	# This is named as AIVIVT in the ARM but documented as reserved
	0b01	RESERVED_AIVIVT

Which have comments explaining why, i.e. they were defined before but are
now reserved.

> +	0b10	EL1_TRAP

Again my understanding - EL1 owns the profiling buffer, but EL1 sysreg
accesses trap to EL2?

Seems to align with docs:

"Profiling Buffer owning Exception level is EL1. Profiling is prohibited at
EL2."

"If EL2 is implemented and enabled in the current Security state, then
accesses to Profiling Buffer System registers at EL1 are trapped to EL2,
unless the access generates a higher priority exception."

> +	0b11	EL1

And finally - EL1, traps to EL1 too right?

Again seems to align with docs:

"Profiling Buffer owning Exception level is EL1. Profiling is prohibited at
EL2."

"Accesses to Profiling Buffer System registers at EL1 are not trapped by
this mechanism."

> +EndEnum
>  Field	11	TDRA
>  Field	10	TDOSA
>  Field	9	TDA
> --
> 2.43.0
>
>

All LGTM!

--
Cheers, Lorenzo

[0]:https://support.arm.com/documentation/ddi0601/2026-09/AArch64-Registers/MDCR-EL2--Monitor-Debug-Configuration-Register--EL2-?lang=en


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option
  2026-09-03 16:05 ` [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option Alexandru Elisei
  2026-09-03 16:13   ` sashiko-bot
@ 2026-10-15 14:15   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 14:15 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:05:58PM +0100, Alexandru Elisei wrote:
> Add a new configuration option that will be used for KVM SPE emulation.
> CONFIG_KVM_ARM_SPE depends on the SPE driver being loaded because:
>
> 1. The SPE driver maintains a cpumask of physical CPUs that support SPE,
>    and that will be used by KVM to emulate SPE on heterogeneous systems.

I guess e.g. big.LITTLE?

>
> 2. KVM will rely on the SPE driver enabling the SPE interrupt at the GIC
>    level.
>
> The option will become user selectable after SPE support in KVM is
> implemented.
>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

With nit addressed the redundant dependency removed (both noted below),
LGTM and:

Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

> ---
>  arch/arm64/kvm/Kconfig | 9 +++++++++
>  1 file changed, 9 insertions(+)
>
> diff --git a/arch/arm64/kvm/Kconfig b/arch/arm64/kvm/Kconfig
> index 449154f9a485..fece4c02b97c 100644
> --- a/arch/arm64/kvm/Kconfig
> +++ b/arch/arm64/kvm/Kconfig
> @@ -103,5 +103,14 @@ config PKVM_STACKTRACE
>  	  If unsure, or not using protected nVHE (pKVM), say N.
>
>  endif # NVHE_EL2_DEBUG
> +
> +config KVM_ARM_SPE

NIT: Nothing else in the file has the _ARM_ prefix, so is that redundant
here?

> +	bool
> +	depends on KVM && ARM_SPE_PMU

I think KVM should be removed here, this is within an 'if KVM' block
already, and nothing else here depends on it either so not a convention.

> +	default n
> +	help
> +	  Adds support for Statistical Profiling Extension (SPE) in virtual
> +	  machines.
> +
>  endif # KVM
>  endif # VIRTUALIZATION
> --
> 2.43.0
>
>

--
Cheers, Lorenzo


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file
  2026-09-03 16:05 ` [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file Alexandru Elisei
  2026-09-03 16:11   ` sashiko-bot
@ 2026-10-15 14:40   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 14:40 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:05:59PM +0100, Alexandru Elisei wrote:
> KVM will soon want to make use of struct arm_spe_pmu, move it to a separate
> header where it will be easily accessible.
>
> Cc: Will Deacon <will@kernel.org>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Looks to be a straightforward mechanical change, couple nits below but with
those addressed:

Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

> ---
>  drivers/perf/arm_spe_pmu.c       | 33 +--------------------
>  include/linux/perf/arm_spe_pmu.h | 50 ++++++++++++++++++++++++++++++++

This should be added to MAINTAINERS too I think?

>  2 files changed, 51 insertions(+), 32 deletions(-)
>  create mode 100644 include/linux/perf/arm_spe_pmu.h
>
> diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
> index b70f3fedf028..79c571a649d5 100644
> --- a/drivers/perf/arm_spe_pmu.c
> +++ b/drivers/perf/arm_spe_pmu.c
> @@ -28,6 +28,7 @@
>  #include <linux/of.h>
>  #include <linux/perf_event.h>
>  #include <linux/perf/arm_pmu.h>
> +#include <linux/perf/arm_spe_pmu.h>
>  #include <linux/platform_device.h>
>  #include <linux/printk.h>
>  #include <linux/slab.h>
> @@ -67,38 +68,6 @@ struct arm_spe_pmu_buf {
>  	void					*base;
>  };
>
> -struct arm_spe_pmu {
> -	struct pmu				pmu;
> -	struct platform_device			*pdev;
> -	cpumask_t				supported_cpus;
> -	struct hlist_node			hotplug_node;
> -
> -	int					irq; /* PPI */
> -	u16					pmsver;
> -	u16					min_period;
> -	u16					counter_sz;
> -
> -#define SPE_PMU_FEAT_FILT_EVT			(1UL << 0)
> -#define SPE_PMU_FEAT_FILT_TYP			(1UL << 1)
> -#define SPE_PMU_FEAT_FILT_LAT			(1UL << 2)
> -#define SPE_PMU_FEAT_ARCH_INST			(1UL << 3)
> -#define SPE_PMU_FEAT_LDS			(1UL << 4)
> -#define SPE_PMU_FEAT_ERND			(1UL << 5)
> -#define SPE_PMU_FEAT_INV_FILT_EVT		(1UL << 6)
> -#define SPE_PMU_FEAT_DISCARD			(1UL << 7)
> -#define SPE_PMU_FEAT_EFT			(1UL << 8)
> -#define SPE_PMU_FEAT_FDS			(1UL << 9)
> -#define SPE_PMU_FEAT_DEV_PROBED			(1UL << 63)
> -	u64					features;
> -
> -	u64					pmsevfr_res0;
> -	u16					max_record_sz;
> -	u16					align;
> -	struct perf_output_handle __percpu	*handle;
> -};
> -
> -#define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
> -
>  /* Convert a free-running index from perf into an SPE buffer offset */
>  #define PERF_IDX2OFF(idx, buf) \
>  	((idx) % ((unsigned long)(buf)->nr_pages << PAGE_SHIFT))
> diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
> new file mode 100644
> index 000000000000..af86fd184050
> --- /dev/null
> +++ b/include/linux/perf/arm_spe_pmu.h
> @@ -0,0 +1,50 @@
> +// SPDX-License-Identifier: GPL-2.0

Pedantic I know, sorry :P but as per Documentation/process/license-rules.rst:

      C source:	// SPDX-License-Identifier: <SPDX License Expression>
      C header:	/* SPDX-License-Identifier: <SPDX License Expression> */
      ...

So should be /* */ here.

> +/*
> + * Split from driver/perf/arm_spe_pmu.c

NIT: should be drivers/...

> + *
> + * Copyright (C) 2016 ARM Limited
> + */
> +
> +#ifndef __PERF_ARM_SPE_PMU_H__
> +#define __PERF_ARM_SPE_PMU_H__
> +
> +#include <linux/cpumask.h>
> +#include <linux/container_of.h>
> +#include <linux/list.h>
> +#include <linux/perf_event.h>
> +#include <linux/platform_device.h>
> +#include <linux/types.h>
> +
> +struct arm_spe_pmu {
> +	struct pmu				pmu;
> +	struct platform_device			*pdev;
> +	cpumask_t				supported_cpus;
> +	struct hlist_node			hotplug_node;
> +
> +	int					irq; /* PPI */
> +	u16					pmsver;
> +	u16					min_period;
> +	u16					counter_sz;
> +
> +#define SPE_PMU_FEAT_FILT_EVT			(1UL << 0)
> +#define SPE_PMU_FEAT_FILT_TYP			(1UL << 1)
> +#define SPE_PMU_FEAT_FILT_LAT			(1UL << 2)
> +#define SPE_PMU_FEAT_ARCH_INST			(1UL << 3)
> +#define SPE_PMU_FEAT_LDS			(1UL << 4)
> +#define SPE_PMU_FEAT_ERND			(1UL << 5)
> +#define SPE_PMU_FEAT_INV_FILT_EVT		(1UL << 6)
> +#define SPE_PMU_FEAT_DISCARD			(1UL << 7)
> +#define SPE_PMU_FEAT_EFT			(1UL << 8)
> +#define SPE_PMU_FEAT_FDS			(1UL << 9)
> +#define SPE_PMU_FEAT_DEV_PROBED			(1UL << 63)
> +	u64					features;
> +
> +	u64					pmsevfr_res0;
> +	u16					max_record_sz;
> +	u16					align;
> +	struct perf_output_handle __percpu	*handle;
> +};
> +
> +#define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
> +
> +#endif /* __PERF_ARM_SPE_PMU_H__ */
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu
  2026-09-03 16:06 ` [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu Alexandru Elisei
  2026-09-03 16:11   ` sashiko-bot
@ 2026-10-15 15:12   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 15:12 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

I think probably some kind of commit message would be good even for a
trivial change :)

On Thu, Sep 03, 2026 at 05:06:00PM +0100, Alexandru Elisei wrote:
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
> ---
>  drivers/perf/arm_spe_pmu.c       | 3 +++
>  include/linux/perf/arm_spe_pmu.h | 2 ++
>  2 files changed, 5 insertions(+)
>
> diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
> index 79c571a649d5..42ba0924bd02 100644
> --- a/drivers/perf/arm_spe_pmu.c
> +++ b/drivers/perf/arm_spe_pmu.c
> @@ -1095,6 +1095,7 @@ static void __arm_spe_pmu_dev_probe(void *info)
>  			"profiling buffer owned by higher exception level\n");
>  		return;
>  	}
> +	spe_pmu->pmbidr_el1 = reg;

This doesn't seem to be used anywhere in the series?

>
>  	/* Minimum alignment. If it's out-of-range, then fail the probe */
>  	fld = FIELD_GET(PMBIDR_EL1_ALIGN, reg);
> @@ -1107,6 +1108,8 @@ static void __arm_spe_pmu_dev_probe(void *info)
>
>  	/* It's now safe to read PMSIDR and figure out what we've got */
>  	reg = read_sysreg_s(SYS_PMSIDR_EL1);
> +	spe_pmu->pmsidr_el1 = reg;
> +
>  	if (FIELD_GET(PMSIDR_EL1_FE, reg))
>  		spe_pmu->features |= SPE_PMU_FEAT_FILT_EVT;
>
> diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
> index af86fd184050..97b14e236fcd 100644
> --- a/include/linux/perf/arm_spe_pmu.h
> +++ b/include/linux/perf/arm_spe_pmu.h
> @@ -21,6 +21,8 @@ struct arm_spe_pmu {
>  	cpumask_t				supported_cpus;
>  	struct hlist_node			hotplug_node;
>
> +	u64					pmbidr_el1;

Equally, this doesn't seem to be used anywhere in the series, maybe drop?

> +	u64					pmsidr_el1;

This field is used in kvm_spe_has_feat_spe_fds() and
kvm_spe_has_feat_spe_fne():

bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
{
	...
	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P4) &&
	       FIELD_GET(PMSIDR_EL1_FDS, spe_pmu->pmsidr_el1);
}

bool kvm_spe_has_feat_spe_fne(struct kvm *kvm)
{
	...
	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P2) &&
	       FIELD_GET(PMSIDR_EL1_FnE, spe_pmu->pmsidr_el1);
}

So checking for FDS and FnE, but these are available as feature flags as
__arm_spe_pmu_dev_probe() gets these:

static void __arm_spe_pmu_dev_probe(void *info)
{
	...
	if (FIELD_GET(PMSIDR_EL1_FnE, reg))
		spe_pmu->features |= SPE_PMU_FEAT_INV_FILT_EVT;
	...
	if (FIELD_GET(PMSIDR_EL1_FDS, reg))
		spe_pmu->features |= SPE_PMU_FEAT_FDS;
	...
}

And then kvm_spe_has_feat_spe_fds() and kvm_spe_has_feat_spe_fne() can be:

bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
{
	...
	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P4) &&
	       (spe_pmu->features & SPE_PMU_FEAT_FDS);
}

bool kvm_spe_has_feat_spe_fne(struct kvm *kvm)
{
	...
	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P2) &&
	       (spe_pmu->features & SPE_PMU_FEAT_INV_FILT_EVT);
}

Right?

So it seems you could drop this patch from the series in general? Is there
another reason to get these fields?

Also - there seems to be a divergence in the naming there between FnE and
INV_FILT_EVT, is that intentional?

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability
  2026-09-03 16:06 ` [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability Alexandru Elisei
  2026-09-03 16:15   ` sashiko-bot
@ 2026-10-15 16:51   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-15 16:51 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:01PM +0100, Alexandru Elisei wrote:
> Add the SPE capability that will be used by userspace to test if KVM
> supports SPE virtualization.

You are also adding spe.c too right? Which is adding functionality for the
perf driver to call into? Probably worth mentioning that you're adding the
foundations of this stuff in the commit message also.

>
> The SPE driver supports heterogenous systems. Keep track of all the
> available Statistical Profiling Units (SPUs) in the system, because KVM
> will require the user to associate a VM with exactly one SPU.

I guess questions more for my understanding here :)

Is it one SPU per core, or is it some property of heterogenous systems,
i.e. 1 per big/LITTLE etc.?

How can a user decide which? Do you actually expose the ability to select a
named SPU, or is it automatically determined based upon which CPUs are used
by the VM?

What if a VM spans both 'big' and 'LITTLE' cores?

>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
> ---
>  Documentation/virt/kvm/api.rst   | 10 ++++++
>  arch/arm64/include/asm/kvm_spe.h | 18 ++++++++++
>  arch/arm64/kvm/Makefile          |  1 +
>  arch/arm64/kvm/arm.c             |  4 +++
>  arch/arm64/kvm/spe.c             | 56 ++++++++++++++++++++++++++++++++
>  drivers/perf/arm_spe_pmu.c       |  4 +++
>  include/linux/perf/arm_spe_pmu.h | 12 +++++++

The KVM arm MAINTAINERS entry has:

F:	arch/arm64/kvm/
F:	include/kvm/arm_*

So arch/arm64/kvm/spe.c and include/linux/perf/arm_spe_pmu.h don't need any
updates there.

>  include/uapi/linux/kvm.h         |  1 +
>  8 files changed, 106 insertions(+)
>  create mode 100644 arch/arm64/include/asm/kvm_spe.h
>  create mode 100644 arch/arm64/kvm/spe.c
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index e0430cc750c9..1ed5c389c7a4 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -9600,6 +9600,16 @@ take care to differentiate between these cases.
>  The presence of this capability indicates that the nested KVM guest can
>  start in ESA mode.
>
> +8.48 KVM_CAP_ARM_SPE
> +--------------------
> +
> +:Capability: KVM_CAP_ARM_SPE
> +:Architectures: arm64
> +:Type: vm
> +
> +This capability indicates that Statistical Profiling Extension (SPE)
> +virtualization is available in KVM.

Is it worth adding something here about the perf driver? As this seems key
to the thing. Or is that too much of an implementation detail?

> +
>  9. Known KVM API problems
>  =========================
>
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> new file mode 100644
> index 000000000000..f05e6d3f5705
> --- /dev/null
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -0,0 +1,18 @@
> +/* SPDX-License-Identifier: GPL-2.0-only */
> +/*
> + * Copyright (C) 2021 - ARM Ltd
> + */
> +
> +#ifndef __ARM64_KVM_SPE_H__
> +#define __ARM64_KVM_SPE_H__
> +
> +#ifdef CONFIG_KVM_ARM_SPE
> +bool kvm_supports_spe(void);
> +#else
> +static __always_inline bool kvm_supports_spe(void)

Very nitty, but not sure if __always_inline is strictly necessary, you'd hope
that the compiler would naturally inline a false on optimisation :)

> +{
> +	return false;
> +}
> +#endif /* CONFIG_KVM_ARM_SPE */
> +
> +#endif /* __ARM64_KVM_SPE_H__ */
> diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile
> index 59612d2f277c..9ee271f713cd 100644
> --- a/arch/arm64/kvm/Makefile
> +++ b/arch/arm64/kvm/Makefile
> @@ -29,6 +29,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \
>  kvm-$(CONFIG_HW_PERF_EVENTS)  += pmu-emul.o pmu.o
>  kvm-$(CONFIG_ARM64_PTR_AUTH)  += pauth.o
>  kvm-$(CONFIG_PTDUMP_STAGE2_DEBUGFS) += ptdump.o
> +kvm-$(CONFIG_KVM_ARM_SPE)     += spe.o
>
>  kvm-$(CONFIG_NVHE_EL2_TRACING) += hyp_trace.o
>
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 8b080804bc90..eca1184ef9dd 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -42,6 +42,7 @@
>  #include <asm/kvm_nested.h>
>  #include <asm/kvm_pkvm.h>
>  #include <asm/kvm_ptrauth.h>
> +#include <asm/kvm_spe.h>
>  #include <asm/sections.h>
>  #include <asm/stacktrace/nvhe.h>
>
> @@ -468,6 +469,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
>  	case KVM_CAP_ARM_PMU_V3_STRICT:
>  		r = kvm_supports_guest_pmuv3();
>  		break;
> +	case KVM_CAP_ARM_SPE:
> +		r = kvm_supports_spe();
> +		break;
>  	case KVM_CAP_ARM_INJECT_SERROR_ESR:
>  		r = cpus_have_final_cap(ARM64_HAS_RAS_EXTN);
>  		break;
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> new file mode 100644
> index 000000000000..646806a60d66
> --- /dev/null
> +++ b/arch/arm64/kvm/spe.c
> @@ -0,0 +1,56 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (C) 2021 - ARM Ltd
> + */
> +
> +#include <linux/cpumask.h>
> +#include <linux/kvm_host.h>
> +#include <linux/perf/arm_spe_pmu.h>
> +
> +#include <asm/kvm_spe.h>
> +#include <asm/sysreg.h>

NIT: include <linux/cleanup.h> for guard(mutex)() etc.?

> +
> +static LIST_HEAD(spe_pmus);
> +static DEFINE_MUTEX(spe_pmus_lock);
> +
> +struct spe_pmu_entry {
> +	struct list_head link;
> +	struct arm_spe_pmu *spe_pmu;
> +};

This seems like an odd way of making this into a list, can't you just embed
list_head in arm_spe_pmu?

Then adding becomes:

list_add_tail(&spe_pmu->link, &spu_pmus);

And removing:

list_del(&spe_pmu->link);

And you can avoid the alloc below too.


> +
> +void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)

Hmm I'm guessing this is per-SPU but no mention of SPU in the function
name? Is an 'SPE instance' equivalent?

If so, maybe worth referencing SPE instances rather than SPUs in the commit
message, or at least say something like 'otherwise known as SPE instances'.

> +{
> +	struct spe_pmu_entry *entry;
> +
> +	guard(mutex)(&spe_pmus_lock);
> +
> +	entry = kmalloc(sizeof(*entry), GFP_KERNEL);

The convention is kmalloc_obj() now. Which also lets you elide GFP_KERNEL
so this can be:

	entry = kmalloc_obj(*entry);

> +	if (!entry)
> +		return;

Hmm, shouldn't failing to allocate here be an error, or at least a warning?

> +
> +	entry->spe_pmu = spe_pmu;
> +	list_add_tail(&entry->link, &spe_pmus);
> +}
> +EXPORT_SYMBOL_FOR_MODULES(kvm_spe_add_instance, "arm_spe_pmu");
> +
> +void kvm_spe_remove_instance(struct arm_spe_pmu *spe_pmu)
> +{
> +	struct spe_pmu_entry *entry, *tmp;
> +
> +	guard(mutex)(&spe_pmus_lock);
> +
> +	list_for_each_entry_safe(entry, tmp, &spe_pmus, link) {

You break once you find the entry, so doesn't have to be the _safe variant.

But can avoid all the complexity with an embedded list_head in the spe_pmu
type if it's possible to put it there!

> +		if (entry->spe_pmu == spe_pmu) {
> +			list_del(&entry->link);
> +			kfree(entry);
> +			break;
> +		}
> +	}
> +}
> +EXPORT_SYMBOL_FOR_MODULES(kvm_spe_remove_instance, "arm_spe_pmu");
> +
> +bool kvm_supports_spe(void)
> +{
> +	guard(mutex)(&spe_pmus_lock);
> +	return !list_empty(&spe_pmus);
> +}
> diff --git a/drivers/perf/arm_spe_pmu.c b/drivers/perf/arm_spe_pmu.c
> index 42ba0924bd02..f24b122a087c 100644
> --- a/drivers/perf/arm_spe_pmu.c
> +++ b/drivers/perf/arm_spe_pmu.c
> @@ -1370,6 +1370,8 @@ static int arm_spe_pmu_device_probe(struct platform_device *pdev)
>  	if (ret)
>  		goto out_teardown_dev;
>
> +	kvm_spe_add_instance(spe_pmu);
> +
>  	return 0;
>
>  out_teardown_dev:
> @@ -1383,6 +1385,8 @@ static void arm_spe_pmu_device_remove(struct platform_device *pdev)
>  {
>  	struct arm_spe_pmu *spe_pmu = platform_get_drvdata(pdev);
>
> +	kvm_spe_remove_instance(spe_pmu);
> +
>  	arm_spe_pmu_perf_destroy(spe_pmu);
>  	arm_spe_pmu_dev_teardown(spe_pmu);
>  	free_percpu(spe_pmu->handle);
> diff --git a/include/linux/perf/arm_spe_pmu.h b/include/linux/perf/arm_spe_pmu.h
> index 97b14e236fcd..bdb703558a87 100644
> --- a/include/linux/perf/arm_spe_pmu.h
> +++ b/include/linux/perf/arm_spe_pmu.h
> @@ -49,4 +49,16 @@ struct arm_spe_pmu {
>
>  #define to_spe_pmu(p) (container_of(p, struct arm_spe_pmu, pmu))
>
> +#ifdef CONFIG_KVM_ARM_SPE
> +void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu);
> +void kvm_spe_remove_instance(struct arm_spe_pmu *spe_pmu);
> +#else
> +static inline void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
> +{
> +}
> +static inline void kvm_spe_remove_instance(struct arm_spe_pmu *spe_pmu)
> +{
> +}
> +#endif /* CONFIG_KVM_ARM_SPE */
> +
>  #endif /* __PERF_ARM_SPE_PMU_H__ */
> diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
> index ac2d77d14963..1565a609890b 100644
> --- a/include/uapi/linux/kvm.h
> +++ b/include/uapi/linux/kvm.h
> @@ -999,6 +999,7 @@ struct kvm_enable_cap {
>  #define KVM_CAP_S390_HPAGE_2G 249
>  #define KVM_CAP_PPC_COMPAT_CAPS 250
>  #define KVM_CAP_ARM_PMU_V3_STRICT 251
> +#define KVM_CAP_ARM_SPE 252
>
>  struct kvm_irq_routing_irqchip {
>  	__u32 irqchip;
> --
> 2.43.0
>
>

--
Cheers, Lorenzo


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature
  2026-09-03 16:06 ` [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature Alexandru Elisei
  2026-09-03 16:21   ` sashiko-bot
@ 2026-10-16 14:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-16 14:23 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:02PM +0100, Alexandru Elisei wrote:
> Add a new VCPU feature that enables SPE virtualization when set by
> userspace. KVM_VCPU_MAX_FEATURE will be bumped and the feature will
> become selectable by userspace once SPE support in KVM is implemented.

I guess it's also limited currently at this point in the series by
KVM_CAP_ARM_SPE being unselectable as it stands I think?

Maybe worth mentioning here that you're adding KVM_ARM_VPCU_SPE to the uapi,
updating docs, etc.?

>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

I wonder if there are any dependencies that need expressing in
arch/arm64/kvm/config.c?

In any case all LGTM, with mega-nit below addressed :):

Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

> ---
>  Documentation/virt/kvm/api.rst    | 2 ++
>  arch/arm64/include/asm/kvm_spe.h  | 5 +++++
>  arch/arm64/include/uapi/asm/kvm.h | 1 +
>  arch/arm64/kvm/arm.c              | 7 +++++++
>  4 files changed, 15 insertions(+)
>
> diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
> index 1ed5c389c7a4..654736fac78c 100644
> --- a/Documentation/virt/kvm/api.rst
> +++ b/Documentation/virt/kvm/api.rst
> @@ -3591,6 +3591,8 @@ Possible features:
>  	  Depends on KVM_CAP_ARM_EL2_E2H0.
>  	  KVM_ARM_VCPU_HAS_EL2 must also be set.
>
> +        - KVM_ARM_VCPU_SPE: Enable SPE for the CPU. Depends on KVM_CAP_ARM_SPE.

Super nitty, sorry, but it seems other parts of the docs use tabs, whereas this
uses spaces?

> +
>  4.83 KVM_ARM_PREFERRED_TARGET
>  -----------------------------
>
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index f05e6d3f5705..e452da82666d 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -8,11 +8,16 @@
>
>  #ifdef CONFIG_KVM_ARM_SPE
>  bool kvm_supports_spe(void);
> +
> +#define vcpu_has_spe(vcpu)					\
> +	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
>  #else
>  static __always_inline bool kvm_supports_spe(void)
>  {
>  	return false;
>  }
> +
> +#define vcpu_has_spe(vcpu)	false
>  #endif /* CONFIG_KVM_ARM_SPE */
>
>  #endif /* __ARM64_KVM_SPE_H__ */
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 019e5e3d892e..f7f751c00e6f 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -107,6 +107,7 @@ struct kvm_regs {
>  #define KVM_ARM_VCPU_HAS_EL2		7 /* Support nested virtualization */
>  #define KVM_ARM_VCPU_HAS_EL2_E2H0	8 /* Limit NV support to E2H RES0 */
>  #define KVM_ARM_VCPU_PMU_V3_STRICT	9 /* No default PMU creation */
> +#define KVM_ARM_VCPU_SPE		10 /* Support SPE in guest */
>
>  struct kvm_vcpu_init {
>  	__u32 target;
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index eca1184ef9dd..b9cee4cf293d 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -1604,6 +1604,9 @@ static unsigned long system_supported_vcpu_features(void)
>  	if (!cpus_have_final_cap(ARM64_HAS_NESTED_VIRT))
>  		clear_bit(KVM_ARM_VCPU_HAS_EL2, &features);
>
> +	if (!kvm_supports_spe())
> +		clear_bit(KVM_ARM_VCPU_SPE, &features);
> +
>  	return features;
>  }
>
> @@ -1648,6 +1651,10 @@ static int kvm_vcpu_init_check_features(struct kvm_vcpu *vcpu,
>  	if (test_bit(KVM_ARM_VCPU_HAS_EL2, &features))
>  		return -EINVAL;
>
> +	/* SPE is incompatible with AArch32 */
> +	if (test_bit(KVM_ARM_VCPU_SPE, &features))
> +		return -EINVAL;
> +
>  	return 0;
>  }
>
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled
  2026-09-03 16:06 ` [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled Alexandru Elisei
  2026-09-03 16:21   ` sashiko-bot
@ 2026-10-16 14:30   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-16 14:30 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:03PM +0100, Alexandru Elisei wrote:
> For RFC only.
>

I realise this isn't intended for the final series :) but be good to put
something here briefly for reviewers to explain why this is here, e.g. pKVM
needs special attention etc. Similar comment for 9, 10 if there's an RFC
respin.

So for this and patches 9, 10 in general it seems you disable the feature
for:

- pKVM (at global level)
- non-VHE
- nested virt

Are these temporary for proof-of-concept reasons or do you plan to make
them permanent limitations for the initial implementation of SPE?

If the former it'd be good to know what your plans are to provide support
for these, if the latter it seems sensible to put these all in one patch
that describes why you are limiting each.

> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

I won't bother actually reviewing the hacks, so just the queries above for
8, 9, 10. Will resume review at 11 :)

> ---
>  arch/arm64/kvm/spe.c | 4 ++++
>  1 file changed, 4 insertions(+)
>
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index 646806a60d66..97160d1fae81 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -22,6 +22,10 @@ void kvm_spe_add_instance(struct arm_spe_pmu *spe_pmu)
>  {
>  	struct spe_pmu_entry *entry;
>
> +	/* TODO: pKVM support */
> +	if (is_protected_kvm_enabled())
> +		return;
> +
>  	guard(mutex)(&spe_pmus_lock);
>
>  	entry = kmalloc(sizeof(*entry), GFP_KERNEL);
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE
  2026-09-03 16:06 ` [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE Alexandru Elisei
  2026-09-03 16:22   ` sashiko-bot
@ 2026-10-16 14:44   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-16 14:44 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark, Sudeep Holla

On Thu, Sep 03, 2026 at 05:06:06PM +0100, Alexandru Elisei wrote:
> From: Sudeep Holla <sudeep.holla@arm.com>
>
> Add a new VCPU device control group to control various aspects of KVM's SPE
> emulation. Functionality will be added in later patches.

Again fairly thin commit message, be useful to provide more details.

I'd also explicitly say you're adding stubs and the foundations for this but the
actual implementation isn't in place yet.

>
> [ Alexandru E: Major rework ]

Generally [] stuff is for small fixups done to a patch, so a major rework isn't
really suited to this form.

The done thing for this kind of thing I think is something like:

Co-developed-by: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Note that you require Sudeep's explicit permission to do this though! :)

>
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Overall this looks obviously correct (TM), so with the commit message +
authorship stuff addressed:

Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number
  2026-09-03 16:06 ` [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number Alexandru Elisei
  2026-09-03 16:27   ` sashiko-bot
@ 2026-10-20 17:20   ` Lorenzo Stoakes (ARM)
  2026-10-28 16:52   ` Suzuki K Poulose
  2 siblings, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-20 17:20 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark, Sudeep Holla

On Thu, Sep 03, 2026 at 05:06:07PM +0100, Alexandru Elisei wrote:
> From: Sudeep Holla <sudeep.holla@arm.com>
>
> Add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_IRQ) to allow the user to set
> the interrupt number for the buffer management interrupt.

I know this is a bit of a theme of the review :P but feels a little short.

Be good to see why you want to do this, broadly what you are changing
(shouldn't duplicate stuff you can see from the diff but something like '

>
> [ Alexandru E: Split from "KVM: arm64: Add a new VCPU device control group
>                for SPE" ]
>
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

I guess similar comment to before - is this a significant change? If so
then probably best to go Co-developed-by route, otherwise not a big deal.

> ---
>  Documentation/virt/kvm/devices/vcpu.rst | 22 +++++++
>  arch/arm64/include/asm/kvm_host.h       |  2 +
>  arch/arm64/include/asm/kvm_spe.h        | 11 ++++
>  arch/arm64/include/uapi/asm/kvm.h       |  1 +
>  arch/arm64/kvm/guest.c                  |  2 +
>  arch/arm64/kvm/spe.c                    | 82 +++++++++++++++++++++++++
>  6 files changed, 120 insertions(+)
>
> diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
> index e0a7fa35dfd2..d30b2f7fec9a 100644
> --- a/Documentation/virt/kvm/devices/vcpu.rst
> +++ b/Documentation/virt/kvm/devices/vcpu.rst
> @@ -307,3 +307,25 @@ From the destination VMM process:
>  ===============================
>
>  :Architectures: ARM64
> +
> +5.1 ATTRIBUTE: KVM_ARM_VCPU_SPE_IRQ
> +-----------------------------------
> +
> +:Parameters: in kvm_device_attr.addr the address for the Profiling Buffer
> +             management interrupt number as a pointer to an int
> +
> +Returns:

Was going to say maybe it should be ':Returns:' but actually the doc seems
super inconsistent on this... :P so it doesn't really matter.

> +
> +	 =======  ==========================================================
> +	 -EFAULT  Error accessing the buffer management interrupt number
> +	 -EINVAL  Invalid interrupt number or not using an in-kernel irqchip
> +	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set

Seems like a typo, should be KVM_ARM_VCPU_SPE_IRQ right?

> +	 -ENXIO   SPE not supported or not properly configured
> +	 =======  ==========================================================
> +
> +Required.
> +
> +Specifies the Profiling Buffer management interrupt number. The interrupt number
> +must be a PPI and the interrupt number must be the same for each VCPU. Arm
> +recommends 21 as the interrupt number. SPE virtualization requires an in-kernel
> +vGIC implementation.
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index 27fe0cd5b2d7..e7332bd82bf3 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -27,6 +27,7 @@
>  #include <asm/fpsimd.h>
>  #include <asm/kvm.h>
>  #include <asm/kvm_asm.h>
> +#include <asm/kvm_spe.h>
>  #include <asm/vncr_mapping.h>
>
>  #define __KVM_HAVE_ARCH_INTC_INITIALIZED
> @@ -918,6 +919,7 @@ struct kvm_vcpu_arch {
>  	struct vgic_cpu vgic_cpu;
>  	struct arch_timer_cpu timer_cpu;
>  	struct kvm_pmu pmu;
> +	struct kvm_vcpu_spe vcpu_spe;
>
>  	/* vcpu power state */
>  	struct kvm_mp_state mp_state;
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index 1327064d0984..8ded2d821bff 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -6,7 +6,15 @@
>  #ifndef __ARM64_KVM_SPE_H__
>  #define __ARM64_KVM_SPE_H__
>
> +struct kvm;
> +struct kvm_vcpu;
> +struct kvm_device_attr;
> +
>  #ifdef CONFIG_KVM_ARM_SPE
> +struct kvm_vcpu_spe {
> +	int irq_num;		/* Buffer management interrupt number */
> +};

I guess this struct will be expanded over time?

> +
>  bool kvm_supports_spe(void);
>
>  #define vcpu_has_spe(vcpu)					\
> @@ -16,6 +24,9 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>  int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>  int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>  #else

NIT, and probably applicable to a previous patch, but useful to put /*
CONFIG_KVM_ARM_SPE */ there, certainly useful as things grow.

> +struct kvm_vcpu_spe {
> +};
> +

OK good this covers off the !CONFIG_KVM_ARM_SPE case.

>  static __always_inline bool kvm_supports_spe(void)
>  {
>  	return false;
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index e8a68e34be63..b9e5019181eb 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -447,6 +447,7 @@ enum {
>  #define KVM_ARM_VCPU_PVTIME_CTRL	2
>  #define   KVM_ARM_VCPU_PVTIME_IPA	0
>  #define KVM_ARM_VCPU_SPE_CTRL		3
> +#define   KVM_ARM_VCPU_SPE_IRQ		0
>
>  /* KVM_IRQ_LINE irq field index values */
>  #define KVM_ARM_IRQ_VCPU2_SHIFT		28
> diff --git a/arch/arm64/kvm/guest.c b/arch/arm64/kvm/guest.c
> index 56c5f937fcdb..f953b235b458 100644
> --- a/arch/arm64/kvm/guest.c
> +++ b/arch/arm64/kvm/guest.c
> @@ -920,7 +920,9 @@ int kvm_arm_vcpu_arch_set_attr(struct kvm_vcpu *vcpu,
>  		ret = kvm_arm_pvtime_set_attr(vcpu, attr);
>  		break;
>  	case KVM_ARM_VCPU_SPE_CTRL:
> +		mutex_lock(&vcpu->kvm->arch.config_lock);
>  		ret = kvm_spe_set_attr(vcpu, attr);
> +		mutex_unlock(&vcpu->kvm->arch.config_lock);
>  		break;
>  	default:
>  		ret = -ENXIO;
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index fe45ad4c703a..dd5f80318ba4 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -59,17 +59,99 @@ bool kvm_supports_spe(void)
>  	return !list_empty(&spe_pmus);
>  }
>
> +static bool kvm_spe_irq_is_valid(struct kvm *kvm, int irq)
> +{
> +	struct kvm_vcpu *vcpu;
> +	struct kvm_vcpu_spe *vcpu_spe;
> +	unsigned long i;
> +

Maybe worth a small comment:

	/* Every vCPU must have either the same or no IRQ set. */

> +	kvm_for_each_vcpu(i, vcpu, kvm) {
> +		vcpu_spe = &vcpu->arch.vcpu_spe;
> +
> +		if (!vcpu_spe->irq_num)
> +			continue;
> +
> +		if (vcpu_spe->irq_num != irq)
> +			return false;
> +	}
> +
> +	return true;
> +}
> +

>  int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  {
> +	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
> +	struct kvm *kvm = vcpu->kvm;
> +
> +	lockdep_assert_held(&kvm->arch.config_lock);
> +
> +	if (!vcpu_has_spe(vcpu))
> +		return -ENODEV;
> +
> +	switch (attr->attr) {
> +	case KVM_ARM_VCPU_SPE_IRQ: {
> +		int __user *uaddr = (int __user *)(long)attr->addr;
> +		int irq;
> +
> +		if (!irqchip_in_kernel(kvm))
> +			return -EINVAL;
> +
> +		if (get_user(irq, uaddr))
> +			return -EFAULT;
> +
> +		if (!irq_is_ppi(kvm, irq))
> +			return -EINVAL;
> +
> +		if (!kvm_spe_irq_is_valid(kvm, irq))
> +			return -EINVAL;

What about changing the IRQ for vcpu? kvm_spe_irq_is_valid() will abort
even if only the current vCPU has an IRQ set.

Also what about the case of setting the current vCPU's IRQ to the same
value? Should that be -EBUSY?

PMU seems to do something like that, so maybe worth doing that for
consistency? Not exactly a huge thing, that though!

Also looking at PMU there's:

	/* On GICv5, the PMUIRQ is architecturally mandated to be PPI 23 */
	if (vgic_is_v5(kvm) && irq != KVM_ARMV8_PMU_GICV5_IRQ)
		return false;

Is that something you have to worry about for SPE?


> +
> +		vcpu_spe->irq_num = irq;
> +		return 0;
> +	}
> +	}
> +
>  	return -ENXIO;
>  }
>
>  int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  {
> +	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
> +	struct kvm *kvm = vcpu->kvm;
> +
> +	if (!vcpu_has_spe(vcpu))
> +		return -ENODEV;
> +
> +	switch (attr->attr) {
> +	case KVM_ARM_VCPU_SPE_IRQ: {
> +		int __user *uaddr = (int __user *)(long)attr->addr;
> +		int irq;
> +
> +		if (!irqchip_in_kernel(kvm))
> +			return -EINVAL;
> +
> +		if (!vcpu_spe->irq_num)
> +			return -ENXIO;
> +
> +		irq = vcpu_spe->irq_num;
> +		if (put_user(irq, uaddr))
> +			return -EFAULT;
> +
> +		return 0;
> +	}
> +	}
> +
>  	return -ENXIO;
>  }
>
>  int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  {
> +	if (!vcpu_has_spe(vcpu))
> +		return -ENODEV;
> +
> +	switch(attr->attr) {
> +	case KVM_ARM_VCPU_SPE_IRQ:
> +		return 0;
> +	}
> +
>  	return -ENXIO;
>  }
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device
  2026-09-03 16:06 ` [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device Alexandru Elisei
  2026-09-03 16:39   ` sashiko-bot
@ 2026-10-28 13:11   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-28 13:11 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:08PM +0100, Alexandru Elisei wrote:
> To support systems where there is more than one SPE instance, or where not
> all the PEs have SPE, add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_ID) for
> userspace to set the SPE instance it wants the virtual machine to use.
> Similar to the PMU, it is entirely up to userspace to make sure the VCPUs
> are run only on the physical CPUs which share this SPE instance.

OK but this is enforced by KVM right? So while it's up to userspace to
ensure affinity, this is checked for and if the user gets it wrong KVM will
bail AFAICT?

So I'd probably add something here about what happens if you don't do this
- i.e. 'this is checked on vCPU load via kvm_arch_vcpu_load(), and if the
CPU does not share the SPE instance specified, KVM will eventually error
out to userspace.'

>
> If the ioctl is called for multiple VCPUs, userspace must use the same
> SPE identifier for each of the VCPUs.
>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
> ---
>  Documentation/virt/kvm/devices/vcpu.rst | 32 +++++++++
>  arch/arm64/include/asm/kvm_host.h       |  4 ++
>  arch/arm64/include/asm/kvm_spe.h        | 19 ++++++
>  arch/arm64/include/uapi/asm/kvm.h       |  1 +
>  arch/arm64/kvm/arm.c                    | 43 ++++++++++++
>  arch/arm64/kvm/pmu-emul.c               | 11 ++-
>  arch/arm64/kvm/spe.c                    | 90 +++++++++++++++++++++++++
>  7 files changed, 198 insertions(+), 2 deletions(-)
>
> diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
> index d30b2f7fec9a..d60dd1daa67f 100644
> --- a/Documentation/virt/kvm/devices/vcpu.rst
> +++ b/Documentation/virt/kvm/devices/vcpu.rst
> @@ -329,3 +329,35 @@ Specifies the Profiling Buffer management interrupt number. The interrupt number
>  must be a PPI and the interrupt number must be the same for each VCPU. Arm
>  recommends 21 as the interrupt number. SPE virtualization requires an in-kernel
>  vGIC implementation.
> +
> +5.2 ATTRIBUTE: KVM_ARM_VCPU_SPE_ID
> +------------------------------------------
> +
> +:Parameters: in kvm_device_attr.addr the address to an int representing the SPE
> +             identifier.
> +
> +:Returns:

Hmm this is a nit obviously but this is inconsistent from the previous doc
edit which uses 'Returns:' so I think better to choose one and stick with
it :)

> +
> +	 =======  =========================================================
> +	 -EBUSY   Virtual machine has already run
> +	 -EFAULT  Error accessing the SPE identifier
> +	 -EINVAL  A different SPE instance already assigned, or no physical
> +                  CPUs found

With the introduction of kvm_update_supported_cpus() and PMU also using
that, I think you need to add an -EINVAL line to the PMU doc entry too.

> +	 -ENXIO   SPE not supported or not properly configured, or the SPE
> +                  module not loaded
> +	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set

Should be KVM_ARM_VCPU_SPE? Looking back I think you also typo'd this in
the previous commit? Very easily done obviously :P

> +	 =======  =========================================================
> +
> +Required.
> +
> +Request that the VCPU uses the specified SPE instance. The SPE identifier can be
> +read from the 'type' file for the desired SPE instance under /sys/devices (or,
> +equivalent, /sys/bus/event_source). Must be set for at least one VCPU, in which
> +case all the other VCPUs will use the same SPE instance. Once a SPE instance has
> +been set, attempting to set a different one will result in an error.
> +

I think you need a paragraph here to explain that you must ensure that
physical CPUs assigned to vCPUs must share the same SPE instance, and
failing to do so will result in KVM returning an error when the vCPU is
attempted to be run.

> +Similar to KVM_ARM_VCPU_PMU_V3_CTRL(KVM_ARM_VCPU_PMU_SET_PMU), userspace is
> +responsible for making sure that the VCPU is run only on physical CPUs which
> +share the specified SPE instance. If the VCPU has a PMU assigned, and there are

I think the 'userspace is responsible for making sure that the VCPU is run
only on physical CPUs which share the specified SPE instance' bit can be
hoisted into the suggested paragraph above, leaving this one to focus on
SPE/PMU interactions.

And I think the SPE-specific bit shouldn't mention a similarity to PMU, I
think that's confusing in itself.

> +no physical CPUs which share the same SPE and PMU instance, an error is
> +returned.

This is overall unclear I think. It starts with 'similar to PMU' then
mentions an actual interaction between PMU/SPE. So, as above, I suggest
separating these two out.

And here I think you should be a lot clearer about that PMU/SPE
interaction, something like:

"Both SPE and PMU restrict supported physical CPUs to the subset which
share the selected SPE or PMU instance.

If both are selected, the subsets must overlap, otherwise there would be no
physical CPU on which the vCPUs could run.

If this happens, whichever of KVM_ARM_VCPU_SPE_ID or
KVM_ARM_VCPU_PMU_V3_SET_PMU specified it returns -EINVAL.

Note that the default PMU selected by KVM is not taken into account by this
check, it only applies when a specific PMU instance has been selected via
KVM_ARM_VCPU_PMU_V3_SET_PMU."

> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index e7332bd82bf3..10fa223bdbda 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -382,6 +382,7 @@ struct kvm_arch {
>  	 */
>  	unsigned long *pmu_filter;
>  	struct arm_pmu *arm_pmu;
> +	struct kvm_spe kvm_spe;

I think we can do away with the kvm_ prefix on the value name?

>
>  	cpumask_var_t supported_cpus;
>
> @@ -1499,6 +1500,9 @@ void kvm_vcpu_put_vhe(struct kvm_vcpu *vcpu);
>  int __init kvm_set_ipa_limit(void);
>  u32 kvm_get_pa_bits(struct kvm *kvm);
>
> +int kvm_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus,
> +			      cpumask_t *spe_cpus);
> +
>  #define __KVM_HAVE_ARCH_VM_ALLOC
>  struct kvm *kvm_arch_alloc_vm(void);
>
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index 8ded2d821bff..b8afb419c851 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -11,6 +11,11 @@ struct kvm_vcpu;
>  struct kvm_device_attr;
>
>  #ifdef CONFIG_KVM_ARM_SPE
> +struct arm_spe_pmu;
> +struct kvm_spe {
> +	struct arm_spe_pmu *spe_pmu;

I feel like 'spe_pmu' as a name is pretty confusing as there is PMU as
regards the perf driver thing, which SPE is implemented as, and also the
general PMUv3 architecture :)

Maybe the _pmu suffix makes sense then on the type (which has to be a
performance monitoring unit driver), but could we replace '_pmu' with
'_instance' in the field, etc. names?

> +};

This looks a little silly as a one field struct, but presumably because you
add more later. I think worth saying so in the commit message?

> +
>  struct kvm_vcpu_spe {
>  	int irq_num;		/* Buffer management interrupt number */
>  };
> @@ -20,10 +25,17 @@ bool kvm_supports_spe(void);
>  #define vcpu_has_spe(vcpu)					\
>  	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
>
> +void kvm_spe_destroy_vm(struct kvm *kvm);

This naming feels weird, reads to me like 'spe, destroy the vm' which is
probably not the intent :P

Maybe just kvm_spe_destroy()?

> +
>  int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>  int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>  int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
> +
> +bool kvm_spe_pmu_set(struct kvm *kvm);
>  #else
> +struct kvm_spe {
> +};
> +
>  struct kvm_vcpu_spe {
>  };
>
> @@ -34,6 +46,9 @@ static __always_inline bool kvm_supports_spe(void)
>
>  #define vcpu_has_spe(vcpu)	false
>
> +static inline void kvm_spe_destroy_vm(struct kvm *kvm)
> +{
> +}
>  static inline int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  {
>  	return -ENXIO;
> @@ -46,6 +61,10 @@ static inline int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr
>  {
>  	return -ENXIO;
>  }
> +static inline bool kvm_spe_pmu_set(struct kvm *kvm)
> +{
> +	return false;
> +}
>  #endif /* CONFIG_KVM_ARM_SPE */
>
>  #endif /* __ARM64_KVM_SPE_H__ */
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index b9e5019181eb..8ffa3e89d38c 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -448,6 +448,7 @@ enum {
>  #define   KVM_ARM_VCPU_PVTIME_IPA	0
>  #define KVM_ARM_VCPU_SPE_CTRL		3
>  #define   KVM_ARM_VCPU_SPE_IRQ		0
> +#define   KVM_ARM_VCPU_SPE_ID		1
>
>  /* KVM_IRQ_LINE irq field index values */
>  #define KVM_ARM_IRQ_VCPU2_SHIFT		28
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 763b102dc51f..132df44c3088 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -336,6 +336,8 @@ void kvm_arch_destroy_vm(struct kvm *kvm)
>  	kfree(kvm->arch.sysreg_masks);
>  	kvm_destroy_vcpus(kvm);
>
> +	kvm_spe_destroy_vm(kvm);
> +
>  	kvm_unshare_hyp(kvm, kvm + 1);
>
>  	kvm_arm_teardown_hypercalls(kvm);
> @@ -612,6 +614,47 @@ void kvm_arch_vcpu_unblocking(struct kvm_vcpu *vcpu)
>
>  }
>
> +int kvm_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus,
> +			      cpumask_t *spe_cpus)

This interface is really awkward - it's unclear whether you need to pass
one + other NULL, or both, and what happens in each situation.

And it turns out that actually you should only specify one of them with the
other NULL as it stands.

It seems to me SPE vs. PMU is just immaterial here, and you should simply
have:

int kvm_restrict_supported_cpus(struct kvm *kvm, const cpumask_t *mask)
{
	lockdep_assert_held(&kvm->arch.config_lock);

	if (!cpumask_intersects(kvm->arch.supported_cpus, mask))
		return -EINVAL;

	cpumask_and(kvm->arch.supported_cpus, kvm->arch.supported_cpus, mask);
	return 0;
}

Then all the complexity goes away.

I think it's then simplest to simply disallow re-selection for both SPE and
PMU, returning -EBUSY if they're already selected.

That way it's very clear-cut - you set these once and only once.

You have kvm_spe_pmu_set() already. However that name is also a bit
confusing, since maybe it means 'set it' or refers to its CPU subset or
something. Also I dislike 'pmu' in there as mentioned before.

So that should probably be renamed to kvm_spe_has_instance()?

And to handle the PMU side, introduce KVM_ARCH_FLAG_PMU_SELECTED in
kvm->arch.flags and set this when PMU is selected.

Then add kvm_pmu_has_instance() which tests for this flag.

That way everything is consistent and simple.

> +int kvm_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus,
> +			      cpumask_t *spe_cpus)

> +{
> +	cpumask_t *supported_cpus;

I see the bot has already reported about this, but supported_cpus should be
a cpumask_var_t (which is cpumask_t * if CONFIG_CPUMASK_OFFSTACK, but a
1-element array if not), as zalloc_cpumask_var() accepts a pointer of this
type (cpumask_t ** or cpumask_t (*)[1]).

I think if !CONFIG_CPUMASK_OFFSTACK that's actually a compile error.

So fix is:

	cpumask_var_t supported_cpus;

But as per above, I think better to just radically simplify this function.

> +	cpumask_t *cpus;
> +	int ret;
> +
> +	if (!zalloc_cpumask_var(&supported_cpus, GFP_KERNEL_ACCOUNT))
> +		return -ENOMEM;
> +
> +	cpumask_copy(supported_cpus, kvm->arch.supported_cpus);
> +
> +	lockdep_assert_held(&kvm->arch.config_lock);
> +
> +	ret = -EINVAL;
> +	/* Cannot update for SPE and PMU at the same time. */
> +	if (WARN_ON_ONCE(pmu_cpus && spe_cpus))
> +		goto out;
> +
> +	if (pmu_cpus)
> +		cpus = pmu_cpus;
> +	else
> +		cpus = spe_cpus;
> +
> +	if ((spe_cpus && kvm->arch.arm_pmu) ||
> +	    (pmu_cpus && kvm_spe_pmu_set(kvm)))
> +		cpumask_and(supported_cpus, cpus, supported_cpus);
> +	else
> +		cpumask_copy(supported_cpus, cpus);
> +
> +	if (cpumask_empty(supported_cpus))
> +		goto out;
> +
> +	cpumask_copy(kvm->arch.supported_cpus, supported_cpus);
> +	ret = 0;
> +out:
> +	free_cpumask_var(supported_cpus);
> +
> +	return ret;
> +}

See above re: significant simplification of this.

> +
>  static void vcpu_set_pauth_traps(struct kvm_vcpu *vcpu)
>  {
>  	if (vcpu_has_ptrauth(vcpu) && !is_protected_kvm_enabled()) {
> diff --git a/arch/arm64/kvm/pmu-emul.c b/arch/arm64/kvm/pmu-emul.c
> index 5b1af7e2176f..59930e1bf357 100644
> --- a/arch/arm64/kvm/pmu-emul.c
> +++ b/arch/arm64/kvm/pmu-emul.c
> @@ -1085,6 +1085,11 @@ int kvm_arm_set_default_pmu(struct kvm *kvm)
>  	return 0;
>  }
>
> +static int kvm_pmu_update_supported_cpus(struct kvm *kvm, cpumask_t *pmu_cpus)
> +{
> +	return kvm_update_supported_cpus(kvm, pmu_cpus, NULL);
> +}

Again the naming bites again here :) kvm_spe_pmu and kvm_pmu are confusing.

Anyway, I think with the suggested changes above we can do away with this
wrapper, like (untested):

--- a/arch/arm64/kvm/pmu-emul.c
+++ b/arch/arm64/kvm/pmu-emul.c
@@ kvm_arm_pmu_v3_set_pmu()
                if (arm_pmu->pmu.type == pmu_id) {
-                       if (kvm_vm_has_ran_once(kvm) ||
-                           (kvm->arch.pmu_filter && kvm->arch.arm_pmu != arm_pmu)) {
-                               ret = -EBUSY;
-                               break;
-                       }
-
-                       ret = kvm_pmu_update_supported_cpus(kvm, &arm_pmu->supported_cpus);
+                       if (kvm_pmu_has_instance(kvm) && kvm->arch.arm_pmu == arm_pmu)
+                               ret = 0;
+                       else if (kvm_vm_has_ran_once(kvm) || kvm_pmu_has_instance(kvm) ||
+                                (kvm->arch.pmu_filter && kvm->arch.arm_pmu != arm_pmu))
+                               ret = -EBUSY;
+                       else
+                               ret = kvm_restrict_supported_cpus(kvm, &arm_pmu->supported_cpus);
                        if (ret)
                                break;
                        kvm_arm_set_pmu(kvm, arm_pmu);
+                       set_bit(KVM_ARCH_FLAG_PMU_SELECTED, &kvm->arch.flags);

Now this does change the semantics - before a VMM could set PMU A, then
change its mind and set PMU B, and on and on as many times as it likes as long
as no event filter was set, with the last one winning.

After this, you set it once.

I don't imagine any VMM is doing or needs to do the former, and if they are
it's a bug really. So I think it's a valid thing to do, and makes
everything consistent.

The SPE side would be easier:

--- a/arch/arm64/kvm/spe.c
+++ b/arch/arm64/kvm/spe.c
@@ kvm_spe_set_spe_id()
        struct arm_spe_pmu *existing_spe_pmu, *spe_pmu = NULL;
        struct spe_pmu_entry *entry;
+       int err;

        if (kvm_vm_has_ran_once(kvm))
                return -EBUSY;
@@
        existing_spe_pmu = kvm_spe->spe_pmu;
        if (existing_spe_pmu) {
                if (existing_spe_pmu != spe_pmu)
-                       return -EINVAL;
+                       return -EBUSY;
                return 0;
        }

        if (!try_module_get(spe_pmu->pmu.module))
                return -ENXIO;

-       if (kvm_spe_update_supported_cpus(kvm, &spe_pmu->supported_cpus)) {
+       err = kvm_restrict_supported_cpus(kvm, &spe_pmu->supported_cpus);
+       if (err) {
                module_put(spe_pmu->pmu.module);
-               return -EINVAL;
+               return err;
        }

> +
>  static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
>  {
>  	struct kvm *kvm = vcpu->kvm;
> @@ -1104,8 +1109,11 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
>  				break;
>  			}
>
> +			ret = kvm_pmu_update_supported_cpus(kvm, &arm_pmu->supported_cpus);
> +			if (ret)
> +				break;
>  			kvm_arm_set_pmu(kvm, arm_pmu);
> -			cpumask_copy(kvm->arch.supported_cpus, &arm_pmu->supported_cpus);
> +

Looks like a stray extra line here?

>
>  			/*
>  			 * Since a specific PMU is explicitly selected,
> @@ -1117,7 +1125,6 @@ static int kvm_arm_pmu_v3_set_pmu(struct kvm_vcpu *vcpu, int pmu_id)
>  				kvm->arch.pmmir_slots =
>  					FIELD_GET(ARMV8_PMU_SLOTS,
>  						  arm_pmu->reg_pmmir);
> -			ret = 0;

I guess because you assign ret above (and if non-zero bail) you don't need
to reset here, but probably worth keeping just to be really clear as to
intent?

>  			break;
>  		}
>  	}
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index dd5f80318ba4..87fcdbbcbd67 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -59,6 +59,71 @@ bool kvm_supports_spe(void)
>  	return !list_empty(&spe_pmus);
>  }
>
> +void kvm_spe_destroy_vm(struct kvm *kvm)

See above re: naming.

> +{
> +	struct arm_spe_pmu *spe_pmu;
> +
> +	guard(mutex)(&spe_pmus_lock);
> +
> +	spe_pmu = kvm->arch.kvm_spe.spe_pmu;
> +	if (!spe_pmu)
> +		return;
> +
> +	module_put(spe_pmu->pmu.module);

Does spe_pmus_lock (hopefully spe_instances_lock in future :P) need to be
held over module_put()? Seems unnecessary.

> +}
> +
> +bool kvm_spe_pmu_set(struct kvm *kvm)

See comments above re: naming.

> +{
> +	return !!kvm->arch.kvm_spe.spe_pmu;

This !! is unnecessary. The conversion to bool handles it for you.

> +}
> +
> +static int kvm_spe_update_supported_cpus(struct kvm *kvm, cpumask_t *spe_cpus)
> +{
> +	return kvm_update_supported_cpus(kvm, NULL, spe_cpus);
> +}

See above re: the helpers.

> +
> +static int kvm_spe_set_spe_id(struct kvm_vcpu *vcpu, int spe_id)
> +{
> +	struct kvm *kvm = vcpu->kvm;
> +	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
> +	struct arm_spe_pmu *existing_spe_pmu, *spe_pmu = NULL;

Obviously similar comments to above about 'spe_pmu' -> 'spe_instance' :)

> +	struct spe_pmu_entry *entry;
> +
> +	if (kvm_vm_has_ran_once(kvm))
> +		return -EBUSY;
> +
> +	guard(mutex)(&spe_pmus_lock);
> +
> +	list_for_each_entry(entry, &spe_pmus, link) {
> +		if (entry->spe_pmu->pmu.type == spe_id) {
> +			spe_pmu = entry->spe_pmu;
> +			break;
> +		}
> +	}
> +
> +	if (!spe_pmu)
> +		return -ENXIO;
> +
> +	existing_spe_pmu = kvm_spe->spe_pmu;
> +	if (existing_spe_pmu) {
> +		if (existing_spe_pmu != spe_pmu)
> +			return -EINVAL;

This really should be -EBUSY for consistency I think.

See above for suggested change to the code for setting up the CPU mask.

> +		return 0;
> +	}
> +
> +	if (!try_module_get(spe_pmu->pmu.module))
> +		return -ENXIO;
> +
> +	if (kvm_spe_update_supported_cpus(kvm, &spe_pmu->supported_cpus)) {

You're ignoring the error here, better to do:

	int err;
	...
	err = kvm_spe_update_supported_cpus(kvm, &spe_pmu->supported_cpus);
	if (err) {
		...
		return err;
	}

> +		module_put(spe_pmu->pmu.module);
> +		return -EINVAL;
> +	}
> +
> +	kvm_spe->spe_pmu = spe_pmu;
> +
> +	return 0;
> +}
> +
>  static bool kvm_spe_irq_is_valid(struct kvm *kvm, int irq)
>  {
>  	struct kvm_vcpu *vcpu;
> @@ -108,6 +173,15 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  		vcpu_spe->irq_num = irq;
>  		return 0;
>  	}
> +	case KVM_ARM_VCPU_SPE_ID: {
> +		int __user *uaddr = (int __user *)(long)attr->addr;
> +		int spe_id;
> +
> +		if (get_user(spe_id, uaddr))
> +			return -EFAULT;
> +
> +		return kvm_spe_set_spe_id(vcpu, spe_id);
> +	}
>  	}
>
>  	return -ENXIO;
> @@ -117,6 +191,7 @@ int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  {
>  	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
>  	struct kvm *kvm = vcpu->kvm;
> +	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
>
>  	if (!vcpu_has_spe(vcpu))
>  		return -ENODEV;
> @@ -138,6 +213,20 @@ int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>
>  		return 0;
>  	}
> +	case KVM_ARM_VCPU_SPE_ID: {
> +		struct arm_spe_pmu *spe_pmu = kvm_spe->spe_pmu;
> +		int __user *uaddr = (int __user *)(long)attr->addr;
> +		int spe_id;
> +
> +		if (!spe_pmu)
> +			return -ENXIO;
> +
> +		spe_id = spe_pmu->pmu.type;
> +		if (put_user(spe_id, uaddr))
> +			return -EFAULT;
> +
> +		return 0;
> +	}
>  	}
>
>  	return -ENXIO;
> @@ -150,6 +239,7 @@ int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>
>  	switch(attr->attr) {
>  	case KVM_ARM_VCPU_SPE_IRQ:
> +	case KVM_ARM_VCPU_SPE_ID:
>  		return 0;
>  	}
>
> --
> 2.43.0
>
>

Assuming you are on board with the suggested rework, I feel like this patch
needs splitting up into:

1. "KVM: arm64: Add helper to restrict a VM's supported CPUs"

Adds the helper, converts SET_PMU, discuss the change in
behaviour. PMU-only, establishes the foundations.

Update PMU doc to reflect new -EINVAL return.

2. "KVM: arm64: Add SPE VCPU device attribute to set the SPE device"

All the SPE bits.

Does that make sense?

--
Cheers, Lorenzo


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE
  2026-09-03 16:06 ` [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE Alexandru Elisei
  2026-09-03 16:28   ` sashiko-bot
@ 2026-10-28 13:45   ` Lorenzo Stoakes (ARM)
  2026-10-29 18:03   ` Lorenzo Stoakes (ARM)
  2 siblings, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-28 13:45 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark, Sudeep Holla

On Thu, Sep 03, 2026 at 05:06:09PM +0100, Alexandru Elisei wrote:
> Add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_INIT) VCPU ioctl to initialize
> SPE. Initialization must be done exactly once for each VCPU.

Again be good to have a little more in the commit message.

>
> [ Alexandru E: Split from "KVM: arm64: Add a new VCPU device control group
> 	   for SPE" ]
>
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
> ---
>  Documentation/virt/kvm/devices/vcpu.rst | 28 ++++++++++++++++++++---
>  arch/arm64/include/asm/kvm_spe.h        |  6 +++++
>  arch/arm64/include/uapi/asm/kvm.h       |  1 +
>  arch/arm64/kvm/arm.c                    |  6 +++++
>  arch/arm64/kvm/spe.c                    | 30 +++++++++++++++++++++++++
>  5 files changed, 68 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
> index d60dd1daa67f..e2eac48b9d28 100644
> --- a/Documentation/virt/kvm/devices/vcpu.rst
> +++ b/Documentation/virt/kvm/devices/vcpu.rst
> @@ -317,6 +317,7 @@ From the destination VMM process:
>  Returns:
>
>  	 =======  ==========================================================
> +	 -EBUSY   SPE already initialized
>  	 -EFAULT  Error accessing the buffer management interrupt number
>  	 -EINVAL  Invalid interrupt number or not using an in-kernel irqchip
>  	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set
> @@ -338,15 +339,15 @@ vGIC implementation.
>
>  :Returns:

Just highlighting again the very nitty point about consistency between
'Returns:' and ':Returns:' :P.

>
> -	 =======  =========================================================
> -	 -EBUSY   Virtual machine has already run
> +	 =======  ===========================================================
> +	 -EBUSY   Virtual machine has already run, or SPE already initialized
>  	 -EFAULT  Error accessing the SPE identifier
>  	 -EINVAL  A different SPE instance already assigned, or no physical
>                    CPUs found
>  	 -ENXIO   SPE not supported or not properly configured, or the SPE
>                    module not loaded
>  	 -ENODEV  KVM_ARM_VCPU_HAS_SPE VCPU feature not set
> -	 =======  =========================================================
> +	 =======  ===========================================================
>
>  Required.
>
> @@ -361,3 +362,24 @@ responsible for making sure that the VCPU is run only on physical CPUs which
>  share the specified SPE instance. If the VCPU has a PMU assigned, and there are
>  no physical CPUs which share the same SPE and PMU instance, an error is
>  returned.
> +
> +5.3 ATTRIBUTE: KVM_ARM_VCPU_SPE_INIT
> +-----------------------------------

This underline looks short?

> +
> +:Parameters: no additional parameter in kvm_device_attr.addr
> +
> +Returns:
> +
> +	 =======  ============================================
> +	 -EBUSY   SPE already initialized for this VCPU
> +	 -ENXIO   SPE not supported or not properly configured
> +	 =======  ============================================
> +
> +Required.
> +
> +Request initialization of the Statistical Profiling Extension for this VCPU.
> +Must be done last, after SPE has been fully configured for the VCPU, and after
> +the in-kernel irqchip has been initialized.
> +
> +KVM will refuse to run the VCPU and KVM_RUN will return an error if the SPE
> +feature has been set for the VCPU, but SPE hasn't been initialized.
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index b8afb419c851..a6999709b298 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -18,6 +18,7 @@ struct kvm_spe {
>
>  struct kvm_vcpu_spe {
>  	int irq_num;		/* Buffer management interrupt number */
> +	bool initialized;	/* SPE initialized for the VCPU */
>  };
>
>  bool kvm_supports_spe(void);
> @@ -25,6 +26,7 @@ bool kvm_supports_spe(void);
>  #define vcpu_has_spe(vcpu)					\
>  	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
>
> +int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu);
>  void kvm_spe_destroy_vm(struct kvm *kvm);
>
>  int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
> @@ -46,6 +48,10 @@ static __always_inline bool kvm_supports_spe(void)
>
>  #define vcpu_has_spe(vcpu)	false
>
> +static inline int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
> +{
> +	return 0;
> +}
>  static inline void kvm_spe_destroy_vm(struct kvm *kvm)
>  {
>  }
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 8ffa3e89d38c..c695aae484d3 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -449,6 +449,7 @@ enum {
>  #define KVM_ARM_VCPU_SPE_CTRL		3
>  #define   KVM_ARM_VCPU_SPE_IRQ		0
>  #define   KVM_ARM_VCPU_SPE_ID		1
> +#define   KVM_ARM_VCPU_SPE_INIT		2
>
>  /* KVM_IRQ_LINE irq field index values */
>  #define KVM_ARM_IRQ_VCPU2_SHIFT		28
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 132df44c3088..c9a026c61ce7 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -1026,6 +1026,12 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
>  			return ret;
>  	}
>
> +	if (vcpu_has_spe(vcpu)) {
> +		ret = kvm_spe_vcpu_first_run_init(vcpu);
> +		if (ret)
> +			return ret;
> +	}
> +
>  	ret = vgic_v5_finalize_ppi_state(kvm);
>  	if (ret)
>  		return ret;
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index 87fcdbbcbd67..fa87d224c8af 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -59,6 +59,16 @@ bool kvm_supports_spe(void)
>  	return !list_empty(&spe_pmus);
>  }
>
> +int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)

This isn't initialising anything, I guess you plan to build it up over patches
but maybe worth mentioning that in the commit msg?


> +{
> +	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
> +
> +	if (!vcpu_spe->initialized)
> +		return -EINVAL;
> +
> +	return 0;
> +}
> +
>  void kvm_spe_destroy_vm(struct kvm *kvm)
>  {
>  	struct arm_spe_pmu *spe_pmu;
> @@ -147,12 +157,16 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  {
>  	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
>  	struct kvm *kvm = vcpu->kvm;
> +	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
>
>  	lockdep_assert_held(&kvm->arch.config_lock);
>
>  	if (!vcpu_has_spe(vcpu))
>  		return -ENODEV;
>
> +	if (vcpu_spe->initialized)
> +		return -EBUSY;
> +
>  	switch (attr->attr) {
>  	case KVM_ARM_VCPU_SPE_IRQ: {
>  		int __user *uaddr = (int __user *)(long)attr->addr;
> @@ -182,6 +196,21 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>
>  		return kvm_spe_set_spe_id(vcpu, spe_id);
>  	}
> +	case KVM_ARM_VCPU_SPE_INIT:
> +		if (!vcpu_spe->irq_num)
> +			return -ENXIO;
> +
> +		if (!kvm_spe->spe_pmu)
> +			return -ENXIO;
> +
> +		if (!vgic_initialized(kvm))
> +			return -ENXIO;

PMU returns -ENODEV in this case (+ documents it), worth keeping consistent with
that?

E.g.:

	1.2 ATTRIBUTE: KVM_ARM_VCPU_PMU_V3_INIT
	...
	 -ENODEV  PMUv3 not supported or GIC not initialized

> +
> +		if (kvm_vgic_set_owner(vcpu, vcpu_spe->irq_num, vcpu_spe))

This is getting an error then replacing it with -ENXIO, so probably better as:

	int err;
	...
	err = kvm_vgic_set_owner(vcpu, vcpu_spe->irq_num, vcpu_spe);
	if (err)
		return err;

> +			return -ENXIO;
> +
> +		vcpu_spe->initialized = true;
> +		return 0;
>  	}
>
>  	return -ENXIO;
> @@ -240,6 +269,7 @@ int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  	switch(attr->attr) {
>  	case KVM_ARM_VCPU_SPE_IRQ:
>  	case KVM_ARM_VCPU_SPE_ID:
> +	case KVM_ARM_VCPU_SPE_INIT:
>  		return 0;
>  	}
>
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance
  2026-09-03 16:06 ` [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance Alexandru Elisei
  2026-09-03 16:41   ` sashiko-bot
@ 2026-10-28 16:41   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-28 16:41 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:10PM +0100, Alexandru Elisei wrote:
> KVM allows SPE to be virtualized on heterogeneous system, where hardware
> can implement multiple SPE instances for different CPUs, or where SPE
> might even be missing on some of the physical CPUs.
>
> KVM resets the VCPU ID registers following a KVM_ARM_VCPU_INIT ioctl.
> Using the value for ID_AA64DFR0_EL1.PMSVer on the physical CPU where the
> ioctl is executing is unreliable, since userspace might want to later
> assign a different SPE instance, with a different PMSVer value. Worse yet,
> it is be possible that the VCPU reset is being performed on a physical CPU

'it is be' -> 'it is'.

> without SPE. Be consistent and use PMSVer for the SPE instance assigned to
> the VM to initialize ID_AA64DFR0_EL1.PMSVer.
>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Note that this has to be rebased as 2ce80f624032 changed how all this works :)

> ---
>  arch/arm64/include/asm/kvm_spe.h | 10 ++++++++++
>  arch/arm64/kvm/spe.c             | 24 ++++++++++++++++++++++++
>  arch/arm64/kvm/sys_regs.c        |  8 +++++++-
>  3 files changed, 41 insertions(+), 1 deletion(-)
>
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index a6999709b298..4bae3e6b05c8 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -29,11 +29,14 @@ bool kvm_supports_spe(void);
>  int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu);
>  void kvm_spe_destroy_vm(struct kvm *kvm);
>
> +void kvm_spe_finalize_idregs(struct kvm *kvm);
> +
>  int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>  int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>  int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>
>  bool kvm_spe_pmu_set(struct kvm *kvm);
> +u8 kvm_spe_get_pmsver(struct kvm *kvm);

Hmm, the field it returns is u16?

	u16					pmsver;

>  #else
>  struct kvm_spe {
>  };
> @@ -55,6 +58,9 @@ static inline int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
>  static inline void kvm_spe_destroy_vm(struct kvm *kvm)
>  {
>  }
> +static inline void kvm_spe_finalize_idregs(struct kvm *kvm)
> +{
> +}
>  static inline int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  {
>  	return -ENXIO;
> @@ -71,6 +77,10 @@ static inline bool kvm_spe_pmu_set(struct kvm *kvm)
>  {
>  	return false;
>  }
> +static inline u8 kvm_spe_get_pmsver(struct kvm *kvm)
> +{
> +	return 0;
> +}
>  #endif /* CONFIG_KVM_ARM_SPE */
>
>  #endif /* __ARM64_KVM_SPE_H__ */
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index fa87d224c8af..4426d614a9e0 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -87,6 +87,30 @@ bool kvm_spe_pmu_set(struct kvm *kvm)
>  	return !!kvm->arch.kvm_spe.spe_pmu;
>  }
>
> +void kvm_spe_finalize_idregs(struct kvm *kvm)
> +{
> +	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
> +	u64 reg;
> +
> +	if (!spe_pmu)
> +		return;
> +
> +	reg = kvm_read_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1);
> +	reg &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
> +	reg |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer, spe_pmu->pmsver);
> +	kvm_set_vm_id_reg(kvm, SYS_ID_AA64DFR0_EL1, reg);
> +}

I know sashiko raised something about this, and mostly you're right to dismiss
those, but I think one issue remains here.

Consider:

1. KVM_ARCH_VCPU_INIT

__kvm_vcpu_set_target()
  -> kvm_reset_vcpu()
    -> ...
      -> __kvm_read_sanitised_id_reg()
        -> sanitise_id_aa64dfr0_el1()
          -> kvm_spe_get_pmsver()
	    No instance assigned yet

	    SET: PMSVer = 0

2. KVM_SET_DEVICE_ATTR(KVM_ARM_VCPU_SPE_ID)

kvm_arm_vcpu_arch_set_attr()
  -> kvm_spe_set_attr()
    -> kvm_spe_set_spe_id()
       Nothing updates the register.

       SET: kvm_spe->spe_pmu = spe_pmu

3. KVM_GET_ONE_REG(ID_AA64DFR0_EL1)

get_id_reg()
  -> read_id_reg()
    -> kvm_read_vm_id_reg()

       RETURNS: PMSVer == 0

4. KVM_SET_ONE_REG(ID_AA64DFR0_EL1) with val from 3 (=0)

set_id_aa64dfr0_el1()
  -> set_id_reg()
    -> arm64_check_features()
       u64 limit = rd->reset(vcpu, rd);
         -> ...
           -> [ same sanitise hook as 1 ]
              kvm_spe->spe_pmu now assigned.

       limit = N (!!)

       Fails, because:

            if ((val & ~mask) != (limit & ~mask))
                return -E2BIG;

       PMSVer isn't in the writable mask, val from step 3 is 0, limit
       is N.

So I think the right solution is to call kvm_spe_finalize_idregs() in
kvm_spe_set_spe_id(), after setting kvm_spe->spe_pmu = spe_pmu.

That way steps 3 and 4 above work, and step 2, which sets the SPE ID,
also sets PMSVer.

> +
> +u8 kvm_spe_get_pmsver(struct kvm *kvm)
> +{
> +	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;

NIT: could be const?

> +
> +	if (!spe_pmu)
> +		return 0;
> +
> +	return spe_pmu->pmsver;
> +}
> +
>  static int kvm_spe_update_supported_cpus(struct kvm *kvm, cpumask_t *spe_cpus)
>  {
>  	return kvm_update_supported_cpus(kvm, NULL, spe_cpus);
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index 44aae52c473d..a6bb6884b965 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -2220,8 +2220,11 @@ static u64 sanitise_id_aa64dfr0_el1(const struct kvm_vcpu *vcpu, u64 val)
>  		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMUVer,
>  				      kvm_arm_pmu_get_pmuver_limit());
>
> -	/* Hide SPE from guests */
> +	/* Only initialize the SPE version if the vCPU has the feature. */
>  	val &= ~ID_AA64DFR0_EL1_PMSVer_MASK;
> +	if (vcpu_has_spe(vcpu))
> +		val |= SYS_FIELD_PREP(ID_AA64DFR0_EL1, PMSVer,
> +				      kvm_spe_get_pmsver(vcpu->kvm));
>
>  	/* Hide BRBE from guests */
>  	val &= ~ID_AA64DFR0_EL1_BRBE_MASK;
> @@ -5928,6 +5931,9 @@ int kvm_finalize_sys_regs(struct kvm_vcpu *vcpu)
>  		kvm_vgic_finalize_idregs(kvm);
>  	}
>
> +	if (vcpu_has_spe(vcpu))
> +		kvm_spe_finalize_idregs(kvm);

kvm_spe_finalize_idregs() already exits if !spe_pmu, so this could be
invoked unconditionally?

> +
>  	return 0;
>  }
>
> --
> 2.43.0
>
>

--
Cheers, Lorenzo


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number
  2026-09-03 16:06 ` [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number Alexandru Elisei
  2026-09-03 16:27   ` sashiko-bot
  2026-10-20 17:20   ` Lorenzo Stoakes (ARM)
@ 2026-10-28 16:52   ` Suzuki K Poulose
  2 siblings, 0 replies; 87+ messages in thread
From: Suzuki K Poulose @ 2026-10-28 16:52 UTC (permalink / raw)
  To: Alexandru Elisei, maz, oupton, fuad.tabba, joey.gouly, seiden,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark
  Cc: Sudeep Holla

On 03/09/2026 17:06, Alexandru Elisei wrote:
> From: Sudeep Holla <sudeep.holla@arm.com>
> 
> Add KVM_ARM_VCPU_SPE_CTRL(KVM_ARM_VCPU_SPE_IRQ) to allow the user to set
> the interrupt number for the buffer management interrupt.
> 
> [ Alexandru E: Split from "KVM: arm64: Add a new VCPU device control group
>                 for SPE" ]
> 
> Signed-off-by: Sudeep Holla <sudeep.holla@arm.com>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>
> ---

...

> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index fe45ad4c703a..dd5f80318ba4 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -59,17 +59,99 @@ bool kvm_supports_spe(void)
>   	return !list_empty(&spe_pmus);
>   }
>   

...

>   int kvm_spe_get_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>   {
> +	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
> +	struct kvm *kvm = vcpu->kvm;
> +
> +	if (!vcpu_has_spe(vcpu))
> +		return -ENODEV;
> +
> +	switch (attr->attr) {
> +	case KVM_ARM_VCPU_SPE_IRQ: {
> +		int __user *uaddr = (int __user *)(long)attr->addr;
> +		int irq;
> +
> +		if (!irqchip_in_kernel(kvm))
> +			return -EINVAL;
> +
> +		if (!vcpu_spe->irq_num)
> +			return -ENXIO;
> +
> +		irq = vcpu_spe->irq_num;
> +		if (put_user(irq, uaddr))
> +			return -EFAULT;
> +
> +		return 0;
> +	}
> +	}
> +
>   	return -ENXIO;
>   }
>   
>   int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>   {
> +	if (!vcpu_has_spe(vcpu))
> +		return -ENODEV;
> +
> +	switch(attr->attr) {
> +	case KVM_ARM_VCPU_SPE_IRQ:
> +		return 0;

Should we gate this too on irqchip_in_kernel(vcpu->kvm) ? Otherwise
we would advertise to the userspace that it has the SPE_IRQ, but
would return -EINVAL when we try get_attr if the irqchip is not
in kernel.

Also, do we need to mandate the ordering of the SPE configuration on
VGIC ?

Cheers
Suzuki


> +	}
> +
>   	return -ENXIO;
>   }


^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context
  2026-09-03 16:06 ` [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context Alexandru Elisei
  2026-09-03 16:32   ` sashiko-bot
@ 2026-10-29 15:42   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-29 15:42 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:11PM +0100, Alexandru Elisei wrote:
> Add the SPE registers to the VCPU context. Guest writes to the service bit,
> which change the state of the SPE maintenance interrupt, will be handled in
> a subsequent patch.
>
> Only the buffer registers are trapped, because KVM will need to keep track

Maybe worth saying which registers for ease?

Is this currently true as of this commit message? As this it is this commit
that's introducing these registers and I don't see anything that enforces
this?

If not, maybe worth saying something like 'ultimately, only the buffer registers
will be trapped. A warning is raised if a trap occurs for

> of the buffer enabled state for dirty page logging, errata handling and to
> sanitise writes to PMBLIMITR_EL1 based on the features exposed to the VM.

I think the commit message needs detail on what each of these means.

I think a key aspect of commit messages is to orient the reader in what the
series is trying to achieve - the code already tells you WHAT is going on but
the WHY and what foundations this patch establishes, etc. are not things that
people can automatically derive from the code.

In this case for instance, coming fresh to it - I wonder 'how does this interact
with dirty page logging, errata handling and santising writes to PMBLIMITR_EL1?
And why does SPE need to touch these things?' etc.

So I think thinking in those terms - giving context and orientating people
really helps drive a good commit message :)

I guess this is a long-winded way of suggesting that you should describe what
teach of these is needed for and how it influenced what you do here :P

>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Logic looks good here, but various nits and thoughts, and obviously the bot
raised a couple of things also.

> ---
>  arch/arm64/include/asm/kvm_host.h | 13 +++++++
>  arch/arm64/include/asm/kvm_spe.h  | 11 ++++++
>  arch/arm64/kvm/spe.c              | 29 ++++++++++++++++
>  arch/arm64/kvm/sys_regs.c         | 56 ++++++++++++++++++++++++-------
>  4 files changed, 96 insertions(+), 13 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h
> index 10fa223bdbda..b92b19229c36 100644
> --- a/arch/arm64/include/asm/kvm_host.h
> +++ b/arch/arm64/include/asm/kvm_host.h
> @@ -483,6 +483,19 @@ enum vcpu_sysreg {
>  	PMOVSSET_EL0,	/* Overflow Flag Status Set Register */
>  	PMUSERENR_EL0,	/* User Enable Register */
>
> +	/* SPE registers */
> +	PMSCR_EL1,
> +	PMSNEVFR_EL1,
> +	PMSICR_EL1,
> +	PMSIRR_EL1,
> +	PMSFCR_EL1,
> +	PMSEVFR_EL1,
> +	PMSLATFR_EL1,
> +	PMBLIMITR_EL1,
> +	PMBPTR_EL1,
> +	PMBSR_EL1,
> +	PMSDSFR_EL1,

OK so this is establishing the registers that SPE uses.

> +
>  	/* Pointer Authentication Registers in a strict increasing order. */
>  	APIAKEYLO_EL1,
>  	APIAKEYHI_EL1,
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index 4bae3e6b05c8..56dc9e660bef 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -37,6 +37,9 @@ int kvm_spe_has_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr);
>
>  bool kvm_spe_pmu_set(struct kvm *kvm);
>  u8 kvm_spe_get_pmsver(struct kvm *kvm);
> +
> +bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val);
> +u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg);
>  #else
>  struct kvm_spe {
>  };
> @@ -81,6 +84,14 @@ static inline u8 kvm_spe_get_pmsver(struct kvm *kvm)
>  {
>  	return 0;
>  }
> +static inline bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
> +{
> +	return true;
> +}
> +static inline u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
> +{
> +	return 0;
> +}
>  #endif /* CONFIG_KVM_ARM_SPE */
>
>  #endif /* __ARM64_KVM_SPE_H__ */
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index 4426d614a9e0..6a00a44c73ed 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -7,6 +7,7 @@
>  #include <linux/kvm_host.h>
>  #include <linux/perf/arm_spe_pmu.h>
>
> +#include <asm/kvm_emulate.h>
>  #include <asm/kvm_spe.h>
>  #include <asm/sysreg.h>
>
> @@ -82,6 +83,34 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
>  	module_put(spe_pmu->pmu.module);
>  }
>
> +bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
> +{
> +	switch (reg) {
> +	case PMBLIMITR_EL1:
> +	case PMBSR_EL1:
> +	case PMBPTR_EL1:
> +		__vcpu_assign_sys_reg(vcpu, reg, val);
> +		break;
> +	default:
> +		WARN_ON_ONCE("unexpected trap");

I see the bot already raised this :)

> +	}
> +
> +	return true;

Hmm, this returns true unconditionally, should it be a void?

> +}
> +
> +u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
> +{
> +	switch (reg) {
> +	case PMBLIMITR_EL1:
> +	case PMBSR_EL1:
> +	case PMBPTR_EL1:
> +		return __vcpu_sys_reg(vcpu, reg);

OK so only the buffer registers can be read?

> +	default:
> +		WARN_ON_ONCE("unexpected trap");

Obviously also needs a fixup as per bot :P (I'm pretty sure I've made this
exactly same mistake myself quite a few times so don't worry!)

> +		return 0;
> +	}
> +}
> +
>  bool kvm_spe_pmu_set(struct kvm *kvm)
>  {
>  	return !!kvm->arch.kvm_spe.spe_pmu;
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index a6bb6884b965..89b01b6ac783 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -1554,6 +1554,28 @@ static int set_pmcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
>  	return 0;
>  }
>
> +static unsigned int spe_visibility(const struct kvm_vcpu *vcpu,
> +				   const struct sys_reg_desc *r)
> +{
> +	if (vcpu_has_spe(vcpu))
> +		return 0;
> +
> +	return REG_HIDDEN;
> +}

OK so this, I gather, is simply saying 'if the VCPU supports SPE, allow access
to the registers, otherwise don't.

> +
> +static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
> +			   const struct sys_reg_desc *r)
> +{
> +	u64 val = p->regval;
> +	int reg = r->reg;
> +
> +	if (p->is_write)
> +		return kvm_spe_write_sysreg(vcpu, reg, val);
> +
> +	p->regval = kvm_spe_read_sysreg(vcpu, reg);
> +	return true;

NIT: I'm not sure the locals are really needed here, especially since you
reassign p->regval, so I think this would be better as:

static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
			   const struct sys_reg_desc *r)
{
	if (p->is_write)
		return kvm_spe_write_sysreg(vcpu, r->reg, p->regval);

	p->regval = kvm_spe_read_sysreg(vcpu, r->reg);
	return true;
}

> +}
> +
>  /* Silly macro to expand the DBG{BCR,BVR,WVR,WCR}n_EL1 registers in one go */
>  #define DBG_BCR_BVR_WCR_WVR_EL1(n)					\
>  	{ SYS_DESC(SYS_DBGBVRn_EL1(n)),					\
> @@ -1586,6 +1608,14 @@ static int set_pmcr(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
>  	  .reset = reset_pmevtyper,					\
>  	  .access = access_pmu_evtyper, .reg = (PMEVTYPER0_EL0 + n), }
>
> +#define SPE_SYS_REG(name)						\
> +	SYS_DESC(SYS_##name), .reg = name, .access = access_spe_reg,	\
> +	.reset = reset_val, .val = 0, .visibility = spe_visibility
> +
> +#define SPE_UNTRAPPED_REG(name)						\
> +	SYS_DESC(SYS_##name), .reg = name, .access = undef_access,	\
> +	.reset = reset_val, .val = 0, .visibility = spe_visibility

Could the first just be SPE_REG()? 'SYS' seems a bit redundant here.

And these are identical other than access, could you just make this
SPE_REG() with a parameter for visibility?

Actually, it seems like this pattern already exists, e.g. TIMER_REG():

#define TIMER_REG(name, vis)					   \
	SYS_REG_USER_FILTER(name, access_arch_timer, reset_val, 0, \
			    arch_timer_get_user, arch_timer_set_user, vis)

So this could be something like:

#define SPE_REG(name, acc)						\
	SYS_REG_USER_FILTER(name, acc, reset_val, 0, NULL, NULL,	\
			    spe_visibility)

Perhaps?

> +
>  /* Macro to expand the AMU counter and type registers*/
>  #define AMU_AMEVCNTR0_EL0(n) { SYS_DESC(SYS_AMEVCNTR0_EL0(n)), undef_access }
>  #define AMU_AMEVTYPER0_EL0(n) { SYS_DESC(SYS_AMEVTYPER0_EL0(n)), undef_access }
> @@ -3525,19 +3555,19 @@ static const struct sys_reg_desc sys_reg_descs[] = {
>  	{ SYS_DESC(SYS_FAR_EL1), access_vm_reg, reset_unknown, FAR_EL1 },
>  	{ SYS_DESC(SYS_PAR_EL1), NULL, reset_unknown, PAR_EL1 },
>
> -	{ SYS_DESC(SYS_PMSCR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSNEVFR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSICR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSIRR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSFCR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSEVFR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSLATFR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSIDR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMBLIMITR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMBPTR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMBSR_EL1), undef_access },
> -	{ SYS_DESC(SYS_PMSDSFR_EL1), undef_access },
> -	/* PMBIDR_EL1 is not trapped */
> +	{ SPE_UNTRAPPED_REG(PMSCR_EL1) },
> +	{ SPE_UNTRAPPED_REG(PMSNEVFR_EL1) },
> +	{ SPE_UNTRAPPED_REG(PMSICR_EL1) },
> +	{ SPE_UNTRAPPED_REG(PMSIRR_EL1) },
> +	{ SPE_UNTRAPPED_REG(PMSFCR_EL1) },
> +	{ SPE_UNTRAPPED_REG(PMSEVFR_EL1) },
> +	{ SPE_UNTRAPPED_REG(PMSLATFR_EL1) },
> +	{ SYS_DESC(SYS_PMSIDR_EL1), .access = undef_access },
> +	{ SPE_SYS_REG(PMBLIMITR_EL1) },
> +	{ SPE_SYS_REG(PMBPTR_EL1) },
> +	{ SPE_SYS_REG(PMBSR_EL1) },
> +	{ SPE_UNTRAPPED_REG(PMSDSFR_EL1) },
> +	/* PMBIDR_EL1 is not trapped and read-only */
>
>  	{ PMU_SYS_REG(PMINTENSET_EL1),
>  	  .access = access_pminten, .reg = PMINTENSET_EL1,
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes
  2026-09-03 16:06 ` [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes Alexandru Elisei
  2026-09-03 16:37   ` sashiko-bot
@ 2026-10-29 17:23   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-29 17:23 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:12PM +0100, Alexandru Elisei wrote:
> PMBLIMITR_EL1 is special, because it has feature enable fields. If those

OK, so this is one of the buffer regs that traps in KVM right?

Maybe worth explicitly calling that out, just so it's clear.

> features are hidden from a guest, ignore them when the guest or the VMM
> attempt to set them.
>
> The least significant bits of the buffer limit address are RES0 based on
> the smallest granule implemented. Make sure they are treated as zero even
> if the smallest granule exposed to the VM is larger than what the hardware
> implements.
>
> Note that it is up to the user to write to the SPE buffer registers only
> after the VM features have been configured in the VM id registers.
>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Obviously sashiko had some feedback on this also, but a few things to
address here as below! :)

> ---
>  Documentation/virt/kvm/devices/vcpu.rst |  3 ++
>  arch/arm64/include/asm/kvm_spe.h        |  7 +++++
>  arch/arm64/kvm/spe.c                    | 40 +++++++++++++++++++++++--
>  arch/arm64/kvm/sys_regs.c               | 38 +++++++++++++++++++++--
>  4 files changed, 83 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/virt/kvm/devices/vcpu.rst b/Documentation/virt/kvm/devices/vcpu.rst
> index e2eac48b9d28..eb352760869c 100644
> --- a/Documentation/virt/kvm/devices/vcpu.rst
> +++ b/Documentation/virt/kvm/devices/vcpu.rst
> @@ -383,3 +383,6 @@ the in-kernel irqchip has been initialized.
>
>  KVM will refuse to run the VCPU and KVM_RUN will return an error if the SPE
>  feature has been set for the VCPU, but SPE hasn't been initialized.
> +
> +Attempting to access any of the SPE registers until SPE has been initialized on
> +the VCPU will fail with -EBUSY.
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index 56dc9e660bef..3d8e9720daca 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -14,6 +14,7 @@ struct kvm_device_attr;
>  struct arm_spe_pmu;
>  struct kvm_spe {
>  	struct arm_spe_pmu *spe_pmu;
> +	u64 pmblimitr_el1_res0_mask;
>  };
>
>  struct kvm_vcpu_spe {
> @@ -26,6 +27,8 @@ bool kvm_supports_spe(void);
>  #define vcpu_has_spe(vcpu)					\
>  	(vcpu_has_feature(vcpu, KVM_ARM_VCPU_SPE))
>
> +bool kvm_vcpu_spe_initialized(struct kvm_vcpu *vcpu);
> +
>  int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu);
>  void kvm_spe_destroy_vm(struct kvm *kvm);
>
> @@ -54,6 +57,10 @@ static __always_inline bool kvm_supports_spe(void)
>
>  #define vcpu_has_spe(vcpu)	false
>
> +static inline bool kvm_vcpu_spe_initialized(struct kvm_vcpu *vcpu)
> +{
> +	return false;
> +}
>  static inline int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
>  {
>  	return 0;
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index 6a00a44c73ed..8c173a1f48bd 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -60,13 +60,43 @@ bool kvm_supports_spe(void)
>  	return !list_empty(&spe_pmus);
>  }
>
> -int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
> +bool kvm_vcpu_spe_initialized(struct kvm_vcpu *vcpu)
>  {
>  	struct kvm_vcpu_spe *vcpu_spe = &vcpu->arch.vcpu_spe;
>
> -	if (!vcpu_spe->initialized)
> +	return vcpu_spe->initialized;
> +}
> +
> +static void kvm_spe_compute_pmblimitr_el1_res0_mask(struct kvm *kvm)
> +{
> +	u64 res0_mask = GENMASK_ULL(11, 8) | BIT(6) | GENMASK_ULL(4, 3);

Maybe worth a comment saying what you're res0-ing here by default?

> +
> +	if (!kvm_has_feat(kvm, ID_AA64DFR2_EL1, SPE_nVM, IMP))
> +		res0_mask |= PMBLIMITR_EL1_nVM_MASK;

Getting the LLM to dig in a bit on this says:

#define SYS_ID_AA64DFR2_EL1                             sys_reg(3, 0, 0, 5, 2)

Matches:

	ID_FILTERED(ID_AA64DFR0_EL1, id_aa64dfr0_el1, ...)
	ID_SANITISED(ID_AA64DFR1_EL1),
	ID_UNALLOCATED(5,2), <--- here

In sys_regs.c, i.e. not wired up and:

	if (!kvm_has_feat(kvm, ID_AA64DFR2_EL1, SPE_nVM, IMP))

Is always true.

So can this just be something like:

	/*
	 * ID_AA64DFR2_EL1 is not exposed to guests, so FEAT_SPE_nVM is never
	 * enabled.
	 */
	res0_mask |= PMBLIMITR_EL1_nVM_MASK;

Perhaps?

> +
> +	if (!kvm_has_feat_enum(kvm, ID_AA64MMFR0_EL1, TGRAN4, NI))

I was very confused by this btw, but it turns out that TGRAN4 = NOT
supports TGRAN4. But then the 'NI' == not implemented... :)) So ! here
means not not, i.e. does, so nothing to do.

Why the _enum() btw?

Could this just be:

	kvm_has_feat(kvm, ID_AA64MMFR0_EL1, TGRAN4, IMP)

?

> +		goto set_mask;

While it involves more nesting, I think it'd be clearer to just do
something like:

	/* If 4 KB unsupported, extend res0 to 16 KiB or 64 KiB granule size. */
	if (!kvm_has_feat(kvm, ID_AA64MMFR0_EL1, TGRAN4, IMP)) {
		if (kvm_has_feat(kvm, ID_AA64MMFR0_EL1, TGRAN16, IMP))
			res0_mask |= GENMASK_ULL(13, 12);
		else
			res0_mask |= GENMASK_ULL(15, 12);
	}

Are these magic numbers really necessary in general btw? Can you not use
defined values? That would eliminate the need for too many comments too
actually.

If I go off pattern matching I see (the very long line :P):

#define PMBLIMITR_EL1_RES0                              (UL(0) | GENMASK_ULL(11, 8) | GENMASK_ULL(6, 6) | GENMASK_ULL(4, 3))

So:

	u64 res0_mask = GENMASK_ULL(11, 8) | BIT(6) | GENMASK_ULL(4, 3);

Could be:

	u64 res0_mask = PMBLIMITR_EL1_RES0;

And you could do similar things for the rest I think?

Like:

	/* If 4 KB unsupported, extend res0 to 16 KiB or 64 KiB granule size. */
	if (!kvm_has_feat(kvm, ID_AA64MMFR0_EL1, TGRAN4, IMP)) {
		if (kvm_has_feat(kvm, ID_AA64MMFR0_EL1, TGRAN16, IMP))
			res0_mask |= PMBLIMITR_EL1_LIMIT & (SZ_16K - 1);
		else
			res0_mask |= PMBLIMITR_EL1_LIMIT & (SZ_64K - 1);
	}

> +
> +	if (kvm_has_feat(kvm, ID_AA64MMFR0_EL1, TGRAN16, IMP))
> +		res0_mask |= GENMASK_ULL(13, 12);
> +	else
> +		res0_mask |= GENMASK_ULL(15, 12);
> +
> +set_mask:
> +	WRITE_ONCE(kvm->arch.kvm_spe.pmblimitr_el1_res0_mask, res0_mask);

RE: The sashiko thing about WRITE_ONCE()/READ_ONCE() pairing (valid I
think), could you just compute this in kvm_vm_finalize_sys_regs() on first
run, rather than in kvm_spe_vcpu_first_run_init() below?

Then that, under the config lock, will be a single writer with no vCPUs
racing?

> +}
> +
> +int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
> +{
> +	struct kvm *kvm = vcpu->kvm;
> +	struct kvm_spe *kvm_spe = &kvm->arch.kvm_spe;
> +
> +	if (!kvm_vcpu_spe_initialized(vcpu))
>  		return -EINVAL;
>
> +	if (!kvm_spe->pmblimitr_el1_res0_mask)
> +		kvm_spe_compute_pmblimitr_el1_res0_mask(kvm);
> +
>  	return 0;
>  }
>
> @@ -85,8 +115,12 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
>
>  bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
>  {
> +	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
> +
>  	switch (reg) {
>  	case PMBLIMITR_EL1:
> +		val &= ~kvm_spe->pmblimitr_el1_res0_mask;

The bot raised this potentially allowing raw writes I think, as the mask is
only actually set up on first run, so it does seem sensible then I think to
recalculate the value of PMBLIMITR_EL1 at first run time.

(Also obviously sashiko noted the need for a READ_ONCE() of the mask here
also!)

> +		fallthrough;
>  	case PMBSR_EL1:
>  	case PMBPTR_EL1:
>  		__vcpu_assign_sys_reg(vcpu, reg, val);
> @@ -217,7 +251,7 @@ int kvm_spe_set_attr(struct kvm_vcpu *vcpu, struct kvm_device_attr *attr)
>  	if (!vcpu_has_spe(vcpu))
>  		return -ENODEV;
>
> -	if (vcpu_spe->initialized)
> +	if (kvm_vcpu_spe_initialized(vcpu))
>  		return -EBUSY;
>
>  	switch (attr->attr) {
> diff --git a/arch/arm64/kvm/sys_regs.c b/arch/arm64/kvm/sys_regs.c
> index 89b01b6ac783..4c4bf88c9aa5 100644
> --- a/arch/arm64/kvm/sys_regs.c
> +++ b/arch/arm64/kvm/sys_regs.c
> @@ -1563,6 +1563,38 @@ static unsigned int spe_visibility(const struct kvm_vcpu *vcpu,
>  	return REG_HIDDEN;
>  }
>
> +static int set_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
> +			       u64 val)
> +{
> +	int reg = r->reg;
> +
> +	if (!kvm_vcpu_spe_initialized(vcpu))
> +		return -EBUSY;

I know the bot raised this -EBUSY return. Digging into it, it seems it's 4.84
from Documentation/virt/kvm/api.rst, i.e. the KVM_GET_REG_LIST docs:

	This ioctl returns the guest registers that are supported for the
	KVM_GET_ONE_REG/KVM_SET_ONE_REG calls.

You said to sashiko that you update the interrupt level here via a
PMBSR_EL1 write in a later patch, but I think that's questionable in
general.

The semantics seem clear that this should be a plain write, and if it's
sometimes not correct to take a further action (i.e. initialisation has not
completed yet), that implies it should live somewhere else.

So I'd update things to keep this as a plain read/write, and do whatever
else you need to do at the right time, e.g. at first run?

Given that I think the right thing to do here is to remove the checks, I
_think_ everything should still work fine right?

> +
> +	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)
> +		kvm_spe_write_sysreg(vcpu, reg, val);
> +	else
> +		__vcpu_assign_sys_reg(vcpu, reg, val);
> +
> +	return 0;
> +}
> +
> +static int get_user_spe_sysreg(struct kvm_vcpu *vcpu, const struct sys_reg_desc *r,
> +			       u64 *val)
> +{
> +	int reg = r->reg;
> +
> +	if (!kvm_vcpu_spe_initialized(vcpu))
> +		return -EBUSY;

Similar comments here to the above obviously.

> +
> +	if (reg == PMBLIMITR_EL1 || reg == PMBPTR_EL1 || reg == PMBSR_EL1)

Hmm some duplication here, and you do also elsewhere adjust behaviour based
on buffer registers or not, so could this be separated out into a
predicate?

> +		*val = kvm_spe_read_sysreg(vcpu, reg);
> +	else
> +		*val = __vcpu_sys_reg(vcpu, reg);
> +
> +	return 0;
> +}
> +
>  static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
>  			   const struct sys_reg_desc *r)
>  {
> @@ -1610,11 +1642,13 @@ static bool access_spe_reg(struct kvm_vcpu *vcpu, struct sys_reg_params *p,
>
>  #define SPE_SYS_REG(name)						\
>  	SYS_DESC(SYS_##name), .reg = name, .access = access_spe_reg,	\
> -	.reset = reset_val, .val = 0, .visibility = spe_visibility
> +	.reset = reset_val, .val = 0, .visibility = spe_visibility,	\
> +	.set_user = set_user_spe_sysreg, .get_user = get_user_spe_sysreg
>
>  #define SPE_UNTRAPPED_REG(name)						\
>  	SYS_DESC(SYS_##name), .reg = name, .access = undef_access,	\
> -	.reset = reset_val, .val = 0, .visibility = spe_visibility
> +	.reset = reset_val, .val = 0, .visibility = spe_visibility,	\
> +	.set_user = set_user_spe_sysreg, .get_user = get_user_spe_sysreg

Similar comment about the duplication of these as per previous patch review :)

>
>  /* Macro to expand the AMU counter and type registers*/
>  #define AMU_AMEVCNTR0_EL0(n) { SYS_DESC(SYS_AMEVCNTR0_EL0(n)), undef_access }
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS}
  2026-09-03 16:06 ` [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS} Alexandru Elisei
  2026-09-03 16:40   ` sashiko-bot
@ 2026-10-29 17:47   ` Lorenzo Stoakes (ARM)
  1 sibling, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-29 17:47 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark

On Thu, Sep 03, 2026 at 05:06:13PM +0100, Alexandru Elisei wrote:
> KVM's FGU mechanism will trap the registers introduced by FEAT_SPE_FnE and
> FEAT_SPE_FDS if the feature is not present for the VM. Move the functions
> that check for the presence of these features out of config.c and into
> spe.c, since that's where the bulk of SPE virtualization lies.
>
> Signed-off-by: Alexandru Elisei <alexandru.elisei@arm.com>

Aside from naming nit below, LGTM and:

Reviewed-by: Lorenzo Stoakes (ARM) <ljs@kernel.org>

> ---
>  arch/arm64/include/asm/kvm_spe.h | 11 +++++++++++
>  arch/arm64/kvm/config.c          | 30 +++++-------------------------
>  arch/arm64/kvm/spe.c             | 22 ++++++++++++++++++++++
>  3 files changed, 38 insertions(+), 25 deletions(-)
>
> diff --git a/arch/arm64/include/asm/kvm_spe.h b/arch/arm64/include/asm/kvm_spe.h
> index 3d8e9720daca..618051dcf59f 100644
> --- a/arch/arm64/include/asm/kvm_spe.h
> +++ b/arch/arm64/include/asm/kvm_spe.h
> @@ -43,6 +43,9 @@ u8 kvm_spe_get_pmsver(struct kvm *kvm);
>
>  bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val);
>  u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg);
> +
> +bool kvm_spe_has_feat_spe_fne(struct kvm *kvm);
> +bool kvm_spe_has_feat_spe_fds(struct kvm *kvm);

These both say spe twice, can probably cut the 2nd one in each case I think
:) Could you drop the 'feat' too, kvm_spe_has_fne() and kvm_spe_has_fds()
seem better!

>  #else
>  struct kvm_spe {
>  };
> @@ -99,6 +102,14 @@ static inline u64 kvm_spe_read_sysreg(struct kvm_vcpu *vcpu, int reg)
>  {
>  	return 0;
>  }
> +static inline bool kvm_spe_has_feat_spe_fne(struct kvm *kvm)
> +{
> +	return false;
> +}
> +static inline bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
> +{
> +	return false;
> +}
>  #endif /* CONFIG_KVM_ARM_SPE */
>
>  #endif /* __ARM64_KVM_SPE_H__ */
> diff --git a/arch/arm64/kvm/config.c b/arch/arm64/kvm/config.c
> index 1053676551af..78e414ed736d 100644
> --- a/arch/arm64/kvm/config.c
> +++ b/arch/arm64/kvm/config.c
> @@ -7,6 +7,7 @@
>  #include <linux/kvm_host.h>
>  #include <asm/kvm_emulate.h>
>  #include <asm/kvm_nested.h>
> +#include <asm/kvm_spe.h>
>  #include <asm/sysreg.h>
>
>  /*
> @@ -159,7 +160,6 @@ struct reg_feat_map_desc {
>  #define FEAT_GICv3		ID_AA64PFR0_EL1, GIC, IMP
>  #define FEAT_LOR		ID_AA64MMFR1_EL1, LO, IMP
>  #define FEAT_SPEv1p2		ID_AA64DFR0_EL1, PMSVer, V1P2
> -#define FEAT_SPEv1p4		ID_AA64DFR0_EL1, PMSVer, V1P4
>  #define FEAT_SPEv1p5		ID_AA64DFR0_EL1, PMSVer, V1P5
>  #define FEAT_ATS1A		ID_AA64ISAR2_EL1, ATS1A, IMP
>  #define FEAT_SPECRES2		ID_AA64ISAR1_EL1, SPECRES, COSP_RCTX
> @@ -292,26 +292,6 @@ static bool feat_sme_smps(struct kvm *kvm)
>  		(read_sysreg_s(SYS_SMIDR_EL1) & SMIDR_EL1_SMPS));
>  }
>
> -static bool feat_spe_fds(struct kvm *kvm)
> -{
> -	/*
> -	 * Revisit this if KVM ever supports SPE -- this really should
> -	 * look at the guest's view of PMSIDR_EL1.
> -	 */
> -	return (kvm_has_feat(kvm, FEAT_SPEv1p4) &&
> -		(read_sysreg_s(SYS_PMSIDR_EL1) & PMSIDR_EL1_FDS));
> -}
> -
> -static bool feat_spe_fne(struct kvm *kvm)
> -{
> -	/*
> -	 * Revisit this if KVM ever supports SPE -- this really should
> -	 * look at the guest's view of PMSIDR_EL1.
> -	 */
> -	return (kvm_has_feat(kvm, FEAT_SPEv1p2) &&
> -		(read_sysreg_s(SYS_PMSIDR_EL1) & PMSIDR_EL1_FnE));
> -}
> -
>  static bool feat_trbe_mpam(struct kvm *kvm)
>  {
>  	/*
> @@ -547,7 +527,7 @@ static const struct reg_bits_to_feat_map hdfgrtr_feat_map[] = {
>  		   HDFGRTR_EL2_PMBPTR_EL1	|
>  		   HDFGRTR_EL2_PMBLIMITR_EL1,
>  		   FEAT_SPE),
> -	NEEDS_FEAT(HDFGRTR_EL2_nPMSNEVFR_EL1, feat_spe_fne),
> +	NEEDS_FEAT(HDFGRTR_EL2_nPMSNEVFR_EL1, kvm_spe_has_feat_spe_fne),
>  	NEEDS_FEAT(HDFGRTR_EL2_nBRBDATA		|
>  		   HDFGRTR_EL2_nBRBCTL		|
>  		   HDFGRTR_EL2_nBRBIDR,
> @@ -615,7 +595,7 @@ static const struct reg_bits_to_feat_map hdfgwtr_feat_map[] = {
>  		   HDFGWTR_EL2_PMBPTR_EL1	|
>  		   HDFGWTR_EL2_PMBLIMITR_EL1,
>  		   FEAT_SPE),
> -	NEEDS_FEAT(HDFGWTR_EL2_nPMSNEVFR_EL1, feat_spe_fne),
> +	NEEDS_FEAT(HDFGWTR_EL2_nPMSNEVFR_EL1, kvm_spe_has_feat_spe_fne),
>  	NEEDS_FEAT(HDFGWTR_EL2_nBRBDATA		|
>  		   HDFGWTR_EL2_nBRBCTL,
>  		   FEAT_BRBE),
> @@ -851,7 +831,7 @@ static const struct reg_bits_to_feat_map hdfgrtr2_feat_map[] = {
>  		   HDFGRTR2_EL2_nPMSSDATA,
>  		   FEAT_PMUv3_SS),
>  	NEEDS_FEAT(HDFGRTR2_EL2_nPMIAR_EL1, FEAT_SEBEP),
> -	NEEDS_FEAT(HDFGRTR2_EL2_nPMSDSFR_EL1, feat_spe_fds),
> +	NEEDS_FEAT(HDFGRTR2_EL2_nPMSDSFR_EL1, kvm_spe_has_feat_spe_fds),
>  	NEEDS_FEAT(HDFGRTR2_EL2_nPMBMAR_EL1, FEAT_SPE_nVM),
>  	NEEDS_FEAT(HDFGRTR2_EL2_nSPMACCESSR_EL1	|
>  		   HDFGRTR2_EL2_nSPMCNTEN	|
> @@ -884,7 +864,7 @@ static const struct reg_bits_to_feat_map hdfgwtr2_feat_map[] = {
>  		   feat_pmuv3p9),
>  	NEEDS_FEAT(HDFGWTR2_EL2_nPMSSCR_EL1, FEAT_PMUv3_SS),
>  	NEEDS_FEAT(HDFGWTR2_EL2_nPMIAR_EL1, FEAT_SEBEP),
> -	NEEDS_FEAT(HDFGWTR2_EL2_nPMSDSFR_EL1, feat_spe_fds),
> +	NEEDS_FEAT(HDFGWTR2_EL2_nPMSDSFR_EL1, kvm_spe_has_feat_spe_fds),
>  	NEEDS_FEAT(HDFGWTR2_EL2_nPMBMAR_EL1, FEAT_SPE_nVM),
>  	NEEDS_FEAT(HDFGWTR2_EL2_nSPMACCESSR_EL1	|
>  		   HDFGWTR2_EL2_nSPMCNTEN	|
> diff --git a/arch/arm64/kvm/spe.c b/arch/arm64/kvm/spe.c
> index 8c173a1f48bd..3b285b45332b 100644
> --- a/arch/arm64/kvm/spe.c
> +++ b/arch/arm64/kvm/spe.c
> @@ -113,6 +113,28 @@ void kvm_spe_destroy_vm(struct kvm *kvm)
>  	module_put(spe_pmu->pmu.module);
>  }
>
> +bool kvm_spe_has_feat_spe_fds(struct kvm *kvm)
> +{
> +	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
> +
> +	if (!spe_pmu)
> +		return false;
> +
> +	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P4) &&
> +	       FIELD_GET(PMSIDR_EL1_FDS, spe_pmu->pmsidr_el1);
> +}
> +
> +bool kvm_spe_has_feat_spe_fne(struct kvm *kvm)
> +{
> +	struct arm_spe_pmu *spe_pmu = kvm->arch.kvm_spe.spe_pmu;
> +
> +	if (!spe_pmu)
> +		return false;
> +
> +	return kvm_has_feat(kvm, ID_AA64DFR0_EL1, PMSVer, V1P2) &&
> +	       FIELD_GET(PMSIDR_EL1_FnE, spe_pmu->pmsidr_el1);
> +}
> +
>  bool kvm_spe_write_sysreg(struct kvm_vcpu *vcpu, int reg, u64 val)
>  {
>  	struct kvm_spe *kvm_spe = &vcpu->kvm->arch.kvm_spe;
> --
> 2.43.0
>
>

--
Cheers, Lorenzo

^ permalink raw reply	[flat|nested] 87+ messages in thread

* Re: [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE
  2026-09-03 16:06 ` [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE Alexandru Elisei
  2026-09-03 16:28   ` sashiko-bot
  2026-10-28 13:45   ` Lorenzo Stoakes (ARM)
@ 2026-10-29 18:03   ` Lorenzo Stoakes (ARM)
  2 siblings, 0 replies; 87+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-29 18:03 UTC (permalink / raw)
  To: Alexandru Elisei
  Cc: maz, oupton, fuad.tabba, joey.gouly, seiden, suzuki.poulose,
	yuzenghui, linux-arm-kernel, kvmarm, will, mark.rutland,
	linux-perf-users, catalin.marinas, james.clark, Sudeep Holla

On Thu, Sep 03, 2026 at 05:06:09PM +0100, Alexandru Elisei wrote:
> diff --git a/arch/arm64/include/uapi/asm/kvm.h b/arch/arm64/include/uapi/asm/kvm.h
> index 8ffa3e89d38c..c695aae484d3 100644
> --- a/arch/arm64/include/uapi/asm/kvm.h
> +++ b/arch/arm64/include/uapi/asm/kvm.h
> @@ -449,6 +449,7 @@ enum {
>  #define KVM_ARM_VCPU_SPE_CTRL		3
>  #define   KVM_ARM_VCPU_SPE_IRQ		0
>  #define   KVM_ARM_VCPU_SPE_ID		1
> +#define   KVM_ARM_VCPU_SPE_INIT		2
>
>  /* KVM_IRQ_LINE irq field index values */
>  #define KVM_ARM_IRQ_VCPU2_SHIFT		28
> diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c
> index 132df44c3088..c9a026c61ce7 100644
> --- a/arch/arm64/kvm/arm.c
> +++ b/arch/arm64/kvm/arm.c
> @@ -1026,6 +1026,12 @@ int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
>  			return ret;
>  	}
>
> +	if (vcpu_has_spe(vcpu)) {
> +		ret = kvm_spe_vcpu_first_run_init(vcpu);
> +		if (ret)
> +			return ret;
> +	}
> +

So, staring at patch 18 made me think about ordering again, which seems to be
_super_ sensitive in KVM :)

The next patch in the series (15) makes PMSVer depend on the SPE instance having
been assigned via KVM_ARM_VCPU_SPE_ID.

But you can still KVM_RUN before KVM_ARM_VCPU_SPE_INIT, which means this will
return -EINVAL:

int kvm_spe_vcpu_first_run_init(struct kvm_vcpu *vcpu)
{
	...
	if (!kvm_vcpu_spe_initialized(vcpu))
		return -EINVAL;
	...
}

And this is quite far down the function:

int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
{
	struct kvm *kvm = vcpu->kvm;
	int ret;

	if (!kvm_vcpu_initialized(vcpu))
		return -ENOEXEC;

	if (!kvm_arm_vcpu_is_finalized(vcpu))
		return -EPERM;

	if (likely(vcpu_has_run_once(vcpu)))
		return 0;

	...

	ret = kvm_vcpu_finalize_sys_regs(vcpu);
	if (ret)
		return ret;

	...

	/*
	 * This needs to happen after any restriction has been applied
	 * to the feature set.
	 */
	kvm_calculate_traps(vcpu);

	...

	if (vcpu_has_spe(vcpu)) {
		ret = kvm_spe_vcpu_first_run_init(vcpu);
		if (ret)
			return ret;
	}

	...
}

patch 18 made me think of this as kvm_calculate_traps() does the FGU
initialisation (which derives from SPE state), and once done sets
KVM_ARCH_FLAG_FGU_INITIALIZED, meaning this state is incorrectly setup.

This is a real problem as HAS_RAN_ONCE isn't set on that failed path,
userland can finish the SPE setup, set KVM_ARCH_FLAG_FGU_INITIALIZED, and
that state is never recomputed.

Also kvm_vcpu_finalize_sys_regs() calls kvm_spe_finalize_idregs() (via
kvm_vm_finalize_sys_regs()) which writes PMSVer in the first place.

So it seems you should put this check _before_ these, e.g.:

int kvm_arch_vcpu_run_pid_change(struct kvm_vcpu *vcpu)
{
	struct kvm *kvm = vcpu->kvm;
	int ret;

	if (!kvm_vcpu_initialized(vcpu))
		return -ENOEXEC;

	if (!kvm_arm_vcpu_is_finalized(vcpu))
		return -EPERM;

	if (likely(vcpu_has_run_once(vcpu)))
		return 0;

	...

>>>>
	if (vcpu_has_spe(vcpu)) {
		ret = kvm_spe_vcpu_first_run_init(vcpu);
		if (ret)
			return ret;
	}
>>>

	ret = kvm_vcpu_finalize_sys_regs(vcpu);
	if (ret)
		return ret;

	...

	/*
	 * This needs to happen after any restriction has been applied
	 * to the feature set.
	 */
	kvm_calculate_traps(vcpu);

	...

}

--
Cheers, Lorenzo


^ permalink raw reply	[flat|nested] 87+ messages in thread

end of thread, other threads:[~2026-10-29 18:03 UTC | newest]

Thread overview: 87+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 16:05 [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
2026-09-03 16:05 ` [RFC PATCH v7 01/28] arm64/sysreg: Add the nVM field to PMBLIMITR_EL1 Alexandru Elisei
2026-09-03 16:14   ` sashiko-bot
2026-10-15 13:23   ` Lorenzo Stoakes (ARM)
2026-09-03 16:05 ` [RFC PATCH v7 02/28] arm64/sysreg: Define MDCR_EL2.E2PB values Alexandru Elisei
2026-09-03 16:12   ` sashiko-bot
2026-10-15 13:52   ` Lorenzo Stoakes (ARM)
2026-09-03 16:05 ` [RFC PATCH v7 03/28] KVM: arm64: Add CONFIG_KVM_ARM_SPE Kconfig option Alexandru Elisei
2026-09-03 16:13   ` sashiko-bot
2026-10-15 14:15   ` Lorenzo Stoakes (ARM)
2026-09-03 16:05 ` [RFC PATCH v7 04/28] perf: arm_spe_pmu: Move struct arm_spe_pmu to a separate header file Alexandru Elisei
2026-09-03 16:11   ` sashiko-bot
2026-10-15 14:40   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 05/28] perf: arm_spe_pmu: Add PMBIDR_EL1 and PMSIDR_EL1 to struct arm_spe_pmu Alexandru Elisei
2026-09-03 16:11   ` sashiko-bot
2026-10-15 15:12   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 06/28] KVM: arm64: Add KVM_CAP_ARM_SPE capability Alexandru Elisei
2026-09-03 16:15   ` sashiko-bot
2026-10-15 16:51   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 07/28] KVM: arm64: Add KVM_ARM_VCPU_SPE VCPU feature Alexandru Elisei
2026-09-03 16:21   ` sashiko-bot
2026-10-16 14:23   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 08/28] HACK! KVM: arm64: Disable SPE virtualization if protected KVM is enabled Alexandru Elisei
2026-09-03 16:21   ` sashiko-bot
2026-10-16 14:30   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 09/28] HACK! KVM: arm64: Enable SPE virtualization only in VHE mode Alexandru Elisei
2026-09-03 16:15   ` sashiko-bot
2026-09-03 16:06 ` [RFC PATCH v7 10/28] HACK! KVM: arm64: Disable SPE virtualization if nested virt is enabled Alexandru Elisei
2026-09-03 16:20   ` sashiko-bot
2026-09-03 16:06 ` [RFC PATCH v7 11/28] KVM: arm64: Add a new VCPU device control group for SPE Alexandru Elisei
2026-09-03 16:22   ` sashiko-bot
2026-10-16 14:44   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 12/28] KVM: arm64: Add SPE VCPU device attribute to set the interrupt number Alexandru Elisei
2026-09-03 16:27   ` sashiko-bot
2026-10-20 17:20   ` Lorenzo Stoakes (ARM)
2026-10-28 16:52   ` Suzuki K Poulose
2026-09-03 16:06 ` [RFC PATCH v7 13/28] KVM: arm64: Add SPE VCPU device attribute to set the SPE device Alexandru Elisei
2026-09-03 16:39   ` sashiko-bot
2026-09-04  9:32     ` Alexandru Elisei
2026-10-28 13:11   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 14/28] KVM: arm64: Add SPE VCPU device attribute to initialize SPE Alexandru Elisei
2026-09-03 16:28   ` sashiko-bot
2026-10-28 13:45   ` Lorenzo Stoakes (ARM)
2026-10-29 18:03   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 15/28] KVM: arm64: Use PMSVer from the assigned SPE instance Alexandru Elisei
2026-09-03 16:41   ` sashiko-bot
2026-09-04 10:26     ` Alexandru Elisei
2026-10-28 16:41   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 16/28] KVM: arm64: Add SPE system registers to VCPU context Alexandru Elisei
2026-09-03 16:32   ` sashiko-bot
2026-09-04 10:28     ` Alexandru Elisei
2026-10-29 15:42   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 17/28] KVM: arm64: Apply a RES0 mask to PMBLIMITR_EL1 writes Alexandru Elisei
2026-09-03 16:37   ` sashiko-bot
2026-09-04 10:41     ` Alexandru Elisei
2026-10-29 17:23   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 18/28] KVM: arm64: config: Use functions from spe.c to test FEAT_SPE_{FnE,FDS} Alexandru Elisei
2026-09-03 16:40   ` sashiko-bot
2026-10-29 17:47   ` Lorenzo Stoakes (ARM)
2026-09-03 16:06 ` [RFC PATCH v7 19/28] KVM: arm64: VHE: Context switch SPE state Alexandru Elisei
2026-09-03 16:43   ` sashiko-bot
2026-09-04 11:35     ` Alexandru Elisei
2026-09-03 16:06 ` [RFC PATCH v7 20/28] KVM: arm64: Allow guest SPE physical timestamps only if kernel allows it Alexandru Elisei
2026-09-03 16:48   ` sashiko-bot
2026-09-04 13:45     ` Alexandru Elisei
2026-09-03 16:06 ` [RFC PATCH v7 21/28] KVM: arm64: Handle SPE maintenance interrupts Alexandru Elisei
2026-09-03 16:58   ` sashiko-bot
2026-09-04 14:04     ` Alexandru Elisei
2026-09-03 16:06 ` [RFC PATCH v7 22/28] arm64: errata: Disable SPE in KVM Alexandru Elisei
2026-09-03 16:50   ` sashiko-bot
2026-09-03 16:06 ` [RFC PATCH v7 23/28] KVM: arm64: Add kvm-arm.ignore_spe_errata kernel parameter Alexandru Elisei
2026-09-03 16:46   ` sashiko-bot
2026-09-03 16:06 ` [RFC PATCH v7 24/28] arm64: errata: Don't enable guest buffer if misprogrammed Alexandru Elisei
2026-09-03 17:00   ` sashiko-bot
2026-09-03 16:06 ` [RFC PATCH v7 25/28] KVM: arm64: at: Use callback for reading descriptor Alexandru Elisei
2026-09-03 16:51   ` sashiko-bot
2026-09-03 16:06 ` [RFC PATCH v7 26/28] KVM: arm64: Map memory on a SPE stage 2 fault Alexandru Elisei
2026-09-03 17:08   ` sashiko-bot
2026-09-03 16:06 ` [RFC PATCH v7 27/28] KVM: arm64: Handle dirty page logging when SPE feature is set Alexandru Elisei
2026-09-03 17:06   ` sashiko-bot
2026-09-04 14:41     ` Alexandru Elisei
2026-09-03 16:06 ` [RFC PATCH v7 28/28] KVM: arm64: Allow the creation of a SPE enabled VM Alexandru Elisei
2026-09-03 16:59   ` sashiko-bot
2026-09-04 14:09     ` Alexandru Elisei
2026-10-15 10:25 ` [RFC PATCH v7 00/28] KVM: arm64: Add Statistical Profiling Extension (SPE) support Alexandru Elisei
2026-10-15 12:50   ` Lorenzo Stoakes (ARM)
2026-10-15 13:22     ` Alexandru Elisei

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.