// SPDX-License-Identifier: GPL-2.0
/*
* vgic_irq.c - Test userspace injection of IRQs
*
* This test validates the injection of IRQs from userspace using various
* methods (e.g., KVM_IRQ_LINE) and modes (e.g., EOI). The guest "asks" the
* host to inject a specific intid via a GUEST_SYNC call, and then checks that
* it received it.
*/
#include <asm/kvm.h>
#include <asm/kvm_para.h>
#include <sys/eventfd.h>
#include <linux/sizes.h>
#include "processor.h"
#include "test_util.h"
#include "kvm_util.h"
#include "gic.h"
#include "gic_v3.h"
#include "vgic.h"
/*
* Stores the user specified args; it's passed to the guest and to every test
* function.
*/
struct test_args {
u32 nr_irqs; /* number of KVM supported IRQs. */
bool eoi_split; /* 1 is eoir+dir, 0 is eoir only */
bool level_sensitive; /* 1 is level, 0 is edge */
int kvm_max_routes; /* output of KVM_CAP_IRQ_ROUTING */
bool kvm_supports_irqfd; /* output of KVM_CAP_IRQFD */
u32 shared_data;
};
/*
* KVM implements 32 priority levels:
* 0x00 (highest priority) - 0xF8 (lowest priority), in steps of 8
*
* Note that these macros will still be correct in the case that KVM implements
* more priority levels. Also note that 32 is the minimum for GICv3 and GICv2.
*/
#define KVM_NUM_PRIOS 32
#define KVM_PRIO_SHIFT 3 /* steps of 8 = 1 << 3 */
#define KVM_PRIO_STEPS (1 << KVM_PRIO_SHIFT) /* 8 */
#define LOWEST_PRIO (KVM_NUM_PRIOS - 1)
#define CPU_PRIO_MASK (LOWEST_PRIO << KVM_PRIO_SHIFT) /* 0xf8 */
#define IRQ_DEFAULT_PRIO (LOWEST_PRIO - 1)
#define IRQ_DEFAULT_PRIO_REG (IRQ_DEFAULT_PRIO << KVM_PRIO_SHIFT) /* 0xf0 */
/*
* The kvm_inject_* utilities are used by the guest to ask the host to inject
* interrupts (e.g., using the KVM_IRQ_LINE ioctl).
*/
typedef enum {
KVM_INJECT_EDGE_IRQ_LINE = 1,
KVM_SET_IRQ_LINE,
KVM_SET_IRQ_LINE_HIGH,
KVM_SET_LEVEL_INFO_HIGH,
KVM_INJECT_IRQFD,
KVM_WRITE_ISPENDR,
KVM_WRITE_ISACTIVER,
} kvm_inject_cmd;
struct kvm_inject_args {
kvm_inject_cmd cmd;
u32 first_intid;
u32 num;
int level;
bool expect_failure;
};
/* Used on the guest side to perform the hypercall. */
static void kvm_inject_call(kvm_inject_cmd cmd, u32 first_intid,
u32 num, int level, bool expect_failure);
/* Used on the host side to get the hypercall info. */
static void kvm_inject_get_call(struct kvm_vm *vm, struct ucall *uc,
struct kvm_inject_args *args);
#define _KVM_INJECT_MULTI(cmd, intid, num, expect_failure) \
kvm_inject_call(cmd, intid, num, -1 /* not used */, expect_failure)
#define KVM_INJECT_MULTI(cmd, intid, num) \
_KVM_INJECT_MULTI(cmd, intid, num, false)
#define _KVM_INJECT(cmd, intid, expect_failure) \
_KVM_INJECT_MULTI(cmd, intid, 1, expect_failure)
#define KVM_INJECT(cmd, intid) \
_KVM_INJECT_MULTI(cmd, intid, 1, false)
#define KVM_ACTIVATE(cmd, intid) \
kvm_inject_call(cmd, intid, 1, 1, false);
struct kvm_inject_desc {
kvm_inject_cmd cmd;
/* can inject PPIs, PPIs, and/or SPIs. */
bool sgi, ppi, spi;
};
static struct kvm_inject_desc inject_edge_fns[] = {
/* sgi ppi spi */
{ KVM_INJECT_EDGE_IRQ_LINE, false, false, true },
{ KVM_INJECT_IRQFD, false, false, true },
{ KVM_WRITE_ISPENDR, true, false, true },
{ 0, },
};
static struct kvm_inject_desc inject_level_fns[] = {
/* sgi ppi spi */
{ KVM_SET_IRQ_LINE_HIGH, false, true, true },
{ KVM_SET_LEVEL_INFO_HIGH, false, true, true },
{ KVM_INJECT_IRQFD, false, false, true },
{ KVM_WRITE_ISPENDR, false, true, true },
{ 0, },
};
static struct kvm_inject_desc set_active_fns[] = {
/* sgi ppi spi */
{ KVM_WRITE_ISACTIVER, true, true, true },
{ 0, },
};
#define for_each_inject_fn(t, f) \
for ((f) = (t); (f)->cmd; (f)++)
#define for_each_supported_inject_fn(args, t, f) \
for_each_inject_fn(t, f) \
if ((args)->kvm_supports_irqfd || (f)->cmd != KVM_INJECT_IRQFD)
#define for_each_supported_activate_fn(args, t, f) \
for_each_supported_inject_fn((args), (t), (f))
/* Shared between the guest main thread and the IRQ handlers. */
volatile u64 irq_handled;
volatile u32 irqnr_received[MAX_SPI + 1];
static void reset_stats(void)
{
int i;
irq_handled = 0;
for (i = 0; i <= MAX_SPI; i++)
irqnr_received[i] = 0;
}
static u64 gic_read_ap1r0(void)
{
u64 reg = read_sysreg_s(SYS_ICC_AP1R0_EL1);
dsb(sy);
return reg;
}
static void gic_write_ap1r0(u64 val)
{
write_sysreg_s(val, SYS_ICC_AP1R0_EL1);
isb();
}
static void guest_set_irq_line(u32 intid, u32 level