aboutsummaryrefslogtreecommitdiffstats
path: root/arm
diff options
context:
space:
mode:
authorJean-Philippe Brucker <jean-philippe.brucker@arm.com>2017-11-03 11:38:41 +0000
committerWill Deacon <will.deacon@arm.com>2017-11-03 14:37:00 +0000
commitf6108d72e977cce00e7bc824acd1d73da8cc9729 (patch)
treef862a7e0b19036498461a8f1005439f56defa7c1 /arm
parent37b8e06be3ce63d4b50a5066446ca0cb5c61ba43 (diff)
downloadkvmtool-f6108d72e977cce00e7bc824acd1d73da8cc9729.tar.gz
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: Jean-Philippe Brucker <jean-philippe.brucker@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
Diffstat (limited to 'arm')
-rw-r--r--arm/aarch32/include/asm/kvm.h2
-rw-r--r--arm/aarch64/include/asm/kvm.h2
-rw-r--r--arm/gic.c13
-rw-r--r--arm/gicv2m.c153
-rw-r--r--arm/include/arm-common/gic.h2
-rw-r--r--arm/include/arm-common/kvm-config-arch.h2
6 files changed, 173 insertions, 1 deletions
diff --git a/arm/aarch32/include/asm/kvm.h b/arm/aarch32/include/asm/kvm.h
index 6ebd3e6a..02206673 100644
--- a/arm/aarch32/include/asm/kvm.h
+++ b/arm/aarch32/include/asm/kvm.h
@@ -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
diff --git a/arm/aarch64/include/asm/kvm.h b/arm/aarch64/include/asm/kvm.h
index c2860358..7d14507b 100644
--- a/arm/aarch64/include/asm/kvm.h
+++ b/arm/aarch64/include/asm/kvm.h
@@ -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
diff --git a/arm/gic.c b/arm/gic.c
index 9de6a9c9..aca0b939 100644
--- a/arm/gic.c
+++ b/arm/gic.c
@@ -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);
diff --git a/arm/gicv2m.c b/arm/gicv2m.c
new file mode 100644
index 00000000..d7e6398a
--- /dev/null
+++ b/arm/gicv2m.c
@@ -0,0 +1,153 @@
+#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);
+}
diff --git a/arm/include/arm-common/gic.h b/arm/include/arm-common/gic.h
index 433dd237..687effc6 100644
--- a/arm/include/arm-common/gic.h
+++ b/arm/include/arm-common/gic.h
@@ -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 */
diff --git a/arm/include/arm-common/kvm-config-arch.h b/arm/include/arm-common/kvm-config-arch.h
index f18b2c88..6a196f18 100644
--- a/arm/include/arm-common/kvm-config-arch.h
+++ b/arm/include/arm-common/kvm-config-arch.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),