diff mbox

[kvmtool,2/2] Add GICv2m support

Message ID 20171103113841.3066-3-jean-philippe.brucker@arm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Jean-Philippe Brucker Nov. 3, 2017, 11:38 a.m. UTC
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>
---
 Makefile                                 |   2 +-
 arm/aarch32/include/asm/kvm.h            |   2 +
 arm/aarch64/include/asm/kvm.h            |   2 +
 arm/gic.c                                |  13 +++
 arm/gicv2m.c                             | 153 +++++++++++++++++++++++++++++++
 arm/include/arm-common/gic.h             |   3 +
 arm/include/arm-common/kvm-config-arch.h |   2 +-
 include/kvm/irq.h                        |  11 +++
 irq.c                                    |  68 +++++++++++---
 virtio/pci.c                             |   4 +-
 10 files changed, 244 insertions(+), 16 deletions(-)
 create mode 100644 arm/gicv2m.c

Comments

Andre Przywara Nov. 3, 2017, 2:27 p.m. UTC | #1
Hi,

good stuff, thanks for doing this!

On 03/11/17 11:38, Jean-Philippe Brucker wrote:
> 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.

I was wondering if it would be worth to split this up, and introduce the
msi_routing_ops in a separate patch first, possibly even the changes to
pci.c.

> 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.

As others have pointed out already, these patches seem to rely on some
other patches.
However by ignoring the two hunks in non-yet-existing functions in irq.c
and adding #include <stdbool.h> to include/kvm/irq.h I could compile and
run it on a Juno.

> The device can be enabled by passing "--irqchip gicv2m" to kvmtool.

Might be worth to mention that for actually using MSIs in general one
needs "--force-pci" on the command line as well.

Given the compile fixes mentioned above, this boots with MSIs on my
Juno. Testing 32 bit in a minute.

Cheers,
Andre.

> 
> Signed-off-by: Jean-Philippe Brucker <jean-philippe.brucker@arm.com>
> ---
>  Makefile                                 |   2 +-
>  arm/aarch32/include/asm/kvm.h            |   2 +
>  arm/aarch64/include/asm/kvm.h            |   2 +
>  arm/gic.c                                |  13 +++
>  arm/gicv2m.c                             | 153 +++++++++++++++++++++++++++++++
>  arm/include/arm-common/gic.h             |   3 +
>  arm/include/arm-common/kvm-config-arch.h |   2 +-
>  include/kvm/irq.h                        |  11 +++
>  irq.c                                    |  68 +++++++++++---
>  virtio/pci.c                             |   4 +-
>  10 files changed, 244 insertions(+), 16 deletions(-)
>  create mode 100644 arm/gicv2m.c
> 
> diff --git a/Makefile b/Makefile
> index 6414b5853947..93dc0673571d 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -153,7 +153,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
> diff --git a/arm/aarch32/include/asm/kvm.h b/arm/aarch32/include/asm/kvm.h
> index 6ebd3e6a1fd1..022066730dc7 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 c2860358ae3e..7d14507bd329 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 e53f932e28c3..46cfa245e590 100644
> --- a/arm/gic.c
> +++ b/arm/gic.c
> @@ -32,6 +32,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")) {
> @@ -134,6 +136,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 */
> @@ -165,6 +169,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;
> @@ -183,6 +188,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;
> @@ -243,6 +249,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:
> @@ -325,6 +335,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 000000000000..d7e6398a13a8
> --- /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 0c279aca8a5e..560fea2a2c5a 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,
>  };
> @@ -39,4 +40,6 @@ void gic__del_irqfd(struct kvm *kvm, unsigned int gsi, int trigger_fd);
>  #define irq__add_irqfd gic__add_irqfd
>  #define irq__del_irqfd gic__del_irqfd
>  
> +int gic__create_gicv2m_frame(struct kvm *kvm, u64 msi_frame_addr);
> +
>  #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 f18b2c88fa79..6a196f1852de 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),
>  
> diff --git a/include/kvm/irq.h b/include/kvm/irq.h
> index b24385b13f66..530ce3f9133d 100644
> --- a/include/kvm/irq.h
> +++ b/include/kvm/irq.h
> @@ -11,6 +11,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;
>  
> @@ -24,6 +32,9 @@ 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);
> +
>  /*
>   * The function takes two eventfd arguments, trigger_fd and resample_fd. If
>   * resample_fd is <= 0, resampling is disabled and the IRQ is edge-triggered
> diff --git a/irq.c b/irq.c
> index 25fa572761fa..cdcf99233baa 100644
> --- a/irq.c
> +++ b/irq.c
> @@ -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");
>  }
>  
> @@ -143,6 +180,10 @@ int irq__common_add_irqfd(struct kvm *kvm, unsigned int gsi, int trigger_fd,
>  		.resamplefd	= resample_fd,
>  	};
>  
> +	/* If we emulate MSI routing, translate the MSI to the corresponding IRQ */
> +	if (msi_routing_ops->translate_gsi)
> +		irqfd.gsi = msi_routing_ops->translate_gsi(kvm, gsi);
> +
>  	return ioctl(kvm->vm_fd, KVM_IRQFD, &irqfd);
>  }
>  
> @@ -154,6 +195,9 @@ void irq__common_del_irqfd(struct kvm *kvm, unsigned int gsi, int trigger_fd)
>  		.flags		= KVM_IRQFD_FLAG_DEASSIGN,
>  	};
>  
> +	if (msi_routing_ops->translate_gsi)
> +		irqfd.gsi = msi_routing_ops->translate_gsi(kvm, gsi);
> +
>  	ioctl(kvm->vm_fd, KVM_IRQFD, &irqfd);
>  }
>  
> diff --git a/virtio/pci.c b/virtio/pci.c
> index 46473b00029b..4ce111108100 100644
> --- a/virtio/pci.c
> +++ b/virtio/pci.c
> @@ -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);
>
Jean-Philippe Brucker Nov. 3, 2017, 3:03 p.m. UTC | #2
On 03/11/17 14:27, Andre Przywara wrote:
> Hi,
> 
> good stuff, thanks for doing this!
> 
> On 03/11/17 11:38, Jean-Philippe Brucker wrote:
>> 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.
> 
> I was wondering if it would be worth to split this up, and introduce the
> msi_routing_ops in a separate patch first, possibly even the changes to
> pci.c.
> 
>> 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.
> 
> As others have pointed out already, these patches seem to rely on some
> other patches.
> However by ignoring the two hunks in non-yet-existing functions in irq.c

