|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v6 09/36] ARM: GICv3 ITS: introduce device mapping
On Fri, 7 Apr 2017, Andre Przywara wrote:
> The ITS uses device IDs to map LPIs to a device. Dom0 will later use
> those IDs, which we directly pass on to the host.
> For this we have to map each device that Dom0 may request to a host
> ITS device with the same identifier.
> Allocate the respective memory and enter each device into an rbtree to
> later be able to iterate over it or to easily teardown guests.
> Because device IDs are per ITS, we need to identify a virtual ITS. We
> use the doorbell address for that purpose, as it is a nice architectural
> MSI property and spares us handling with opaque pointer or break
> the VGIC abstraction.
>
> Signed-off-by: Andre Przywara <andre.przywara@xxxxxxx>
> ---
> xen/arch/arm/gic-v3-its.c | 345
> +++++++++++++++++++++++++++++++++++++++
> xen/arch/arm/vgic-v3-its.c | 4 +
> xen/include/asm-arm/domain.h | 3 +
> xen/include/asm-arm/gic_v3_its.h | 13 ++
> 4 files changed, 365 insertions(+)
>
> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
> index 0164b96..1951ec8 100644
> --- a/xen/arch/arm/gic-v3-its.c
> +++ b/xen/arch/arm/gic-v3-its.c
> @@ -21,6 +21,8 @@
> #include <xen/lib.h>
> #include <xen/delay.h>
> #include <xen/mm.h>
> +#include <xen/rbtree.h>
> +#include <xen/sched.h>
> #include <xen/sizes.h>
> #include <asm/gic.h>
> #include <asm/gic_v3_defs.h>
> @@ -36,6 +38,26 @@
> */
> LIST_HEAD(host_its_list);
>
> +/*
> + * Describes a device which is using the ITS and is used by a guest.
> + * Since device IDs are per ITS (in contrast to vLPIs, which are per
> + * guest), we have to differentiate between different virtual ITSes.
> + * We use the doorbell address here, since this is a nice architectural
> + * property of MSIs in general and we can easily get to the base address
> + * of the ITS and look that up.
> + */
> +struct its_device {
> + struct rb_node rbnode;
> + struct host_its *hw_its;
> + void *itt_addr;
> + paddr_t guest_doorbell; /* Identifies the virtual ITS */
> + uint32_t host_devid;
> + uint32_t guest_devid;
> + uint32_t eventids; /* Number of event IDs (MSIs) */
> + uint32_t *host_lpi_blocks; /* Which LPIs are used on the host */
> + struct pending_irq *pend_irqs; /* One struct per event */
> +};
> +
> bool gicv3_its_host_has_its(void)
> {
> return !list_empty(&host_its_list);
> @@ -157,6 +179,20 @@ static int its_send_cmd_sync(struct host_its *its,
> unsigned int cpu)
> return its_send_command(its, cmd);
> }
>
> +static int its_send_cmd_mapti(struct host_its *its,
> + uint32_t deviceid, uint32_t eventid,
> + uint32_t pintid, uint16_t icid)
> +{
> + uint64_t cmd[4];
> +
> + cmd[0] = GITS_CMD_MAPTI | ((uint64_t)deviceid << 32);
> + cmd[1] = eventid | ((uint64_t)pintid << 32);
> + cmd[2] = icid;
> + cmd[3] = 0x00;
> +
> + return its_send_command(its, cmd);
> +}
> +
> static int its_send_cmd_mapc(struct host_its *its, uint32_t collection_id,
> unsigned int cpu)
> {
> @@ -171,6 +207,43 @@ static int its_send_cmd_mapc(struct host_its *its,
> uint32_t collection_id,
> return its_send_command(its, cmd);
> }
>
> +static int its_send_cmd_mapd(struct host_its *its, uint32_t deviceid,
> + uint8_t size_bits, paddr_t itt_addr, bool valid)
> +{
> + uint64_t cmd[4];
> +
> + if ( valid )
> + {
> + ASSERT(size_bits <= its->evid_bits);
> + ASSERT(size_bits > 0);
> + ASSERT(!(itt_addr & ~GENMASK(51, 8)));
> +
> + /* The number of events is encoded as "number of bits minus one". */
> + size_bits--;
> + }
> + cmd[0] = GITS_CMD_MAPD | ((uint64_t)deviceid << 32);
> + cmd[1] = size_bits;
> + cmd[2] = itt_addr;
> + if ( valid )
> + cmd[2] |= GITS_VALID_BIT;
> + cmd[3] = 0x00;
> +
> + return its_send_command(its, cmd);
> +}
> +
> +static int its_send_cmd_inv(struct host_its *its,
> + uint32_t deviceid, uint32_t eventid)
> +{
> + uint64_t cmd[4];
> +
> + cmd[0] = GITS_CMD_INV | ((uint64_t)deviceid << 32);
> + cmd[1] = eventid;
> + cmd[2] = 0x00;
> + cmd[3] = 0x00;
> +
> + return its_send_command(its, cmd);
> +}
> +
> /* Set up the (1:1) collection mapping for the given host CPU. */
> int gicv3_its_setup_collection(unsigned int cpu)
> {
> @@ -450,6 +523,278 @@ int gicv3_its_init(void)
> return 0;
> }
>
> +/*
> + * TODO: Investiage the interaction when a guest removes a device while
> + * some LPIs are still in flight.
> + */
> +static int remove_mapped_guest_device(struct its_device *dev)
> +{
> + int ret = 0;
> + unsigned int i;
> +
> + if ( dev->hw_its )
> + /* MAPD also discards all events with this device ID. */
> + ret = its_send_cmd_mapd(dev->hw_its, dev->host_devid, 0, 0, false);
> +
> + for ( i = 0; i < dev->eventids / LPI_BLOCK; i++ )
> + gicv3_free_host_lpi_block(dev->host_lpi_blocks[i]);
> +
> + /* Make sure the MAPD command above is really executed. */
> + if ( !ret )
> + ret = gicv3_its_wait_commands(dev->hw_its);
> +
> + /* This should never happen, but just in case ... */
> + if ( ret && printk_ratelimit() )
> + printk(XENLOG_WARNING "Can't unmap host ITS device 0x%x\n",
> + dev->host_devid);
> +
> + xfree(dev->itt_addr);
> + xfree(dev->pend_irqs);
> + xfree(dev->host_lpi_blocks);
> + xfree(dev);
> +
> + return 0;
> +}
> +
> +static struct host_its *gicv3_its_find_by_doorbell(paddr_t doorbell_address)
> +{
> + struct host_its *hw_its;
> +
> + list_for_each_entry(hw_its, &host_its_list, entry)
> + {
> + if ( hw_its->addr + ITS_DOORBELL_OFFSET == doorbell_address )
> + return hw_its;
> + }
> +
> + return NULL;
> +}
> +
> +static int compare_its_guest_devices(struct its_device *dev,
> + paddr_t vdoorbell, uint32_t vdevid)
> +{
> + if ( dev->guest_doorbell < vdoorbell )
> + return -1;
> +
> + if ( dev->guest_doorbell > vdoorbell )
> + return 1;
> +
> + if ( dev->guest_devid < vdevid )
> + return -1;
> +
> + if ( dev->guest_devid > vdevid )
> + return 1;
> +
> + return 0;
> +}
> +
> +/*
> + * On the host ITS @its, map @nr_events consecutive LPIs.
> + * The mapping connects a device @devid and event @eventid pair to LPI @lpi,
> + * increasing both @eventid and @lpi to cover the number of requested LPIs.
> + */
> +static int gicv3_its_map_host_events(struct host_its *its,
> + uint32_t devid, uint32_t eventid,
> + uint32_t lpi, uint32_t nr_events)
> +{
> + uint32_t i;
> + int ret;
> +
> + for ( i = 0; i < nr_events; i++ )
> + {
> + /* For now we map every host LPI to host CPU 0 */
> + ret = its_send_cmd_mapti(its, devid, eventid + i, lpi + i, 0);
> + if ( ret )
> + return ret;
> +
> + ret = its_send_cmd_inv(its, devid, eventid + i);
> + if ( ret )
> + return ret;
> + }
> +
> + /* TODO: Consider using INVALL here. Didn't work on the model, though. */
> +
> + ret = its_send_cmd_sync(its, 0);
> + if ( ret )
> + return ret;
> +
> + return gicv3_its_wait_commands(its);
> +}
> +
> +/*
> + * Map a hardware device, identified by a certain host ITS and its device ID
> + * to domain d, a guest ITS (identified by its doorbell address) and device
> ID.
> + * Also provide the number of events (MSIs) needed for that device.
> + * This does not check if this particular hardware device is already mapped
> + * at another domain, it is expected that this would be done by the caller.
> + */
> +int gicv3_its_map_guest_device(struct domain *d,
> + paddr_t host_doorbell, uint32_t host_devid,
> + paddr_t guest_doorbell, uint32_t guest_devid,
> + uint64_t nr_events, bool valid)
> +{
> + void *itt_addr = NULL;
> + struct host_its *hw_its;
> + struct its_device *dev = NULL;
> + struct rb_node **new = &d->arch.vgic.its_devices.rb_node, *parent = NULL;
> + int i, ret = -ENOENT; /* "i" must be signed to check for >= 0
> below. */
> +
> + hw_its = gicv3_its_find_by_doorbell(host_doorbell);
> + if ( !hw_its )
> + return ret;
> +
> + /* Sanitise the provided hardware values against the host ITS. */
> + if ( host_devid >= BIT(hw_its->devid_bits) )
> + return -EINVAL;
> +
> + /*
> + * The ITS requires the number of events to be a power of 2. We allocate
> + * events and LPIs in chunks of LPI_BLOCK (=32), so make sure we
> + * allocate at least that many.
> + * TODO: Investigate if the number of events can be limited to smaller
> + * values if the guest does not require that many.
> + */
> + nr_events = BIT(fls(nr_events - 1));
> + if ( nr_events < LPI_BLOCK )
> + nr_events = LPI_BLOCK;
> + if ( nr_events >= BIT(hw_its->evid_bits) )
> + return -EINVAL;
> +
> + /* check for already existing mappings */
> + spin_lock(&d->arch.vgic.its_devices_lock);
> + while ( *new )
> + {
> + struct its_device *temp;
> + int cmp;
> +
> + temp = rb_entry(*new, struct its_device, rbnode);
> +
> + parent = *new;
> + cmp = compare_its_guest_devices(temp, guest_doorbell, guest_devid);
> + if ( !cmp )
> + {
> + if ( !valid )
> + rb_erase(&temp->rbnode, &d->arch.vgic.its_devices);
> +
> + spin_unlock(&d->arch.vgic.its_devices_lock);
> +
> + if ( valid )
> + {
> + printk(XENLOG_G_WARNING "d%d tried to remap guest ITS device
> 0x%x to host device 0x%x\n",
> + d->domain_id, guest_devid, host_devid);
> + return -EBUSY;
> + }
> +
> + return remove_mapped_guest_device(temp);
> + }
> +
> + if ( cmp > 0 )
> + new = &((*new)->rb_left);
> + else
> + new = &((*new)->rb_right);
> + }
> +
> + if ( !valid )
> + goto out_unlock;
> +
> + ret = -ENOMEM;
> +
> + /* An Interrupt Translation Table needs to be 256-byte aligned. */
> + itt_addr = _xzalloc(nr_events * hw_its->itte_size, 256);
> + if ( !itt_addr )
> + goto out_unlock;
> +
> + dev = xzalloc(struct its_device);
> + if ( !dev )
> + goto out_unlock;
> +
> + /*
> + * Allocate the pending_irqs for each virtual LPI. They will be put
> + * into the domain's radix tree upon the guest's MAPTI command.
> + * Pre-allocating memory for each *possible* LPI would be using way
> + * too much memory (they can be sparsely used by the guest), also
> + * allocating them on demand requires memory allocation in the interrupt
> + * injection code path, which is not really desired.
> + * So we compromise here by pre-allocating memory for each *mapped* LPI.
> + * See the mailing list discussion for some background:
> + * https://lists.xen.org/archives/html/xen-devel/2017-03/msg03645.html
I don't mean to be picky, but I don't think this comment is entirely
accurate: a "mapped LPI" should be an LPI for which MAPTI has been
called, but actually here we are allocating pending_irq struct for every
possible event specified by MAPD. Am I right?
I would rephrase the last sentence to:
So we compromise here by pre-allocating memory for each possible event
up to the max specified by MAPD.
With this comment fixed, you can add my reviewed-by.
> + */
> + dev->pend_irqs = xzalloc_array(struct pending_irq, nr_events);
> + if ( !dev->pend_irqs )
> + goto out_unlock;
> +
> + dev->host_lpi_blocks = xzalloc_array(uint32_t, nr_events);
> + if ( !dev->host_lpi_blocks )
> + goto out_unlock;
> +
> + ret = its_send_cmd_mapd(hw_its, host_devid, fls(nr_events - 1),
> + virt_to_maddr(itt_addr), true);
> + if ( ret )
> + goto out_unlock;
> +
> + dev->itt_addr = itt_addr;
> + dev->hw_its = hw_its;
> + dev->guest_doorbell = guest_doorbell;
> + dev->guest_devid = guest_devid;
> + dev->host_devid = host_devid;
> + dev->eventids = nr_events;
> +
> + rb_link_node(&dev->rbnode, parent, new);
> + rb_insert_color(&dev->rbnode, &d->arch.vgic.its_devices);
> +
> + spin_unlock(&d->arch.vgic.its_devices_lock);
> +
> + /*
> + * Map all host LPIs within this device already. We can't afford to queue
> + * any host ITS commands later on during the guest's runtime.
> + */
> + for ( i = 0; i < nr_events / LPI_BLOCK; i++ )
> + {
> + ret = gicv3_allocate_host_lpi_block(d, &dev->host_lpi_blocks[i]);
> + if ( ret < 0 )
> + break;
> +
> + ret = gicv3_its_map_host_events(hw_its, host_devid, i * LPI_BLOCK,
> + dev->host_lpi_blocks[i], LPI_BLOCK);
> + if ( ret < 0 )
> + break;
> + }
> +
> + if ( ret )
> + {
> + /* Clean up all allocated host LPI blocks. */
> + for ( ; i >= 0; i-- )
> + {
> + if ( dev->host_lpi_blocks[i] )
> + gicv3_free_host_lpi_block(dev->host_lpi_blocks[i]);
> + }
> +
> + /*
> + * Unmapping the device will discard all LPIs mapped so far.
> + * We are already on the failing path, so no error checking to
> + * not mask the original error value. This should never fail anyway.
> + */
> + its_send_cmd_mapd(hw_its, host_devid, 0, 0, false);
> +
> + goto out;
> + }
> +
> + return 0;
> +
> +out_unlock:
> + spin_unlock(&d->arch.vgic.its_devices_lock);
> +
> +out:
> + if ( dev )
> + {
> + xfree(dev->pend_irqs);
> + xfree(dev->host_lpi_blocks);
> + }
> + xfree(itt_addr);
> + xfree(dev);
> +
> + return ret;
> +}
> +
> /* Scan the DT for any ITS nodes and create a list of host ITSes out of it.
> */
> void gicv3_its_dt_init(const struct dt_device_node *node)
> {
> diff --git a/xen/arch/arm/vgic-v3-its.c b/xen/arch/arm/vgic-v3-its.c
> index 2f1a255..065ffe2 100644
> --- a/xen/arch/arm/vgic-v3-its.c
> +++ b/xen/arch/arm/vgic-v3-its.c
> @@ -69,11 +69,15 @@ struct vits_itte
>
> int vgic_v3_its_init_domain(struct domain *d)
> {
> + spin_lock_init(&d->arch.vgic.its_devices_lock);
> + d->arch.vgic.its_devices = RB_ROOT;
> +
> return 0;
> }
>
> void vgic_v3_its_free_domain(struct domain *d)
> {
> + ASSERT(RB_EMPTY_ROOT(&d->arch.vgic.its_devices));
> }
>
> /*
> diff --git a/xen/include/asm-arm/domain.h b/xen/include/asm-arm/domain.h
> index 68185e2..6de8082 100644
> --- a/xen/include/asm-arm/domain.h
> +++ b/xen/include/asm-arm/domain.h
> @@ -10,6 +10,7 @@
> #include <asm/gic.h>
> #include <public/hvm/params.h>
> #include <xen/serial.h>
> +#include <xen/rbtree.h>
>
> struct hvm_domain
> {
> @@ -108,6 +109,8 @@ struct arch_domain
> } *rdist_regions;
> int nr_regions; /* Number of rdist regions */
> uint32_t rdist_stride; /* Re-Distributor stride */
> + struct rb_root its_devices; /* Devices mapped to an ITS */
> + spinlock_t its_devices_lock; /* Protects the its_devices tree
> */
> #endif
> } vgic;
>
> diff --git a/xen/include/asm-arm/gic_v3_its.h
> b/xen/include/asm-arm/gic_v3_its.h
> index 84d1692..29559a3 100644
> --- a/xen/include/asm-arm/gic_v3_its.h
> +++ b/xen/include/asm-arm/gic_v3_its.h
> @@ -98,7 +98,10 @@
> #define GITS_CMD_MOVALL 0x0e
> #define GITS_CMD_DISCARD 0x0f
>
> +#define ITS_DOORBELL_OFFSET 0x10040
> +
> #include <xen/device_tree.h>
> +#include <xen/rbtree.h>
>
> #define HOST_ITS_FLUSH_CMD_QUEUE (1U << 0)
> #define HOST_ITS_USES_PTA (1U << 1)
> @@ -148,6 +151,16 @@ int gicv3_its_setup_collection(unsigned int cpu);
> int vgic_v3_its_init_domain(struct domain *d);
> void vgic_v3_its_free_domain(struct domain *d);
>
> +/*
> + * Map a device on the host by allocating an ITT on the host (ITS).
> + * "nr_event" specifies how many events (interrupts) this device will need.
> + * Setting "valid" to false deallocates the device.
> + */
> +int gicv3_its_map_guest_device(struct domain *d,
> + paddr_t host_doorbell, uint32_t host_devid,
> + paddr_t guest_doorbell, uint32_t guest_devid,
> + uint64_t nr_events, bool valid);
> +
> int gicv3_allocate_host_lpi_block(struct domain *d, uint32_t *first_lpi);
> void gicv3_free_host_lpi_block(uint32_t first_lpi);
>
> --
> 2.9.0
>
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |