|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] Re: [Xen-devel] [PATCH v5 11/30] ARM: GICv3 ITS: introduce device mapping
On Thu, 6 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 | 263
> +++++++++++++++++++++++++++++++++++++++
> xen/arch/arm/vgic-v3-its.c | 3 +
> xen/include/asm-arm/domain.h | 3 +
> xen/include/asm-arm/gic_v3_its.h | 13 ++
> 4 files changed, 282 insertions(+)
>
> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
> index eb47c9d..45bbfa7 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_devices {
> + 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);
> @@ -185,6 +207,30 @@ 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_ULL(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)
> {
> @@ -477,6 +523,66 @@ int gicv3_its_init(void)
> return 0;
> }
>
> +static int remove_mapped_guest_device(struct its_devices *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(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_devices *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,
> @@ -510,6 +616,163 @@ static int gicv3_its_map_host_events(struct host_its
> *its,
> 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,
> + uint32_t nr_events, bool valid)
> +{
> + void *itt_addr = NULL;
> + struct host_its *hw_its;
> + struct its_devices *dev = NULL;
> + struct rb_node **new = &d->arch.vgic.its_devices.rb_node, *parent = NULL;
> + unsigned int i;
> + int ret = -ENOENT;
> +
> + 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;
> +
> + /* We allocate events and LPIs in chunks of LPI_BLOCK (=32). */
> + nr_events = ROUNDUP(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_devices *temp;
> + int cmp;
> +
> + temp = rb_entry(*new, struct its_devices, 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_devices);
> + 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.
> + */
Please expand the comment with a link to the xen-devel conversation
about allocation strategy and a TODO saying that we shouldn't need to
preallocate all pending_irq struct, but we do it for simplicity at the
moment.
> + 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 )
> + {
> + do {
> + i--;
> + gicv3_free_host_lpi_block(dev->host_lpi_blocks[i]);
> + if ( i == 0 )
> + break;
> + } while (1);
> +
> + /* Unmapping the device will discard all LPIs mapped so far. */
> + its_send_cmd_mapd(hw_its, host_devid, 1, 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 94c3c44..9dfda59 100644
> --- a/xen/arch/arm/vgic-v3-its.c
> +++ b/xen/arch/arm/vgic-v3-its.c
> @@ -69,10 +69,13 @@ struct vits_itte
>
> void vgic_v3_its_init_domain(struct domain *d)
> {
> + spin_lock_init(&d->arch.vgic.its_devices_lock);
> + d->arch.vgic.its_devices = RB_ROOT;
> }
>
> 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 09fe502..503a3cf 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 9b0c0ec..3b7c724 100644
> --- a/xen/include/asm-arm/gic_v3_its.h
> +++ b/xen/include/asm-arm/gic_v3_its.h
> @@ -103,7 +103,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)
> @@ -153,6 +156,16 @@ int gicv3_its_setup_collection(unsigned int cpu);
> void 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,
> + uint32_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.8.2
>
_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel
|
![]() |
Lists.xenproject.org is hosted with RackSpace, monitoring our |