| // 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; |
| } |