#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;
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();
GUEST_SYNC(GUEST_CMD_IS_READY);
}
static void guest_code(void)
{
local_irq_disable();
gicv5_cpu_enable_interrupts();
local_irq_enable();
write_sysreg_s(BIT_ULL(3), SYS_ICC_PPI_ENABLER0_EL1);
GUEST_SYNC(GUEST_CMD_IS_READY);
while (1);
}
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);
kvm_device_attr_get(v.gic_fd, KVM_DEV_ARM_VGIC_GRP_CTRL,
KVM_DEV_ARM_VGIC_USERSPACE_PPIS, &user_ppis);
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:
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);
}
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);
ret = __kvm_test_create_device(v.vm, 0);
TEST_ASSERT(ret && errno == ENODEV, "unsupported device");
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;
}