Right, this was based on the VFIO series and a bit rushed, sorry about
that. A little more work is needed to support irqfd ("irq: add irqfd
helpers") and the combination GICv2m+force-pci+vhost, but it should be
ready soon enough.

Thanks,
Jean
diff mbox

Patch

diff --git a/Makefile b/Makefile
index 6414b5853947..93dc0673571d 100644
--- a/Makefile
+++ b/Makefile
@@ -153,7 +153,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
diff --git a/arm/aarch32/include/asm/kvm.h b/arm/aarch32/include/asm/kvm.h
index 6ebd3e6a1fd1..022066730dc7 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 c2860358ae3e..7d14507bd329 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 e53f932e28c3..46cfa245e590 100644
--- a/arm/gic.c
+++ b/arm/gic.c
@@ -32,6 +32,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")) {
@@ -134,6 +136,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 */
@@ -165,6 +169,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;
@@ -183,6 +188,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;
@@ -243,6 +249,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:
@@ -325,6 +335,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 000000000000..d7e6398a13a8
--- /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 0c279aca8a5e..560fea2a2c5a 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,
 };
@@ -39,4 +40,6 @@  void gic__del_irqfd(struct kvm *kvm, unsigned int gsi, int trigger_fd);
 #define irq__add_irqfd gic__add_irqfd
 #define irq__del_irqfd gic__del_irqfd
 
+int gic__create_gicv2m_frame(struct kvm *kvm, u64 msi_frame_addr);
+
 #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 f18b2c88fa79..6a196f1852de 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),
 
diff --git a/include/kvm/irq.h b/include/kvm/irq.h
index b24385b13f66..530ce3f9133d 100644
--- a/include/kvm/irq.h
+++ b/include/kvm/irq.h
@@ -11,6 +11,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;
 
@@ -24,6 +32,9 @@  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);
+
 /*
  * The function takes two eventfd arguments, trigger_fd and resample_fd. If
  * resample_fd is <= 0, resampling is disabled and the IRQ is edge-triggered
diff --git a/irq.c b/irq.c
index 25fa572761fa..cdcf99233baa 100644
--- a/irq.c
+++ b/irq.c
@@ -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");
 }
 
@@ -143,6 +180,10 @@  int irq__common_add_irqfd(struct kvm *kvm, unsigned int gsi, int trigger_fd,
 		.resamplefd	= resample_fd,
 	};
 
+	/* If we emulate MSI routing, translate the MSI to the corresponding IRQ */
+	if (msi_routing_ops->translate_gsi)
+		irqfd.gsi = msi_routing_ops->translate_gsi(kvm, gsi);
+
 	return ioctl(kvm->vm_fd, KVM_IRQFD, &irqfd);
 }
 
@@ -154,6 +195,9 @@  void irq__common_del_irqfd(struct kvm *kvm, unsigned int gsi, int trigger_fd)
 		.flags		= KVM_IRQFD_FLAG_DEASSIGN,
 	};
 
+	if (msi_routing_ops->translate_gsi)
+		irqfd.gsi = msi_routing_ops->translate_gsi(kvm, gsi);
+
 	ioctl(kvm->vm_fd, KVM_IRQFD, &irqfd);
 }
 
diff --git a/virtio/pci.c b/virtio/pci.c
index 46473b00029b..4ce111108100 100644
--- a/virtio/pci.c
+++ b/virtio/pci.c
@@ -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);