mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
synced 2025-08-31 06:09:56 +00:00

While BRBE can record branches within guests, the host recording branches in guests is not supported by perf (though events are). Support for BRBE in guests will supported by providing direct access to BRBE within the guests. That is how x86 LBR works for guests. Therefore, BRBE needs to be disabled on guest entry and restored on exit. For nVHE, this requires explicit handling for guests. Before entering a guest, save the BRBE state and disable the it. When returning to the host, restore the state. For VHE, it is not necessary. We initialize BRBCR_EL1.{E1BRE,E0BRE}=={0,0} at boot time, and HCR_EL2.TGE==1 while running in the host. We configure BRBCR_EL2.{E2BRE,E0HBRE} to enable branch recording in the host. When entering the guest, we set HCR_EL2.TGE==0 which means BRBCR_EL1 is used instead of BRBCR_EL2. Consequently for VHE, BRBE recording is disabled at EL1 and EL0 when running a guest. Should recording in guests (by the host) ever be desired, the perf ABI will need to be extended to distinguish guest addresses (struct perf_branch_entry.priv) for starters. BRBE records would also need to be invalidated on guest entry/exit as guest/host EL1 and EL0 records can't be distinguished. Signed-off-by: Anshuman Khandual <anshuman.khandual@arm.com> Signed-off-by: Mark Rutland <mark.rutland@arm.com> Co-developed-by: Rob Herring (Arm) <robh@kernel.org> Signed-off-by: Rob Herring (Arm) <robh@kernel.org> Tested-by: James Clark <james.clark@linaro.org> Reviewed-by: Leo Yan <leo.yan@arm.com> Reviewed-by: Suzuki K Poulose <suzuki.poulose@arm.com> Acked-by: Marc Zyngier <maz@kernel.org> Acked-by: Mark Rutland <mark.rutland@arm.com> Link: https://lore.kernel.org/r/20250611-arm-brbe-v19-v23-3-e7775563036e@kernel.org Signed-off-by: Will Deacon <will@kernel.org>
154 lines
3.5 KiB
C
154 lines
3.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (C) 2015 - ARM Ltd
|
|
* Author: Marc Zyngier <marc.zyngier@arm.com>
|
|
*/
|
|
|
|
#include <hyp/debug-sr.h>
|
|
|
|
#include <linux/compiler.h>
|
|
#include <linux/kvm_host.h>
|
|
|
|
#include <asm/debug-monitors.h>
|
|
#include <asm/kvm_asm.h>
|
|
#include <asm/kvm_hyp.h>
|
|
#include <asm/kvm_mmu.h>
|
|
|
|
static void __debug_save_spe(u64 *pmscr_el1)
|
|
{
|
|
u64 reg;
|
|
|
|
/* Clear pmscr in case of early return */
|
|
*pmscr_el1 = 0;
|
|
|
|
/*
|
|
* At this point, we know that this CPU implements
|
|
* SPE and is available to the host.
|
|
* Check if the host is actually using it ?
|
|
*/
|
|
reg = read_sysreg_s(SYS_PMBLIMITR_EL1);
|
|
if (!(reg & BIT(PMBLIMITR_EL1_E_SHIFT)))
|
|
return;
|
|
|
|
/* Yes; save the control register and disable data generation */
|
|
*pmscr_el1 = read_sysreg_el1(SYS_PMSCR);
|
|
write_sysreg_el1(0, SYS_PMSCR);
|
|
isb();
|
|
|
|
/* Now drain all buffered data to memory */
|
|
psb_csync();
|
|
}
|
|
|
|
static void __debug_restore_spe(u64 pmscr_el1)
|
|
{
|
|
if (!pmscr_el1)
|
|
return;
|
|
|
|
/* The host page table is installed, but not yet synchronised */
|
|
isb();
|
|
|
|
/* Re-enable data generation */
|
|
write_sysreg_el1(pmscr_el1, SYS_PMSCR);
|
|
}
|
|
|
|
static void __trace_do_switch(u64 *saved_trfcr, u64 new_trfcr)
|
|
{
|
|
*saved_trfcr = read_sysreg_el1(SYS_TRFCR);
|
|
write_sysreg_el1(new_trfcr, SYS_TRFCR);
|
|
}
|
|
|
|
static bool __trace_needs_drain(void)
|
|
{
|
|
if (is_protected_kvm_enabled() && host_data_test_flag(HAS_TRBE))
|
|
return read_sysreg_s(SYS_TRBLIMITR_EL1) & TRBLIMITR_EL1_E;
|
|
|
|
return host_data_test_flag(TRBE_ENABLED);
|
|
}
|
|
|
|
static bool __trace_needs_switch(void)
|
|
{
|
|
return host_data_test_flag(TRBE_ENABLED) ||
|
|
host_data_test_flag(EL1_TRACING_CONFIGURED);
|
|
}
|
|
|
|
static void __trace_switch_to_guest(void)
|
|
{
|
|
/* Unsupported with TRBE so disable */
|
|
if (host_data_test_flag(TRBE_ENABLED))
|
|
*host_data_ptr(trfcr_while_in_guest) = 0;
|
|
|
|
__trace_do_switch(host_data_ptr(host_debug_state.trfcr_el1),
|
|
*host_data_ptr(trfcr_while_in_guest));
|
|
|
|
if (__trace_needs_drain()) {
|
|
isb();
|
|
tsb_csync();
|
|
}
|
|
}
|
|
|
|
static void __trace_switch_to_host(void)
|
|
{
|
|
__trace_do_switch(host_data_ptr(trfcr_while_in_guest),
|
|
*host_data_ptr(host_debug_state.trfcr_el1));
|
|
}
|
|
|
|
static void __debug_save_brbe(u64 *brbcr_el1)
|
|
{
|
|
*brbcr_el1 = 0;
|
|
|
|
/* Check if the BRBE is enabled */
|
|
if (!(read_sysreg_el1(SYS_BRBCR) & (BRBCR_ELx_E0BRE | BRBCR_ELx_ExBRE)))
|
|
return;
|
|
|
|
/*
|
|
* Prohibit branch record generation while we are in guest.
|
|
* Since access to BRBCR_EL1 is trapped, the guest can't
|
|
* modify the filtering set by the host.
|
|
*/
|
|
*brbcr_el1 = read_sysreg_el1(SYS_BRBCR);
|
|
write_sysreg_el1(0, SYS_BRBCR);
|
|
}
|
|
|
|
static void __debug_restore_brbe(u64 brbcr_el1)
|
|
{
|
|
if (!brbcr_el1)
|
|
return;
|
|
|
|
/* Restore BRBE controls */
|
|
write_sysreg_el1(brbcr_el1, SYS_BRBCR);
|
|
}
|
|
|
|
void __debug_save_host_buffers_nvhe(struct kvm_vcpu *vcpu)
|
|
{
|
|
/* Disable and flush SPE data generation */
|
|
if (host_data_test_flag(HAS_SPE))
|
|
__debug_save_spe(host_data_ptr(host_debug_state.pmscr_el1));
|
|
|
|
/* Disable BRBE branch records */
|
|
if (host_data_test_flag(HAS_BRBE))
|
|
__debug_save_brbe(host_data_ptr(host_debug_state.brbcr_el1));
|
|
|
|
if (__trace_needs_switch())
|
|
__trace_switch_to_guest();
|
|
}
|
|
|
|
void __debug_switch_to_guest(struct kvm_vcpu *vcpu)
|
|
{
|
|
__debug_switch_to_guest_common(vcpu);
|
|
}
|
|
|
|
void __debug_restore_host_buffers_nvhe(struct kvm_vcpu *vcpu)
|
|
{
|
|
if (host_data_test_flag(HAS_SPE))
|
|
__debug_restore_spe(*host_data_ptr(host_debug_state.pmscr_el1));
|
|
if (host_data_test_flag(HAS_BRBE))
|
|
__debug_restore_brbe(*host_data_ptr(host_debug_state.brbcr_el1));
|
|
if (__trace_needs_switch())
|
|
__trace_switch_to_host();
|
|
}
|
|
|
|
void __debug_switch_to_host(struct kvm_vcpu *vcpu)
|
|
{
|
|
__debug_switch_to_host_common(vcpu);
|
|
}
|