mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 13:23:02 -04:00
Cases where the KVM_RUN ioctl returned an error were wrongly reported
as incorrect ucalls. Furthermore, potential failures when calling
KVM_IRQ_LINE were being hidden.
Improve the error handling to correctly propagate the error in both
cases.
Fixes: 0a9f38bf61 ("KVM: arm64: selftests: Introduce a minimal GICv5 PPI selftest")
Link: https://sashiko.dev/#/patchset/20260319154937.3619520-1-sascha.bischoff%40arm.com
Signed-off-by: Sascha Bischoff <sascha.bischoff@arm.com>
Link: https://lore.kernel.org/r/20260520091949.542365-15-maz@kernel.org
Signed-off-by: Marc Zyngier <maz@kernel.org>
225 lines
4.6 KiB
C
225 lines
4.6 KiB
C
// 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;
|
|
}
|