Hi,
I'm looking for help in testing this and for feedback on whether it's useful to anyone. Testing it requires hardware that has Feat_TRF (v8.4) but no TRBE. This is because TRBE usage is disabled in nVHE guests.
I don't currently have any access to any hardware, and the FVP model can only do self hosted trace using TRBE.
Currently with nVHE you would always get trace from guests, and filtering out isn't possible without this patchset. In comparison, with VHE guests, they never generate guest trace without [1]. I think the existence of trace rather than lack of could suggest that this change is less useful than [1]. Also the restricted set of hardware that it works on supports that too.
Apart from compilation and checking that the exclude guest settings are correctly programmed on guest switch, this is untested by me.
Applies to kvmarm/next (3b4e3afb2032)
[1]: https://lore.kernel.org/linux-arm-kernel/20230804085219.260790-3-james.clark...
James Clark (3): arm64: KVM: Add support for exclude_guest and exclude_host for ETM arm64: KVM: Support exclude_guest for Coresight trace in nVHE coresight: Support exclude_guest with Feat_TRF and nVHE
arch/arm64/include/asm/kvm_host.h | 10 +++- arch/arm64/kvm/Makefile | 1 + arch/arm64/kvm/arm.c | 1 + arch/arm64/kvm/debug.c | 7 +++ arch/arm64/kvm/etm.c | 48 ++++++++++++++++ arch/arm64/kvm/hyp/nvhe/debug-sr.c | 56 +++++++++++++++++-- .../hwtracing/coresight/coresight-etm-perf.c | 4 ++ include/kvm/etm.h | 43 ++++++++++++++ 8 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 arch/arm64/kvm/etm.c create mode 100644 include/kvm/etm.h
Add an interface for the Coresight driver to use to set the current exclude settings for the current CPU. This will be used to configure TRFCR_EL1.
The settings must be copied to the vCPU before each run in the same way that PMU events are because the per-cpu struct isn't accessible in protected mode.
This is only needed for nVHE, otherwise it works automatically with TRFCR_EL{1,2}. Unfortunately it can't be gated on CONFIG_CORESIGHT because Coresight can be built as a module. It can however be gated on CONFIG_PERF_EVENTS because that is required by Coresight.
Signed-off-by: James Clark james.clark@arm.com --- arch/arm64/include/asm/kvm_host.h | 10 ++++++- arch/arm64/kvm/Makefile | 1 + arch/arm64/kvm/arm.c | 1 + arch/arm64/kvm/etm.c | 48 +++++++++++++++++++++++++++++++ include/kvm/etm.h | 43 +++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 arch/arm64/kvm/etm.c create mode 100644 include/kvm/etm.h
diff --git a/arch/arm64/include/asm/kvm_host.h b/arch/arm64/include/asm/kvm_host.h index d7b1403a3fb2..f33262217c84 100644 --- a/arch/arm64/include/asm/kvm_host.h +++ b/arch/arm64/include/asm/kvm_host.h @@ -35,6 +35,7 @@ #include <kvm/arm_vgic.h> #include <kvm/arm_arch_timer.h> #include <kvm/arm_pmu.h> +#include <kvm/etm.h>
#define KVM_MAX_VCPUS VGIC_V3_MAX_CPUS
@@ -500,7 +501,7 @@ struct kvm_vcpu_arch { u8 cflags;
/* Input flags to the hypervisor code, potentially cleared after use */ - u8 iflags; + u16 iflags;
/* State flags for kernel bookkeeping, unused by the hypervisor code */ u8 sflags; @@ -541,6 +542,9 @@ struct kvm_vcpu_arch { u64 pmscr_el1; /* Self-hosted trace */ u64 trfcr_el1; + /* exclude_guest settings for nVHE */ + struct kvm_etm_event etm_event; + } host_debug_state;
/* VGIC state */ @@ -713,6 +717,8 @@ struct kvm_vcpu_arch { #define DEBUG_STATE_SAVE_TRBE __vcpu_single_flag(iflags, BIT(6)) /* vcpu running in HYP context */ #define VCPU_HYP_CONTEXT __vcpu_single_flag(iflags, BIT(7)) +/* Save TRFCR and apply exclude_guest rules */ +#define DEBUG_STATE_SAVE_TRFCR __vcpu_single_flag(iflags, BIT(8))
/* SVE enabled for host EL0 */ #define HOST_SVE_ENABLED __vcpu_single_flag(sflags, BIT(0)) @@ -1096,6 +1102,8 @@ void kvm_arch_vcpu_put_debug_state_flags(struct kvm_vcpu *vcpu); void kvm_set_pmu_events(u32 set, struct perf_event_attr *attr); void kvm_clr_pmu_events(u32 clr); bool kvm_set_pmuserenr(u64 val); +void kvm_set_etm_events(struct perf_event_attr *attr); +void kvm_clr_etm_events(void); #else static inline void kvm_set_pmu_events(u32 set, struct perf_event_attr *attr) {} static inline void kvm_clr_pmu_events(u32 clr) {} diff --git a/arch/arm64/kvm/Makefile b/arch/arm64/kvm/Makefile index c0c050e53157..0faff57423c4 100644 --- a/arch/arm64/kvm/Makefile +++ b/arch/arm64/kvm/Makefile @@ -23,6 +23,7 @@ kvm-y += arm.o mmu.o mmio.o psci.o hypercalls.o pvtime.o \ vgic/vgic-its.o vgic/vgic-debug.o
kvm-$(CONFIG_HW_PERF_EVENTS) += pmu-emul.o pmu.o +kvm-$(CONFIG_PERF_EVENTS) += etm.o
always-y := hyp_constants.h hyp-constants.s
diff --git a/arch/arm64/kvm/arm.c b/arch/arm64/kvm/arm.c index b1a9d47fb2f3..7bd5975328a3 100644 --- a/arch/arm64/kvm/arm.c +++ b/arch/arm64/kvm/arm.c @@ -952,6 +952,7 @@ int kvm_arch_vcpu_ioctl_run(struct kvm_vcpu *vcpu) kvm_vgic_flush_hwstate(vcpu);
kvm_pmu_update_vcpu_events(vcpu); + kvm_etm_update_vcpu_events(vcpu);
/* * Ensure we set mode to IN_GUEST_MODE after we disable diff --git a/arch/arm64/kvm/etm.c b/arch/arm64/kvm/etm.c new file mode 100644 index 000000000000..359c37745de2 --- /dev/null +++ b/arch/arm64/kvm/etm.c @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include <linux/kvm_host.h> + +#include <kvm/etm.h> + +static DEFINE_PER_CPU(struct kvm_etm_event, kvm_etm_events); + +struct kvm_etm_event *kvm_get_etm_event(void) +{ + return this_cpu_ptr(&kvm_etm_events); +} + +void kvm_etm_set_events(struct perf_event_attr *attr) +{ + struct kvm_etm_event *etm_event; + + /* + * Exclude guest option only requires extra work with nVHE. + * Otherwise it works automatically with TRFCR_EL{1,2} + */ + if (has_vhe()) + return; + + etm_event = kvm_get_etm_event(); + + etm_event->exclude_guest = attr->exclude_guest; + etm_event->exclude_host = attr->exclude_host; + etm_event->exclude_kernel = attr->exclude_kernel; + etm_event->exclude_user = attr->exclude_user; +} +EXPORT_SYMBOL_GPL(kvm_etm_set_events); + +void kvm_etm_clr_events(void) +{ + struct kvm_etm_event *etm_event; + + if (has_vhe()) + return; + + etm_event = kvm_get_etm_event(); + + etm_event->exclude_guest = false; + etm_event->exclude_host = false; + etm_event->exclude_kernel = false; + etm_event->exclude_user = false; +} +EXPORT_SYMBOL_GPL(kvm_etm_clr_events); diff --git a/include/kvm/etm.h b/include/kvm/etm.h new file mode 100644 index 000000000000..95c4809fa2b0 --- /dev/null +++ b/include/kvm/etm.h @@ -0,0 +1,43 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef __KVM_DEBUG_H +#define __KVM_DEBUG_H + +struct perf_event_attr; +struct kvm_vcpu; + +#if IS_ENABLED(CONFIG_KVM) && IS_ENABLED(CONFIG_PERF_EVENTS) + +struct kvm_etm_event { + bool exclude_host; + bool exclude_guest; + bool exclude_kernel; + bool exclude_user; +}; + +struct kvm_etm_event *kvm_get_etm_event(void); +void kvm_etm_clr_events(void); +void kvm_etm_set_events(struct perf_event_attr *attr); + +/* + * Updates the vcpu's view of the etm events for this cpu. Must be + * called before every vcpu run after disabling interrupts, to ensure + * that an interrupt cannot fire and update the structure. + */ +#define kvm_etm_update_vcpu_events(vcpu) \ + do { \ + if (!has_vhe() && vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_TRFCR)) \ + vcpu->arch.host_debug_state.etm_event = *kvm_get_etm_event(); \ + } while (0) + +#else + +struct kvm_etm_event {}; + +static inline void kvm_etm_update_vcpu_events(struct kvm_vcpu *vcpu) {} +static inline void kvm_etm_set_events(struct perf_event_attr *attr) {} +static inline void kvm_etm_clr_events(void) {} + +#endif + +#endif
Currently trace will always be generated in nVHE as long as TRBE isn't being used. To allow filtering out guest trace, re-apply the filter rules before switching to the guest.
The TRFCR restore function remains the same.
Signed-off-by: James Clark james.clark@arm.com --- arch/arm64/kvm/debug.c | 7 ++++ arch/arm64/kvm/hyp/nvhe/debug-sr.c | 56 +++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 4 deletions(-)
diff --git a/arch/arm64/kvm/debug.c b/arch/arm64/kvm/debug.c index 8725291cb00a..ebb4db20a859 100644 --- a/arch/arm64/kvm/debug.c +++ b/arch/arm64/kvm/debug.c @@ -335,10 +335,17 @@ void kvm_arch_vcpu_load_debug_state_flags(struct kvm_vcpu *vcpu) if (cpuid_feature_extract_unsigned_field(dfr0, ID_AA64DFR0_EL1_TraceBuffer_SHIFT) && !(read_sysreg_s(SYS_TRBIDR_EL1) & TRBIDR_EL1_P)) vcpu_set_flag(vcpu, DEBUG_STATE_SAVE_TRBE); + /* + * Save TRFCR on nVHE if FEAT_TRF exists. This will be done in cases + * where DEBUG_STATE_SAVE_TRBE doesn't completely disable trace. + */ + if (cpuid_feature_extract_unsigned_field(dfr0, ID_AA64DFR0_EL1_TraceFilt_SHIFT)) + vcpu_set_flag(vcpu, DEBUG_STATE_SAVE_TRFCR); }
void kvm_arch_vcpu_put_debug_state_flags(struct kvm_vcpu *vcpu) { vcpu_clear_flag(vcpu, DEBUG_STATE_SAVE_SPE); vcpu_clear_flag(vcpu, DEBUG_STATE_SAVE_TRBE); + vcpu_clear_flag(vcpu, DEBUG_STATE_SAVE_TRFCR); } diff --git a/arch/arm64/kvm/hyp/nvhe/debug-sr.c b/arch/arm64/kvm/hyp/nvhe/debug-sr.c index 4558c02eb352..0e8c85b29b92 100644 --- a/arch/arm64/kvm/hyp/nvhe/debug-sr.c +++ b/arch/arm64/kvm/hyp/nvhe/debug-sr.c @@ -51,13 +51,17 @@ static void __debug_restore_spe(u64 pmscr_el1) write_sysreg_s(pmscr_el1, SYS_PMSCR_EL1); }
-static void __debug_save_trace(u64 *trfcr_el1) +/* + * Save TRFCR and disable trace completely if TRBE is being used. Return true + * if trace was disabled. + */ +static bool __debug_save_trace(u64 *trfcr_el1) { *trfcr_el1 = 0;
/* Check if the TRBE is enabled */ if (!(read_sysreg_s(SYS_TRBLIMITR_EL1) & TRBLIMITR_EL1_E)) - return; + return false; /* * Prohibit trace generation while we are in guest. * Since access to TRFCR_EL1 is trapped, the guest can't @@ -68,6 +72,8 @@ static void __debug_save_trace(u64 *trfcr_el1) isb(); /* Drain the trace buffer to memory */ tsb_csync(); + + return true; }
static void __debug_restore_trace(u64 trfcr_el1) @@ -79,14 +85,55 @@ static void __debug_restore_trace(u64 trfcr_el1) write_sysreg_s(trfcr_el1, SYS_TRFCR_EL1); }
+#if IS_ENABLED(CONFIG_PERF_EVENTS) +static inline void __debug_save_trfcr(struct kvm_vcpu *vcpu) +{ + u64 trfcr; + struct kvm_etm_event etm_event = vcpu->arch.host_debug_state.etm_event; + + /* No change if neither are excluded */ + if (!etm_event.exclude_guest && !etm_event.exclude_host) { + /* Zeroing prevents restoring a stale value */ + vcpu->arch.host_debug_state.trfcr_el1 = 0; + return; + } + + trfcr = read_sysreg_s(SYS_TRFCR_EL1); + vcpu->arch.host_debug_state.trfcr_el1 = trfcr; + + if (etm_event.exclude_guest) { + trfcr &= ~(TRFCR_ELx_ExTRE | TRFCR_ELx_E0TRE); + } else { + /* + * If host was excluded then EL0 and ELx tracing bits will + * already be cleared so they need to be set now for the guest. + */ + trfcr |= etm_event.exclude_kernel ? 0 : TRFCR_ELx_ExTRE; + trfcr |= etm_event.exclude_user ? 0 : TRFCR_ELx_E0TRE; + } + write_sysreg_s(trfcr, SYS_TRFCR_EL1); +} +#else +static inline void __debug_save_trfcr(struct kvm_vcpu *vcpu) {} +#endif + void __debug_save_host_buffers_nvhe(struct kvm_vcpu *vcpu) { + bool trc_disabled = false; + /* Disable and flush SPE data generation */ if (vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_SPE)) __debug_save_spe(&vcpu->arch.host_debug_state.pmscr_el1); /* Disable and flush Self-Hosted Trace generation */ if (vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_TRBE)) - __debug_save_trace(&vcpu->arch.host_debug_state.trfcr_el1); + trc_disabled = __debug_save_trace(&vcpu->arch.host_debug_state.trfcr_el1); + + /* + * As long as trace wasn't completely disabled due to use of TRBE, + * TRFCR can be saved and the exclude_guest rules applied. + */ + if (!trc_disabled && vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_TRFCR)) + __debug_save_trfcr(vcpu); }
void __debug_switch_to_guest(struct kvm_vcpu *vcpu) @@ -98,7 +145,8 @@ void __debug_restore_host_buffers_nvhe(struct kvm_vcpu *vcpu) { if (vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_SPE)) __debug_restore_spe(vcpu->arch.host_debug_state.pmscr_el1); - if (vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_TRBE)) + if (vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_TRBE) || + vcpu_get_flag(vcpu, DEBUG_STATE_SAVE_TRFCR)) __debug_restore_trace(vcpu->arch.host_debug_state.trfcr_el1); }
With nVHE the filters need to be applied before switching to the guest, so supply the per-cpu filter status to KVM.
Signed-off-by: James Clark james.clark@arm.com --- drivers/hwtracing/coresight/coresight-etm-perf.c | 4 ++++ 1 file changed, 4 insertions(+)
diff --git a/drivers/hwtracing/coresight/coresight-etm-perf.c b/drivers/hwtracing/coresight/coresight-etm-perf.c index 5ca6278baff4..f78f05e656f5 100644 --- a/drivers/hwtracing/coresight/coresight-etm-perf.c +++ b/drivers/hwtracing/coresight/coresight-etm-perf.c @@ -9,6 +9,7 @@ #include <linux/coresight-pmu.h> #include <linux/cpumask.h> #include <linux/device.h> +#include <linux/kvm_host.h> #include <linux/list.h> #include <linux/mm.h> #include <linux/init.h> @@ -510,6 +511,7 @@ static void etm_event_start(struct perf_event *event, int flags) }
out: + kvm_etm_set_events(&event->attr); /* Tell the perf core the event is alive */ event->hw.state = 0; /* Save the event_data for this ETM */ @@ -627,6 +629,8 @@ static void etm_event_stop(struct perf_event *event, int mode)
/* Disabling the path make its elements available to other sessions */ coresight_disable_path(path); + + kvm_etm_clr_events(); }
static int etm_event_add(struct perf_event *event, int mode)
Cc: Ganpatrao, Steve, Tanmay
On 04/08/2023 11:13, James Clark wrote:
Hi,
I'm looking for help in testing this and for feedback on whether it's useful to anyone. Testing it requires hardware that has Feat_TRF (v8.4) but no TRBE. This is because TRBE usage is disabled in nVHE guests.
I don't currently have any access to any hardware, and the FVP model can only do self hosted trace using TRBE.
If you have a v8.4+ (and not v9) HW, please could you give this a spin ?
Suzuki
Currently with nVHE you would always get trace from guests, and filtering out isn't possible without this patchset. In comparison, with VHE guests, they never generate guest trace without [1]. I think the existence of trace rather than lack of could suggest that this change is less useful than [1]. Also the restricted set of hardware that it works on supports that too.
Apart from compilation and checking that the exclude guest settings are correctly programmed on guest switch, this is untested by me.
Applies to kvmarm/next (3b4e3afb2032)
James Clark (3): arm64: KVM: Add support for exclude_guest and exclude_host for ETM arm64: KVM: Support exclude_guest for Coresight trace in nVHE coresight: Support exclude_guest with Feat_TRF and nVHE
arch/arm64/include/asm/kvm_host.h | 10 +++- arch/arm64/kvm/Makefile | 1 + arch/arm64/kvm/arm.c | 1 + arch/arm64/kvm/debug.c | 7 +++ arch/arm64/kvm/etm.c | 48 ++++++++++++++++ arch/arm64/kvm/hyp/nvhe/debug-sr.c | 56 +++++++++++++++++-- .../hwtracing/coresight/coresight-etm-perf.c | 4 ++ include/kvm/etm.h | 43 ++++++++++++++ 8 files changed, 165 insertions(+), 5 deletions(-) create mode 100644 arch/arm64/kvm/etm.c create mode 100644 include/kvm/etm.h