Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,167 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* aarch32_id_regs - Test for ID register behavior on AArch64-only systems
*
* Copyright (c) 2022 Google LLC.
*
* Test that KVM handles the AArch64 views of the AArch32 ID registers as RAZ
* and WI from userspace.
*/
#include <stdint.h>
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
#include <linux/bitfield.h>
#define BAD_ID_REG_VAL 0x1badc0deul
#define GUEST_ASSERT_REG_RAZ(reg) GUEST_ASSERT_EQ(read_sysreg_s(reg), 0)
static void guest_main(void)
{
GUEST_ASSERT_REG_RAZ(SYS_ID_PFR0_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_PFR1_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_DFR0_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_AFR0_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_MMFR0_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_MMFR1_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_MMFR2_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_MMFR3_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_ISAR0_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_ISAR1_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_ISAR2_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_ISAR3_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_ISAR4_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_ISAR5_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_MMFR4_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_ISAR6_EL1);
GUEST_ASSERT_REG_RAZ(SYS_MVFR0_EL1);
GUEST_ASSERT_REG_RAZ(SYS_MVFR1_EL1);
GUEST_ASSERT_REG_RAZ(SYS_MVFR2_EL1);
GUEST_ASSERT_REG_RAZ(sys_reg(3, 0, 0, 3, 3));
GUEST_ASSERT_REG_RAZ(SYS_ID_PFR2_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_DFR1_EL1);
GUEST_ASSERT_REG_RAZ(SYS_ID_MMFR5_EL1);
GUEST_ASSERT_REG_RAZ(sys_reg(3, 0, 0, 3, 7));
GUEST_DONE();
}
static void test_guest_raz(struct kvm_vcpu *vcpu)
{
struct ucall uc;
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_DONE:
break;
default:
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
}
}
static u64 raz_wi_reg_ids[] = {
KVM_ARM64_SYS_REG(SYS_ID_PFR0_EL1),
KVM_ARM64_SYS_REG(SYS_ID_PFR1_EL1),
KVM_ARM64_SYS_REG(SYS_ID_DFR0_EL1),
KVM_ARM64_SYS_REG(SYS_ID_MMFR0_EL1),
KVM_ARM64_SYS_REG(SYS_ID_MMFR1_EL1),
KVM_ARM64_SYS_REG(SYS_ID_MMFR2_EL1),
KVM_ARM64_SYS_REG(SYS_ID_MMFR3_EL1),
KVM_ARM64_SYS_REG(SYS_ID_ISAR0_EL1),
KVM_ARM64_SYS_REG(SYS_ID_ISAR1_EL1),
KVM_ARM64_SYS_REG(SYS_ID_ISAR2_EL1),
KVM_ARM64_SYS_REG(SYS_ID_ISAR3_EL1),
KVM_ARM64_SYS_REG(SYS_ID_ISAR4_EL1),
KVM_ARM64_SYS_REG(SYS_ID_ISAR5_EL1),
KVM_ARM64_SYS_REG(SYS_ID_MMFR4_EL1),
KVM_ARM64_SYS_REG(SYS_ID_ISAR6_EL1),
KVM_ARM64_SYS_REG(SYS_MVFR0_EL1),
KVM_ARM64_SYS_REG(SYS_MVFR1_EL1),
KVM_ARM64_SYS_REG(SYS_MVFR2_EL1),
KVM_ARM64_SYS_REG(SYS_ID_PFR2_EL1),
KVM_ARM64_SYS_REG(SYS_ID_MMFR5_EL1),
};
static void test_user_raz_wi(struct kvm_vcpu *vcpu)
{
int i;
for (i = 0; i < ARRAY_SIZE(raz_wi_reg_ids); i++) {
u64 reg_id = raz_wi_reg_ids[i];
u64 val;
val = vcpu_get_reg(vcpu, reg_id);
TEST_ASSERT_EQ(val, 0);
/*
* Expect the ioctl to succeed with no effect on the register
* value.
*/
vcpu_set_reg(vcpu, reg_id, BAD_ID_REG_VAL);
val = vcpu_get_reg(vcpu, reg_id);
TEST_ASSERT_EQ(val, 0);
}
}
static u64 raz_invariant_reg_ids[] = {
KVM_ARM64_SYS_REG(SYS_ID_AFR0_EL1),
KVM_ARM64_SYS_REG(sys_reg(3, 0, 0, 3, 3)),
KVM_ARM64_SYS_REG(SYS_ID_DFR1_EL1),
KVM_ARM64_SYS_REG(sys_reg(3, 0, 0, 3, 7)),
};
static void test_user_raz_invariant(struct kvm_vcpu *vcpu)
{
int i, r;
for (i = 0; i < ARRAY_SIZE(raz_invariant_reg_ids); i++) {
u64 reg_id = raz_invariant_reg_ids[i];
u64 val;
val = vcpu_get_reg(vcpu, reg_id);
TEST_ASSERT_EQ(val, 0);
r = __vcpu_set_reg(vcpu, reg_id, BAD_ID_REG_VAL);
TEST_ASSERT(r < 0 && errno == EINVAL,
"unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
val = vcpu_get_reg(vcpu, reg_id);
TEST_ASSERT_EQ(val, 0);
}
}
static bool vcpu_aarch64_only(struct kvm_vcpu *vcpu)
{
u64 val, el0;
val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1));
el0 = FIELD_GET(ID_AA64PFR0_EL1_EL0, val);
return el0 == ID_AA64PFR0_EL1_EL0_IMP;
}
int main(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
vm = vm_create_with_one_vcpu(&vcpu, guest_main);
TEST_REQUIRE(vcpu_aarch64_only(vcpu));
test_user_raz_wi(vcpu);
test_user_raz_invariant(vcpu);
test_guest_raz(vcpu);
kvm_vm_free(vm);
}
@@ -0,0 +1,215 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* The test validates both the virtual and physical timer IRQs using
* CVAL and TVAL registers.
*
* Copyright (c) 2021, Google LLC.
*/
#include "arch_timer.h"
#include "delay.h"
#include "gic.h"
#include "processor.h"
#include "timer_test.h"
#include "ucall_common.h"
#include "vgic.h"
enum guest_stage {
GUEST_STAGE_VTIMER_CVAL = 1,
GUEST_STAGE_VTIMER_TVAL,
GUEST_STAGE_PTIMER_CVAL,
GUEST_STAGE_PTIMER_TVAL,
GUEST_STAGE_MAX,
};
static int vtimer_irq, ptimer_irq;
static void
guest_configure_timer_action(struct test_vcpu_shared_data *shared_data)
{
switch (shared_data->guest_stage) {
case GUEST_STAGE_VTIMER_CVAL:
timer_set_next_cval_ms(VIRTUAL, test_args.timer_period_ms);
shared_data->xcnt = timer_get_cntct(VIRTUAL);
timer_set_ctl(VIRTUAL, CTL_ENABLE);
break;
case GUEST_STAGE_VTIMER_TVAL:
timer_set_next_tval_ms(VIRTUAL, test_args.timer_period_ms);
shared_data->xcnt = timer_get_cntct(VIRTUAL);
timer_set_ctl(VIRTUAL, CTL_ENABLE);
break;
case GUEST_STAGE_PTIMER_CVAL:
timer_set_next_cval_ms(PHYSICAL, test_args.timer_period_ms);
shared_data->xcnt = timer_get_cntct(PHYSICAL);
timer_set_ctl(PHYSICAL, CTL_ENABLE);
break;
case GUEST_STAGE_PTIMER_TVAL:
timer_set_next_tval_ms(PHYSICAL, test_args.timer_period_ms);
shared_data->xcnt = timer_get_cntct(PHYSICAL);
timer_set_ctl(PHYSICAL, CTL_ENABLE);
break;
default:
GUEST_ASSERT(0);
}
}
static void guest_validate_irq(unsigned int intid,
struct test_vcpu_shared_data *shared_data)
{
enum guest_stage stage = shared_data->guest_stage;
u64 xcnt = 0, xcnt_diff_us, cval = 0;
unsigned long xctl = 0;
unsigned int timer_irq = 0;
unsigned int accessor;
if (intid == IAR_SPURIOUS)
return;
switch (stage) {
case GUEST_STAGE_VTIMER_CVAL:
case GUEST_STAGE_VTIMER_TVAL:
accessor = VIRTUAL;
timer_irq = vtimer_irq;
break;
case GUEST_STAGE_PTIMER_CVAL:
case GUEST_STAGE_PTIMER_TVAL:
accessor = PHYSICAL;
timer_irq = ptimer_irq;
break;
default:
GUEST_ASSERT(0);
return;
}
xctl = timer_get_ctl(accessor);
if ((xctl & CTL_IMASK) || !(xctl & CTL_ENABLE))
return;
timer_set_ctl(accessor, CTL_IMASK);
xcnt = timer_get_cntct(accessor);
cval = timer_get_cval(accessor);
xcnt_diff_us = cycles_to_usec(xcnt - shared_data->xcnt);
/* Make sure we are dealing with the correct timer IRQ */
GUEST_ASSERT_EQ(intid, timer_irq);
/* Basic 'timer condition met' check */
__GUEST_ASSERT(xcnt >= cval,
"xcnt = 0x%lx, cval = 0x%lx, xcnt_diff_us = 0x%lx",
xcnt, cval, xcnt_diff_us);
__GUEST_ASSERT(xctl & CTL_ISTATUS, "xctl = 0x%lx", xctl);
WRITE_ONCE(shared_data->nr_iter, shared_data->nr_iter + 1);
}
static void guest_irq_handler(struct ex_regs *regs)
{
unsigned int intid = gic_get_and_ack_irq();
u32 cpu = guest_get_vcpuid();
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
guest_validate_irq(intid, shared_data);
gic_set_eoi(intid);
}
static void guest_run_stage(struct test_vcpu_shared_data *shared_data,
enum guest_stage stage)
{
u32 irq_iter, config_iter;
shared_data->guest_stage = stage;
shared_data->nr_iter = 0;
for (config_iter = 0; config_iter < test_args.nr_iter; config_iter++) {
/* Setup the next interrupt */
guest_configure_timer_action(shared_data);
/* Setup a timeout for the interrupt to arrive */
udelay(msecs_to_usecs(test_args.timer_period_ms) +
test_args.timer_err_margin_us);
irq_iter = READ_ONCE(shared_data->nr_iter);
__GUEST_ASSERT(config_iter + 1 == irq_iter,
"config_iter + 1 = 0x%x, irq_iter = 0x%x.\n"
" Guest timer interrupt was not triggered within the specified\n"
" interval, try to increase the error margin by [-e] option.\n",
config_iter + 1, irq_iter);
}
}
static void guest_code(void)
{
u32 cpu = guest_get_vcpuid();
struct test_vcpu_shared_data *shared_data = &vcpu_shared_data[cpu];
local_irq_disable();
gic_init(GIC_V3, test_args.nr_vcpus);
timer_set_ctl(VIRTUAL, CTL_IMASK);
timer_set_ctl(PHYSICAL, CTL_IMASK);
gic_irq_enable(vtimer_irq);
gic_irq_enable(ptimer_irq);
local_irq_enable();
guest_run_stage(shared_data, GUEST_STAGE_VTIMER_CVAL);
guest_run_stage(shared_data, GUEST_STAGE_VTIMER_TVAL);
guest_run_stage(shared_data, GUEST_STAGE_PTIMER_CVAL);
guest_run_stage(shared_data, GUEST_STAGE_PTIMER_TVAL);
GUEST_DONE();
}
static void test_init_timer_irq(struct kvm_vm *vm)
{
/* Timer initid should be same for all the vCPUs, so query only vCPU-0 */
ptimer_irq = vcpu_get_ptimer_irq(vcpus[0]);
vtimer_irq = vcpu_get_vtimer_irq(vcpus[0]);
sync_global_to_guest(vm, ptimer_irq);
sync_global_to_guest(vm, vtimer_irq);
pr_debug("ptimer_irq: %d; vtimer_irq: %d\n", ptimer_irq, vtimer_irq);
}
struct kvm_vm *test_vm_create(void)
{
struct kvm_vm *vm;
unsigned int i;
int nr_vcpus = test_args.nr_vcpus;
TEST_REQUIRE(kvm_supports_vgic_v3());
vm = vm_create_with_vcpus(nr_vcpus, guest_code, vcpus);
vm_init_descriptor_tables(vm);
vm_install_exception_handler(vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
if (!test_args.reserved) {
if (kvm_has_cap(KVM_CAP_COUNTER_OFFSET)) {
struct kvm_arm_counter_offset offset = {
.counter_offset = test_args.counter_offset,
.reserved = 0,
};
vm_ioctl(vm, KVM_ARM_SET_COUNTER_OFFSET, &offset);
} else
TEST_FAIL("no support for global offset");
}
for (i = 0; i < nr_vcpus; i++)
vcpu_init_descriptor_tables(vcpus[i]);
test_init_timer_irq(vm);
/* Make all the test's cmdline args visible to the guest */
sync_global_to_guest(vm, test_args);
return vm;
}
void test_vm_cleanup(struct kvm_vm *vm)
{
kvm_vm_free(vm);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* at - Test for KVM's AT emulation in the EL2&0 and EL1&0 translation regimes.
*/
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
#include "ucall.h"
#include <asm/sysreg.h>
#define TEST_ADDR 0x80000000
enum {
CLEAR_ACCESS_FLAG,
};
static u64 *ptep_hva;
#define copy_el2_to_el1(reg) \
write_sysreg_s(read_sysreg_s(SYS_##reg##_EL1), SYS_##reg##_EL12)
/* Yes, this is an ugly hack */
#define __at(op, addr) write_sysreg_s(addr, op)
#define test_at_insn(op, expect_fault) \
do { \
u64 par, fsc; \
bool fault; \
\
GUEST_SYNC(CLEAR_ACCESS_FLAG); \
\
__at(OP_AT_##op, TEST_ADDR); \
isb(); \
par = read_sysreg(par_el1); \
\
fault = par & SYS_PAR_EL1_F; \
fsc = FIELD_GET(SYS_PAR_EL1_FST, par); \
\
__GUEST_ASSERT((expect_fault) == fault, \
"AT "#op": %sexpected fault (par: %lx)1", \
(expect_fault) ? "" : "un", par); \
if ((expect_fault)) { \
__GUEST_ASSERT(fsc == ESR_ELx_FSC_ACCESS_L(3), \
"AT "#op": expected access flag fault (par: %lx)", \
par); \
} else { \
GUEST_ASSERT_EQ(FIELD_GET(SYS_PAR_EL1_ATTR, par), MAIR_ATTR_NORMAL); \
GUEST_ASSERT_EQ(FIELD_GET(SYS_PAR_EL1_SH, par), PTE_SHARED >> 8); \
GUEST_ASSERT_EQ(par & SYS_PAR_EL1_PA, TEST_ADDR); \
} \
} while (0)
static void test_at(bool expect_fault)
{
test_at_insn(S1E2R, expect_fault);
test_at_insn(S1E2W, expect_fault);
/* Reuse the stage-1 MMU context from EL2 at EL1 */
copy_el2_to_el1(SCTLR);
copy_el2_to_el1(MAIR);
copy_el2_to_el1(TCR);
copy_el2_to_el1(TTBR0);
copy_el2_to_el1(TTBR1);
/* Disable stage-2 translation and enter a non-host context */
write_sysreg(0, vtcr_el2);
write_sysreg(0, vttbr_el2);
sysreg_clear_set(hcr_el2, HCR_EL2_TGE | HCR_EL2_VM, 0);
isb();
test_at_insn(S1E1R, expect_fault);
test_at_insn(S1E1W, expect_fault);
}
static void guest_code(void)
{
sysreg_clear_set(tcr_el1, TCR_HA, 0);
isb();
test_at(true);
if (!SYS_FIELD_GET(ID_AA64MMFR1_EL1, HAFDBS, read_sysreg(id_aa64mmfr1_el1)))
GUEST_DONE();
sysreg_clear_set(tcr_el1, 0, TCR_HA);
isb();
test_at(false);
GUEST_DONE();
}
static void handle_sync(struct kvm_vcpu *vcpu, struct ucall *uc)
{
switch (uc->args[1]) {
case CLEAR_ACCESS_FLAG:
/*
* Delete + reinstall the memslot to invalidate stage-2
* mappings of the stage-1 page tables, allowing KVM to
* potentially use the 'slow' AT emulation path.
*
* This and clearing the access flag from host userspace
* ensures that the access flag cannot be set speculatively
* and is reliably cleared at the time of the AT instruction.
*/
clear_bit(__ffs(PTE_AF), ptep_hva);
vm_mem_region_reload(vcpu->vm, vcpu->vm->memslots[MEM_REGION_PT]);
break;
default:
TEST_FAIL("Unexpected SYNC arg: %lu", uc->args[1]);
}
}
static void run_test(struct kvm_vcpu *vcpu)
{
struct ucall uc;
while (true) {
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_DONE:
return;
case UCALL_SYNC:
handle_sync(vcpu, &uc);
continue;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
return;
default:
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
}
}
}
int main(void)
{
struct kvm_vcpu_init init;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
vm = vm_create(1);
kvm_get_default_vcpu_target(vm, &init);
init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
kvm_arch_vm_finalize_vcpus(vm);
virt_map(vm, TEST_ADDR, TEST_ADDR, 1);
ptep_hva = virt_get_pte_hva_at_level(vm, TEST_ADDR, 3);
run_test(vcpu);
kvm_vm_free(vm);
return 0;
}
@@ -0,0 +1,605 @@
// SPDX-License-Identifier: GPL-2.0
#include <test_util.h>
#include <kvm_util.h>
#include <processor.h>
#include <linux/bitfield.h>
#define MDSCR_KDE (1 << 13)
#define MDSCR_MDE (1 << 15)
#define MDSCR_SS (1 << 0)
#define DBGBCR_LEN8 (0xff << 5)
#define DBGBCR_EXEC (0x0 << 3)
#define DBGBCR_EL1 (0x1 << 1)
#define DBGBCR_E (0x1 << 0)
#define DBGBCR_LBN_SHIFT 16
#define DBGBCR_BT_SHIFT 20
#define DBGBCR_BT_ADDR_LINK_CTX (0x1 << DBGBCR_BT_SHIFT)
#define DBGBCR_BT_CTX_LINK (0x3 << DBGBCR_BT_SHIFT)
#define DBGWCR_LEN8 (0xff << 5)
#define DBGWCR_RD (0x1 << 3)
#define DBGWCR_WR (0x2 << 3)
#define DBGWCR_EL1 (0x1 << 1)
#define DBGWCR_E (0x1 << 0)
#define DBGWCR_LBN_SHIFT 16
#define DBGWCR_WT_SHIFT 20
#define DBGWCR_WT_LINK (0x1 << DBGWCR_WT_SHIFT)
#define SPSR_D (1 << 9)
#define SPSR_SS (1 << 21)
extern unsigned char sw_bp, sw_bp2, hw_bp, hw_bp2, bp_svc, bp_brk, hw_wp, ss_start, hw_bp_ctx;
extern unsigned char iter_ss_begin, iter_ss_end;
static volatile u64 sw_bp_addr, hw_bp_addr;
static volatile u64 wp_addr, wp_data_addr;
static volatile u64 svc_addr;
static volatile u64 ss_addr[4], ss_idx;
#define PC(v) ((u64)&(v))
#define GEN_DEBUG_WRITE_REG(reg_name) \
static void write_##reg_name(int num, u64 val) \
{ \
switch (num) { \
case 0: \
write_sysreg(val, reg_name##0_el1); \
break; \
case 1: \
write_sysreg(val, reg_name##1_el1); \
break; \
case 2: \
write_sysreg(val, reg_name##2_el1); \
break; \
case 3: \
write_sysreg(val, reg_name##3_el1); \
break; \
case 4: \
write_sysreg(val, reg_name##4_el1); \
break; \
case 5: \
write_sysreg(val, reg_name##5_el1); \
break; \
case 6: \
write_sysreg(val, reg_name##6_el1); \
break; \
case 7: \
write_sysreg(val, reg_name##7_el1); \
break; \
case 8: \
write_sysreg(val, reg_name##8_el1); \
break; \
case 9: \
write_sysreg(val, reg_name##9_el1); \
break; \
case 10: \
write_sysreg(val, reg_name##10_el1); \
break; \
case 11: \
write_sysreg(val, reg_name##11_el1); \
break; \
case 12: \
write_sysreg(val, reg_name##12_el1); \
break; \
case 13: \
write_sysreg(val, reg_name##13_el1); \
break; \
case 14: \
write_sysreg(val, reg_name##14_el1); \
break; \
case 15: \
write_sysreg(val, reg_name##15_el1); \
break; \
default: \
GUEST_ASSERT(0); \
} \
}
/* Define write_dbgbcr()/write_dbgbvr()/write_dbgwcr()/write_dbgwvr() */
GEN_DEBUG_WRITE_REG(dbgbcr)
GEN_DEBUG_WRITE_REG(dbgbvr)
GEN_DEBUG_WRITE_REG(dbgwcr)
GEN_DEBUG_WRITE_REG(dbgwvr)
static void reset_debug_state(void)
{
u8 brps, wrps, i;
u64 dfr0;
asm volatile("msr daifset, #8");
write_sysreg(0, osdlr_el1);
write_sysreg(0, oslar_el1);
isb();
write_sysreg(0, mdscr_el1);
write_sysreg(0, contextidr_el1);
/* Reset all bcr/bvr/wcr/wvr registers */
dfr0 = read_sysreg(id_aa64dfr0_el1);
brps = FIELD_GET(ID_AA64DFR0_EL1_BRPs, dfr0);
for (i = 0; i <= brps; i++) {
write_dbgbcr(i, 0);
write_dbgbvr(i, 0);
}
wrps = FIELD_GET(ID_AA64DFR0_EL1_WRPs, dfr0);
for (i = 0; i <= wrps; i++) {
write_dbgwcr(i, 0);
write_dbgwvr(i, 0);
}
isb();
}
static void enable_os_lock(void)
{
write_sysreg(1, oslar_el1);
isb();
GUEST_ASSERT(read_sysreg(oslsr_el1) & 2);
}
static void enable_monitor_debug_exceptions(void)
{
u64 mdscr;
asm volatile("msr daifclr, #8");
mdscr = read_sysreg(mdscr_el1) | MDSCR_KDE | MDSCR_MDE;
write_sysreg(mdscr, mdscr_el1);
isb();
}
static void install_wp(u8 wpn, u64 addr)
{
u32 wcr;
wcr = DBGWCR_LEN8 | DBGWCR_RD | DBGWCR_WR | DBGWCR_EL1 | DBGWCR_E;
write_dbgwcr(wpn, wcr);
write_dbgwvr(wpn, addr);
isb();
enable_monitor_debug_exceptions();
}
static void install_hw_bp(u8 bpn, u64 addr)
{
u32 bcr;
bcr = DBGBCR_LEN8 | DBGBCR_EXEC | DBGBCR_EL1 | DBGBCR_E;
write_dbgbcr(bpn, bcr);
write_dbgbvr(bpn, addr);
isb();
enable_monitor_debug_exceptions();
}
static void install_wp_ctx(u8 addr_wp, u8 ctx_bp, u64 addr, u64 ctx)
{
u32 wcr;
u64 ctx_bcr;
/* Setup a context-aware breakpoint for Linked Context ID Match */
ctx_bcr = DBGBCR_LEN8 | DBGBCR_EXEC | DBGBCR_EL1 | DBGBCR_E |
DBGBCR_BT_CTX_LINK;
write_dbgbcr(ctx_bp, ctx_bcr);
write_dbgbvr(ctx_bp, ctx);
/* Setup a linked watchpoint (linked to the context-aware breakpoint) */
wcr = DBGWCR_LEN8 | DBGWCR_RD | DBGWCR_WR | DBGWCR_EL1 | DBGWCR_E |
DBGWCR_WT_LINK | ((u32)ctx_bp << DBGWCR_LBN_SHIFT);
write_dbgwcr(addr_wp, wcr);
write_dbgwvr(addr_wp, addr);
isb();
enable_monitor_debug_exceptions();
}
void install_hw_bp_ctx(u8 addr_bp, u8 ctx_bp, u64 addr, u64 ctx)
{
u32 addr_bcr, ctx_bcr;
/* Setup a context-aware breakpoint for Linked Context ID Match */
ctx_bcr = DBGBCR_LEN8 | DBGBCR_EXEC | DBGBCR_EL1 | DBGBCR_E |
DBGBCR_BT_CTX_LINK;
write_dbgbcr(ctx_bp, ctx_bcr);
write_dbgbvr(ctx_bp, ctx);
/*
* Setup a normal breakpoint for Linked Address Match, and link it
* to the context-aware breakpoint.
*/
addr_bcr = DBGBCR_LEN8 | DBGBCR_EXEC | DBGBCR_EL1 | DBGBCR_E |
DBGBCR_BT_ADDR_LINK_CTX |
((u32)ctx_bp << DBGBCR_LBN_SHIFT);
write_dbgbcr(addr_bp, addr_bcr);
write_dbgbvr(addr_bp, addr);
isb();
enable_monitor_debug_exceptions();
}
static void install_ss(void)
{
u64 mdscr;
asm volatile("msr daifclr, #8");
mdscr = read_sysreg(mdscr_el1) | MDSCR_KDE | MDSCR_SS;
write_sysreg(mdscr, mdscr_el1);
isb();
}
static volatile char write_data;
static void guest_code(u8 bpn, u8 wpn, u8 ctx_bpn)
{
u64 ctx = 0xabcdef; /* a random context number */
/* Software-breakpoint */
reset_debug_state();
asm volatile("sw_bp: brk #0");
GUEST_ASSERT_EQ(sw_bp_addr, PC(sw_bp));
/* Hardware-breakpoint */
reset_debug_state();
install_hw_bp(bpn, PC(hw_bp));
asm volatile("hw_bp: nop");
GUEST_ASSERT_EQ(hw_bp_addr, PC(hw_bp));
/* Hardware-breakpoint + svc */
reset_debug_state();
install_hw_bp(bpn, PC(bp_svc));
asm volatile("bp_svc: svc #0");
GUEST_ASSERT_EQ(hw_bp_addr, PC(bp_svc));
GUEST_ASSERT_EQ(svc_addr, PC(bp_svc) + 4);
/* Hardware-breakpoint + software-breakpoint */
reset_debug_state();
install_hw_bp(bpn, PC(bp_brk));
asm volatile("bp_brk: brk #0");
GUEST_ASSERT_EQ(sw_bp_addr, PC(bp_brk));
GUEST_ASSERT_EQ(hw_bp_addr, PC(bp_brk));
/* Watchpoint */
reset_debug_state();
install_wp(wpn, PC(write_data));
write_data = 'x';
GUEST_ASSERT_EQ(write_data, 'x');
GUEST_ASSERT_EQ(wp_data_addr, PC(write_data));
/* Single-step */
reset_debug_state();
install_ss();
ss_idx = 0;
asm volatile("ss_start:\n"
"mrs x0, esr_el1\n"
"add x0, x0, #1\n"
"msr daifset, #8\n"
: : : "x0");
GUEST_ASSERT_EQ(ss_addr[0], PC(ss_start));
GUEST_ASSERT_EQ(ss_addr[1], PC(ss_start) + 4);
GUEST_ASSERT_EQ(ss_addr[2], PC(ss_start) + 8);
/* OS Lock does not block software-breakpoint */
reset_debug_state();
enable_os_lock();
sw_bp_addr = 0;
asm volatile("sw_bp2: brk #0");
GUEST_ASSERT_EQ(sw_bp_addr, PC(sw_bp2));
/* OS Lock blocking hardware-breakpoint */
reset_debug_state();
enable_os_lock();
install_hw_bp(bpn, PC(hw_bp2));
hw_bp_addr = 0;
asm volatile("hw_bp2: nop");
GUEST_ASSERT_EQ(hw_bp_addr, 0);
/* OS Lock blocking watchpoint */
reset_debug_state();
enable_os_lock();
write_data = '\0';
wp_data_addr = 0;
install_wp(wpn, PC(write_data));
write_data = 'x';
GUEST_ASSERT_EQ(write_data, 'x');
GUEST_ASSERT_EQ(wp_data_addr, 0);
/* OS Lock blocking single-step */
reset_debug_state();
enable_os_lock();
ss_addr[0] = 0;
install_ss();
ss_idx = 0;
asm volatile("mrs x0, esr_el1\n\t"
"add x0, x0, #1\n\t"
"msr daifset, #8\n\t"
: : : "x0");
GUEST_ASSERT_EQ(ss_addr[0], 0);
/* Linked hardware-breakpoint */
hw_bp_addr = 0;
reset_debug_state();
install_hw_bp_ctx(bpn, ctx_bpn, PC(hw_bp_ctx), ctx);
/* Set context id */
write_sysreg(ctx, contextidr_el1);
isb();
asm volatile("hw_bp_ctx: nop");
write_sysreg(0, contextidr_el1);
GUEST_ASSERT_EQ(hw_bp_addr, PC(hw_bp_ctx));
/* Linked watchpoint */
reset_debug_state();
install_wp_ctx(wpn, ctx_bpn, PC(write_data), ctx);
/* Set context id */
write_sysreg(ctx, contextidr_el1);
isb();
write_data = 'x';
GUEST_ASSERT_EQ(write_data, 'x');
GUEST_ASSERT_EQ(wp_data_addr, PC(write_data));
GUEST_DONE();
}
static void guest_sw_bp_handler(struct ex_regs *regs)
{
sw_bp_addr = regs->pc;
regs->pc += 4;
}
static void guest_hw_bp_handler(struct ex_regs *regs)
{
hw_bp_addr = regs->pc;
regs->pstate |= SPSR_D;
}
static void guest_wp_handler(struct ex_regs *regs)
{
wp_data_addr = read_sysreg(far_el1);
wp_addr = regs->pc;
regs->pstate |= SPSR_D;
}
static void guest_ss_handler(struct ex_regs *regs)
{
__GUEST_ASSERT(ss_idx < 4, "Expected index < 4, got '%lu'", ss_idx);
ss_addr[ss_idx++] = regs->pc;
regs->pstate |= SPSR_SS;
}
static void guest_svc_handler(struct ex_regs *regs)
{
svc_addr = regs->pc;
}
static void guest_code_ss(int test_cnt)
{
u64 i;
u64 bvr, wvr, w_bvr, w_wvr;
for (i = 0; i < test_cnt; i++) {
/* Bits [1:0] of dbg{b,w}vr are RES0 */
w_bvr = i << 2;
w_wvr = i << 2;
/*
* Enable Single Step execution. Note! This _must_ be a bare
* ucall as the ucall() path uses atomic operations to manage
* the ucall structures, and the built-in "atomics" are usually
* implemented via exclusive access instructions. The exlusive
* monitor is cleared on ERET, and so taking debug exceptions
* during a LDREX=>STREX sequence will prevent forward progress
* and hang the guest/test.
*/
GUEST_UCALL_NONE();
/*
* The userspace will verify that the pc is as expected during
* single step execution between iter_ss_begin and iter_ss_end.
*/
asm volatile("iter_ss_begin:nop\n");
write_sysreg(w_bvr, dbgbvr0_el1);
write_sysreg(w_wvr, dbgwvr0_el1);
bvr = read_sysreg(dbgbvr0_el1);
wvr = read_sysreg(dbgwvr0_el1);
/* Userspace disables Single Step when the end is nigh. */
asm volatile("iter_ss_end:\n");
GUEST_ASSERT_EQ(bvr, w_bvr);
GUEST_ASSERT_EQ(wvr, w_wvr);
}
GUEST_DONE();
}
static int debug_version(u64 id_aa64dfr0)
{
return FIELD_GET(ID_AA64DFR0_EL1_DebugVer, id_aa64dfr0);
}
static void test_guest_debug_exceptions(u8 bpn, u8 wpn, u8 ctx_bpn)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(vcpu);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_BRK64, guest_sw_bp_handler);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_BREAKPT_CUR, guest_hw_bp_handler);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_WATCHPT_CUR, guest_wp_handler);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_SOFTSTP_CUR, guest_ss_handler);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_SVC64, guest_svc_handler);
/* Specify bpn/wpn/ctx_bpn to be tested */
vcpu_args_set(vcpu, 3, bpn, wpn, ctx_bpn);
pr_debug("Use bpn#%d, wpn#%d and ctx_bpn#%d\n", bpn, wpn, ctx_bpn);
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_DONE:
goto done;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
}
done:
kvm_vm_free(vm);
}
void test_single_step_from_userspace(int test_cnt)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
struct kvm_run *run;
u64 pc, cmd;
u64 test_pc = 0;
bool ss_enable = false;
struct kvm_guest_debug debug = {};
vm = vm_create_with_one_vcpu(&vcpu, guest_code_ss);
run = vcpu->run;
vcpu_args_set(vcpu, 1, test_cnt);
while (1) {
vcpu_run(vcpu);
if (run->exit_reason != KVM_EXIT_DEBUG) {
cmd = get_ucall(vcpu, &uc);
if (cmd == UCALL_ABORT) {
REPORT_GUEST_ASSERT(uc);
/* NOT REACHED */
} else if (cmd == UCALL_DONE) {
break;
}
TEST_ASSERT(cmd == UCALL_NONE,
"Unexpected ucall cmd 0x%lx", cmd);
debug.control = KVM_GUESTDBG_ENABLE |
KVM_GUESTDBG_SINGLESTEP;
ss_enable = true;
vcpu_guest_debug_set(vcpu, &debug);
continue;
}
TEST_ASSERT(ss_enable, "Unexpected KVM_EXIT_DEBUG");
/* Check if the current pc is expected. */
pc = vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.pc));
TEST_ASSERT(!test_pc || pc == test_pc,
"Unexpected pc 0x%lx (expected 0x%lx)",
pc, test_pc);
if ((pc + 4) == (u64)&iter_ss_end) {
test_pc = 0;
debug.control = KVM_GUESTDBG_ENABLE;
ss_enable = false;
vcpu_guest_debug_set(vcpu, &debug);
continue;
}
/*
* If the current pc is between iter_ss_bgin and
* iter_ss_end, the pc for the next KVM_EXIT_DEBUG should
* be the current pc + 4.
*/
if ((pc >= (u64)&iter_ss_begin) &&
(pc < (u64)&iter_ss_end))
test_pc = pc + 4;
else
test_pc = 0;
}
kvm_vm_free(vm);
}
/*
* Run debug testing using the various breakpoint#, watchpoint# and
* context-aware breakpoint# with the given ID_AA64DFR0_EL1 configuration.
*/
void test_guest_debug_exceptions_all(u64 aa64dfr0)
{
u8 brp_num, wrp_num, ctx_brp_num, normal_brp_num, ctx_brp_base;
int b, w, c;
/* Number of breakpoints */
brp_num = FIELD_GET(ID_AA64DFR0_EL1_BRPs, aa64dfr0) + 1;
__TEST_REQUIRE(brp_num >= 2, "At least two breakpoints are required");
/* Number of watchpoints */
wrp_num = FIELD_GET(ID_AA64DFR0_EL1_WRPs, aa64dfr0) + 1;
/* Number of context aware breakpoints */
ctx_brp_num = FIELD_GET(ID_AA64DFR0_EL1_CTX_CMPs, aa64dfr0) + 1;
pr_debug("%s brp_num:%d, wrp_num:%d, ctx_brp_num:%d\n", __func__,
brp_num, wrp_num, ctx_brp_num);
/* Number of normal (non-context aware) breakpoints */
normal_brp_num = brp_num - ctx_brp_num;
/* Lowest context aware breakpoint number */
ctx_brp_base = normal_brp_num;
/* Run tests with all supported breakpoints/watchpoints */
for (c = ctx_brp_base; c < ctx_brp_base + ctx_brp_num; c++) {
for (b = 0; b < normal_brp_num; b++) {
for (w = 0; w < wrp_num; w++)
test_guest_debug_exceptions(b, w, c);
}
}
}
static void help(char *name)
{
puts("");
printf("Usage: %s [-h] [-i iterations of the single step test]\n", name);
puts("");
exit(0);
}
int main(int argc, char *argv[])
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
int opt;
int ss_iteration = 10000;
u64 aa64dfr0;
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
aa64dfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64DFR0_EL1));
__TEST_REQUIRE(debug_version(aa64dfr0) >= 6,
"Armv8 debug architecture not supported.");
kvm_vm_free(vm);
while ((opt = getopt(argc, argv, "i:")) != -1) {
switch (opt) {
case 'i':
ss_iteration = atoi_positive("Number of iterations", optarg);
break;
case 'h':
default:
help(argv[0]);
break;
}
}
test_guest_debug_exceptions_all(aa64dfr0);
test_single_step_from_userspace(ss_iteration);
return 0;
}
@@ -0,0 +1,415 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* external_abort - Tests for userspace external abort injection
*
* Copyright (c) 2024 Google LLC
*/
#include "processor.h"
#include "test_util.h"
#define MMIO_ADDR 0x8000000ULL
#define EXPECTED_SERROR_ISS (ESR_ELx_ISV | 0x1d1ed)
static u64 expected_abort_pc;
static void expect_sea_handler(struct ex_regs *regs)
{
u64 esr = read_sysreg(esr_el1);
GUEST_ASSERT_EQ(regs->pc, expected_abort_pc);
GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
GUEST_DONE();
}
static void unexpected_dabt_handler(struct ex_regs *regs)
{
GUEST_FAIL("Unexpected data abort at PC: %lx\n", regs->pc);
}
static struct kvm_vm *vm_create_with_dabt_handler(struct kvm_vcpu **vcpu, void *guest_code,
handler_fn dabt_handler)
{
struct kvm_vm *vm = vm_create_with_one_vcpu(vcpu, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(*vcpu);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT, ESR_ELx_EC_DABT_CUR, dabt_handler);
virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
return vm;
}
static void vcpu_inject_sea(struct kvm_vcpu *vcpu)
{
struct kvm_vcpu_events events = {};
events.exception.ext_dabt_pending = true;
vcpu_events_set(vcpu, &events);
}
static bool vcpu_has_ras(struct kvm_vcpu *vcpu)
{
u64 pfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1));
return SYS_FIELD_GET(ID_AA64PFR0_EL1, RAS, pfr0);
}
static bool guest_has_ras(void)
{
return SYS_FIELD_GET(ID_AA64PFR0_EL1, RAS, read_sysreg(id_aa64pfr0_el1));
}
static void vcpu_inject_serror(struct kvm_vcpu *vcpu)
{
struct kvm_vcpu_events events = {};
events.exception.serror_pending = true;
if (vcpu_has_ras(vcpu)) {
events.exception.serror_has_esr = true;
events.exception.serror_esr = EXPECTED_SERROR_ISS;
}
vcpu_events_set(vcpu, &events);
}
static void __vcpu_run_expect(struct kvm_vcpu *vcpu, unsigned int cmd)
{
struct ucall uc;
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
default:
if (uc.cmd == cmd)
return;
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
}
}
static void vcpu_run_expect_done(struct kvm_vcpu *vcpu)
{
__vcpu_run_expect(vcpu, UCALL_DONE);
}
static void vcpu_run_expect_sync(struct kvm_vcpu *vcpu)
{
__vcpu_run_expect(vcpu, UCALL_SYNC);
}
extern char test_mmio_abort_insn;
static noinline void test_mmio_abort_guest(void)
{
WRITE_ONCE(expected_abort_pc, (u64)&test_mmio_abort_insn);
asm volatile("test_mmio_abort_insn:\n\t"
"ldr x0, [%0]\n\t"
: : "r" (MMIO_ADDR) : "x0", "memory");
GUEST_FAIL("MMIO instruction should not retire");
}
/*
* Test that KVM doesn't complete MMIO emulation when userspace has made an
* external abort pending for the instruction.
*/
static void test_mmio_abort(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_abort_guest,
expect_sea_handler);
struct kvm_run *run = vcpu->run;
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR);
TEST_ASSERT_EQ(run->mmio.len, sizeof(unsigned long));
TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read");
vcpu_inject_sea(vcpu);
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
extern char test_mmio_nisv_insn;
static void test_mmio_nisv_guest(void)
{
WRITE_ONCE(expected_abort_pc, (u64)&test_mmio_nisv_insn);
asm volatile("test_mmio_nisv_insn:\n\t"
"ldr x0, [%0], #8\n\t"
: : "r" (MMIO_ADDR) : "x0", "memory");
GUEST_FAIL("MMIO instruction should not retire");
}
/*
* Test that the KVM_RUN ioctl fails for ESR_EL2.ISV=0 MMIO aborts if userspace
* hasn't enabled KVM_CAP_ARM_NISV_TO_USER.
*/
static void test_mmio_nisv(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_nisv_guest,
unexpected_dabt_handler);
TEST_ASSERT(_vcpu_run(vcpu), "Expected nonzero return code from KVM_RUN");
TEST_ASSERT_EQ(errno, ENOSYS);
kvm_vm_free(vm);
}
/*
* Test that ESR_EL2.ISV=0 MMIO aborts reach userspace and that an injected SEA
* reaches the guest.
*/
static void test_mmio_nisv_abort(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_nisv_guest,
expect_sea_handler);
struct kvm_run *run = vcpu->run;
vm_enable_cap(vm, KVM_CAP_ARM_NISV_TO_USER, 1);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_NISV);
TEST_ASSERT_EQ(run->arm_nisv.fault_ipa, MMIO_ADDR);
vcpu_inject_sea(vcpu);
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
static void unexpected_serror_handler(struct ex_regs *regs)
{
GUEST_FAIL("Took unexpected SError exception");
}
static void test_serror_masked_guest(void)
{
GUEST_ASSERT(read_sysreg(isr_el1) & ISR_EL1_A);
isb();
GUEST_DONE();
}
static void test_serror_masked(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_masked_guest,
unexpected_dabt_handler);
vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, unexpected_serror_handler);
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
static void expect_serror_handler(struct ex_regs *regs)
{
u64 esr = read_sysreg(esr_el1);
GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_SERROR);
if (guest_has_ras())
GUEST_ASSERT_EQ(ESR_ELx_ISS(esr), EXPECTED_SERROR_ISS);
GUEST_DONE();
}
static void test_serror_guest(void)
{
GUEST_ASSERT(read_sysreg(isr_el1) & ISR_EL1_A);
local_serror_enable();
isb();
local_serror_disable();
GUEST_FAIL("Should've taken pending SError exception");
}
static void test_serror(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_guest,
unexpected_dabt_handler);
vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, expect_serror_handler);
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
static void expect_sea_s1ptw_handler(struct ex_regs *regs)
{
u64 esr = read_sysreg(esr_el1);
GUEST_ASSERT_EQ(regs->pc, expected_abort_pc);
GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
GUEST_ASSERT_EQ((esr & ESR_ELx_FSC), ESR_ELx_FSC_SEA_TTW(3));
GUEST_DONE();
}
static noinline void test_s1ptw_abort_guest(void)
{
extern char test_s1ptw_abort_insn;
WRITE_ONCE(expected_abort_pc, (u64)&test_s1ptw_abort_insn);
asm volatile("test_s1ptw_abort_insn:\n\t"
"ldr x0, [%0]\n\t"
: : "r" (MMIO_ADDR) : "x0", "memory");
GUEST_FAIL("Load on S1PTW abort should not retire");
}
static void test_s1ptw_abort(void)
{
struct kvm_vcpu *vcpu;
u64 *ptep, bad_pa;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_s1ptw_abort_guest,
expect_sea_s1ptw_handler);
ptep = virt_get_pte_hva_at_level(vm, MMIO_ADDR, 2);
bad_pa = BIT(vm->pa_bits) - vm->page_size;
*ptep &= ~GENMASK(47, 12);
*ptep |= bad_pa;
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
static void test_serror_emulated_guest(void)
{
GUEST_ASSERT(!(read_sysreg(isr_el1) & ISR_EL1_A));
local_serror_enable();
GUEST_SYNC(0);
local_serror_disable();
GUEST_FAIL("Should've taken unmasked SError exception");
}
static void test_serror_emulated(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_emulated_guest,
unexpected_dabt_handler);
vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, expect_serror_handler);
vcpu_run_expect_sync(vcpu);
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
static void test_mmio_ease_guest(void)
{
sysreg_clear_set_s(SYS_SCTLR2_EL1, 0, SCTLR2_EL1_EASE);
isb();
test_mmio_abort_guest();
}
/*
* Test that KVM doesn't complete MMIO emulation when userspace has made an
* external abort pending for the instruction.
*/
static void test_mmio_ease(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_mmio_ease_guest,
unexpected_dabt_handler);
struct kvm_run *run = vcpu->run;
u64 pfr1;
pfr1 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1));
if (!SYS_FIELD_GET(ID_AA64PFR1_EL1, DF2, pfr1)) {
pr_debug("Skipping %s\n", __func__);
return;
}
/*
* SCTLR2_ELx.EASE changes the exception vector to the SError vector but
* doesn't further modify the exception context (e.g. ESR_ELx, FAR_ELx).
*/
vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, expect_sea_handler);
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR);
TEST_ASSERT_EQ(run->mmio.len, sizeof(unsigned long));
TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read");
vcpu_inject_sea(vcpu);
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
static void test_serror_amo_guest(void)
{
/*
* The ISB is entirely unnecessary (and highlights how FEAT_NV2 is borked)
* since the write is redirected to memory. But don't write (intentionally)
* broken code!
*/
sysreg_clear_set(hcr_el2, HCR_EL2_AMO | HCR_EL2_TGE, 0);
isb();
GUEST_SYNC(0);
GUEST_ASSERT(read_sysreg(isr_el1) & ISR_EL1_A);
/*
* KVM treats the effective value of AMO as 1 when
* HCR_EL2.{E2H,TGE} = {1, 0}, meaning the SError will be taken when
* unmasked.
*/
local_serror_enable();
isb();
local_serror_disable();
GUEST_FAIL("Should've taken pending SError exception");
}
static void test_serror_amo(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = vm_create_with_dabt_handler(&vcpu, test_serror_amo_guest,
unexpected_dabt_handler);
vm_install_exception_handler(vm, VECTOR_ERROR_CURRENT, expect_serror_handler);
vcpu_run_expect_sync(vcpu);
vcpu_inject_serror(vcpu);
vcpu_run_expect_done(vcpu);
kvm_vm_free(vm);
}
int main(void)
{
test_mmio_abort();
test_mmio_nisv();
test_mmio_nisv_abort();
test_serror();
test_serror_masked();
test_serror_emulated();
test_mmio_ease();
test_s1ptw_abort();
if (!test_supports_el2())
return 0;
test_serror_amo();
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,71 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* hello_el2 - Basic KVM selftest for VM running at EL2 with E2H=RES1
*
* Copyright 2025 Google LLC
*/
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
#include "ucall.h"
#include <asm/sysreg.h>
static void guest_code(void)
{
u64 mmfr0 = read_sysreg_s(SYS_ID_AA64MMFR0_EL1);
u64 mmfr1 = read_sysreg_s(SYS_ID_AA64MMFR1_EL1);
u64 mmfr4 = read_sysreg_s(SYS_ID_AA64MMFR4_EL1);
u8 e2h0 = SYS_FIELD_GET(ID_AA64MMFR4_EL1, E2H0, mmfr4);
GUEST_ASSERT_EQ(get_current_el(), 2);
GUEST_ASSERT(read_sysreg(hcr_el2) & HCR_EL2_E2H);
GUEST_ASSERT_EQ(SYS_FIELD_GET(ID_AA64MMFR1_EL1, VH, mmfr1),
ID_AA64MMFR1_EL1_VH_IMP);
/*
* Traps of the complete ID register space are IMPDEF without FEAT_FGT,
* which is really annoying to deal with in KVM describing E2H as RES1.
*
* If the implementation doesn't honor the trap then expect the register
* to return all zeros.
*/
if (e2h0 == ID_AA64MMFR4_EL1_E2H0_IMP)
GUEST_ASSERT_EQ(SYS_FIELD_GET(ID_AA64MMFR0_EL1, FGT, mmfr0),
ID_AA64MMFR0_EL1_FGT_NI);
else
GUEST_ASSERT_EQ(e2h0, ID_AA64MMFR4_EL1_E2H0_NI_NV1);
GUEST_DONE();
}
int main(void)
{
struct kvm_vcpu_init init;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
TEST_REQUIRE(kvm_check_cap(KVM_CAP_ARM_EL2));
vm = vm_create(1);
kvm_get_default_vcpu_target(vm, &init);
init.features[0] |= BIT(KVM_ARM_VCPU_HAS_EL2);
vcpu = aarch64_vcpu_add(vm, 0, &init, guest_code);
kvm_arch_vm_finalize_vcpus(vm);
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_DONE:
break;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
default:
TEST_FAIL("Unhandled ucall: %ld\n", uc.cmd);
}
kvm_vm_free(vm);
return 0;
}
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Host SVE: Check FPSIMD/SVE/SME save/restore over KVM_RUN ioctls.
*
* Copyright 2025 Arm, Ltd
*/
#include <errno.h>
#include <signal.h>
#include <sys/auxv.h>
#include <asm/kvm.h>
#include <kvm_util.h>
#include "ucall_common.h"
static void guest_code(void)
{
for (int i = 0; i < 10; i++) {
GUEST_UCALL_NONE();
}
GUEST_DONE();
}
void handle_sigill(int sig, siginfo_t *info, void *ctx)
{
ucontext_t *uctx = ctx;
printf(" < host signal %d >\n", sig);
/*
* Skip the UDF
*/
uctx->uc_mcontext.pc += 4;
}
void register_sigill_handler(void)
{
struct sigaction sa = {
.sa_sigaction = handle_sigill,
.sa_flags = SA_SIGINFO,
};
sigaction(SIGILL, &sa, NULL);
}
static void do_sve_roundtrip(void)
{
unsigned long before, after;
/*
* Set all bits in a predicate register, force a save/restore via a
* SIGILL (which handle_sigill() will recover from), then report
* whether the value has changed.
*/
asm volatile(
" .arch_extension sve\n"
" ptrue p0.B\n"
" cntp %[before], p0, p0.B\n"
" udf #0\n"
" cntp %[after], p0, p0.B\n"
: [before] "=r" (before),
[after] "=r" (after)
:
: "p0"
);
if (before != after) {
TEST_FAIL("Signal roundtrip discarded predicate bits (%ld => %ld)\n",
before, after);
} else {
printf("Signal roundtrip preserved predicate bits (%ld => %ld)\n",
before, after);
}
}
static void test_run(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
bool guest_done = false;
register_sigill_handler();
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
do_sve_roundtrip();
while (!guest_done) {
printf("Running VCPU...\n");
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_NONE:
do_sve_roundtrip();
do_sve_roundtrip();
break;
case UCALL_DONE:
guest_done = true;
break;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
default:
TEST_FAIL("Unexpected guest exit");
}
}
kvm_vm_free(vm);
}
int main(void)
{
/*
* This is testing the host environment, we don't care about
* guest SVE support.
*/
if (!(getauxval(AT_HWCAP) & HWCAP_SVE)) {
printf("SVE not supported\n");
return KSFT_SKIP;
}
test_run();
return 0;
}
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0-only
/* hypercalls: Check the ARM64's psuedo-firmware bitmap register interface.
*
* The test validates the basic hypercall functionalities that are exposed
* via the psuedo-firmware bitmap register. This includes the registers'
* read/write behavior before and after the VM has started, and if the
* hypercalls are properly masked or unmasked to the guest when disabled or
* enabled from the KVM userspace, respectively.
*/
#include <errno.h>
#include <linux/arm-smccc.h>
#include <asm/kvm.h>
#include <kvm_util.h>
#include "processor.h"
#define FW_REG_ULIMIT_VAL(max_feat_bit) (GENMASK(max_feat_bit, 0))
/* Last valid bits of the bitmapped firmware registers */
#define KVM_REG_ARM_STD_BMAP_BIT_MAX 0
#define KVM_REG_ARM_STD_HYP_BMAP_BIT_MAX 0
#define KVM_REG_ARM_VENDOR_HYP_BMAP_BIT_MAX 1
#define KVM_REG_ARM_VENDOR_HYP_BMAP_2_BIT_MAX 1
#define KVM_REG_ARM_STD_BMAP_RESET_VAL FW_REG_ULIMIT_VAL(KVM_REG_ARM_STD_BMAP_BIT_MAX)
#define KVM_REG_ARM_STD_HYP_BMAP_RESET_VAL FW_REG_ULIMIT_VAL(KVM_REG_ARM_STD_HYP_BMAP_BIT_MAX)
#define KVM_REG_ARM_VENDOR_HYP_BMAP_RESET_VAL FW_REG_ULIMIT_VAL(KVM_REG_ARM_VENDOR_HYP_BMAP_BIT_MAX)
#define KVM_REG_ARM_VENDOR_HYP_BMAP_2_RESET_VAL 0
struct kvm_fw_reg_info {
u64 reg; /* Register definition */
u64 max_feat_bit; /* Bit that represents the upper limit of the feature-map */
u64 reset_val; /* Reset value for the register */
};
#define FW_REG_INFO(r) \
{ \
.reg = r, \
.max_feat_bit = r##_BIT_MAX, \
.reset_val = r##_RESET_VAL \
}
static const struct kvm_fw_reg_info fw_reg_info[] = {
FW_REG_INFO(KVM_REG_ARM_STD_BMAP),
FW_REG_INFO(KVM_REG_ARM_STD_HYP_BMAP),
FW_REG_INFO(KVM_REG_ARM_VENDOR_HYP_BMAP),
FW_REG_INFO(KVM_REG_ARM_VENDOR_HYP_BMAP_2),
};
enum test_stage {
TEST_STAGE_REG_IFACE,
TEST_STAGE_HVC_IFACE_FEAT_DISABLED,
TEST_STAGE_HVC_IFACE_FEAT_ENABLED,
TEST_STAGE_HVC_IFACE_FALSE_INFO,
TEST_STAGE_END,
};
static int stage = TEST_STAGE_REG_IFACE;
struct test_hvc_info {
u32 func_id;
u64 arg1;
};
#define TEST_HVC_INFO(f, a1) \
{ \
.func_id = f, \
.arg1 = a1, \
}
static const struct test_hvc_info hvc_info[] = {
/* KVM_REG_ARM_STD_BMAP */
TEST_HVC_INFO(ARM_SMCCC_TRNG_VERSION, 0),
TEST_HVC_INFO(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_TRNG_RND64),
TEST_HVC_INFO(ARM_SMCCC_TRNG_GET_UUID, 0),
TEST_HVC_INFO(ARM_SMCCC_TRNG_RND32, 0),
TEST_HVC_INFO(ARM_SMCCC_TRNG_RND64, 0),
/* KVM_REG_ARM_STD_HYP_BMAP */
TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_HV_PV_TIME_FEATURES),
TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_FEATURES, ARM_SMCCC_HV_PV_TIME_ST),
TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_ST, 0),
/* KVM_REG_ARM_VENDOR_HYP_BMAP */
TEST_HVC_INFO(ARM_SMCCC_VENDOR_HYP_KVM_FEATURES_FUNC_ID,
ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID),
TEST_HVC_INFO(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID, 0),
TEST_HVC_INFO(ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID, KVM_PTP_VIRT_COUNTER),
};
/* Feed false hypercall info to test the KVM behavior */
static const struct test_hvc_info false_hvc_info[] = {
/* Feature support check against a different family of hypercalls */
TEST_HVC_INFO(ARM_SMCCC_TRNG_FEATURES, ARM_SMCCC_VENDOR_HYP_KVM_PTP_FUNC_ID),
TEST_HVC_INFO(ARM_SMCCC_ARCH_FEATURES_FUNC_ID, ARM_SMCCC_TRNG_RND64),
TEST_HVC_INFO(ARM_SMCCC_HV_PV_TIME_FEATURES, ARM_SMCCC_TRNG_RND64),
};
static void guest_test_hvc(const struct test_hvc_info *hc_info)
{
unsigned int i;
struct arm_smccc_res res;
unsigned int hvc_info_arr_sz;
hvc_info_arr_sz =
hc_info == hvc_info ? ARRAY_SIZE(hvc_info) : ARRAY_SIZE(false_hvc_info);
for (i = 0; i < hvc_info_arr_sz; i++, hc_info++) {
memset(&res, 0, sizeof(res));
do_smccc(hc_info->func_id, hc_info->arg1, 0, 0, 0, 0, 0, 0, &res);
switch (stage) {
case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
case TEST_STAGE_HVC_IFACE_FALSE_INFO:
__GUEST_ASSERT(res.a0 == SMCCC_RET_NOT_SUPPORTED,
"a0 = 0x%lx, func_id = 0x%x, arg1 = 0x%lx, stage = %u",
res.a0, hc_info->func_id, hc_info->arg1, stage);
break;
case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
__GUEST_ASSERT(res.a0 != SMCCC_RET_NOT_SUPPORTED,
"a0 = 0x%lx, func_id = 0x%x, arg1 = 0x%lx, stage = %u",
res.a0, hc_info->func_id, hc_info->arg1, stage);
break;
default:
GUEST_FAIL("Unexpected stage = %u", stage);
}
}
}
static void guest_code(void)
{
while (stage != TEST_STAGE_END) {
switch (stage) {
case TEST_STAGE_REG_IFACE:
break;
case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
guest_test_hvc(hvc_info);
break;
case TEST_STAGE_HVC_IFACE_FALSE_INFO:
guest_test_hvc(false_hvc_info);
break;
default:
GUEST_FAIL("Unexpected stage = %u", stage);
}
GUEST_SYNC(stage);
}
GUEST_DONE();
}
struct st_time {
u32 rev;
u32 attr;
u64 st_time;
};
#define STEAL_TIME_SIZE ((sizeof(struct st_time) + 63) & ~63)
#define ST_GPA_BASE (1 << 30)
static void steal_time_init(struct kvm_vcpu *vcpu)
{
u64 st_ipa = (ulong)ST_GPA_BASE;
unsigned int gpages;
gpages = vm_calc_num_guest_pages(VM_MODE_DEFAULT, STEAL_TIME_SIZE);
vm_userspace_mem_region_add(vcpu->vm, VM_MEM_SRC_ANONYMOUS, ST_GPA_BASE, 1, gpages, 0);
vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PVTIME_CTRL,
KVM_ARM_VCPU_PVTIME_IPA, &st_ipa);
}
static void test_fw_regs_before_vm_start(struct kvm_vcpu *vcpu)
{
u64 val;
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(fw_reg_info); i++) {
const struct kvm_fw_reg_info *reg_info = &fw_reg_info[i];
u64 set_val;
/* First 'read' should be the reset value for the reg */
val = vcpu_get_reg(vcpu, reg_info->reg);
TEST_ASSERT(val == reg_info->reset_val,
"Unexpected reset value for reg: 0x%lx; expected: 0x%lx; read: 0x%lx",
reg_info->reg, reg_info->reset_val, val);
if (reg_info->reset_val)
set_val = 0;
else
set_val = FW_REG_ULIMIT_VAL(reg_info->max_feat_bit);
ret = __vcpu_set_reg(vcpu, reg_info->reg, set_val);
TEST_ASSERT(ret == 0,
"Failed to %s all the features of reg: 0x%lx; ret: %d",
(set_val ? "set" : "clear"), reg_info->reg, errno);
val = vcpu_get_reg(vcpu, reg_info->reg);
TEST_ASSERT(val == set_val,
"Expected all the features to be %s for reg: 0x%lx",
(set_val ? "set" : "cleared"), reg_info->reg);
/*
* If the reg has been set, clear it as test_fw_regs_after_vm_start()
* expects it to be cleared.
*/
if (set_val) {
ret = __vcpu_set_reg(vcpu, reg_info->reg, 0);
TEST_ASSERT(ret == 0,
"Failed to clear all the features of reg: 0x%lx; ret: %d",
reg_info->reg, errno);
}
/*
* Test enabling a feature that's not supported.
* Avoid this check if all the bits are occupied.
*/
if (reg_info->max_feat_bit < 63) {
ret = __vcpu_set_reg(vcpu, reg_info->reg, BIT(reg_info->max_feat_bit + 1));
TEST_ASSERT(ret != 0 && errno == EINVAL,
"Unexpected behavior or return value (%d) while setting an unsupported feature for reg: 0x%lx",
errno, reg_info->reg);
}
}
}
static void test_fw_regs_after_vm_start(struct kvm_vcpu *vcpu)
{
u64 val;
unsigned int i;
int ret;
for (i = 0; i < ARRAY_SIZE(fw_reg_info); i++) {
const struct kvm_fw_reg_info *reg_info = &fw_reg_info[i];
/*
* Before starting the VM, the test clears all the bits.
* Check if that's still the case.
*/
val = vcpu_get_reg(vcpu, reg_info->reg);
TEST_ASSERT(val == 0,
"Expected all the features to be cleared for reg: 0x%lx",
reg_info->reg);
/*
* Since the VM has run at least once, KVM shouldn't allow modification of
* the registers and should return EBUSY. Set the registers and check for
* the expected errno.
*/
ret = __vcpu_set_reg(vcpu, reg_info->reg, FW_REG_ULIMIT_VAL(reg_info->max_feat_bit));
TEST_ASSERT(ret != 0 && errno == EBUSY,
"Unexpected behavior or return value (%d) while setting a feature while VM is running for reg: 0x%lx",
errno, reg_info->reg);
}
}
static struct kvm_vm *test_vm_create(struct kvm_vcpu **vcpu)
{
struct kvm_vm *vm;
vm = vm_create_with_one_vcpu(vcpu, guest_code);
steal_time_init(*vcpu);
return vm;
}
static void test_guest_stage(struct kvm_vm **vm, struct kvm_vcpu **vcpu)
{
int prev_stage = stage;
pr_debug("Stage: %d\n", prev_stage);
/* Sync the stage early, the VM might be freed below. */
stage++;
sync_global_to_guest(*vm, stage);
switch (prev_stage) {
case TEST_STAGE_REG_IFACE:
test_fw_regs_after_vm_start(*vcpu);
break;
case TEST_STAGE_HVC_IFACE_FEAT_DISABLED:
/* Start a new VM so that all the features are now enabled by default */
kvm_vm_free(*vm);
*vm = test_vm_create(vcpu);
break;
case TEST_STAGE_HVC_IFACE_FEAT_ENABLED:
case TEST_STAGE_HVC_IFACE_FALSE_INFO:
break;
default:
TEST_FAIL("Unknown test stage: %d", prev_stage);
}
}
static void test_run(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
bool guest_done = false;
vm = test_vm_create(&vcpu);
test_fw_regs_before_vm_start(vcpu);
while (!guest_done) {
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_SYNC:
test_guest_stage(&vm, &vcpu);
break;
case UCALL_DONE:
guest_done = true;
break;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
default:
TEST_FAIL("Unexpected guest exit");
}
}
kvm_vm_free(vm);
}
int main(void)
{
test_run();
return 0;
}
@@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Access all FEAT_IDST-handled registers that depend on more than
* just FEAT_AA64, and fail if we don't get an a trap with an 0x18 EC.
*/
#include <test_util.h>
#include <kvm_util.h>
#include <processor.h>
static volatile bool sys64, undef;
#define __check_sr_read(r) \
({ \
u64 val; \
\
sys64 = false; \
undef = false; \
dsb(sy); \
val = read_sysreg_s(SYS_ ## r); \
val; \
})
/* Fatal checks */
#define check_sr_read(r) \
do { \
__check_sr_read(r); \
__GUEST_ASSERT(!undef, #r " unexpected UNDEF"); \
__GUEST_ASSERT(sys64, #r " didn't trap"); \
} while(0)
static void guest_code(void)
{
check_sr_read(CCSIDR2_EL1);
check_sr_read(SMIDR_EL1);
check_sr_read(GMID_EL1);
GUEST_DONE();
}
static void guest_sys64_handler(struct ex_regs *regs)
{
sys64 = true;
undef = false;
regs->pc += 4;
}
static void guest_undef_handler(struct ex_regs *regs)
{
sys64 = false;
undef = true;
regs->pc += 4;
}
static void test_run_vcpu(struct kvm_vcpu *vcpu)
{
struct ucall uc;
do {
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_PRINTF:
printf("%s", uc.buffer);
break;
case UCALL_DONE:
break;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
}
} while (uc.cmd != UCALL_DONE);
}
static void test_guest_feat_idst(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
/* This VM has no MTE, no SME, no CCIDX */
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(vcpu);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_SYS64, guest_sys64_handler);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_UNKNOWN, guest_undef_handler);
test_run_vcpu(vcpu);
kvm_vm_free(vm);
}
int main(int argc, char *argv[])
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
u64 mmfr2;
test_disable_default_vgic();
vm = vm_create_with_one_vcpu(&vcpu, NULL);
mmfr2 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR2_EL1));
__TEST_REQUIRE(FIELD_GET(ID_AA64MMFR2_EL1_IDS, mmfr2) > 0,
"FEAT_IDST not supported");
kvm_vm_free(vm);
test_guest_feat_idst();
return 0;
}
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0
// Check that nobody has tampered with KVM's UID
#include <errno.h>
#include <linux/arm-smccc.h>
#include <asm/kvm.h>
#include <kvm_util.h>
#include "processor.h"
/*
* Do NOT redefine these constants, or try to replace them with some
* "common" version. They are hardcoded here to detect any potential
* breakage happening in the rest of the kernel.
*
* KVM UID value: 28b46fb6-2ec5-11e9-a9ca-4b564d003a74
*/
#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_0 0xb66fb428U
#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_1 0xe911c52eU
#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_2 0x564bcaa9U
#define ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_3 0x743a004dU
static void guest_code(void)
{
struct arm_smccc_res res = {};
do_smccc(ARM_SMCCC_VENDOR_HYP_CALL_UID_FUNC_ID, 0, 0, 0, 0, 0, 0, 0, &res);
__GUEST_ASSERT(res.a0 == ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_0 &&
res.a1 == ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_1 &&
res.a2 == ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_2 &&
res.a3 == ARM_SMCCC_VENDOR_HYP_UID_KVM_REG_3,
"Unexpected KVM-specific UID %lx %lx %lx %lx\n", res.a0, res.a1, res.a2, res.a3);
GUEST_DONE();
}
int main (int argc, char *argv[])
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
bool guest_done = false;
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
while (!guest_done) {
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_SYNC:
break;
case UCALL_DONE:
guest_done = true;
break;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_PRINTF:
printf("%s", uc.buffer);
break;
default:
TEST_FAIL("Unexpected guest exit");
}
}
kvm_vm_free(vm);
return 0;
}
@@ -0,0 +1,255 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* mmio_sign_ext - Test sign-extending MMIO load emulation (LDRSB/LDRSH/LDRSW)
*
* Copyright (c) 2026 Google LLC
* Author: Fuad Tabba <fuad.tabba@linux.dev>
*/
#include <asm/ptrace.h>
#include "processor.h"
#include "test_util.h"
#define MMIO_ADDR 0x8000000ULL
/* AP[1]: allow unprivileged (EL0) access to a mapping. */
#define PTE_USER BIT(6)
/* SPSR for ERET to EL0t with DAIF masked. */
#define SPSR_EL0 (PSR_MODE_EL0t | PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT)
struct mmio_test {
const char *name;
uint64_t data; /* access-width value, host byte order */
uint8_t len;
uint64_t expected; /* sign-extended result; same for LE and BE */
};
/* Paired 1:1, in order, with the loads in guest_loads_le() and el0_be_loads. */
static const struct mmio_test tests[] = {
/* LDRSB Xt: byte sign-extended to 64 bits */
{ "LDRSB Xt 0xFF", 0xFF, 1, 0xFFFFFFFFFFFFFFFFULL },
{ "LDRSB Xt 0x7F", 0x7F, 1, 0x7FULL },
/* LDRSB Wt: byte sign-extended to 32 bits, upper 32 bits zeroed */
{ "LDRSB Wt 0xFF", 0xFF, 1, 0xFFFFFFFFULL },
{ "LDRSB Wt 0x7F", 0x7F, 1, 0x7FULL },
/* LDRSH Xt: halfword sign-extended to 64 bits */
{ "LDRSH Xt 0x8001", 0x8001, 2, 0xFFFFFFFFFFFF8001ULL },
{ "LDRSH Xt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
/* LDRSH Wt: halfword sign-extended to 32 bits, upper 32 bits zeroed */
{ "LDRSH Wt 0x8001", 0x8001, 2, 0xFFFF8001ULL },
{ "LDRSH Wt 0x7FFF", 0x7FFF, 2, 0x7FFFULL },
/* LDRSW Xt: word sign-extended to 64 bits (no Wt form) */
{ "LDRSW Xt 0x80000001", 0x80000001, 4, 0xFFFFFFFF80000001ULL },
{ "LDRSW Xt 0x7FFFFFFF", 0x7FFFFFFF, 4, 0x7FFFFFFFULL },
};
/* Issue one sign-extending load from MMIO and report the result. */
#define GUEST_LDRS(load) do { \
uint64_t val; \
\
asm volatile(load : "=r"(val) : "r"(MMIO_ADDR) : "memory"); \
GUEST_SYNC(val); \
} while (0)
/* Little-endian pass: loads issued at EL1. */
static void guest_loads_le(void)
{
GUEST_LDRS("ldrsb %0, [%1]");
GUEST_LDRS("ldrsb %0, [%1]");
GUEST_LDRS("ldrsb %w0, [%1]");
GUEST_LDRS("ldrsb %w0, [%1]");
GUEST_LDRS("ldrsh %0, [%1]");
GUEST_LDRS("ldrsh %0, [%1]");
GUEST_LDRS("ldrsh %w0, [%1]");
GUEST_LDRS("ldrsh %w0, [%1]");
GUEST_LDRS("ldrsw %0, [%1]");
GUEST_LDRS("ldrsw %0, [%1]");
}
/*
* Run the big-endian loads at EL0, where SCTLR_EL1.E0E flips only the data
* endianness; at EL1, SCTLR_EL1.EE would also flip the page-table walk and
* fault on the little-endian tables. x0 holds MMIO_ADDR; results return in
* x19..x28 (tests[] order) via a single SVC.
*/
extern char el0_be_loads[];
asm(
" .pushsection .text, \"ax\"\n"
" .global el0_be_loads\n"
"el0_be_loads:\n"
" ldrsb x19, [x0]\n"
" ldrsb x20, [x0]\n"
" ldrsb w21, [x0]\n"
" ldrsb w22, [x0]\n"
" ldrsh x23, [x0]\n"
" ldrsh x24, [x0]\n"
" ldrsh w25, [x0]\n"
" ldrsh w26, [x0]\n"
" ldrsw x27, [x0]\n"
" ldrsw x28, [x0]\n"
" svc #0\n"
" .popsection\n"
);
/* EL1 handler for the EL0 SVC: report the results, then finish. */
static void el0_svc_handler(struct ex_regs *regs)
{
int i;
for (i = 0; i < ARRAY_SIZE(tests); i++)
GUEST_SYNC(regs->regs[19 + i]);
GUEST_DONE();
}
static bool guest_mixed_endian_el0(void)
{
uint64_t mmfr0 = read_sysreg(id_aa64mmfr0_el1);
return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
}
static void guest_code(void)
{
guest_loads_le();
if (guest_mixed_endian_el0()) {
write_sysreg(read_sysreg(sctlr_el1) | SCTLR_EL1_E0E, sctlr_el1);
isb();
asm volatile(
" msr elr_el1, %[pc]\n"
" msr spsr_el1, %[spsr]\n"
" mov x0, %[mmio]\n"
" isb\n"
" eret\n"
:
: [pc] "r"(el0_be_loads),
[spsr] "r"((uint64_t)SPSR_EL0),
[mmio] "r"(MMIO_ADDR)
: "x0", "memory");
__builtin_unreachable(); /* el0_svc_handler ends the test */
}
GUEST_DONE();
}
static void handle_mmio(struct kvm_run *run, const struct mmio_test *t, bool be)
{
int i;
TEST_ASSERT_EQ(run->mmio.phys_addr, MMIO_ADDR);
TEST_ASSERT(!run->mmio.is_write, "Expected MMIO read for %s", t->name);
TEST_ASSERT_EQ(run->mmio.len, t->len);
memset(run->mmio.data, 0, sizeof(run->mmio.data));
if (be) {
/* The guest reads the device bytes most-significant first. */
for (i = 0; i < t->len; i++)
run->mmio.data[i] = t->data >> (8 * (t->len - 1 - i));
} else {
/* Works because arm64 KVM hosts are always little-endian. */
memcpy(run->mmio.data, &t->data, t->len);
}
}
static void expect_sync(struct kvm_vcpu *vcpu, struct ucall *uc,
const struct mmio_test *t)
{
switch (get_ucall(vcpu, uc)) {
case UCALL_SYNC:
TEST_ASSERT(uc->args[1] == t->expected,
"%s: got %#lx, want %#lx", t->name,
(unsigned long)uc->args[1], (unsigned long)t->expected);
break;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(*uc);
break;
default:
TEST_FAIL("Unexpected ucall for %s", t->name);
}
}
/* OR PTE_USER into the leaf descriptors covering [gva, gva + len). */
static void make_el0_accessible(struct kvm_vm *vm, uint64_t gva, uint64_t len)
{
uint64_t addr;
for (addr = gva & ~((uint64_t)vm->page_size - 1); addr < gva + len;
addr += vm->page_size)
*virt_get_pte_hva(vm, addr) |= PTE_USER;
}
static bool vcpu_mixed_endian_el0(struct kvm_vcpu *vcpu)
{
uint64_t mmfr0 = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64MMFR0_EL1));
return SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGEND, mmfr0) ||
SYS_FIELD_GET(ID_AA64MMFR0_EL1, BIGENDEL0, mmfr0);
}
int main(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
struct ucall uc;
unsigned int i;
bool be;
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
virt_map(vm, MMIO_ADDR, MMIO_ADDR, 1);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(vcpu);
vm_install_sync_handler(vm, VECTOR_SYNC_LOWER_64, ESR_ELx_EC_SVC64,
el0_svc_handler);
be = vcpu_mixed_endian_el0(vcpu);
if (be)
make_el0_accessible(vm, MMIO_ADDR, vm->page_size);
ksft_print_header();
ksft_set_plan(ARRAY_SIZE(tests) * (be ? 2 : 1));
/* Little-endian pass: one load and one result per iteration. */
for (i = 0; i < ARRAY_SIZE(tests); i++) {
const struct mmio_test *t = &tests[i];
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
handle_mmio(vcpu->run, t, false);
vcpu_run(vcpu);
expect_sync(vcpu, &uc, t);
ksft_test_result_pass("%s\n", t->name);
}
if (be) {
/* The EL0 stub issues all the loads, then reports the results. */
for (i = 0; i < ARRAY_SIZE(tests); i++) {
vcpu_run(vcpu);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_MMIO);
handle_mmio(vcpu->run, &tests[i], true);
}
for (i = 0; i < ARRAY_SIZE(tests); i++) {
vcpu_run(vcpu);
expect_sync(vcpu, &uc, &tests[i]);
ksft_test_result_pass("BE %s\n", tests[i].name);
}
}
vcpu_run(vcpu);
TEST_ASSERT(get_ucall(vcpu, &uc) == UCALL_DONE, "Expected UCALL_DONE");
kvm_vm_free(vm);
ksft_finished();
}
@@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0
// Check that, on a GICv3-capable system (GICv3 native, or GICv5 with
// FEAT_GCIE_LEGACY), not configuring GICv3 correctly results in all
// of the sysregs generating an UNDEF exception. Do the same for GICv5
// on a GICv5 host.
#include <test_util.h>
#include <kvm_util.h>
#include <processor.h>
#include <arm64/gic_v5.h>
static volatile bool handled;
#define __check_sr_read(r) \
({ \
u64 val; \
\
handled = false; \
dsb(sy); \
val = read_sysreg_s(SYS_ ## r); \
val; \
})
#define __check_sr_write(r) \
do { \
handled = false; \
dsb(sy); \
write_sysreg_s(0, SYS_ ## r); \
isb(); \
} while (0)
#define __check_gicv5_gicr_op(r) \
({ \
u64 val; \
\
handled = false; \
dsb(sy); \
val = read_sysreg_s(GICV5_OP_GICR_ ## r); \
val; \
})
#define __check_gicv5_gic_op(r) \
do { \
handled = false; \
dsb(sy); \
write_sysreg_s(0, GICV5_OP_GIC_ ## r); \
isb(); \
} while (0)
/* Fatal checks */
#define check_sr_read(r) \
do { \
__check_sr_read(r); \
__GUEST_ASSERT(handled, #r " no read trap"); \
} while (0)
#define check_sr_write(r) \
do { \
__check_sr_write(r); \
__GUEST_ASSERT(handled, #r " no write trap"); \
} while (0)
#define check_sr_rw(r) \
do { \
check_sr_read(r); \
check_sr_write(r); \
} while (0)
#define check_gicv5_gicr_op(r) \
do { \
__check_gicv5_gicr_op(r); \
__GUEST_ASSERT(handled, #r " no read trap"); \
} while (0)
#define check_gicv5_gic_op(r) \
do { \
__check_gicv5_gic_op(r); \
__GUEST_ASSERT(handled, #r " no write trap"); \
} while (0)
static void guest_code_gicv3(void)
{
u64 val;
/*
* Check that we advertise that ID_AA64PFR0_EL1.GIC == 0, having
* hidden the feature at runtime without any other userspace action.
*/
__GUEST_ASSERT(FIELD_GET(ID_AA64PFR0_EL1_GIC,
read_sysreg(id_aa64pfr0_el1)) == 0,
"GICv3 wrongly advertised");
/*
* Access all GICv3 registers, and fail if we don't get an UNDEF.
* Note that we happily access all the APxRn registers without
* checking their existence, as all we want to see is a failure.
*/
check_sr_rw(ICC_PMR_EL1);
check_sr_read(ICC_IAR0_EL1);
check_sr_write(ICC_EOIR0_EL1);
check_sr_rw(ICC_HPPIR0_EL1);
check_sr_rw(ICC_BPR0_EL1);
check_sr_rw(ICC_AP0R0_EL1);
check_sr_rw(ICC_AP0R1_EL1);
check_sr_rw(ICC_AP0R2_EL1);
check_sr_rw(ICC_AP0R3_EL1);
check_sr_rw(ICC_AP1R0_EL1);
check_sr_rw(ICC_AP1R1_EL1);
check_sr_rw(ICC_AP1R2_EL1);
check_sr_rw(ICC_AP1R3_EL1);
check_sr_write(ICC_DIR_EL1);
check_sr_read(ICC_RPR_EL1);
check_sr_write(ICC_SGI1R_EL1);
check_sr_write(ICC_ASGI1R_EL1);
check_sr_write(ICC_SGI0R_EL1);
check_sr_read(ICC_IAR1_EL1);
check_sr_write(ICC_EOIR1_EL1);
check_sr_rw(ICC_HPPIR1_EL1);
check_sr_rw(ICC_BPR1_EL1);
check_sr_rw(ICC_CTLR_EL1);
check_sr_rw(ICC_IGRPEN0_EL1);
check_sr_rw(ICC_IGRPEN1_EL1);
/*
* ICC_SRE_EL1 may not be trappable, as ICC_SRE_EL2.Enable can
* be RAO/WI. Engage in non-fatal accesses, starting with a
* write of 0 to try and disable SRE, and let's see if it
* sticks.
*/
__check_sr_write(ICC_SRE_EL1);
if (!handled)
GUEST_PRINTF("ICC_SRE_EL1 write not trapping (OK)\n");
val = __check_sr_read(ICC_SRE_EL1);
if (!handled) {
__GUEST_ASSERT((val & BIT(0)),
"ICC_SRE_EL1 not trapped but ICC_SRE_EL1.SRE not set\n");
GUEST_PRINTF("ICC_SRE_EL1 read not trapping (OK)\n");
}
GUEST_DONE();
}
static void guest_code_gicv5(void)
{
/*
* Check that we advertise that ID_AA64PFR2_EL1.GCIE == 0, having
* hidden the feature at runtime without any other userspace action.
*/
__GUEST_ASSERT(FIELD_GET(ID_AA64PFR2_EL1_GCIE,
read_sysreg_s(SYS_ID_AA64PFR2_EL1)) == 0,
"GICv5 wrongly advertised");
/*
* Try all GICv5 instructions, and fail if we don't get an UNDEF.
*/
check_gicv5_gic_op(CDAFF);
check_gicv5_gic_op(CDDI);
check_gicv5_gic_op(CDDIS);
check_gicv5_gic_op(CDEN);
check_gicv5_gic_op(CDEOI);
check_gicv5_gic_op(CDHM);
check_gicv5_gic_op(CDPEND);
check_gicv5_gic_op(CDPRI);
check_gicv5_gic_op(CDRCFG);
check_gicv5_gicr_op(CDIA);
check_gicv5_gicr_op(CDNMIA);
/* Check General System Register acccesses */
check_sr_rw(ICC_APR_EL1);
check_sr_rw(ICC_CR0_EL1);
check_sr_read(ICC_HPPIR_EL1);
check_sr_read(ICC_IAFFIDR_EL1);
check_sr_rw(ICC_ICSR_EL1);
check_sr_read(ICC_IDR0_EL1);
check_sr_rw(ICC_PCR_EL1);
/* Check PPI System Register accessess */
check_sr_rw(ICC_PPI_CACTIVER0_EL1);
check_sr_rw(ICC_PPI_CACTIVER1_EL1);
check_sr_rw(ICC_PPI_SACTIVER0_EL1);
check_sr_rw(ICC_PPI_SACTIVER1_EL1);
check_sr_rw(ICC_PPI_CPENDR0_EL1);
check_sr_rw(ICC_PPI_CPENDR1_EL1);
check_sr_rw(ICC_PPI_SPENDR0_EL1);
check_sr_rw(ICC_PPI_SPENDR1_EL1);
check_sr_rw(ICC_PPI_ENABLER0_EL1);
check_sr_rw(ICC_PPI_ENABLER1_EL1);
check_sr_read(ICC_PPI_HMR0_EL1);
check_sr_read(ICC_PPI_HMR1_EL1);
check_sr_rw(ICC_PPI_PRIORITYR0_EL1);
check_sr_rw(ICC_PPI_PRIORITYR1_EL1);
check_sr_rw(ICC_PPI_PRIORITYR2_EL1);
check_sr_rw(ICC_PPI_PRIORITYR3_EL1);
check_sr_rw(ICC_PPI_PRIORITYR4_EL1);
check_sr_rw(ICC_PPI_PRIORITYR5_EL1);
check_sr_rw(ICC_PPI_PRIORITYR6_EL1);
check_sr_rw(ICC_PPI_PRIORITYR7_EL1);
check_sr_rw(ICC_PPI_PRIORITYR8_EL1);
check_sr_rw(ICC_PPI_PRIORITYR9_EL1);
check_sr_rw(ICC_PPI_PRIORITYR10_EL1);
check_sr_rw(ICC_PPI_PRIORITYR11_EL1);
check_sr_rw(ICC_PPI_PRIORITYR12_EL1);
check_sr_rw(ICC_PPI_PRIORITYR13_EL1);
check_sr_rw(ICC_PPI_PRIORITYR14_EL1);
check_sr_rw(ICC_PPI_PRIORITYR15_EL1);
GUEST_DONE();
}
static void guest_undef_handler(struct ex_regs *regs)
{
/* Success, we've gracefully exploded! */
handled = true;
regs->pc += 4;
}
static void test_run_vcpu(struct kvm_vcpu *vcpu)
{
struct ucall uc;
do {
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_PRINTF:
printf("%s", uc.buffer);
break;
case UCALL_DONE:
break;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
}
} while (uc.cmd != UCALL_DONE);
}
static void test_guest_no_vgic(void *guest_code)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
/* Create a VM without a GIC */
vm = vm_create_with_one_vcpu(&vcpu, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(vcpu);
vm_install_sync_handler(vm, VECTOR_SYNC_CURRENT,
ESR_ELx_EC_UNKNOWN, guest_undef_handler);
test_run_vcpu(vcpu);
kvm_vm_free(vm);
}
int main(int argc, char *argv[])
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
bool has_v3, has_v5;
u64 pfr;
test_disable_default_vgic();
vm = vm_create_with_one_vcpu(&vcpu, NULL);
pfr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1));
has_v3 = !!FIELD_GET(ID_AA64PFR0_EL1_GIC, pfr);
pfr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR2_EL1));
has_v5 = !!FIELD_GET(ID_AA64PFR2_EL1_GCIE, pfr);
kvm_vm_free(vm);
__TEST_REQUIRE(has_v3 || has_v5,
"Neither GICv3 nor GICv5 supported.");
if (has_v3) {
pr_info("Testing no-vgic-v3\n");
test_guest_no_vgic(guest_code_gicv3);
} else {
pr_info("No GICv3 support: skipping no-vgic-v3 test\n");
}
if (has_v5) {
pr_info("Testing no-vgic-v5\n");
test_guest_no_vgic(guest_code_gicv5);
} else {
pr_info("No GICv5 support: skipping no-vgic-v5 test\n");
}
return 0;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,289 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* psci_test - Tests relating to KVM's PSCI implementation.
*
* Copyright (c) 2021 Google LLC.
*
* This test includes:
* - A regression test for a race between KVM servicing the PSCI CPU_ON call
* and userspace reading the targeted vCPU's registers.
* - A test for KVM's handling of PSCI SYSTEM_SUSPEND and the associated
* KVM_SYSTEM_EVENT_SUSPEND UAPI.
*/
#include <linux/kernel.h>
#include <linux/psci.h>
#include <asm/cputype.h>
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
#define CPU_ON_ENTRY_ADDR 0xfeedf00dul
#define CPU_ON_CONTEXT_ID 0xdeadc0deul
static u64 psci_cpu_on(u64 target_cpu, u64 entry_addr, u64 context_id)
{
struct arm_smccc_res res;
do_smccc(PSCI_0_2_FN64_CPU_ON, target_cpu, entry_addr, context_id,
0, 0, 0, 0, &res);
return res.a0;
}
static u64 psci_affinity_info(u64 target_affinity, u64 lowest_affinity_level)
{
struct arm_smccc_res res;
do_smccc(PSCI_0_2_FN64_AFFINITY_INFO, target_affinity, lowest_affinity_level,
0, 0, 0, 0, 0, &res);
return res.a0;
}
static u64 psci_system_suspend(u64 entry_addr, u64 context_id)
{
struct arm_smccc_res res;
do_smccc(PSCI_1_0_FN64_SYSTEM_SUSPEND, entry_addr, context_id,
0, 0, 0, 0, 0, &res);
return res.a0;
}
static u64 psci_system_off2(u64 type, u64 cookie)
{
struct arm_smccc_res res;
do_smccc(PSCI_1_3_FN64_SYSTEM_OFF2, type, cookie, 0, 0, 0, 0, 0, &res);
return res.a0;
}
static u64 psci_features(u32 func_id)
{
struct arm_smccc_res res;
do_smccc(PSCI_1_0_FN_PSCI_FEATURES, func_id, 0, 0, 0, 0, 0, 0, &res);
return res.a0;
}
static void vcpu_power_off(struct kvm_vcpu *vcpu)
{
struct kvm_mp_state mp_state = {
.mp_state = KVM_MP_STATE_STOPPED,
};
vcpu_mp_state_set(vcpu, &mp_state);
}
static struct kvm_vm *setup_vm(void *guest_code, struct kvm_vcpu **source,
struct kvm_vcpu **target)
{
struct kvm_vcpu_init init;
struct kvm_vm *vm;
vm = vm_create(2);
kvm_get_default_vcpu_target(vm, &init);
init.features[0] |= (1 << KVM_ARM_VCPU_PSCI_0_2);
*source = aarch64_vcpu_add(vm, 0, &init, guest_code);
*target = aarch64_vcpu_add(vm, 1, &init, guest_code);
kvm_arch_vm_finalize_vcpus(vm);
return vm;
}
static void enter_guest(struct kvm_vcpu *vcpu)
{
struct ucall uc;
vcpu_run(vcpu);
if (get_ucall(vcpu, &uc) == UCALL_ABORT)
REPORT_GUEST_ASSERT(uc);
}
static void assert_vcpu_reset(struct kvm_vcpu *vcpu)
{
u64 obs_pc, obs_x0;
obs_pc = vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.pc));
obs_x0 = vcpu_get_reg(vcpu, ARM64_CORE_REG(regs.regs[0]));
TEST_ASSERT(obs_pc == CPU_ON_ENTRY_ADDR,
"unexpected target cpu pc: %lx (expected: %lx)",
obs_pc, CPU_ON_ENTRY_ADDR);
TEST_ASSERT(obs_x0 == CPU_ON_CONTEXT_ID,
"unexpected target context id: %lx (expected: %lx)",
obs_x0, CPU_ON_CONTEXT_ID);
}
static void guest_test_cpu_on(u64 target_cpu)
{
u64 target_state;
GUEST_ASSERT(!psci_cpu_on(target_cpu, CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID));
do {
target_state = psci_affinity_info(target_cpu, 0);
GUEST_ASSERT((target_state == PSCI_0_2_AFFINITY_LEVEL_ON) ||
(target_state == PSCI_0_2_AFFINITY_LEVEL_OFF));
} while (target_state != PSCI_0_2_AFFINITY_LEVEL_ON);
GUEST_DONE();
}
static void host_test_cpu_on(void)
{
struct kvm_vcpu *source, *target;
u64 target_mpidr;
struct kvm_vm *vm;
struct ucall uc;
vm = setup_vm(guest_test_cpu_on, &source, &target);
/*
* make sure the target is already off when executing the test.
*/
vcpu_power_off(target);
target_mpidr = vcpu_get_reg(target, KVM_ARM64_SYS_REG(SYS_MPIDR_EL1));
vcpu_args_set(source, 1, target_mpidr & MPIDR_HWID_BITMASK);
enter_guest(source);
if (get_ucall(source, &uc) != UCALL_DONE)
TEST_FAIL("Unhandled ucall: %lu", uc.cmd);
assert_vcpu_reset(target);
kvm_vm_free(vm);
}
static void guest_test_system_suspend(void)
{
u64 ret;
/* assert that SYSTEM_SUSPEND is discoverable */
GUEST_ASSERT(!psci_features(PSCI_1_0_FN_SYSTEM_SUSPEND));
GUEST_ASSERT(!psci_features(PSCI_1_0_FN64_SYSTEM_SUSPEND));
ret = psci_system_suspend(CPU_ON_ENTRY_ADDR, CPU_ON_CONTEXT_ID);
GUEST_SYNC(ret);
}
static void host_test_system_suspend(void)
{
struct kvm_vcpu *source, *target;
struct kvm_run *run;
struct kvm_vm *vm;
vm = setup_vm(guest_test_system_suspend, &source, &target);
vm_enable_cap(vm, KVM_CAP_ARM_SYSTEM_SUSPEND, 0);
vcpu_power_off(target);
run = source->run;
enter_guest(source);
TEST_ASSERT_KVM_EXIT_REASON(source, KVM_EXIT_SYSTEM_EVENT);
TEST_ASSERT(run->system_event.type == KVM_SYSTEM_EVENT_SUSPEND,
"Unhandled system event: %u (expected: %u)",
run->system_event.type, KVM_SYSTEM_EVENT_SUSPEND);
kvm_vm_free(vm);
}
static void guest_test_system_off2(void)
{
u64 ret;
/* assert that SYSTEM_OFF2 is discoverable */
GUEST_ASSERT(psci_features(PSCI_1_3_FN_SYSTEM_OFF2) &
PSCI_1_3_OFF_TYPE_HIBERNATE_OFF);
GUEST_ASSERT(psci_features(PSCI_1_3_FN64_SYSTEM_OFF2) &
PSCI_1_3_OFF_TYPE_HIBERNATE_OFF);
/* With non-zero 'cookie' field, it should fail */
ret = psci_system_off2(PSCI_1_3_OFF_TYPE_HIBERNATE_OFF, 1);
GUEST_ASSERT(ret == PSCI_RET_INVALID_PARAMS);
/*
* This would normally never return, so KVM sets the return value
* to PSCI_RET_INTERNAL_FAILURE. The test case *does* return, so
* that it can test both values for HIBERNATE_OFF.
*/
ret = psci_system_off2(PSCI_1_3_OFF_TYPE_HIBERNATE_OFF, 0);
GUEST_ASSERT(ret == PSCI_RET_INTERNAL_FAILURE);
/*
* Revision F.b of the PSCI v1.3 specification documents zero as an
* alias for HIBERNATE_OFF, since that's the value used in earlier
* revisions of the spec and some implementations in the field.
*/
ret = psci_system_off2(0, 1);
GUEST_ASSERT(ret == PSCI_RET_INVALID_PARAMS);
ret = psci_system_off2(0, 0);
GUEST_ASSERT(ret == PSCI_RET_INTERNAL_FAILURE);
GUEST_DONE();
}
static void host_test_system_off2(void)
{
struct kvm_vcpu *source, *target;
struct kvm_mp_state mps;
u64 psci_version = 0;
int nr_shutdowns = 0;
struct kvm_run *run;
struct ucall uc;
setup_vm(guest_test_system_off2, &source, &target);
psci_version = vcpu_get_reg(target, KVM_REG_ARM_PSCI_VERSION);
TEST_ASSERT(psci_version >= PSCI_VERSION(1, 3),
"Unexpected PSCI version %lu.%lu",
PSCI_VERSION_MAJOR(psci_version),
PSCI_VERSION_MINOR(psci_version));
vcpu_power_off(target);
run = source->run;
enter_guest(source);
while (run->exit_reason == KVM_EXIT_SYSTEM_EVENT) {
TEST_ASSERT(run->system_event.type == KVM_SYSTEM_EVENT_SHUTDOWN,
"Unhandled system event: %u (expected: %u)",
run->system_event.type, KVM_SYSTEM_EVENT_SHUTDOWN);
TEST_ASSERT(run->system_event.ndata >= 1,
"Unexpected amount of system event data: %u (expected, >= 1)",
run->system_event.ndata);
TEST_ASSERT(run->system_event.data[0] & KVM_SYSTEM_EVENT_SHUTDOWN_FLAG_PSCI_OFF2,
"PSCI_OFF2 flag not set. Flags %llu (expected %llu)",
run->system_event.data[0], KVM_SYSTEM_EVENT_SHUTDOWN_FLAG_PSCI_OFF2);
nr_shutdowns++;
/* Restart the vCPU */
mps.mp_state = KVM_MP_STATE_RUNNABLE;
vcpu_mp_state_set(source, &mps);
enter_guest(source);
}
TEST_ASSERT(get_ucall(source, &uc) == UCALL_DONE, "Guest did not exit cleanly");
TEST_ASSERT(nr_shutdowns == 2, "Two shutdown events were expected, but saw %d", nr_shutdowns);
}
int main(void)
{
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SYSTEM_SUSPEND));
host_test_cpu_on();
host_test_system_suspend();
host_test_system_off2();
return 0;
}
@@ -0,0 +1,328 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Test KVM returns to userspace with KVM_EXIT_ARM_SEA if host APEI fails
* to handle SEA and userspace has opt-ed in KVM_CAP_ARM_SEA_TO_USER.
*
* After reaching userspace with expected arm_sea info, also test userspace
* injecting a synchronous external data abort into the guest.
*
* This test utilizes EINJ to generate a REAL synchronous external data
* abort by consuming a recoverable uncorrectable memory error. Therefore
* the device under test must support EINJ in both firmware and host kernel,
* including the notrigger feature. Otherwise the test will be skipped.
* The under-test platform's APEI should be unable to claim SEA. Otherwise
* the test will also be skipped.
*/
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
#include "guest_modes.h"
#define PAGE_PRESENT (1ULL << 63)
#define PAGE_PHYSICAL 0x007fffffffffffffULL
#define PAGE_ADDR_MASK (~(0xfffULL))
/* Group ISV and ISS[23:14]. */
#define ESR_ELx_INST_SYNDROME ((ESR_ELx_ISV) | (ESR_ELx_SAS) | \
(ESR_ELx_SSE) | (ESR_ELx_SRT_MASK) | \
(ESR_ELx_SF) | (ESR_ELx_AR))
#define EINJ_ETYPE "/sys/kernel/debug/apei/einj/error_type"
#define EINJ_ADDR "/sys/kernel/debug/apei/einj/param1"
#define EINJ_MASK "/sys/kernel/debug/apei/einj/param2"
#define EINJ_FLAGS "/sys/kernel/debug/apei/einj/flags"
#define EINJ_NOTRIGGER "/sys/kernel/debug/apei/einj/notrigger"
#define EINJ_DOIT "/sys/kernel/debug/apei/einj/error_inject"
/* Memory Uncorrectable non-fatal. */
#define ERROR_TYPE_MEMORY_UER 0x10
/* Memory address and mask valid (param1 and param2). */
#define MASK_MEMORY_UER 0b10
/* Guest virtual address region = [2G, 3G). */
#define START_GVA 0x80000000UL
#define VM_MEM_SIZE 0x40000000UL
/* Note: EINJ_OFFSET must < VM_MEM_SIZE. */
#define EINJ_OFFSET 0x01234badUL
#define EINJ_GVA ((START_GVA) + (EINJ_OFFSET))
static gpa_t einj_gpa;
static void *einj_hva;
static u64 einj_hpa;
static bool far_invalid;
static u64 translate_hva_to_hpa(unsigned long hva)
{
u64 pinfo;
s64 offset = hva / getpagesize() * sizeof(pinfo);
int fd;
fd = open("/proc/self/pagemap", O_RDONLY);
if (fd < 0)
ksft_exit_fail_perror("Failed to open /proc/self/pagemap");
if (pread(fd, &pinfo, sizeof(pinfo), offset) != sizeof(pinfo)) {
close(fd);
ksft_exit_fail_perror("Failed to read /proc/self/pagemap");
}
close(fd);
if ((pinfo & PAGE_PRESENT) == 0)
ksft_exit_fail_perror("Page not present");
return ((pinfo & PAGE_PHYSICAL) << MIN_PAGE_SHIFT) +
(hva & (getpagesize() - 1));
}
static void write_einj_entry(const char *einj_path, u64 val)
{
char cmd[256] = {0};
FILE *cmdfile = NULL;
sprintf(cmd, "echo %#lx > %s", val, einj_path);
cmdfile = popen(cmd, "r");
if (pclose(cmdfile) == 0)
ksft_print_msg("echo %#lx > %s - done\n", val, einj_path);
else
ksft_exit_fail_perror("Failed to write EINJ entry");
}
static void inject_uer(u64 hpa)
{
if (access("/sys/firmware/acpi/tables/EINJ", R_OK) == -1)
ksft_test_result_skip("EINJ table no available in firmware");
if (access(EINJ_ETYPE, R_OK | W_OK) == -1)
ksft_test_result_skip("EINJ module probably not loaded?");
write_einj_entry(EINJ_ETYPE, ERROR_TYPE_MEMORY_UER);
write_einj_entry(EINJ_FLAGS, MASK_MEMORY_UER);
write_einj_entry(EINJ_ADDR, hpa);
write_einj_entry(EINJ_MASK, ~0x0UL);
write_einj_entry(EINJ_NOTRIGGER, 1);
write_einj_entry(EINJ_DOIT, 1);
}
/*
* When host APEI successfully claims the SEA caused by guest_code, kernel
* will send SIGBUS signal with BUS_MCEERR_AR to test thread.
*
* We set up this SIGBUS handler to skip the test for that case.
*/
static void sigbus_signal_handler(int sig, siginfo_t *si, void *v)
{
ksft_print_msg("SIGBUS (%d) received, dumping siginfo...\n", sig);
ksft_print_msg("si_signo=%d, si_errno=%d, si_code=%d, si_addr=%p\n",
si->si_signo, si->si_errno, si->si_code, si->si_addr);
if (si->si_code == BUS_MCEERR_AR)
ksft_test_result_skip("SEA is claimed by host APEI\n");
else
ksft_test_result_fail("Exit with signal unhandled\n");
exit(0);
}
static void setup_sigbus_handler(void)
{
struct sigaction act;
memset(&act, 0, sizeof(act));
sigemptyset(&act.sa_mask);
act.sa_sigaction = sigbus_signal_handler;
act.sa_flags = SA_SIGINFO;
TEST_ASSERT(sigaction(SIGBUS, &act, NULL) == 0,
"Failed to setup SIGBUS handler");
}
static void guest_code(void)
{
u64 guest_data;
/* Consumes error will cause a SEA. */
guest_data = *(u64 *)EINJ_GVA;
GUEST_FAIL("Poison not protected by SEA: gva=%#lx, guest_data=%#lx\n",
EINJ_GVA, guest_data);
}
static void expect_sea_handler(struct ex_regs *regs)
{
u64 esr = read_sysreg(esr_el1);
u64 far = read_sysreg(far_el1);
bool expect_far_invalid = far_invalid;
GUEST_PRINTF("Handling Guest SEA\n");
GUEST_PRINTF("ESR_EL1=%#lx, FAR_EL1=%#lx\n", esr, far);
GUEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_CUR);
GUEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
if (expect_far_invalid) {
GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, ESR_ELx_FnV);
GUEST_PRINTF("Guest observed garbage value in FAR\n");
} else {
GUEST_ASSERT_EQ(esr & ESR_ELx_FnV, 0);
GUEST_ASSERT_EQ(far, EINJ_GVA);
}
GUEST_DONE();
}
static void vcpu_inject_sea(struct kvm_vcpu *vcpu)
{
struct kvm_vcpu_events events = {};
events.exception.ext_dabt_pending = true;
vcpu_events_set(vcpu, &events);
}
static void run_vm(struct kvm_vm *vm, struct kvm_vcpu *vcpu)
{
struct ucall uc;
bool guest_done = false;
struct kvm_run *run = vcpu->run;
u64 esr;
/* Resume the vCPU after error injection to consume the error. */
vcpu_run(vcpu);
ksft_print_msg("Dump kvm_run info about KVM_EXIT_%s\n",
exit_reason_str(run->exit_reason));
ksft_print_msg("kvm_run.arm_sea: esr=%#llx, flags=%#llx\n",
run->arm_sea.esr, run->arm_sea.flags);
ksft_print_msg("kvm_run.arm_sea: gva=%#llx, gpa=%#llx\n",
run->arm_sea.gva, run->arm_sea.gpa);
TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_ARM_SEA);
esr = run->arm_sea.esr;
TEST_ASSERT_EQ(ESR_ELx_EC(esr), ESR_ELx_EC_DABT_LOW);
TEST_ASSERT_EQ(esr & ESR_ELx_FSC_TYPE, ESR_ELx_FSC_EXTABT);
TEST_ASSERT_EQ(ESR_ELx_ISS2(esr), 0);
TEST_ASSERT_EQ((esr & ESR_ELx_INST_SYNDROME), 0);
TEST_ASSERT_EQ(esr & ESR_ELx_VNCR, 0);
if (!(esr & ESR_ELx_FnV)) {
ksft_print_msg("Expect gva to match given FnV bit is 0\n");
TEST_ASSERT_EQ(run->arm_sea.gva, EINJ_GVA);
}
if (run->arm_sea.flags & KVM_EXIT_ARM_SEA_FLAG_GPA_VALID) {
ksft_print_msg("Expect gpa to match given KVM_EXIT_ARM_SEA_FLAG_GPA_VALID is set\n");
TEST_ASSERT_EQ(run->arm_sea.gpa, einj_gpa & PAGE_ADDR_MASK);
}
far_invalid = esr & ESR_ELx_FnV;
/* Inject a SEA into guest and expect handled in SEA handler. */
vcpu_inject_sea(vcpu);
/* Expect the guest to reach GUEST_DONE gracefully. */
do {
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_PRINTF:
ksft_print_msg("From guest: %s", uc.buffer);
break;
case UCALL_DONE:
ksft_print_msg("Guest done gracefully!\n");
guest_done = 1;
break;
case UCALL_ABORT:
ksft_print_msg("Guest aborted!\n");
guest_done = 1;
REPORT_GUEST_ASSERT(uc);
break;
default:
TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
}
} while (!guest_done);
}
static struct kvm_vm *vm_create_with_sea_handler(struct kvm_vcpu **vcpu)
{
size_t backing_page_size;
size_t guest_page_size;
size_t alignment;
u64 num_guest_pages;
gpa_t start_gpa;
enum vm_mem_backing_src_type src_type = VM_MEM_SRC_ANONYMOUS_HUGETLB_1GB;
struct kvm_vm *vm;
backing_page_size = get_backing_src_pagesz(src_type);
guest_page_size = vm_guest_mode_params[VM_MODE_DEFAULT].page_size;
alignment = max(backing_page_size, guest_page_size);
num_guest_pages = VM_MEM_SIZE / guest_page_size;
vm = __vm_create_with_one_vcpu(vcpu, num_guest_pages, guest_code);
vm_init_descriptor_tables(vm);
vcpu_init_descriptor_tables(*vcpu);
vm_install_sync_handler(vm,
/*vector=*/VECTOR_SYNC_CURRENT,
/*ec=*/ESR_ELx_EC_DABT_CUR,
/*handler=*/expect_sea_handler);
start_gpa = (vm->max_gfn - num_guest_pages) * guest_page_size;
start_gpa = align_down(start_gpa, alignment);
vm_userspace_mem_region_add(
/*vm=*/vm,
/*src_type=*/src_type,
/*gpa=*/start_gpa,
/*slot=*/1,
/*npages=*/num_guest_pages,
/*flags=*/0);
virt_map(vm, START_GVA, start_gpa, num_guest_pages);
ksft_print_msg("Mapped %#lx pages: gva=%#lx to gpa=%#lx\n",
num_guest_pages, START_GVA, start_gpa);
return vm;
}
static void vm_inject_memory_uer(struct kvm_vm *vm)
{
u64 guest_data;
einj_gpa = addr_gva2gpa(vm, EINJ_GVA);
einj_hva = addr_gva2hva(vm, EINJ_GVA);
/* Populate certain data before injecting UER. */
*(u64 *)einj_hva = 0xBAADCAFE;
guest_data = *(u64 *)einj_hva;
ksft_print_msg("Before EINJect: data=%#lx\n",
guest_data);
einj_hpa = translate_hva_to_hpa((unsigned long)einj_hva);
ksft_print_msg("EINJ_GVA=%#lx, einj_gpa=%#lx, einj_hva=%p, einj_hpa=%#lx\n",
EINJ_GVA, einj_gpa, einj_hva, einj_hpa);
inject_uer(einj_hpa);
ksft_print_msg("Memory UER EINJected\n");
}
int main(int argc, char *argv[])
{
struct kvm_vm *vm;
struct kvm_vcpu *vcpu;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SEA_TO_USER));
setup_sigbus_handler();
vm = vm_create_with_sea_handler(&vcpu);
vm_enable_cap(vm, KVM_CAP_ARM_SEA_TO_USER, 0);
vm_inject_memory_uer(vm);
run_vm(vm, vcpu);
kvm_vm_free(vm);
return 0;
}
@@ -0,0 +1,851 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* set_id_regs - Test for setting ID register from usersapce.
*
* Copyright (c) 2023 Google LLC.
*
*
* Test that KVM supports setting ID registers from userspace and handles the
* feature set correctly.
*/
#include <stdint.h>
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
#include <linux/bitfield.h>
enum ftr_type {
FTR_EXACT, /* Use a predefined safe value */
FTR_LOWER_SAFE, /* Smaller value is safe */
FTR_HIGHER_SAFE, /* Bigger value is safe */
FTR_HIGHER_OR_ZERO_SAFE, /* Bigger value is safe, but 0 is biggest */
FTR_END, /* Mark the last ftr bits */
};
#define FTR_SIGNED true /* Value should be treated as signed */
#define FTR_UNSIGNED false /* Value should be treated as unsigned */
struct reg_ftr_bits {
char *name;
bool sign;
enum ftr_type type;
u8 shift;
u64 mask;
/*
* For FTR_EXACT, safe_val is used as the exact safe value.
* For FTR_LOWER_SAFE, safe_val is used as the minimal safe value.
*/
s64 safe_val;
/* Allowed to be changed by the host after run */
bool mutable;
};
struct test_feature_reg {
u32 reg;
const struct reg_ftr_bits *ftr_bits;
};
#define __REG_FTR_BITS(NAME, SIGNED, TYPE, SHIFT, MASK, SAFE_VAL, MUT) \
{ \
.name = #NAME, \
.sign = SIGNED, \
.type = TYPE, \
.shift = SHIFT, \
.mask = MASK, \
.safe_val = SAFE_VAL, \
.mutable = MUT, \
}
#define REG_FTR_BITS(type, reg, field, safe_val) \
__REG_FTR_BITS(reg##_##field, FTR_UNSIGNED, type, reg##_##field##_SHIFT, \
reg##_##field##_MASK, safe_val, false)
#define REG_FTR_BITS_MUTABLE(type, reg, field, safe_val) \
__REG_FTR_BITS(reg##_##field, FTR_UNSIGNED, type, reg##_##field##_SHIFT, \
reg##_##field##_MASK, safe_val, true)
#define S_REG_FTR_BITS(type, reg, field, safe_val) \
__REG_FTR_BITS(reg##_##field, FTR_SIGNED, type, reg##_##field##_SHIFT, \
reg##_##field##_MASK, safe_val, false)
#define REG_FTR_END \
{ \
.type = FTR_END, \
}
static const struct reg_ftr_bits ftr_id_aa64dfr0_el1[] = {
S_REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64DFR0_EL1, DoubleLock, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64DFR0_EL1, WRPs, 0),
S_REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64DFR0_EL1, PMUVer, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64DFR0_EL1, DebugVer, ID_AA64DFR0_EL1_DebugVer_IMP),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_dfr0_el1[] = {
S_REG_FTR_BITS(FTR_LOWER_SAFE, ID_DFR0_EL1, PerfMon, ID_DFR0_EL1_PerfMon_PMUv3),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_DFR0_EL1, CopDbg, ID_DFR0_EL1_CopDbg_Armv8),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64isar0_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, RNDR, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, TLB, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, TS, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, FHM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, DP, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, SM4, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, SM3, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, SHA3, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, RDM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, ATOMIC, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, CRC32, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, SHA2, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, SHA1, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR0_EL1, AES, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64isar1_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, LS64, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, XS, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, I8MM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, DGH, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, BF16, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, SPECRES, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, SB, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, FRINTTS, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, LRCPC, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, FCMA, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, JSCVT, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR1_EL1, DPB, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64isar2_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR2_EL1, BC, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR2_EL1, RPRES, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR2_EL1, WFxT, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64isar3_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR3_EL1, FPRCVT, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR3_EL1, LSUI, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR3_EL1, LSFE, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ISAR3_EL1, FAMINMAX, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64pfr0_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, CSV3, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, CSV2, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, DIT, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, SEL2, 0),
/* GICv3 support will be forced at run time if available */
REG_FTR_BITS_MUTABLE(FTR_EXACT, ID_AA64PFR0_EL1, GIC, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, EL3, 1),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, EL2, 1),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, EL1, 1),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR0_EL1, EL0, 1),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64pfr1_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, DF2, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, CSV2_frac, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, SSBS, ID_AA64PFR1_EL1_SSBS_NI),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64PFR1_EL1, BT, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64mmfr0_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, ECV, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, EXS, 0),
REG_FTR_BITS(FTR_EXACT, ID_AA64MMFR0_EL1, TGRAN4_2, 1),
REG_FTR_BITS(FTR_EXACT, ID_AA64MMFR0_EL1, TGRAN64_2, 1),
REG_FTR_BITS(FTR_EXACT, ID_AA64MMFR0_EL1, TGRAN16_2, 1),
S_REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, TGRAN4, 0),
S_REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, TGRAN64, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, TGRAN16, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, BIGENDEL0, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, SNSMEM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, BIGEND, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR0_EL1, PARANGE, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64mmfr1_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, TIDCP1, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, AFP, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, HCX, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, ETS, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, TWED, 0),
REG_FTR_BITS(FTR_HIGHER_SAFE, ID_AA64MMFR1_EL1, SpecSEI, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, PAN, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, LO, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, HPDS, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR1_EL1, HAFDBS, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64mmfr2_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, E0PD, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, BBM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, TTL, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, AT, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, ST, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, VARange, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, IESB, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, LSM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, UAO, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR2_EL1, CnP, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64mmfr3_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR3_EL1, S1POE, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR3_EL1, S1PIE, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR3_EL1, SCTLRX, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64MMFR3_EL1, TCRX, 0),
REG_FTR_END,
};
static const struct reg_ftr_bits ftr_id_aa64zfr0_el1[] = {
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, F64MM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, F32MM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, I8MM, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, SM4, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, SHA3, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, BF16, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, BitPerm, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, AES, 0),
REG_FTR_BITS(FTR_LOWER_SAFE, ID_AA64ZFR0_EL1, SVEver, 0),
REG_FTR_END,
};
#define TEST_REG(id, table) \
{ \
.reg = id, \
.ftr_bits = &((table)[0]), \
}
static struct test_feature_reg test_regs[] = {
TEST_REG(SYS_ID_AA64DFR0_EL1, ftr_id_aa64dfr0_el1),
TEST_REG(SYS_ID_DFR0_EL1, ftr_id_dfr0_el1),
TEST_REG(SYS_ID_AA64ISAR0_EL1, ftr_id_aa64isar0_el1),
TEST_REG(SYS_ID_AA64ISAR1_EL1, ftr_id_aa64isar1_el1),
TEST_REG(SYS_ID_AA64ISAR2_EL1, ftr_id_aa64isar2_el1),
TEST_REG(SYS_ID_AA64ISAR3_EL1, ftr_id_aa64isar3_el1),
TEST_REG(SYS_ID_AA64PFR0_EL1, ftr_id_aa64pfr0_el1),
TEST_REG(SYS_ID_AA64PFR1_EL1, ftr_id_aa64pfr1_el1),
TEST_REG(SYS_ID_AA64MMFR0_EL1, ftr_id_aa64mmfr0_el1),
TEST_REG(SYS_ID_AA64MMFR1_EL1, ftr_id_aa64mmfr1_el1),
TEST_REG(SYS_ID_AA64MMFR2_EL1, ftr_id_aa64mmfr2_el1),
TEST_REG(SYS_ID_AA64MMFR3_EL1, ftr_id_aa64mmfr3_el1),
TEST_REG(SYS_ID_AA64ZFR0_EL1, ftr_id_aa64zfr0_el1),
};
#define GUEST_REG_SYNC(id) GUEST_SYNC_ARGS(0, id, read_sysreg_s(id), 0, 0);
static void guest_code(void)
{
GUEST_REG_SYNC(SYS_ID_AA64DFR0_EL1);
GUEST_REG_SYNC(SYS_ID_DFR0_EL1);
GUEST_REG_SYNC(SYS_ID_AA64ISAR0_EL1);
GUEST_REG_SYNC(SYS_ID_AA64ISAR1_EL1);
GUEST_REG_SYNC(SYS_ID_AA64ISAR2_EL1);
GUEST_REG_SYNC(SYS_ID_AA64ISAR3_EL1);
GUEST_REG_SYNC(SYS_ID_AA64PFR0_EL1);
GUEST_REG_SYNC(SYS_ID_AA64PFR1_EL1);
GUEST_REG_SYNC(SYS_ID_AA64MMFR0_EL1);
GUEST_REG_SYNC(SYS_ID_AA64MMFR1_EL1);
GUEST_REG_SYNC(SYS_ID_AA64MMFR2_EL1);
GUEST_REG_SYNC(SYS_ID_AA64MMFR3_EL1);
GUEST_REG_SYNC(SYS_ID_AA64ZFR0_EL1);
GUEST_REG_SYNC(SYS_MPIDR_EL1);
GUEST_REG_SYNC(SYS_CLIDR_EL1);
GUEST_REG_SYNC(SYS_CTR_EL0);
GUEST_REG_SYNC(SYS_MIDR_EL1);
GUEST_REG_SYNC(SYS_REVIDR_EL1);
GUEST_REG_SYNC(SYS_AIDR_EL1);
GUEST_DONE();
}
/* Return a safe value to a given ftr_bits an ftr value */
u64 get_safe_value(const struct reg_ftr_bits *ftr_bits, u64 ftr)
{
u64 ftr_max = ftr_bits->mask >> ftr_bits->shift;
TEST_ASSERT(ftr_max > 1, "This test doesn't support single bit features");
if (ftr_bits->sign == FTR_UNSIGNED) {
switch (ftr_bits->type) {
case FTR_EXACT:
ftr = ftr_bits->safe_val;
break;
case FTR_LOWER_SAFE:
if (ftr > ftr_bits->safe_val)
ftr--;
break;
case FTR_HIGHER_SAFE:
if (ftr < ftr_max)
ftr++;
break;
case FTR_HIGHER_OR_ZERO_SAFE:
if (ftr == ftr_max)
ftr = 0;
else if (ftr != 0)
ftr++;
break;
default:
break;
}
} else if (ftr != ftr_max) {
switch (ftr_bits->type) {
case FTR_EXACT:
ftr = ftr_bits->safe_val;
break;
case FTR_LOWER_SAFE:
if (ftr > ftr_bits->safe_val)
ftr--;
break;
case FTR_HIGHER_SAFE:
if (ftr < ftr_max - 1)
ftr++;
break;
case FTR_HIGHER_OR_ZERO_SAFE:
if (ftr != 0 && ftr != ftr_max - 1)
ftr++;
break;
default:
break;
}
}
return ftr;
}
/* Return an invalid value to a given ftr_bits an ftr value */
u64 get_invalid_value(const struct reg_ftr_bits *ftr_bits, u64 ftr)
{
u64 ftr_max = ftr_bits->mask >> ftr_bits->shift;
TEST_ASSERT(ftr_max > 1, "This test doesn't support single bit features");
if (ftr_bits->sign == FTR_UNSIGNED) {
switch (ftr_bits->type) {
case FTR_EXACT:
ftr = max((u64)ftr_bits->safe_val + 1, ftr + 1);
break;
case FTR_LOWER_SAFE:
ftr++;
break;
case FTR_HIGHER_SAFE:
ftr--;
break;
case FTR_HIGHER_OR_ZERO_SAFE:
if (ftr == 0)
ftr = ftr_max;
else
ftr--;
break;
default:
break;
}
} else if (ftr != ftr_max) {
switch (ftr_bits->type) {
case FTR_EXACT:
ftr = max((u64)ftr_bits->safe_val + 1, ftr + 1);
break;
case FTR_LOWER_SAFE:
ftr++;
break;
case FTR_HIGHER_SAFE:
ftr--;
break;
case FTR_HIGHER_OR_ZERO_SAFE:
if (ftr == 0)
ftr = ftr_max - 1;
else
ftr--;
break;
default:
break;
}
} else {
ftr = 0;
}
return ftr;
}
static u64 test_reg_set_success(struct kvm_vcpu *vcpu, u64 reg,
const struct reg_ftr_bits *ftr_bits)
{
u8 shift = ftr_bits->shift;
u64 mask = ftr_bits->mask;
u64 val, new_val, ftr;
val = vcpu_get_reg(vcpu, reg);
ftr = (val & mask) >> shift;
ftr = get_safe_value(ftr_bits, ftr);
ftr <<= shift;
val &= ~mask;
val |= ftr;
vcpu_set_reg(vcpu, reg, val);
new_val = vcpu_get_reg(vcpu, reg);
TEST_ASSERT_EQ(new_val, val);
return new_val;
}
static void test_reg_set_fail(struct kvm_vcpu *vcpu, u64 reg,
const struct reg_ftr_bits *ftr_bits)
{
u8 shift = ftr_bits->shift;
u64 mask = ftr_bits->mask;
u64 val, old_val, ftr;
int r;
val = vcpu_get_reg(vcpu, reg);
ftr = (val & mask) >> shift;
ftr = get_invalid_value(ftr_bits, ftr);
old_val = val;
ftr <<= shift;
val &= ~mask;
val |= ftr;
r = __vcpu_set_reg(vcpu, reg, val);
TEST_ASSERT(r < 0 && errno == EINVAL,
"Unexpected KVM_SET_ONE_REG error: r=%d, errno=%d", r, errno);
val = vcpu_get_reg(vcpu, reg);
TEST_ASSERT_EQ(val, old_val);
}
static u64 test_reg_vals[KVM_ARM_FEATURE_ID_RANGE_SIZE];
#define encoding_to_range_idx(encoding) \
KVM_ARM_FEATURE_ID_RANGE_IDX(sys_reg_Op0(encoding), sys_reg_Op1(encoding), \
sys_reg_CRn(encoding), sys_reg_CRm(encoding), \
sys_reg_Op2(encoding))
static void test_vm_ftr_id_regs(struct kvm_vcpu *vcpu, bool aarch64_only)
{
u64 masks[KVM_ARM_FEATURE_ID_RANGE_SIZE];
struct reg_mask_range range = {
.addr = (__u64)masks,
};
int ret;
/* KVM should return error when reserved field is not zero */
range.reserved[0] = 1;
ret = __vm_ioctl(vcpu->vm, KVM_ARM_GET_REG_WRITABLE_MASKS, &range);
TEST_ASSERT(ret, "KVM doesn't check invalid parameters.");
/* Get writable masks for feature ID registers */
memset(range.reserved, 0, sizeof(range.reserved));
vm_ioctl(vcpu->vm, KVM_ARM_GET_REG_WRITABLE_MASKS, &range);
for (int i = 0; i < ARRAY_SIZE(test_regs); i++) {
const struct reg_ftr_bits *ftr_bits = test_regs[i].ftr_bits;
u32 reg_id = test_regs[i].reg;
u64 reg = KVM_ARM64_SYS_REG(reg_id);
int idx;
/* Get the index to masks array for the idreg */
idx = encoding_to_range_idx(reg_id);
for (int j = 0; ftr_bits[j].type != FTR_END; j++) {
/* Skip aarch32 reg on aarch64 only system, since they are RAZ/WI. */
if (aarch64_only && sys_reg_CRm(reg_id) < 4) {
ksft_test_result_skip("%s on AARCH64 only system\n",
ftr_bits[j].name);
continue;
}
/* Make sure the feature field is writable */
TEST_ASSERT_EQ(masks[idx] & ftr_bits[j].mask, ftr_bits[j].mask);
test_reg_set_fail(vcpu, reg, &ftr_bits[j]);
test_reg_vals[idx] = test_reg_set_success(vcpu, reg,
&ftr_bits[j]);
ksft_test_result_pass("%s\n", ftr_bits[j].name);
}
}
}
#define MPAM_IDREG_TEST 6
static void test_user_set_mpam_reg(struct kvm_vcpu *vcpu)
{
u64 masks[KVM_ARM_FEATURE_ID_RANGE_SIZE];
struct reg_mask_range range = {
.addr = (__u64)masks,
};
u64 val;
int idx, err;
/*
* If ID_AA64PFR0.MPAM is _not_ officially modifiable and is zero,
* check that if it can be set to 1, (i.e. it is supported by the
* hardware), that it can't be set to other values.
*/
/* Get writable masks for feature ID registers */
memset(range.reserved, 0, sizeof(range.reserved));
vm_ioctl(vcpu->vm, KVM_ARM_GET_REG_WRITABLE_MASKS, &range);
/* Writeable? Nothing to test! */
idx = encoding_to_range_idx(SYS_ID_AA64PFR0_EL1);
if ((masks[idx] & ID_AA64PFR0_EL1_MPAM_MASK) == ID_AA64PFR0_EL1_MPAM_MASK) {
ksft_test_result_skip("ID_AA64PFR0_EL1.MPAM is officially writable, nothing to test\n");
return;
}
/* Get the id register value */
val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1));
/* Try to set MPAM=0. This should always be possible. */
val &= ~ID_AA64PFR0_EL1_MPAM_MASK;
val |= FIELD_PREP(ID_AA64PFR0_EL1_MPAM_MASK, 0);
err = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1), val);
if (err)
ksft_test_result_fail("ID_AA64PFR0_EL1.MPAM=0 was not accepted\n");
else
ksft_test_result_pass("ID_AA64PFR0_EL1.MPAM=0 worked\n");
/* Try to set MPAM=1 */
val &= ~ID_AA64PFR0_EL1_MPAM_MASK;
val |= FIELD_PREP(ID_AA64PFR0_EL1_MPAM_MASK, 1);
err = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1), val);
if (err)
ksft_test_result_skip("ID_AA64PFR0_EL1.MPAM is not writable, nothing to test\n");
else
ksft_test_result_pass("ID_AA64PFR0_EL1.MPAM=1 was writable\n");
/* Try to set MPAM=2 */
val &= ~ID_AA64PFR0_EL1_MPAM_MASK;
val |= FIELD_PREP(ID_AA64PFR0_EL1_MPAM_MASK, 2);
err = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1), val);
if (err)
ksft_test_result_pass("ID_AA64PFR0_EL1.MPAM not arbitrarily modifiable\n");
else
ksft_test_result_fail("ID_AA64PFR0_EL1.MPAM value should not be ignored\n");
/* And again for ID_AA64PFR1_EL1.MPAM_frac */
idx = encoding_to_range_idx(SYS_ID_AA64PFR1_EL1);
if ((masks[idx] & ID_AA64PFR1_EL1_MPAM_frac_MASK) == ID_AA64PFR1_EL1_MPAM_frac_MASK) {
ksft_test_result_skip("ID_AA64PFR1_EL1.MPAM_frac is officially writable, nothing to test\n");
return;
}
/* Get the id register value */
val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1));
/* Try to set MPAM_frac=0. This should always be possible. */
val &= ~ID_AA64PFR1_EL1_MPAM_frac_MASK;
val |= FIELD_PREP(ID_AA64PFR1_EL1_MPAM_frac_MASK, 0);
err = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1), val);
if (err)
ksft_test_result_fail("ID_AA64PFR0_EL1.MPAM_frac=0 was not accepted\n");
else
ksft_test_result_pass("ID_AA64PFR0_EL1.MPAM_frac=0 worked\n");
/* Try to set MPAM_frac=1 */
val &= ~ID_AA64PFR1_EL1_MPAM_frac_MASK;
val |= FIELD_PREP(ID_AA64PFR1_EL1_MPAM_frac_MASK, 1);
err = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1), val);
if (err)
ksft_test_result_skip("ID_AA64PFR1_EL1.MPAM_frac is not writable, nothing to test\n");
else
ksft_test_result_pass("ID_AA64PFR0_EL1.MPAM_frac=1 was writable\n");
/* Try to set MPAM_frac=2 */
val &= ~ID_AA64PFR1_EL1_MPAM_frac_MASK;
val |= FIELD_PREP(ID_AA64PFR1_EL1_MPAM_frac_MASK, 2);
err = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1), val);
if (err)
ksft_test_result_pass("ID_AA64PFR1_EL1.MPAM_frac not arbitrarily modifiable\n");
else
ksft_test_result_fail("ID_AA64PFR1_EL1.MPAM_frac value should not be ignored\n");
}
#define MTE_IDREG_TEST 1
static void test_user_set_mte_reg(struct kvm_vcpu *vcpu)
{
u64 masks[KVM_ARM_FEATURE_ID_RANGE_SIZE];
struct reg_mask_range range = {
.addr = (__u64)masks,
};
u64 val;
u64 mte;
u64 mte_frac;
int idx, err;
val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1));
mte = FIELD_GET(ID_AA64PFR1_EL1_MTE, val);
if (!mte) {
ksft_test_result_skip("MTE capability not supported, nothing to test\n");
return;
}
/* Get writable masks for feature ID registers */
memset(range.reserved, 0, sizeof(range.reserved));
vm_ioctl(vcpu->vm, KVM_ARM_GET_REG_WRITABLE_MASKS, &range);
idx = encoding_to_range_idx(SYS_ID_AA64PFR1_EL1);
if ((masks[idx] & ID_AA64PFR1_EL1_MTE_frac_MASK) == ID_AA64PFR1_EL1_MTE_frac_MASK) {
ksft_test_result_skip("ID_AA64PFR1_EL1.MTE_frac is officially writable, nothing to test\n");
return;
}
/*
* When MTE is supported but MTE_ASYMM is not (ID_AA64PFR1_EL1.MTE == 2)
* ID_AA64PFR1_EL1.MTE_frac == 0xF indicates MTE_ASYNC is unsupported
* and MTE_frac == 0 indicates it is supported.
*
* As MTE_frac was previously unconditionally read as 0, check
* that the set to 0 succeeds but does not change MTE_frac
* from unsupported (0xF) to supported (0).
*
*/
mte_frac = FIELD_GET(ID_AA64PFR1_EL1_MTE_frac, val);
if (mte != ID_AA64PFR1_EL1_MTE_MTE2 ||
mte_frac != ID_AA64PFR1_EL1_MTE_frac_NI) {
ksft_test_result_skip("MTE_ASYNC or MTE_ASYMM are supported, nothing to test\n");
return;
}
/* Try to set MTE_frac=0. */
val &= ~ID_AA64PFR1_EL1_MTE_frac_MASK;
val |= FIELD_PREP(ID_AA64PFR1_EL1_MTE_frac_MASK, 0);
err = __vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1), val);
if (err) {
ksft_test_result_fail("ID_AA64PFR1_EL1.MTE_frac=0 was not accepted\n");
return;
}
val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR1_EL1));
mte_frac = FIELD_GET(ID_AA64PFR1_EL1_MTE_frac, val);
if (mte_frac == ID_AA64PFR1_EL1_MTE_frac_NI)
ksft_test_result_pass("ID_AA64PFR1_EL1.MTE_frac=0 accepted and still 0xF\n");
else
ksft_test_result_pass("ID_AA64PFR1_EL1.MTE_frac no longer 0xF\n");
}
static u64 reset_mutable_bits(u32 id, u64 val)
{
struct test_feature_reg *reg = NULL;
for (int i = 0; i < ARRAY_SIZE(test_regs); i++) {
if (test_regs[i].reg == id) {
reg = &test_regs[i];
break;
}
}
if (!reg)
return val;
for (const struct reg_ftr_bits *bits = reg->ftr_bits; bits->type != FTR_END; bits++) {
if (bits->mutable) {
val &= ~bits->mask;
val |= bits->safe_val << bits->shift;
}
}
return val;
}
static void test_guest_reg_read(struct kvm_vcpu *vcpu)
{
bool done = false;
struct ucall uc;
while (!done) {
u64 val;
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_SYNC:
val = test_reg_vals[encoding_to_range_idx(uc.args[2])];
val = reset_mutable_bits(uc.args[2], val);
/* Make sure the written values are seen by guest */
TEST_ASSERT_EQ(val, reset_mutable_bits(uc.args[2], uc.args[3]));
break;
case UCALL_DONE:
done = true;
break;
default:
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
}
}
}
/* Politely lifted from arch/arm64/include/asm/cache.h */
/* Ctypen, bits[3(n - 1) + 2 : 3(n - 1)], for n = 1 to 7 */
#define CLIDR_CTYPE_SHIFT(level) (3 * (level - 1))
#define CLIDR_CTYPE_MASK(level) (7 << CLIDR_CTYPE_SHIFT(level))
#define CLIDR_CTYPE(clidr, level) \
(((clidr) & CLIDR_CTYPE_MASK(level)) >> CLIDR_CTYPE_SHIFT(level))
static void test_clidr(struct kvm_vcpu *vcpu)
{
u64 clidr;
int level;
clidr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_CLIDR_EL1));
/* find the first empty level in the cache hierarchy */
for (level = 1; level <= 7; level++) {
if (!CLIDR_CTYPE(clidr, level))
break;
}
/*
* If you have a mind-boggling 7 levels of cache, congratulations, you
* get to fix this.
*/
TEST_ASSERT(level <= 7, "can't find an empty level in cache hierarchy");
/* stick in a unified cache level */
clidr |= BIT(2) << CLIDR_CTYPE_SHIFT(level);
vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_CLIDR_EL1), clidr);
test_reg_vals[encoding_to_range_idx(SYS_CLIDR_EL1)] = clidr;
}
static void test_ctr(struct kvm_vcpu *vcpu)
{
u64 ctr;
ctr = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_CTR_EL0));
ctr &= ~CTR_EL0_DIC_MASK;
if (ctr & CTR_EL0_IminLine_MASK)
ctr--;
vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(SYS_CTR_EL0), ctr);
test_reg_vals[encoding_to_range_idx(SYS_CTR_EL0)] = ctr;
}
static void test_id_reg(struct kvm_vcpu *vcpu, u32 id)
{
u64 val;
val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(id));
val++;
vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(id), val);
test_reg_vals[encoding_to_range_idx(id)] = val;
}
static void test_vcpu_ftr_id_regs(struct kvm_vcpu *vcpu)
{
test_clidr(vcpu);
test_ctr(vcpu);
test_id_reg(vcpu, SYS_MPIDR_EL1);
ksft_test_result_pass("%s\n", __func__);
}
static void test_vcpu_non_ftr_id_regs(struct kvm_vcpu *vcpu)
{
test_id_reg(vcpu, SYS_MIDR_EL1);
test_id_reg(vcpu, SYS_REVIDR_EL1);
test_id_reg(vcpu, SYS_AIDR_EL1);
ksft_test_result_pass("%s\n", __func__);
}
static void test_assert_id_reg_unchanged(struct kvm_vcpu *vcpu, u32 encoding)
{
size_t idx = encoding_to_range_idx(encoding);
u64 observed;
observed = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(encoding));
TEST_ASSERT_EQ(reset_mutable_bits(encoding, test_reg_vals[idx]),
reset_mutable_bits(encoding, observed));
}
static void test_reset_preserves_id_regs(struct kvm_vcpu *vcpu)
{
/*
* Calls KVM_ARM_VCPU_INIT behind the scenes, which will do an
* architectural reset of the vCPU.
*/
aarch64_vcpu_setup(vcpu, NULL);
for (int i = 0; i < ARRAY_SIZE(test_regs); i++)
test_assert_id_reg_unchanged(vcpu, test_regs[i].reg);
test_assert_id_reg_unchanged(vcpu, SYS_MPIDR_EL1);
test_assert_id_reg_unchanged(vcpu, SYS_CLIDR_EL1);
test_assert_id_reg_unchanged(vcpu, SYS_CTR_EL0);
test_assert_id_reg_unchanged(vcpu, SYS_MIDR_EL1);
test_assert_id_reg_unchanged(vcpu, SYS_REVIDR_EL1);
test_assert_id_reg_unchanged(vcpu, SYS_AIDR_EL1);
ksft_test_result_pass("%s\n", __func__);
}
int main(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
bool aarch64_only;
u64 val, el0;
int test_cnt, i, j;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_SUPPORTED_REG_MASK_RANGES));
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_WRITABLE_IMP_ID_REGS));
test_wants_mte();
vm = vm_create(1);
vm_enable_cap(vm, KVM_CAP_ARM_WRITABLE_IMP_ID_REGS, 0);
vcpu = vm_vcpu_add(vm, 0, guest_code);
kvm_arch_vm_finalize_vcpus(vm);
/* Check for AARCH64 only system */
val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64PFR0_EL1));
el0 = FIELD_GET(ID_AA64PFR0_EL1_EL0, val);
aarch64_only = (el0 == ID_AA64PFR0_EL1_EL0_IMP);
ksft_print_header();
test_cnt = 3 + MPAM_IDREG_TEST + MTE_IDREG_TEST;
for (i = 0; i < ARRAY_SIZE(test_regs); i++)
for (j = 0; test_regs[i].ftr_bits[j].type != FTR_END; j++)
test_cnt++;
ksft_set_plan(test_cnt);
test_vm_ftr_id_regs(vcpu, aarch64_only);
test_vcpu_ftr_id_regs(vcpu);
test_vcpu_non_ftr_id_regs(vcpu);
test_user_set_mpam_reg(vcpu);
test_user_set_mte_reg(vcpu);
test_guest_reg_read(vcpu);
test_reset_preserves_id_regs(vcpu);
kvm_vm_free(vm);
ksft_finished();
}
@@ -0,0 +1,281 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* smccc_filter - Tests for the SMCCC filter UAPI.
*
* Copyright (c) 2023 Google LLC
*
* This test includes:
* - Tests that the UAPI constraints are upheld by KVM. For example, userspace
* is prevented from filtering the architecture range of SMCCC calls.
* - Test that the filter actions (DENIED, FWD_TO_USER) work as intended.
*/
#include <linux/arm-smccc.h>
#include <linux/psci.h>
#include <stdint.h>
#include "processor.h"
#include "test_util.h"
enum smccc_conduit {
HVC_INSN,
SMC_INSN,
};
static bool test_runs_at_el2(void)
{
struct kvm_vm *vm = vm_create(1);
struct kvm_vcpu_init init;
kvm_get_default_vcpu_target(vm, &init);
kvm_vm_free(vm);
return init.features[0] & BIT(KVM_ARM_VCPU_HAS_EL2);
}
#define for_each_conduit(conduit) \
for (conduit = test_runs_at_el2() ? SMC_INSN : HVC_INSN; \
conduit <= SMC_INSN; conduit++)
static void guest_main(u32 func_id, enum smccc_conduit conduit)
{
struct arm_smccc_res res;
if (conduit == SMC_INSN)
smccc_smc(func_id, 0, 0, 0, 0, 0, 0, 0, &res);
else
smccc_hvc(func_id, 0, 0, 0, 0, 0, 0, 0, &res);
GUEST_SYNC(res.a0);
}
static int __set_smccc_filter(struct kvm_vm *vm, u32 start, u32 nr_functions,
enum kvm_smccc_filter_action action)
{
struct kvm_smccc_filter filter = {
.base = start,
.nr_functions = nr_functions,
.action = action,
};
return __kvm_device_attr_set(vm->fd, KVM_ARM_VM_SMCCC_CTRL,
KVM_ARM_VM_SMCCC_FILTER, &filter);
}
static void set_smccc_filter(struct kvm_vm *vm, u32 start, u32 nr_functions,
enum kvm_smccc_filter_action action)
{
int ret = __set_smccc_filter(vm, start, nr_functions, action);
TEST_ASSERT(!ret, "failed to configure SMCCC filter: %d", ret);
}
static struct kvm_vm *setup_vm(struct kvm_vcpu **vcpu)
{
struct kvm_vcpu_init init;
struct kvm_vm *vm;
vm = vm_create(1);
kvm_get_default_vcpu_target(vm, &init);
/*
* Enable in-kernel emulation of PSCI to ensure that calls are denied
* due to the SMCCC filter, not because of KVM.
*/
init.features[0] |= (1 << KVM_ARM_VCPU_PSCI_0_2);
*vcpu = aarch64_vcpu_add(vm, 0, &init, guest_main);
kvm_arch_vm_finalize_vcpus(vm);
return vm;
}
static void test_pad_must_be_zero(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = setup_vm(&vcpu);
struct kvm_smccc_filter filter = {
.base = PSCI_0_2_FN_PSCI_VERSION,
.nr_functions = 1,
.action = KVM_SMCCC_FILTER_DENY,
.pad = { -1 },
};
int r;
r = __kvm_device_attr_set(vm->fd, KVM_ARM_VM_SMCCC_CTRL,
KVM_ARM_VM_SMCCC_FILTER, &filter);
TEST_ASSERT(r < 0 && errno == EINVAL,
"Setting filter with nonzero padding should return EINVAL");
}
/* Ensure that userspace cannot filter the Arm Architecture SMCCC range */
static void test_filter_reserved_range(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = setup_vm(&vcpu);
u32 smc64_fn;
int r;
r = __set_smccc_filter(vm, ARM_SMCCC_ARCH_WORKAROUND_1,
1, KVM_SMCCC_FILTER_DENY);
TEST_ASSERT(r < 0 && errno == EEXIST,
"Attempt to filter reserved range should return EEXIST");
smc64_fn = ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64,
0, 0);
r = __set_smccc_filter(vm, smc64_fn, 1, KVM_SMCCC_FILTER_DENY);
TEST_ASSERT(r < 0 && errno == EEXIST,
"Attempt to filter reserved range should return EEXIST");
kvm_vm_free(vm);
}
static void test_invalid_nr_functions(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = setup_vm(&vcpu);
int r;
r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 0, KVM_SMCCC_FILTER_DENY);
TEST_ASSERT(r < 0 && errno == EINVAL,
"Attempt to filter 0 functions should return EINVAL");
kvm_vm_free(vm);
}
static void test_overflow_nr_functions(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = setup_vm(&vcpu);
int r;
r = __set_smccc_filter(vm, ~0, ~0, KVM_SMCCC_FILTER_DENY);
TEST_ASSERT(r < 0 && errno == EINVAL,
"Attempt to overflow filter range should return EINVAL");
kvm_vm_free(vm);
}
static void test_reserved_action(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = setup_vm(&vcpu);
int r;
r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, -1);
TEST_ASSERT(r < 0 && errno == EINVAL,
"Attempt to use reserved filter action should return EINVAL");
kvm_vm_free(vm);
}
/* Test that overlapping configurations of the SMCCC filter are rejected */
static void test_filter_overlap(void)
{
struct kvm_vcpu *vcpu;
struct kvm_vm *vm = setup_vm(&vcpu);
int r;
set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, KVM_SMCCC_FILTER_DENY);
r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, KVM_SMCCC_FILTER_DENY);
TEST_ASSERT(r < 0 && errno == EEXIST,
"Attempt to filter already configured range should return EEXIST");
kvm_vm_free(vm);
}
static void expect_call_denied(struct kvm_vcpu *vcpu)
{
struct ucall uc;
if (get_ucall(vcpu, &uc) != UCALL_SYNC)
TEST_FAIL("Unexpected ucall: %lu", uc.cmd);
TEST_ASSERT(uc.args[1] == SMCCC_RET_NOT_SUPPORTED,
"Unexpected SMCCC return code: %lu", uc.args[1]);
}
/* Denied SMCCC calls have a return code of SMCCC_RET_NOT_SUPPORTED */
static void test_filter_denied(void)
{
enum smccc_conduit conduit;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
for_each_conduit(conduit) {
vm = setup_vm(&vcpu);
set_smccc_filter(vm, PSCI_0_2_FN_PSCI_VERSION, 1, KVM_SMCCC_FILTER_DENY);
vcpu_args_set(vcpu, 2, PSCI_0_2_FN_PSCI_VERSION, conduit);
vcpu_run(vcpu);
expect_call_denied(vcpu);
kvm_vm_free(vm);
}
}
static void expect_call_fwd_to_user(struct kvm_vcpu *vcpu, u32 func_id,
enum smccc_conduit conduit)
{
struct kvm_run *run = vcpu->run;
TEST_ASSERT(run->exit_reason == KVM_EXIT_HYPERCALL,
"Unexpected exit reason: %u", run->exit_reason);
TEST_ASSERT(run->hypercall.nr == func_id,
"Unexpected SMCCC function: %llu", run->hypercall.nr);
if (conduit == SMC_INSN)
TEST_ASSERT(run->hypercall.flags & KVM_HYPERCALL_EXIT_SMC,
"KVM_HYPERCALL_EXIT_SMC is not set");
else
TEST_ASSERT(!(run->hypercall.flags & KVM_HYPERCALL_EXIT_SMC),
"KVM_HYPERCALL_EXIT_SMC is set");
}
/* SMCCC calls forwarded to userspace cause KVM_EXIT_HYPERCALL exits */
static void test_filter_fwd_to_user(void)
{
enum smccc_conduit conduit;
struct kvm_vcpu *vcpu;
struct kvm_vm *vm;
for_each_conduit(conduit) {
vm = setup_vm(&vcpu);
set_smccc_filter(vm, PSCI_0_2_FN_PSCI_VERSION, 1, KVM_SMCCC_FILTER_FWD_TO_USER);
vcpu_args_set(vcpu, 2, PSCI_0_2_FN_PSCI_VERSION, conduit);
vcpu_run(vcpu);
expect_call_fwd_to_user(vcpu, PSCI_0_2_FN_PSCI_VERSION, conduit);
kvm_vm_free(vm);
}
}
static bool kvm_supports_smccc_filter(void)
{
struct kvm_vm *vm = vm_create_barebones();
int r;
r = __kvm_has_device_attr(vm->fd, KVM_ARM_VM_SMCCC_CTRL, KVM_ARM_VM_SMCCC_FILTER);
kvm_vm_free(vm);
return !r;
}
int main(void)
{
TEST_REQUIRE(kvm_supports_smccc_filter());
test_pad_must_be_zero();
test_invalid_nr_functions();
test_overflow_nr_functions();
test_reserved_action();
test_filter_reserved_range();
test_filter_overlap();
test_filter_denied();
test_filter_fwd_to_user();
}
@@ -0,0 +1,121 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vcpu_width_config - Test KVM_ARM_VCPU_INIT() with KVM_ARM_VCPU_EL1_32BIT.
*
* Copyright (c) 2022 Google LLC.
*
* This is a test that ensures that non-mixed-width vCPUs (all 64bit vCPUs
* or all 32bit vcPUs) can be configured and mixed-width vCPUs cannot be
* configured.
*/
#include "kvm_util.h"
#include "processor.h"
#include "test_util.h"
/*
* Add a vCPU, run KVM_ARM_VCPU_INIT with @init0, and then
* add another vCPU, and run KVM_ARM_VCPU_INIT with @init1.
*/
static int add_init_2vcpus(struct kvm_vcpu_init *init0,
struct kvm_vcpu_init *init1)
{
struct kvm_vcpu *vcpu0, *vcpu1;
struct kvm_vm *vm;
int ret;
vm = vm_create_barebones();
vcpu0 = __vm_vcpu_add(vm, 0);
ret = __vcpu_ioctl(vcpu0, KVM_ARM_VCPU_INIT, init0);
if (ret)
goto free_exit;
vcpu1 = __vm_vcpu_add(vm, 1);
ret = __vcpu_ioctl(vcpu1, KVM_ARM_VCPU_INIT, init1);
free_exit:
kvm_vm_free(vm);
return ret;
}
/*
* Add two vCPUs, then run KVM_ARM_VCPU_INIT for one vCPU with @init0,
* and run KVM_ARM_VCPU_INIT for another vCPU with @init1.
*/
static int add_2vcpus_init_2vcpus(struct kvm_vcpu_init *init0,
struct kvm_vcpu_init *init1)
{
struct kvm_vcpu *vcpu0, *vcpu1;
struct kvm_vm *vm;
int ret;
vm = vm_create_barebones();
vcpu0 = __vm_vcpu_add(vm, 0);
vcpu1 = __vm_vcpu_add(vm, 1);
ret = __vcpu_ioctl(vcpu0, KVM_ARM_VCPU_INIT, init0);
if (ret)
goto free_exit;
ret = __vcpu_ioctl(vcpu1, KVM_ARM_VCPU_INIT, init1);
free_exit:
kvm_vm_free(vm);
return ret;
}
/*
* Tests that two 64bit vCPUs can be configured, two 32bit vCPUs can be
* configured, and two mixed-width vCPUs cannot be configured.
* Each of those three cases, configure vCPUs in two different orders.
* The one is running KVM_CREATE_VCPU for 2 vCPUs, and then running
* KVM_ARM_VCPU_INIT for them.
* The other is running KVM_CREATE_VCPU and KVM_ARM_VCPU_INIT for a vCPU,
* and then run those commands for another vCPU.
*/
int main(void)
{
struct kvm_vcpu_init init0, init1;
struct kvm_vm *vm;
int ret;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_EL1_32BIT));
/* Get the preferred target type and copy that to init1 for later use */
vm = vm_create_barebones();
vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, &init0);
kvm_vm_free(vm);
init1 = init0;
/* Test with 64bit vCPUs */
ret = add_init_2vcpus(&init0, &init0);
TEST_ASSERT(ret == 0,
"Configuring 64bit EL1 vCPUs failed unexpectedly");
ret = add_2vcpus_init_2vcpus(&init0, &init0);
TEST_ASSERT(ret == 0,
"Configuring 64bit EL1 vCPUs failed unexpectedly");
/* Test with 32bit vCPUs */
init0.features[0] = (1 << KVM_ARM_VCPU_EL1_32BIT);
ret = add_init_2vcpus(&init0, &init0);
TEST_ASSERT(ret == 0,
"Configuring 32bit EL1 vCPUs failed unexpectedly");
ret = add_2vcpus_init_2vcpus(&init0, &init0);
TEST_ASSERT(ret == 0,
"Configuring 32bit EL1 vCPUs failed unexpectedly");
/* Test with mixed-width vCPUs */
init0.features[0] = 0;
init1.features[0] = (1 << KVM_ARM_VCPU_EL1_32BIT);
ret = add_init_2vcpus(&init0, &init1);
TEST_ASSERT(ret != 0,
"Configuring mixed-width vCPUs worked unexpectedly");
ret = add_2vcpus_init_2vcpus(&init0, &init1);
TEST_ASSERT(ret != 0,
"Configuring mixed-width vCPUs worked unexpectedly");
return 0;
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,413 @@
// SPDX-License-Identifier: GPL-2.0
/*
* vgic_lpi_stress - Stress test for KVM's ITS emulation
*
* Copyright (c) 2024 Google LLC
*/
#include <linux/sizes.h>
#include <pthread.h>
#include <stdatomic.h>
#include <sys/sysinfo.h>
#include "kvm_util.h"
#include "gic.h"
#include "gic_v3.h"
#include "gic_v3_its.h"
#include "processor.h"
#include "ucall.h"
#include "vgic.h"
#define TEST_MEMSLOT_INDEX 1
#define GIC_LPI_OFFSET 8192
static size_t nr_iterations = 1000;
static gpa_t gpa_base;
static struct kvm_vm *vm;
static struct kvm_vcpu **vcpus;
static int its_fd;
static struct test_data {
bool request_vcpus_stop;
u32 nr_cpus;
u32 nr_devices;
u32 nr_event_ids;
gpa_t device_table;
gpa_t collection_table;
gpa_t cmdq_base;
void *cmdq_base_va;
gpa_t itt_tables;
gpa_t lpi_prop_table;
gpa_t lpi_pend_tables;
} test_data = {
.nr_cpus = 1,
.nr_devices = 1,
.nr_event_ids = 16,
};
static void guest_irq_handler(struct ex_regs *regs)
{
u32 intid = gic_get_and_ack_irq();
if (intid == IAR_SPURIOUS)
return;
GUEST_ASSERT(intid >= GIC_LPI_OFFSET);
gic_set_eoi(intid);
}
static void guest_setup_its_mappings(void)
{
u32 coll_id, device_id, event_id, intid = GIC_LPI_OFFSET;
u32 nr_events = test_data.nr_event_ids;
u32 nr_devices = test_data.nr_devices;
u32 nr_cpus = test_data.nr_cpus;
for (coll_id = 0; coll_id < nr_cpus; coll_id++)
its_send_mapc_cmd(test_data.cmdq_base_va, coll_id, coll_id, true);
/* Round-robin the LPIs to all of the vCPUs in the VM */
coll_id = 0;
for (device_id = 0; device_id < nr_devices; device_id++) {
gpa_t itt_base = test_data.itt_tables + (device_id * SZ_64K);
its_send_mapd_cmd(test_data.cmdq_base_va, device_id,
itt_base, SZ_64K, true);
for (event_id = 0; event_id < nr_events; event_id++) {
its_send_mapti_cmd(test_data.cmdq_base_va, device_id,
event_id, coll_id, intid++);
coll_id = (coll_id + 1) % test_data.nr_cpus;
}
}
}
static void guest_invalidate_all_rdists(void)
{
int i;
for (i = 0; i < test_data.nr_cpus; i++)
its_send_invall_cmd(test_data.cmdq_base_va, i);
}
static void guest_setup_gic(void)
{
static atomic_int nr_cpus_ready = 0;
u32 cpuid = guest_get_vcpuid();
gic_init(GIC_V3, test_data.nr_cpus);
gic_rdist_enable_lpis(test_data.lpi_prop_table, SZ_64K,
test_data.lpi_pend_tables + (cpuid * SZ_64K));
atomic_fetch_add(&nr_cpus_ready, 1);
if (cpuid > 0)
return;
while (atomic_load(&nr_cpus_ready) < test_data.nr_cpus)
cpu_relax();
its_init(test_data.collection_table, SZ_64K,
test_data.device_table, SZ_64K,
test_data.cmdq_base, SZ_64K);
guest_setup_its_mappings();
guest_invalidate_all_rdists();
/* SYNC to ensure ITS setup is complete */
for (cpuid = 0; cpuid < test_data.nr_cpus; cpuid++)
its_send_sync_cmd(test_data.cmdq_base_va, cpuid);
}
static void guest_code(size_t nr_lpis)
{
guest_setup_gic();
local_irq_enable();
GUEST_SYNC(0);
/*
* Don't use WFI here to avoid blocking the vCPU thread indefinitely and
* never getting the stop signal.
*/
while (!READ_ONCE(test_data.request_vcpus_stop))
cpu_relax();
GUEST_DONE();
}
static void setup_memslot(void)
{
size_t pages;
size_t sz;
/*
* For the ITS:
* - A single level device table
* - A single level collection table
* - The command queue
* - An ITT for each device
*/
sz = (3 + test_data.nr_devices) * SZ_64K;
/*
* For the redistributors:
* - A shared LPI configuration table
* - An LPI pending table for each vCPU
*/
sz += (1 + test_data.nr_cpus) * SZ_64K;
pages = sz / vm->page_size;
gpa_base = ((vm_compute_max_gfn(vm) + 1) * vm->page_size) - sz;
vm_userspace_mem_region_add(vm, VM_MEM_SRC_ANONYMOUS, gpa_base,
TEST_MEMSLOT_INDEX, pages, 0);
}
#define LPI_PROP_DEFAULT_PRIO 0xa0
static void configure_lpis(void)
{
size_t nr_lpis = test_data.nr_devices * test_data.nr_event_ids;
u8 *tbl = addr_gpa2hva(vm, test_data.lpi_prop_table);
size_t i;
for (i = 0; i < nr_lpis; i++) {
tbl[i] = LPI_PROP_DEFAULT_PRIO |
LPI_PROP_GROUP1 |
LPI_PROP_ENABLED;
}
}
static void setup_test_data(void)
{
size_t pages_per_64k = vm_calc_num_guest_pages(vm->mode, SZ_64K);
u32 nr_devices = test_data.nr_devices;
u32 nr_cpus = test_data.nr_cpus;
gpa_t cmdq_base;
test_data.device_table = vm_phy_pages_alloc(vm, pages_per_64k,
gpa_base,
TEST_MEMSLOT_INDEX);
test_data.collection_table = vm_phy_pages_alloc(vm, pages_per_64k,
gpa_base,
TEST_MEMSLOT_INDEX);
cmdq_base = vm_phy_pages_alloc(vm, pages_per_64k, gpa_base,
TEST_MEMSLOT_INDEX);
virt_map(vm, cmdq_base, cmdq_base, pages_per_64k);
test_data.cmdq_base = cmdq_base;
test_data.cmdq_base_va = (void *)cmdq_base;
test_data.itt_tables = vm_phy_pages_alloc(vm, pages_per_64k * nr_devices,
gpa_base, TEST_MEMSLOT_INDEX);
test_data.lpi_prop_table = vm_phy_pages_alloc(vm, pages_per_64k,
gpa_base, TEST_MEMSLOT_INDEX);
configure_lpis();
test_data.lpi_pend_tables = vm_phy_pages_alloc(vm, pages_per_64k * nr_cpus,
gpa_base, TEST_MEMSLOT_INDEX);
sync_global_to_guest(vm, test_data);
}
static void setup_gic(void)
{
its_fd = vgic_its_setup(vm);
}
static void signal_lpi(u32 device_id, u32 event_id)
{
gpa_t db_addr = GITS_BASE_GPA + GITS_TRANSLATER;
struct kvm_msi msi = {
.address_lo = db_addr,
.address_hi = db_addr >> 32,
.data = event_id,
.devid = device_id,
.flags = KVM_MSI_VALID_DEVID,
};
/*
* KVM_SIGNAL_MSI returns 1 if the MSI wasn't 'blocked' by the VM,
* which for arm64 implies having a valid translation in the ITS.
*/
TEST_ASSERT(__vm_ioctl(vm, KVM_SIGNAL_MSI, &msi) == 1,
"KVM_SIGNAL_MSI ioctl failed");
}
static pthread_barrier_t test_setup_barrier;
static void *lpi_worker_thread(void *data)
{
u32 device_id = (size_t)data;
u32 event_id;
size_t i;
pthread_barrier_wait(&test_setup_barrier);
for (i = 0; i < nr_iterations; i++)
for (event_id = 0; event_id < test_data.nr_event_ids; event_id++)
signal_lpi(device_id, event_id);
return NULL;
}
static void *vcpu_worker_thread(void *data)
{
struct kvm_vcpu *vcpu = data;
struct ucall uc;
while (true) {
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_SYNC:
pthread_barrier_wait(&test_setup_barrier);
continue;
case UCALL_DONE:
return NULL;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
default:
TEST_FAIL("Unknown ucall: %lu", uc.cmd);
}
}
return NULL;
}
static void report_stats(struct timespec delta)
{
double nr_lpis;
double time;
nr_lpis = test_data.nr_devices * test_data.nr_event_ids * nr_iterations;
time = delta.tv_sec;
time += ((double)delta.tv_nsec) / NSEC_PER_SEC;
pr_info("Rate: %.2f LPIs/sec\n", nr_lpis / time);
}
static void run_test(void)
{
u32 nr_devices = test_data.nr_devices;
u32 nr_vcpus = test_data.nr_cpus;
pthread_t *lpi_threads = malloc(nr_devices * sizeof(pthread_t));
pthread_t *vcpu_threads = malloc(nr_vcpus * sizeof(pthread_t));
struct timespec start, delta;
size_t i;
TEST_ASSERT(lpi_threads && vcpu_threads, "Failed to allocate pthread arrays");
pthread_barrier_init(&test_setup_barrier, NULL, nr_vcpus + nr_devices + 1);
for (i = 0; i < nr_vcpus; i++)
pthread_create(&vcpu_threads[i], NULL, vcpu_worker_thread, vcpus[i]);
for (i = 0; i < nr_devices; i++)
pthread_create(&lpi_threads[i], NULL, lpi_worker_thread, (void *)i);
pthread_barrier_wait(&test_setup_barrier);
clock_gettime(CLOCK_MONOTONIC, &start);
for (i = 0; i < nr_devices; i++)
pthread_join(lpi_threads[i], NULL);
delta = timespec_elapsed(start);
write_guest_global(vm, test_data.request_vcpus_stop, true);
for (i = 0; i < nr_vcpus; i++)
pthread_join(vcpu_threads[i], NULL);
report_stats(delta);
}
static void setup_vm(void)
{
int i;
vcpus = malloc(test_data.nr_cpus * sizeof(struct kvm_vcpu *));
TEST_ASSERT(vcpus, "Failed to allocate vCPU array");
vm = vm_create_with_vcpus(test_data.nr_cpus, guest_code, vcpus);
vm_init_descriptor_tables(vm);
for (i = 0; i < test_data.nr_cpus; i++)
vcpu_init_descriptor_tables(vcpus[i]);
vm_install_exception_handler(vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
setup_memslot();
setup_gic();
setup_test_data();
}
static void destroy_vm(void)
{
close(its_fd);
kvm_vm_free(vm);
free(vcpus);
}
static void pr_usage(const char *name)
{
pr_info("%s [-v NR_VCPUS] [-d NR_DEVICES] [-e NR_EVENTS] [-i ITERS] -h\n", name);
pr_info(" -v:\tnumber of vCPUs (default: %u)\n", test_data.nr_cpus);
pr_info(" -d:\tnumber of devices (default: %u)\n", test_data.nr_devices);
pr_info(" -e:\tnumber of event IDs per device (default: %u)\n", test_data.nr_event_ids);
pr_info(" -i:\tnumber of iterations (default: %lu)\n", nr_iterations);
}
int main(int argc, char **argv)
{
u32 nr_threads;
int c;
TEST_REQUIRE(kvm_supports_vgic_v3());
while ((c = getopt(argc, argv, "hv:d:e:i:")) != -1) {
switch (c) {
case 'v':
test_data.nr_cpus = atoi(optarg);
break;
case 'd':
test_data.nr_devices = atoi(optarg);
break;
case 'e':
test_data.nr_event_ids = atoi(optarg);
break;
case 'i':
nr_iterations = strtoul(optarg, NULL, 0);
break;
case 'h':
default:
pr_usage(argv[0]);
return 1;
}
}
nr_threads = test_data.nr_cpus + test_data.nr_devices;
if (nr_threads > get_nprocs())
pr_info("WARNING: running %u threads on %d CPUs; performance is degraded.\n",
nr_threads, get_nprocs());
setup_vm();
run_test();
destroy_vm();
return 0;
}
@@ -0,0 +1,224 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/kernel.h>
#include <sys/syscall.h>
#include <asm/kvm.h>
#include <asm/kvm_para.h>
#include <arm64/gic_v5.h>
#include "test_util.h"
#include "kvm_util.h"
#include "processor.h"
#include "vgic.h"
#define NR_VCPUS 1
struct vm_gic {
struct kvm_vm *vm;
int gic_fd;
u32 gic_dev_type;
};
#define GUEST_CMD_IRQ_CDIA 10
#define GUEST_CMD_IRQ_DIEOI 11
#define GUEST_CMD_IS_AWAKE 12
#define GUEST_CMD_IS_READY 13
static void guest_irq_handler(struct ex_regs *regs)
{
bool valid;
u32 hwirq;
u64 ia;
static int count;
/*
* We have pending interrupts. Should never actually enter WFI
* here!
*/
wfi();
GUEST_SYNC(GUEST_CMD_IS_AWAKE);
ia = gicr_insn(CDIA);
valid = GICV5_GICR_CDIA_VALID(ia);
GUEST_SYNC(GUEST_CMD_IRQ_CDIA);
if (!valid)
return;
gsb_ack();
isb();
hwirq = FIELD_GET(GICV5_GICR_CDIA_INTID, ia);
gic_insn(hwirq, CDDI);
gic_insn(0, CDEOI);
GUEST_SYNC(GUEST_CMD_IRQ_DIEOI);
if (++count >= 2)
GUEST_DONE();
/* Ask for the next interrupt to be injected */
GUEST_SYNC(GUEST_CMD_IS_READY);
}
static void guest_code(void)
{
local_irq_disable();
gicv5_cpu_enable_interrupts();
local_irq_enable();
/* Enable the SW_PPI (3) */
write_sysreg_s(BIT_ULL(3), SYS_ICC_PPI_ENABLER0_EL1);
/* Ask for the first interrupt to be injected */
GUEST_SYNC(GUEST_CMD_IS_READY);
/* Loop forever waiting for interrupts */
while (1);
}
/* we don't want to assert on run execution, hence that helper */
static int run_vcpu(struct kvm_vcpu *vcpu)
{
return __vcpu_run(vcpu) ? -errno : 0;
}
static void vm_gic_destroy(struct vm_gic *v)
{
close(v->gic_fd);
kvm_vm_free(v->vm);
}
static void test_vgic_v5_ppis(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
struct ucall uc;
u64 user_ppis[2];
struct vm_gic v;
int ret, i;
v.gic_dev_type = gic_dev_type;
v.vm = __vm_create(VM_SHAPE_DEFAULT, NR_VCPUS, 0);
v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
for (i = 0; i < NR_VCPUS; i++)
vcpus[i] = vm_vcpu_add(v.vm, i, guest_code);
vm_init_descriptor_tables(v.vm);
vm_install_exception_handler(v.vm, VECTOR_IRQ_CURRENT, guest_irq_handler);
for (i = 0; i < NR_VCPUS; i++)
vcpu_init_descriptor_tables(vcpus[i]);
kvm_device_attr_set(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_CTRL_INIT, NULL);
/* Read out the PPIs that user space is allowed to drive. */
kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_USERSPACE_PPIS, &user_ppis);
/* We should always be able to drive the SW_PPI. */
TEST_ASSERT(user_ppis[0] & BIT(GICV5_ARCH_PPI_SW_PPI),
"SW_PPI is not drivable by userspace");
while (1) {
ret = run_vcpu(vcpus[0]);
if (ret)
break;
switch (get_ucall(vcpus[0], &uc)) {
case UCALL_SYNC:
/*
* The guest is ready for the next level change. Set
* high if ready, and lower if it has been consumed.
*/
if (uc.args[1] == GUEST_CMD_IS_READY ||
uc.args[1] == GUEST_CMD_IRQ_DIEOI) {
u64 irq;
bool level = uc.args[1] == GUEST_CMD_IRQ_DIEOI ? 0 : 1;
irq = FIELD_PREP(KVM_ARM_IRQ_NUM_MASK, 3);
irq |= KVM_ARM_IRQ_TYPE_PPI << KVM_ARM_IRQ_TYPE_SHIFT;
kvm_irq_line(v.vm, irq, level);
} else if (uc.args[1] == GUEST_CMD_IS_AWAKE) {
pr_info("Guest skipping WFI due to pending IRQ\n");
} else if (uc.args[1] == GUEST_CMD_IRQ_CDIA) {
pr_info("Guest acknowledged IRQ\n");
}
continue;
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_DONE:
goto done;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
}
}
done:
TEST_ASSERT(ret == 0, "Failed to test GICv5 PPIs");
vm_gic_destroy(&v);
}
/*
* Returns 0 if it's possible to create GIC device of a given type (V5).
*/
int test_kvm_device(u32 gic_dev_type)
{
struct kvm_vcpu *vcpus[NR_VCPUS];
struct vm_gic v;
int ret;
v.vm = vm_create_with_vcpus(NR_VCPUS, guest_code, vcpus);
/* try to create a non existing KVM device */
ret = __kvm_test_create_device(v.vm, 0);
TEST_ASSERT(ret && errno == ENODEV, "unsupported device");
/* trial mode */
ret = __kvm_test_create_device(v.vm, gic_dev_type);
if (ret)
return ret;
v.gic_fd = kvm_create_device(v.vm, gic_dev_type);
ret = __kvm_create_device(v.vm, gic_dev_type);
TEST_ASSERT(ret < 0 && errno == EEXIST, "create GIC device twice");
vm_gic_destroy(&v);
return 0;
}
void run_tests(u32 gic_dev_type)
{
pr_info("Test VGICv5 PPIs\n");
test_vgic_v5_ppis(gic_dev_type);
}
int main(int ac, char **av)
{
int ret;
test_disable_default_vgic();
ret = test_kvm_device(KVM_DEV_TYPE_ARM_VGIC_V5);
if (ret) {
pr_info("No GICv5 support; Not running GIC_v5 tests.\n");
exit(KSFT_SKIP);
}
pr_info("Running VGIC_V5 tests.\n");
run_tests(KVM_DEV_TYPE_ARM_VGIC_V5);
return 0;
}
@@ -0,0 +1,643 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* vpmu_counter_access - Test vPMU event counter access
*
* Copyright (c) 2023 Google LLC.
*
* This test checks if the guest can see the same number of the PMU event
* counters (PMCR_EL0.N) that userspace sets, if the guest can access
* those counters, and if the guest is prevented from accessing any
* other counters.
* It also checks if the userspace accesses to the PMU regsisters honor the
* PMCR.N value that's set for the guest.
* This test runs only when KVM_CAP_ARM_PMU_V3 is supported on the host.
*/
#include <kvm_util.h>
#include <processor.h>
#include <test_util.h>
#include <vgic.h>
#include <perf/arm_pmuv3.h>
#include <linux/bitfield.h>
/* The max number of the PMU event counters (excluding the cycle counter) */
#define ARMV8_PMU_MAX_GENERAL_COUNTERS (ARMV8_PMU_MAX_COUNTERS - 1)
/* The cycle counter bit position that's common among the PMU registers */
#define ARMV8_PMU_CYCLE_IDX 31
struct vpmu_vm {
struct kvm_vm *vm;
struct kvm_vcpu *vcpu;
};
static struct vpmu_vm vpmu_vm;
struct pmreg_sets {
u64 set_reg_id;
u64 clr_reg_id;
};
#define PMREG_SET(set, clr) {.set_reg_id = set, .clr_reg_id = clr}
static u64 get_pmcr_n(u64 pmcr)
{
return FIELD_GET(ARMV8_PMU_PMCR_N, pmcr);
}
static u64 get_counters_mask(u64 n)
{
u64 mask = BIT(ARMV8_PMU_CYCLE_IDX);
if (n)
mask |= GENMASK(n - 1, 0);
return mask;
}
/* Read PMEVTCNTR<n>_EL0 through PMXEVCNTR_EL0 */
static inline unsigned long read_sel_evcntr(int sel)
{
write_sysreg(sel, pmselr_el0);
isb();
return read_sysreg(pmxevcntr_el0);
}
/* Write PMEVTCNTR<n>_EL0 through PMXEVCNTR_EL0 */
static inline void write_sel_evcntr(int sel, unsigned long val)
{
write_sysreg(sel, pmselr_el0);
isb();
write_sysreg(val, pmxevcntr_el0);
isb();
}
/* Read PMEVTYPER<n>_EL0 through PMXEVTYPER_EL0 */
static inline unsigned long read_sel_evtyper(int sel)
{
write_sysreg(sel, pmselr_el0);
isb();
return read_sysreg(pmxevtyper_el0);
}
/* Write PMEVTYPER<n>_EL0 through PMXEVTYPER_EL0 */
static inline void write_sel_evtyper(int sel, unsigned long val)
{
write_sysreg(sel, pmselr_el0);
isb();
write_sysreg(val, pmxevtyper_el0);
isb();
}
static void pmu_disable_reset(void)
{
u64 pmcr = read_sysreg(pmcr_el0);
/* Reset all counters, disabling them */
pmcr &= ~ARMV8_PMU_PMCR_E;
write_sysreg(pmcr | ARMV8_PMU_PMCR_P, pmcr_el0);
isb();
}
#define RETURN_READ_PMEVCNTRN(n) \
return read_sysreg(pmevcntr##n##_el0)
static unsigned long read_pmevcntrn(int n)
{
PMEVN_SWITCH(n, RETURN_READ_PMEVCNTRN);
return 0;
}
#define WRITE_PMEVCNTRN(n) \
write_sysreg(val, pmevcntr##n##_el0)
static void write_pmevcntrn(int n, unsigned long val)
{
PMEVN_SWITCH(n, WRITE_PMEVCNTRN);
isb();
}
#define READ_PMEVTYPERN(n) \
return read_sysreg(pmevtyper##n##_el0)
static unsigned long read_pmevtypern(int n)
{
PMEVN_SWITCH(n, READ_PMEVTYPERN);
return 0;
}
#define WRITE_PMEVTYPERN(n) \
write_sysreg(val, pmevtyper##n##_el0)
static void write_pmevtypern(int n, unsigned long val)
{
PMEVN_SWITCH(n, WRITE_PMEVTYPERN);
isb();
}
/*
* The pmc_accessor structure has pointers to PMEV{CNTR,TYPER}<n>_EL0
* accessors that test cases will use. Each of the accessors will
* either directly reads/writes PMEV{CNTR,TYPER}<n>_EL0
* (i.e. {read,write}_pmev{cnt,type}rn()), or reads/writes them through
* PMXEV{CNTR,TYPER}_EL0 (i.e. {read,write}_sel_ev{cnt,type}r()).
*
* This is used to test that combinations of those accessors provide
* the consistent behavior.
*/
struct pmc_accessor {
/* A function to be used to read PMEVTCNTR<n>_EL0 */
unsigned long (*read_cntr)(int idx);
/* A function to be used to write PMEVTCNTR<n>_EL0 */
void (*write_cntr)(int idx, unsigned long val);
/* A function to be used to read PMEVTYPER<n>_EL0 */
unsigned long (*read_typer)(int idx);
/* A function to be used to write PMEVTYPER<n>_EL0 */
void (*write_typer)(int idx, unsigned long val);
};
struct pmc_accessor pmc_accessors[] = {
/* test with all direct accesses */
{ read_pmevcntrn, write_pmevcntrn, read_pmevtypern, write_pmevtypern },
/* test with all indirect accesses */
{ read_sel_evcntr, write_sel_evcntr, read_sel_evtyper, write_sel_evtyper },
/* read with direct accesses, and write with indirect accesses */
{ read_pmevcntrn, write_sel_evcntr, read_pmevtypern, write_sel_evtyper },
/* read with indirect accesses, and write with direct accesses */
{ read_sel_evcntr, write_pmevcntrn, read_sel_evtyper, write_pmevtypern },
};
/*
* Convert a pointer of pmc_accessor to an index in pmc_accessors[],
* assuming that the pointer is one of the entries in pmc_accessors[].
*/
#define PMC_ACC_TO_IDX(acc) (acc - &pmc_accessors[0])
#define GUEST_ASSERT_BITMAP_REG(regname, mask, set_expected) \
{ \
u64 _tval = read_sysreg(regname); \
\
if (set_expected) \
__GUEST_ASSERT((_tval & mask), \
"tval: 0x%lx; mask: 0x%lx; set_expected: %u", \
_tval, mask, set_expected); \
else \
__GUEST_ASSERT(!(_tval & mask), \
"tval: 0x%lx; mask: 0x%lx; set_expected: %u", \
_tval, mask, set_expected); \
}
/*
* Check if @mask bits in {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers
* are set or cleared as specified in @set_expected.
*/
static void check_bitmap_pmu_regs(u64 mask, bool set_expected)
{
GUEST_ASSERT_BITMAP_REG(pmcntenset_el0, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmcntenclr_el0, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmintenset_el1, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmintenclr_el1, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmovsset_el0, mask, set_expected);
GUEST_ASSERT_BITMAP_REG(pmovsclr_el0, mask, set_expected);
}
/*
* Check if the bit in {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers corresponding
* to the specified counter (@pmc_idx) can be read/written as expected.
* When @set_op is true, it tries to set the bit for the counter in
* those registers by writing the SET registers (the bit won't be set
* if the counter is not implemented though).
* Otherwise, it tries to clear the bits in the registers by writing
* the CLR registers.
* Then, it checks if the values indicated in the registers are as expected.
*/
static void test_bitmap_pmu_regs(int pmc_idx, bool set_op)
{
u64 pmcr_n, test_bit = BIT(pmc_idx);
bool set_expected = false;
if (set_op) {
write_sysreg(test_bit, pmcntenset_el0);
write_sysreg(test_bit, pmintenset_el1);
write_sysreg(test_bit, pmovsset_el0);
/* The bit will be set only if the counter is implemented */
pmcr_n = get_pmcr_n(read_sysreg(pmcr_el0));
set_expected = (pmc_idx < pmcr_n) ? true : false;
} else {
write_sysreg(test_bit, pmcntenclr_el0);
write_sysreg(test_bit, pmintenclr_el1);
write_sysreg(test_bit, pmovsclr_el0);
}
check_bitmap_pmu_regs(test_bit, set_expected);
}
/*
* Tests for reading/writing registers for the (implemented) event counter
* specified by @pmc_idx.
*/
static void test_access_pmc_regs(struct pmc_accessor *acc, int pmc_idx)
{
u64 write_data, read_data;
/* Disable all PMCs and reset all PMCs to zero. */
pmu_disable_reset();
/*
* Tests for reading/writing {PMCNTEN,PMINTEN,PMOVS}{SET,CLR}_EL1.
*/
/* Make sure that the bit in those registers are set to 0 */
test_bitmap_pmu_regs(pmc_idx, false);
/* Test if setting the bit in those registers works */
test_bitmap_pmu_regs(pmc_idx, true);
/* Test if clearing the bit in those registers works */
test_bitmap_pmu_regs(pmc_idx, false);
/*
* Tests for reading/writing the event type register.
*/
/*
* Set the event type register to an arbitrary value just for testing
* of reading/writing the register.
* Arm ARM says that for the event from 0x0000 to 0x003F,
* the value indicated in the PMEVTYPER<n>_EL0.evtCount field is
* the value written to the field even when the specified event
* is not supported.
*/
write_data = (ARMV8_PMU_EXCLUDE_EL1 | ARMV8_PMUV3_PERFCTR_INST_RETIRED);
acc->write_typer(pmc_idx, write_data);
read_data = acc->read_typer(pmc_idx);
__GUEST_ASSERT(read_data == write_data,
"pmc_idx: 0x%x; acc_idx: 0x%lx; read_data: 0x%lx; write_data: 0x%lx",
pmc_idx, PMC_ACC_TO_IDX(acc), read_data, write_data);
/*
* Tests for reading/writing the event count register.
*/
read_data = acc->read_cntr(pmc_idx);
/* The count value must be 0, as it is disabled and reset */
__GUEST_ASSERT(read_data == 0,
"pmc_idx: 0x%x; acc_idx: 0x%lx; read_data: 0x%lx",
pmc_idx, PMC_ACC_TO_IDX(acc), read_data);
write_data = read_data + pmc_idx + 0x12345;
acc->write_cntr(pmc_idx, write_data);
read_data = acc->read_cntr(pmc_idx);
__GUEST_ASSERT(read_data == write_data,
"pmc_idx: 0x%x; acc_idx: 0x%lx; read_data: 0x%lx; write_data: 0x%lx",
pmc_idx, PMC_ACC_TO_IDX(acc), read_data, write_data);
}
#define INVALID_EC (-1ul)
u64 expected_ec = INVALID_EC;
static void guest_sync_handler(struct ex_regs *regs)
{
u64 esr, ec;
esr = read_sysreg(esr_el1);
ec = ESR_ELx_EC(esr);
__GUEST_ASSERT(expected_ec == ec,
"PC: 0x%lx; ESR: 0x%lx; EC: 0x%lx; EC expected: 0x%lx",
regs->pc, esr, ec, expected_ec);
/* skip the trapping instruction */
regs->pc += 4;
/* Use INVALID_EC to indicate an exception occurred */
expected_ec = INVALID_EC;
}
/*
* Run the given operation that should trigger an exception with the
* given exception class. The exception handler (guest_sync_handler)
* will reset op_end_addr to 0, expected_ec to INVALID_EC, and skip
* the instruction that trapped.
*/
#define TEST_EXCEPTION(ec, ops) \
({ \
GUEST_ASSERT(ec != INVALID_EC); \
WRITE_ONCE(expected_ec, ec); \
dsb(ish); \
ops; \
GUEST_ASSERT(expected_ec == INVALID_EC); \
})
/*
* Tests for reading/writing registers for the unimplemented event counter
* specified by @pmc_idx (>= PMCR_EL0.N).
*/
static void test_access_invalid_pmc_regs(struct pmc_accessor *acc, int pmc_idx)
{
/*
* Reading/writing the event count/type registers should cause
* an UNDEFINED exception.
*/
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_cntr(pmc_idx));
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_cntr(pmc_idx, 0));
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->read_typer(pmc_idx));
TEST_EXCEPTION(ESR_ELx_EC_UNKNOWN, acc->write_typer(pmc_idx, 0));
/*
* The bit corresponding to the (unimplemented) counter in
* {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers should be RAZ.
*/
test_bitmap_pmu_regs(pmc_idx, 1);
test_bitmap_pmu_regs(pmc_idx, 0);
}
/*
* The guest is configured with PMUv3 with @expected_pmcr_n number of
* event counters.
* Check if @expected_pmcr_n is consistent with PMCR_EL0.N, and
* if reading/writing PMU registers for implemented or unimplemented
* counters works as expected.
*/
static void guest_code(u64 expected_pmcr_n)
{
u64 pmcr, pmcr_n, unimp_mask;
int i, pmc;
__GUEST_ASSERT(expected_pmcr_n <= ARMV8_PMU_MAX_GENERAL_COUNTERS,
"Expected PMCR.N: 0x%lx; ARMv8 general counters: 0x%x",
expected_pmcr_n, ARMV8_PMU_MAX_GENERAL_COUNTERS);
pmcr = read_sysreg(pmcr_el0);
pmcr_n = get_pmcr_n(pmcr);
/* Make sure that PMCR_EL0.N indicates the value userspace set */
__GUEST_ASSERT(pmcr_n == expected_pmcr_n,
"Expected PMCR.N: 0x%lx, PMCR.N: 0x%lx",
expected_pmcr_n, pmcr_n);
/*
* Make sure that (RAZ) bits corresponding to unimplemented event
* counters in {PMCNTEN,PMINTEN,PMOVS}{SET,CLR} registers are reset
* to zero.
* (NOTE: bits for implemented event counters are reset to UNKNOWN)
*/
unimp_mask = GENMASK_ULL(ARMV8_PMU_MAX_GENERAL_COUNTERS - 1, pmcr_n);
check_bitmap_pmu_regs(unimp_mask, false);
/*
* Tests for reading/writing PMU registers for implemented counters.
* Use each combination of PMEV{CNTR,TYPER}<n>_EL0 accessor functions.
*/
for (i = 0; i < ARRAY_SIZE(pmc_accessors); i++) {
for (pmc = 0; pmc < pmcr_n; pmc++)
test_access_pmc_regs(&pmc_accessors[i], pmc);
}
/*
* Tests for reading/writing PMU registers for unimplemented counters.
* Use each combination of PMEV{CNTR,TYPER}<n>_EL0 accessor functions.
*/
for (i = 0; i < ARRAY_SIZE(pmc_accessors); i++) {
for (pmc = pmcr_n; pmc < ARMV8_PMU_MAX_GENERAL_COUNTERS; pmc++)
test_access_invalid_pmc_regs(&pmc_accessors[i], pmc);
}
GUEST_DONE();
}
/* Create a VM that has one vCPU with PMUv3 configured. */
static void create_vpmu_vm(void *guest_code)
{
struct kvm_vcpu_init init;
u8 pmuver, ec;
u64 dfr0, irq = 23;
struct kvm_device_attr irq_attr = {
.group = KVM_ARM_VCPU_PMU_V3_CTRL,
.attr = KVM_ARM_VCPU_PMU_V3_IRQ,
.addr = (u64)&irq,
};
/* The test creates the vpmu_vm multiple times. Ensure a clean state */
memset(&vpmu_vm, 0, sizeof(vpmu_vm));
vpmu_vm.vm = vm_create(1);
vm_init_descriptor_tables(vpmu_vm.vm);
for (ec = 0; ec < ESR_ELx_EC_MAX + 1; ec++) {
vm_install_sync_handler(vpmu_vm.vm, VECTOR_SYNC_CURRENT, ec,
guest_sync_handler);
}
/* Create vCPU with PMUv3 */
kvm_get_default_vcpu_target(vpmu_vm.vm, &init);
init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3);
vpmu_vm.vcpu = aarch64_vcpu_add(vpmu_vm.vm, 0, &init, guest_code);
vcpu_init_descriptor_tables(vpmu_vm.vcpu);
kvm_arch_vm_finalize_vcpus(vpmu_vm.vm);
/* Make sure that PMUv3 support is indicated in the ID register */
dfr0 = vcpu_get_reg(vpmu_vm.vcpu, KVM_ARM64_SYS_REG(SYS_ID_AA64DFR0_EL1));
pmuver = FIELD_GET(ID_AA64DFR0_EL1_PMUVer, dfr0);
TEST_ASSERT(pmuver != ID_AA64DFR0_EL1_PMUVer_IMP_DEF &&
pmuver >= ID_AA64DFR0_EL1_PMUVer_IMP,
"Unexpected PMUVER (0x%x) on the vCPU with PMUv3", pmuver);
vcpu_ioctl(vpmu_vm.vcpu, KVM_SET_DEVICE_ATTR, &irq_attr);
}
static void destroy_vpmu_vm(void)
{
kvm_vm_free(vpmu_vm.vm);
}
static void run_vcpu(struct kvm_vcpu *vcpu, u64 pmcr_n)
{
struct ucall uc;
vcpu_args_set(vcpu, 1, pmcr_n);
vcpu_run(vcpu);
switch (get_ucall(vcpu, &uc)) {
case UCALL_ABORT:
REPORT_GUEST_ASSERT(uc);
break;
case UCALL_DONE:
break;
default:
TEST_FAIL("Unknown ucall %lu", uc.cmd);
break;
}
}
static void test_create_vpmu_vm_with_nr_counters(unsigned int nr_counters, bool expect_fail)
{
struct kvm_vcpu *vcpu;
unsigned int prev;
int ret;
create_vpmu_vm(guest_code);
vcpu = vpmu_vm.vcpu;
prev = get_pmcr_n(vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0)));
ret = __vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS, &nr_counters);
if (expect_fail)
TEST_ASSERT(ret && errno == EINVAL,
"Setting more PMU counters (%u) than available (%u) unexpectedly succeeded",
nr_counters, prev);
else
TEST_ASSERT(!ret, KVM_IOCTL_ERROR(KVM_SET_DEVICE_ATTR, ret));
vcpu_device_attr_set(vcpu, KVM_ARM_VCPU_PMU_V3_CTRL, KVM_ARM_VCPU_PMU_V3_INIT, NULL);
}
/*
* Create a guest with one vCPU, set the PMCR_EL0.N for the vCPU to @pmcr_n,
* and run the test.
*/
static void run_access_test(u64 pmcr_n)
{
u64 sp;
struct kvm_vcpu *vcpu;
struct kvm_vcpu_init init;
pr_debug("Test with pmcr_n %lu\n", pmcr_n);
test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
vcpu = vpmu_vm.vcpu;
/* Save the initial sp to restore them later to run the guest again */
sp = vcpu_get_reg(vcpu, ctxt_reg_alias(vcpu, SYS_SP_EL1));
run_vcpu(vcpu, pmcr_n);
/*
* Reset and re-initialize the vCPU, and run the guest code again to
* check if PMCR_EL0.N is preserved.
*/
kvm_get_default_vcpu_target(vpmu_vm.vm, &init);
init.features[0] |= (1 << KVM_ARM_VCPU_PMU_V3);
aarch64_vcpu_setup(vcpu, &init);
vcpu_init_descriptor_tables(vcpu);
vcpu_set_reg(vcpu, ctxt_reg_alias(vcpu, SYS_SP_EL1), sp);
vcpu_set_reg(vcpu, ARM64_CORE_REG(regs.pc), (u64)guest_code);
run_vcpu(vcpu, pmcr_n);
destroy_vpmu_vm();
}
static struct pmreg_sets validity_check_reg_sets[] = {
PMREG_SET(SYS_PMCNTENSET_EL0, SYS_PMCNTENCLR_EL0),
PMREG_SET(SYS_PMINTENSET_EL1, SYS_PMINTENCLR_EL1),
PMREG_SET(SYS_PMOVSSET_EL0, SYS_PMOVSCLR_EL0),
};
/*
* Create a VM, and check if KVM handles the userspace accesses of
* the PMU register sets in @validity_check_reg_sets[] correctly.
*/
static void run_pmregs_validity_test(u64 pmcr_n)
{
int i;
struct kvm_vcpu *vcpu;
u64 set_reg_id, clr_reg_id, reg_val;
u64 valid_counters_mask, max_counters_mask;
test_create_vpmu_vm_with_nr_counters(pmcr_n, false);
vcpu = vpmu_vm.vcpu;
valid_counters_mask = get_counters_mask(pmcr_n);
max_counters_mask = get_counters_mask(ARMV8_PMU_MAX_COUNTERS);
for (i = 0; i < ARRAY_SIZE(validity_check_reg_sets); i++) {
set_reg_id = validity_check_reg_sets[i].set_reg_id;
clr_reg_id = validity_check_reg_sets[i].clr_reg_id;
/*
* Test if the 'set' and 'clr' variants of the registers
* are initialized based on the number of valid counters.
*/
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(set_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Initial read of set_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(set_reg_id), reg_val);
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(clr_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Initial read of clr_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(clr_reg_id), reg_val);
/*
* Using the 'set' variant, force-set the register to the
* max number of possible counters and test if KVM discards
* the bits for unimplemented counters as it should.
*/
vcpu_set_reg(vcpu, KVM_ARM64_SYS_REG(set_reg_id), max_counters_mask);
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(set_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Read of set_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(set_reg_id), reg_val);
reg_val = vcpu_get_reg(vcpu, KVM_ARM64_SYS_REG(clr_reg_id));
TEST_ASSERT((reg_val & (~valid_counters_mask)) == 0,
"Read of clr_reg: 0x%llx has unimplemented counters enabled: 0x%lx",
KVM_ARM64_SYS_REG(clr_reg_id), reg_val);
}
destroy_vpmu_vm();
}
/*
* Create a guest with one vCPU, and attempt to set the PMCR_EL0.N for
* the vCPU to @pmcr_n, which is larger than the host value.
* The attempt should fail as @pmcr_n is too big to set for the vCPU.
*/
static void run_error_test(u64 pmcr_n)
{
pr_debug("Error test with pmcr_n %lu (larger than the host)\n", pmcr_n);
test_create_vpmu_vm_with_nr_counters(pmcr_n, true);
destroy_vpmu_vm();
}
/*
* Return the default number of implemented PMU event counters excluding
* the cycle counter (i.e. PMCR_EL0.N value) for the guest.
*/
static u64 get_pmcr_n_limit(void)
{
u64 pmcr;
create_vpmu_vm(guest_code);
pmcr = vcpu_get_reg(vpmu_vm.vcpu, KVM_ARM64_SYS_REG(SYS_PMCR_EL0));
destroy_vpmu_vm();
return get_pmcr_n(pmcr);
}
static bool kvm_supports_nr_counters_attr(void)
{
bool supported;
create_vpmu_vm(NULL);
supported = !__vcpu_has_device_attr(vpmu_vm.vcpu, KVM_ARM_VCPU_PMU_V3_CTRL,
KVM_ARM_VCPU_PMU_V3_SET_NR_COUNTERS);
destroy_vpmu_vm();
return supported;
}
int main(void)
{
u64 i, pmcr_n;
TEST_REQUIRE(kvm_has_cap(KVM_CAP_ARM_PMU_V3));
TEST_REQUIRE(kvm_supports_vgic_v3());
TEST_REQUIRE(kvm_supports_nr_counters_attr());
pmcr_n = get_pmcr_n_limit();
for (i = 0; i <= pmcr_n; i++) {
run_access_test(i);
run_pmregs_validity_test(i);
}
for (i = pmcr_n + 1; i < ARMV8_PMU_MAX_COUNTERS; i++)
run_error_test(i);
return 0;
}