root/tools/testing/selftests/kvm/arm64/vgic_v5.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;
}