Commit f6108d72 authored by Jean-Philippe Brucker's avatar Jean-Philippe Brucker Committed by Will Deacon
Browse files

Add GICv2m support



GICv2m is a small extension to the GICv2 architecture, specified in the
Server Base System Architecture (SBSA). It adds a set of register to
converts MSIs into SPIs, effectively enabling MSI support for pre-GICv3
platforms.

Implement a GICv2m emulation entirely in userspace. Add a thin translation
layer in irq.c to catch the MSI->SPI routing setup of the guest, and then
transform irqfd injection of MSI into the associated SPI. There shouldn't
be any significant runtime overhead compared to gicv3-its.

The device can be enabled by passing "--irqchip gicv2m" to kvmtool.
Signed-off-by: default avatarJean-Philippe Brucker <jean-philippe.brucker@arm.com>
Signed-off-by: default avatarWill Deacon <will.deacon@arm.com>
parent 37b8e06b
......@@ -151,7 +151,7 @@ ifeq ($(ARCH), powerpc)
endif
# ARM
OBJS_ARM_COMMON := arm/fdt.o arm/gic.o arm/ioport.o \
OBJS_ARM_COMMON := arm/fdt.o arm/gic.o arm/gicv2m.o arm/ioport.o \
arm/kvm.o arm/kvm-cpu.o arm/pci.o arm/timer.o \
arm/pmu.o
HDRS_ARM_COMMON := arm/include
......
......@@ -84,6 +84,8 @@ struct kvm_regs {
#define KVM_VGIC_V2_DIST_SIZE 0x1000
#define KVM_VGIC_V2_CPU_SIZE 0x2000
#define KVM_VGIC_V2M_SIZE 0x1000
/* Supported VGICv3 address types */
#define KVM_VGIC_V3_ADDR_TYPE_DIST 2
#define KVM_VGIC_V3_ADDR_TYPE_REDIST 3
......
......@@ -84,6 +84,8 @@ struct kvm_regs {
#define KVM_VGIC_V2_DIST_SIZE 0x1000
#define KVM_VGIC_V2_CPU_SIZE 0x2000
#define KVM_VGIC_V2M_SIZE 0x1000
/* Supported VGICv3 address types */
#define KVM_VGIC_V3_ADDR_TYPE_DIST 2
#define KVM_VGIC_V3_ADDR_TYPE_REDIST 3
......
......@@ -24,6 +24,8 @@ int irqchip_parser(const struct option *opt, const char *arg, int unset)
if (!strcmp(arg, "gicv2")) {
*type = IRQCHIP_GICV2;
} else if (!strcmp(arg, "gicv2m")) {
*type = IRQCHIP_GICV2M;
} else if (!strcmp(arg, "gicv3")) {
*type = IRQCHIP_GICV3;
} else if (!strcmp(arg, "gicv3-its")) {
......@@ -107,6 +109,8 @@ static int gic__create_msi_frame(struct kvm *kvm, enum irqchip_type type,
u64 msi_frame_addr)
{
switch (type) {
case IRQCHIP_GICV2M:
return gic__create_gicv2m_frame(kvm, msi_frame_addr);
case IRQCHIP_GICV3_ITS:
return gic__create_its_frame(kvm, msi_frame_addr);
default: /* No MSI frame needed */
......@@ -138,6 +142,7 @@ static int gic__create_device(struct kvm *kvm, enum irqchip_type type)
};
switch (type) {
case IRQCHIP_GICV2M:
case IRQCHIP_GICV2:
gic_device.type = KVM_DEV_TYPE_ARM_VGIC_V2;
dist_attr.attr = KVM_VGIC_V2_ADDR_TYPE_DIST;
......@@ -156,6 +161,7 @@ static int gic__create_device(struct kvm *kvm, enum irqchip_type type)
gic_fd = gic_device.fd;
switch (type) {
case IRQCHIP_GICV2M:
case IRQCHIP_GICV2:
err = ioctl(gic_fd, KVM_SET_DEVICE_ATTR, &cpu_if_attr);
break;
......@@ -216,6 +222,10 @@ int gic__create(struct kvm *kvm, enum irqchip_type type)
int err;
switch (type) {
case IRQCHIP_GICV2M:
gic_msi_size = KVM_VGIC_V2M_SIZE;
gic_msi_base = ARM_GIC_DIST_BASE - gic_msi_size;
break;
case IRQCHIP_GICV2:
break;
case IRQCHIP_GICV3_ITS:
......@@ -296,6 +306,9 @@ void gic__generate_fdt_nodes(void *fdt, enum irqchip_type type)
};
switch (type) {
case IRQCHIP_GICV2M:
msi_compatible = "arm,gic-v2m-frame";
/* fall-through */
case IRQCHIP_GICV2:
compatible = "arm,cortex-a15-gic";
reg_prop[2] = cpu_to_fdt64(ARM_GIC_CPUI_BASE);
......
#include <errno.h>
#include <stdlib.h>
#include "kvm/irq.h"
#include "kvm/kvm.h"
#include "kvm/util.h"
#include "arm-common/gic.h"
#define GICV2M_MSI_TYPER 0x008
#define GICV2M_MSI_SETSPI 0x040
#define GICV2M_MSI_IIDR 0xfcc
#define GICV2M_SPI_MASK 0x3ff
#define GICV2M_MSI_TYPER_VAL(start, nr) \
(((start) & GICV2M_SPI_MASK) << 16 | ((nr) & GICV2M_SPI_MASK))
struct gicv2m_chip {
int first_spi;
int num_spis;
int *spis;
u64 base;
u64 size;
};
static struct gicv2m_chip v2m;
/*
* MSI routing is setup lazily, when the guest writes the MSI tables. The guest
* writes which SPI is associated to an MSI vector into the message data field.
* The IRQ code notifies us of any change to MSI routing via this callback.
* Store the MSI->SPI translation for later.
*
* Data is the GIC interrupt ID, that includes SGIs and PPIs. SGIs at 0-15, PPIs
* are 16-31 and SPIs are 32-1019. What we're saving for later is the MSI's GSI
* number, a logical ID used by KVM for routing. The GSI of an SPI is implicitly
* defined by KVM to be its pin number (SPI index), and the GSI of an MSI is
* allocated by kvmtool.
*/
static int gicv2m_update_routing(struct kvm *kvm,
struct kvm_irq_routing_entry *entry)
{
int spi;
if (entry->type != KVM_IRQ_ROUTING_MSI)
return -EINVAL;
if (!entry->u.msi.address_hi && !entry->u.msi.address_lo)
return 0;
spi = entry->u.msi.data & GICV2M_SPI_MASK;
if (spi < v2m.first_spi || spi >= v2m.first_spi + v2m.num_spis) {
pr_err("invalid SPI number %d", spi);
return -EINVAL;
}
v2m.spis[spi - v2m.first_spi] = entry->gsi;
return 0;
}
/*
* Find SPI bound to the given MSI and return the associated GSI.
*/
static int gicv2m_translate_gsi(struct kvm *kvm, u32 gsi)
{
int i;
for (i = 0; i < v2m.num_spis; i++) {
if (v2m.spis[i] == (int)gsi)
return i + v2m.first_spi - KVM_IRQ_OFFSET;
}
/* Not an MSI */
return gsi;
}
static bool gicv2m_can_signal_msi(struct kvm *kvm)
{
return true;
}
/*
* Instead of setting up MSI routes, virtual devices can also trigger them
* manually (like a direct write to MSI_SETSPI). In this case, trigger the SPI
* directly.
*/
static int gicv2m_signal_msi(struct kvm *kvm, struct kvm_msi *msi)
{
int spi = msi->data & GICV2M_SPI_MASK;
if (spi < v2m.first_spi || spi >= v2m.first_spi + v2m.num_spis) {
pr_err("invalid SPI number %d", spi);
return -EINVAL;
}
kvm__irq_trigger(kvm, spi);
return 0;
}
static struct msi_routing_ops gicv2m_routing = {
.update_route = gicv2m_update_routing,
.translate_gsi = gicv2m_translate_gsi,
.can_signal_msi = gicv2m_can_signal_msi,
.signal_msi = gicv2m_signal_msi,
};
static void gicv2m_mmio_callback(struct kvm_cpu *vcpu, u64 addr, u8 *data,
u32 len, u8 is_write, void *ptr)
{
if (is_write)
return;
addr -= v2m.base;
switch (addr) {
case GICV2M_MSI_TYPER:
*(u32 *)data = GICV2M_MSI_TYPER_VAL(v2m.first_spi,
v2m.num_spis);
break;
case GICV2M_MSI_IIDR:
*(u32 *)data = 0x0;
break;
}
}
int gic__create_gicv2m_frame(struct kvm *kvm, u64 base)
{
int i;
int irq = irq__alloc_line();
v2m = (struct gicv2m_chip) {
.first_spi = irq, /* Includes GIC_SPI_IRQ_BASE */
.num_spis = 64, /* arbitrary */
.base = base,
.size = KVM_VGIC_V2M_SIZE,
};
v2m.spis = calloc(v2m.num_spis, sizeof(int));
if (!v2m.spis)
return -ENOMEM;
v2m.spis[0] = -1;
for (i = 1; i < v2m.num_spis; i++) {
irq__alloc_line();
v2m.spis[i] = -1;
}
msi_routing_ops = &gicv2m_routing;
return kvm__register_mmio(kvm, base, KVM_VGIC_V2M_SIZE, false,
gicv2m_mmio_callback, kvm);
}
......@@ -23,6 +23,7 @@
enum irqchip_type {
IRQCHIP_GICV2,
IRQCHIP_GICV2M,
IRQCHIP_GICV3,
IRQCHIP_GICV3_ITS,
};
......@@ -31,6 +32,7 @@ struct kvm;
int gic__alloc_irqnum(void);
int gic__create(struct kvm *kvm, enum irqchip_type type);
int gic__create_gicv2m_frame(struct kvm *kvm, u64 msi_frame_addr);
void gic__generate_fdt_nodes(void *fdt, enum irqchip_type type);
#endif /* ARM_COMMON__GIC_H */
......@@ -28,7 +28,7 @@ int irqchip_parser(const struct option *opt, const char *arg, int unset);
"Force virtio devices to use PCI as their default " \
"transport"), \
OPT_CALLBACK('\0', "irqchip", &(cfg)->irqchip, \
"[gicv2|gicv3|gicv3-its]", \
"[gicv2|gicv2m|gicv3|gicv3-its]", \
"Type of interrupt controller to emulate in the guest", \
irqchip_parser, NULL),
......
......@@ -10,6 +10,14 @@
struct kvm;
struct msi_routing_ops {
int (*update_route)(struct kvm *kvm, struct kvm_irq_routing_entry *);
bool (*can_signal_msi)(struct kvm *kvm);
int (*signal_msi)(struct kvm *kvm, struct kvm_msi *msi);
int (*translate_gsi)(struct kvm *kvm, u32 gsi);
};
extern struct msi_routing_ops *msi_routing_ops;
extern struct kvm_irq_routing *irq_routing;
extern int next_gsi;
......@@ -23,4 +31,7 @@ int irq__allocate_routing_entry(void);
int irq__add_msix_route(struct kvm *kvm, struct msi_msg *msg, u32 device_id);
void irq__update_msix_route(struct kvm *kvm, u32 gsi, struct msi_msg *msg);
bool irq__can_signal_msi(struct kvm *kvm);
int irq__signal_msi(struct kvm *kvm, struct kvm_msi *msi);
#endif
......@@ -13,6 +13,9 @@ static int allocated_gsis = 0;
int next_gsi;
struct msi_routing_ops irq__default_routing_ops;
struct msi_routing_ops *msi_routing_ops = &irq__default_routing_ops;
struct kvm_irq_routing *irq_routing = NULL;
int irq__alloc_line(void)
......@@ -66,9 +69,42 @@ static bool check_for_irq_routing(struct kvm *kvm)
return has_irq_routing > 0;
}
static int irq__update_msix_routes(struct kvm *kvm,
struct kvm_irq_routing_entry *entry)
{
return ioctl(kvm->vm_fd, KVM_SET_GSI_ROUTING, irq_routing);
}
static bool irq__default_can_signal_msi(struct kvm *kvm)
{
return kvm__supports_extension(kvm, KVM_CAP_SIGNAL_MSI);
}
static int irq__default_signal_msi(struct kvm *kvm, struct kvm_msi *msi)
{
return ioctl(kvm->vm_fd, KVM_SIGNAL_MSI, msi);
}
struct msi_routing_ops irq__default_routing_ops = {
.update_route = irq__update_msix_routes,
.signal_msi = irq__default_signal_msi,
.can_signal_msi = irq__default_can_signal_msi,
};
bool irq__can_signal_msi(struct kvm *kvm)
{
return msi_routing_ops->can_signal_msi(kvm);
}
int irq__signal_msi(struct kvm *kvm, struct kvm_msi *msi)
{
return msi_routing_ops->signal_msi(kvm, msi);
}
int irq__add_msix_route(struct kvm *kvm, struct msi_msg *msg, u32 device_id)
{
int r;
struct kvm_irq_routing_entry *entry;
if (!check_for_irq_routing(kvm))
return -ENXIO;
......@@ -77,22 +113,23 @@ int irq__add_msix_route(struct kvm *kvm, struct msi_msg *msg, u32 device_id)
if (r)
return r;
irq_routing->entries[irq_routing->nr] =
(struct kvm_irq_routing_entry) {
.gsi = next_gsi,
.type = KVM_IRQ_ROUTING_MSI,
.u.msi.address_hi = msg->address_hi,
.u.msi.address_lo = msg->address_lo,
.u.msi.data = msg->data,
};
entry = &irq_routing->entries[irq_routing->nr];
*entry = (struct kvm_irq_routing_entry) {
.gsi = next_gsi,
.type = KVM_IRQ_ROUTING_MSI,
.u.msi.address_hi = msg->address_hi,
.u.msi.address_lo = msg->address_lo,
.u.msi.data = msg->data,
};
if (kvm->msix_needs_devid) {
irq_routing->entries[irq_routing->nr].flags = KVM_MSI_VALID_DEVID;
irq_routing->entries[irq_routing->nr].u.msi.devid = device_id;
entry->flags = KVM_MSI_VALID_DEVID;
entry->u.msi.devid = device_id;
}
irq_routing->nr++;
r = ioctl(kvm->vm_fd, KVM_SET_GSI_ROUTING, irq_routing);
r = msi_routing_ops->update_route(kvm, entry);
if (r)
return r;
......@@ -129,7 +166,7 @@ void irq__update_msix_route(struct kvm *kvm, u32 gsi, struct msi_msg *msg)
if (!changed)
return;
if (ioctl(kvm->vm_fd, KVM_SET_GSI_ROUTING, irq_routing) == -1)
if (msi_routing_ops->update_route(kvm, &irq_routing->entries[i]))
die_perror("KVM_SET_GSI_ROUTING");
}
......
......@@ -350,7 +350,7 @@ static void virtio_pci__signal_msi(struct kvm *kvm, struct virtio_pci *vpci,
msi.devid = vpci->dev_hdr.dev_num << 3;
}
ioctl(kvm->vm_fd, KVM_SIGNAL_MSI, &msi);
irq__signal_msi(kvm, &msi);
}
int virtio_pci__signal_vq(struct kvm *kvm, struct virtio_device *vdev, u32 vq)
......@@ -488,7 +488,7 @@ int virtio_pci__init(struct kvm *kvm, void *dev, struct virtio_device *vdev,
vpci->pci_hdr.msix.pba_offset = cpu_to_le32(2 | PCI_IO_SIZE);
vpci->config_vector = 0;
if (kvm__supports_extension(kvm, KVM_CAP_SIGNAL_MSI))
if (irq__can_signal_msi(kvm))
vpci->features |= VIRTIO_PCI_F_SIGNAL_MSI;
r = device__register(&vpci->dev_hdr);
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment