[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Xen-devel] [PATCH v12 25/34] ARM: vITS: handle MAPD command



On Wed, 14 Jun 2017, Andre Przywara wrote:
> The MAPD command maps a device by associating a memory region for
> storing ITEs with a certain device ID. Since it features a valid bit,
> MAPD also covers the "unmap" functionality, which we also cover here.
> We store the given guest physical address in the device table, and, if
> this command comes from Dom0, tell the host ITS driver about this new
> mapping, so it can issue the corresponding host MAPD command and create
> the required tables. We take care of rolling back actions should one
> step fail.
> Upon unmapping a device we make sure we clean up all associated
> resources and release the memory again.
> We use our existing guest memory access function to find the right ITT
> entry and store the mapping there (in guest memory).
> 
> Signed-off-by: Andre Przywara <andre.przywara@xxxxxxx>

Acked-by: Stefano Stabellini <sstabellini@xxxxxxxxxx>


> ---
>  xen/arch/arm/gic-v3-its.c        |  17 +++++
>  xen/arch/arm/gic-v3-lpi.c        |  17 +++++
>  xen/arch/arm/vgic-v3-its.c       | 142 
> +++++++++++++++++++++++++++++++++++++++
>  xen/include/asm-arm/gic_v3_its.h |   5 ++
>  4 files changed, 181 insertions(+)
> 
> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
> index 38f0840..8864e0b 100644
> --- a/xen/arch/arm/gic-v3-its.c
> +++ b/xen/arch/arm/gic-v3-its.c
> @@ -859,6 +859,23 @@ struct pending_irq 
> *gicv3_its_get_event_pending_irq(struct domain *d,
>      return get_event_pending_irq(d, vdoorbell_address, vdevid, eventid, 
> NULL);
>  }
>  
> +int gicv3_remove_guest_event(struct domain *d, paddr_t vdoorbell_address,
> +                             uint32_t vdevid, uint32_t eventid)
> +{
> +    uint32_t host_lpi = INVALID_LPI;
> +
> +    if ( !get_event_pending_irq(d, vdoorbell_address, vdevid, eventid,
> +                                &host_lpi) )
> +        return -EINVAL;
> +
> +    if ( host_lpi == INVALID_LPI )
> +        return -EINVAL;
> +
> +    gicv3_lpi_update_host_entry(host_lpi, d->domain_id, INVALID_LPI);
> +
> +    return 0;
> +}
> +
>  /* 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/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c
> index dc936fa..c3474f5 100644
> --- a/xen/arch/arm/gic-v3-lpi.c
> +++ b/xen/arch/arm/gic-v3-lpi.c
> @@ -215,6 +215,23 @@ out:
>      irq_exit();
>  }
>  
> +void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id,
> +                                 uint32_t virt_lpi)
> +{
> +    union host_lpi *hlpip, hlpi;
> +
> +    ASSERT(host_lpi >= LPI_OFFSET);
> +
> +    host_lpi -= LPI_OFFSET;
> +
> +    hlpip = &lpi_data.host_lpis[host_lpi / HOST_LPIS_PER_PAGE][host_lpi % 
> HOST_LPIS_PER_PAGE];
> +
> +    hlpi.virt_lpi = virt_lpi;
> +    hlpi.dom_id = domain_id;
> +
> +    write_u64_atomic(&hlpip->data, hlpi.data);
> +}
> +
>  static int gicv3_lpi_allocate_pendtable(uint64_t *reg)
>  {
>      uint64_t val;
> diff --git a/xen/arch/arm/vgic-v3-its.c b/xen/arch/arm/vgic-v3-its.c
> index 4552bc9..d236bbe 100644
> --- a/xen/arch/arm/vgic-v3-its.c
> +++ b/xen/arch/arm/vgic-v3-its.c
> @@ -159,6 +159,21 @@ static struct vcpu *get_vcpu_from_collection(struct 
> virt_its *its,
>      return its->d->vcpu[vcpu_id];
>  }
>  
> +/* Set the address of an ITT for a given device ID. */
> +static int its_set_itt_address(struct virt_its *its, uint32_t devid,
> +                               paddr_t itt_address, uint32_t nr_bits)
> +{
> +    paddr_t addr = get_baser_phys_addr(its->baser_dev);
> +    dev_table_entry_t itt_entry = DEV_TABLE_ENTRY(itt_address, nr_bits);
> +
> +    if ( devid >= its->max_devices )
> +        return -ENOENT;
> +
> +    return vgic_access_guest_memory(its->d,
> +                                    addr + devid * sizeof(dev_table_entry_t),
> +                                    &itt_entry, sizeof(itt_entry), true);
> +}
> +
>  /*
>   * Lookup the address of the Interrupt Translation Table associated with
>   * that device ID.
> @@ -375,6 +390,130 @@ out_unlock:
>      return ret;
>  }
>  
> +/* Must be called with the ITS lock held. */
> +static int its_discard_event(struct virt_its *its,
> +                             uint32_t vdevid, uint32_t vevid)
> +{
> +    struct pending_irq *p;
> +    unsigned long flags;
> +    struct vcpu *vcpu;
> +    uint32_t vlpi;
> +
> +    ASSERT(spin_is_locked(&its->its_lock));
> +
> +    if ( !read_itte(its, vdevid, vevid, &vcpu, &vlpi) )
> +        return -ENOENT;
> +
> +    if ( vlpi == INVALID_LPI )
> +        return -ENOENT;
> +
> +    /*
> +     * TODO: This relies on the VCPU being correct in the ITS tables.
> +     * This can be fixed by either using a per-IRQ lock or by using
> +     * the VCPU ID from the pending_irq instead.
> +     */
> +    spin_lock_irqsave(&vcpu->arch.vgic.lock, flags);
> +
> +    /* Remove the pending_irq from the tree. */
> +    write_lock(&its->d->arch.vgic.pend_lpi_tree_lock);
> +    p = radix_tree_delete(&its->d->arch.vgic.pend_lpi_tree, vlpi);
> +    write_unlock(&its->d->arch.vgic.pend_lpi_tree_lock);
> +
> +    if ( !p )
> +    {
> +        spin_unlock_irqrestore(&vcpu->arch.vgic.lock, flags);
> +
> +        return -ENOENT;
> +    }
> +
> +    /* Cleanup the pending_irq and disconnect it from the LPI. */
> +    gic_remove_irq_from_queues(vcpu, p);
> +    vgic_init_pending_irq(p, INVALID_LPI);
> +
> +    spin_unlock_irqrestore(&vcpu->arch.vgic.lock, flags);
> +
> +    /* Remove the corresponding host LPI entry */
> +    return gicv3_remove_guest_event(its->d, its->doorbell_address,
> +                                    vdevid, vevid);
> +}
> +
> +static void its_unmap_device(struct virt_its *its, uint32_t devid)
> +{
> +    dev_table_entry_t itt;
> +    uint64_t evid;
> +
> +    spin_lock(&its->its_lock);
> +
> +    if ( its_get_itt(its, devid, &itt) )
> +        goto out;
> +
> +    /*
> +     * For DomUs we need to check that the number of events per device
> +     * is really limited, otherwise looping over all events can take too
> +     * long for a guest. This ASSERT can then be removed if that is
> +     * covered.
> +     */
> +    ASSERT(is_hardware_domain(its->d));
> +
> +    for ( evid = 0; evid < DEV_TABLE_ITT_SIZE(itt); evid++ )
> +        /* Don't care about errors here, clean up as much as possible. */
> +        its_discard_event(its, devid, evid);
> +
> +out:
> +    spin_unlock(&its->its_lock);
> +}
> +
> +static int its_handle_mapd(struct virt_its *its, uint64_t *cmdptr)
> +{
> +    /* size and devid get validated by the functions called below. */
> +    uint32_t devid = its_cmd_get_deviceid(cmdptr);
> +    unsigned int size = its_cmd_get_size(cmdptr) + 1;
> +    bool valid = its_cmd_get_validbit(cmdptr);
> +    paddr_t itt_addr = its_cmd_get_ittaddr(cmdptr);
> +    int ret;
> +
> +    /* Sanitize the number of events. */
> +    if ( valid && (size > its->evid_bits) )
> +        return -1;
> +
> +    if ( !valid )
> +        /* Discard all events and remove pending LPIs. */
> +        its_unmap_device(its, devid);
> +
> +    /*
> +     * There is no easy and clean way for Xen to know the ITS device ID of a
> +     * particular (PCI) device, so we have to rely on the guest telling
> +     * us about it. For *now* we are just using the device ID *Dom0* uses,
> +     * because the driver there has the actual knowledge.
> +     * Eventually this will be replaced with a dedicated hypercall to
> +     * announce pass-through of devices.
> +     */
> +    if ( is_hardware_domain(its->d) )
> +    {
> +
> +        /*
> +         * Dom0's ITSes are mapped 1:1, so both addresses are the same.
> +         * Also the device IDs are equal.
> +         */
> +        ret = gicv3_its_map_guest_device(its->d, its->doorbell_address, 
> devid,
> +                                         its->doorbell_address, devid,
> +                                         BIT(size), valid);
> +        if ( ret && valid )
> +            return ret;
> +    }
> +
> +    spin_lock(&its->its_lock);
> +
> +    if ( valid )
> +        ret = its_set_itt_address(its, devid, itt_addr, size);
> +    else
> +        ret = its_set_itt_address(its, devid, INVALID_PADDR, 1);
> +
> +    spin_unlock(&its->its_lock);
> +
> +    return ret;
> +}
> +
>  #define ITS_CMD_BUFFER_SIZE(baser)      ((((baser) & 0xff) + 1) << 12)
>  #define ITS_CMD_OFFSET(reg)             ((reg) & GENMASK(19, 5))
>  
> @@ -420,6 +559,9 @@ static int vgic_its_handle_cmds(struct domain *d, struct 
> virt_its *its)
>          case GITS_CMD_MAPC:
>              ret = its_handle_mapc(its, command);
>              break;
> +        case GITS_CMD_MAPD:
> +            ret = its_handle_mapd(its, command);
> +            break;
>          case GITS_CMD_SYNC:
>              /* We handle ITS commands synchronously, so we ignore SYNC. */
>              break;
> diff --git a/xen/include/asm-arm/gic_v3_its.h 
> b/xen/include/asm-arm/gic_v3_its.h
> index be67726..0089ac2 100644
> --- a/xen/include/asm-arm/gic_v3_its.h
> +++ b/xen/include/asm-arm/gic_v3_its.h
> @@ -175,6 +175,11 @@ struct pending_irq 
> *gicv3_its_get_event_pending_irq(struct domain *d,
>                                                      paddr_t 
> vdoorbell_address,
>                                                      uint32_t vdevid,
>                                                      uint32_t eventid);
> +int gicv3_remove_guest_event(struct domain *d, paddr_t vdoorbell_address,
> +                                     uint32_t vdevid, uint32_t eventid);
> +void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id,
> +                                 uint32_t virt_lpi);
> +
>  #else
>  
>  static inline void gicv3_its_dt_init(const struct dt_device_node *node)
> -- 
> 2.9.0
> 

_______________________________________________
Xen-devel mailing list
Xen-devel@xxxxxxxxxxxxx
https://lists.xen.org/xen-devel

 


Rackspace

Lists.xenproject.org is hosted with RackSpace, monitoring our
servers 24x7x365 and backed by RackSpace's Fanatical Support®.