diff mbox

[v10,25/32] ARM: vITS: handle MAPTI/MAPI command

Message ID 20170526173540.10066-26-andre.przywara@arm.com (mailing list archive)
State New, archived
Headers show

Commit Message

Andre Przywara May 26, 2017, 5:35 p.m. UTC
The MAPTI commands associates a DeviceID/EventID pair with a LPI/CPU
pair and actually instantiates LPI interrupts. MAPI is just a variant
of this comment, where the LPI ID is the same as the event ID.
We connect the already allocated host LPI to this virtual LPI, so that
any triggering LPI on the host can be quickly forwarded to a guest.
Beside entering the domain and the virtual LPI number in the respective
host LPI entry, we also initialize and add the already allocated
struct pending_irq to our radix tree, so that we can now easily find it
by its virtual LPI number.
We also read the property table to update the enabled bit and the
priority for our new LPI, as we might have missed this during an earlier
INVALL call (which only checks mapped LPIs). But we make sure that the
property table is actually valid, as all redistributors might still
be disabled at this point.
Since write_itte_locked() now sees its first usage, we change the
declaration to static.

Signed-off-by: Andre Przywara <andre.przywara@arm.com>
---
 xen/arch/arm/gic-v3-its.c        |  27 ++++++++
 xen/arch/arm/vgic-v3-its.c       | 138 ++++++++++++++++++++++++++++++++++++++-
 xen/include/asm-arm/gic_v3_its.h |   3 +
 3 files changed, 165 insertions(+), 3 deletions(-)

Comments

Julien Grall June 2, 2017, 5:12 p.m. UTC | #1
Hi Andre,

On 05/26/2017 06:35 PM, Andre Przywara wrote:
> The MAPTI commands associates a DeviceID/EventID pair with a LPI/CPU
> pair and actually instantiates LPI interrupts. MAPI is just a variant
> of this comment, where the LPI ID is the same as the event ID.
> We connect the already allocated host LPI to this virtual LPI, so that
> any triggering LPI on the host can be quickly forwarded to a guest.
> Beside entering the domain and the virtual LPI number in the respective
> host LPI entry, we also initialize and add the already allocated
> struct pending_irq to our radix tree, so that we can now easily find it
> by its virtual LPI number.
> We also read the property table to update the enabled bit and the
> priority for our new LPI, as we might have missed this during an earlier
> INVALL call (which only checks mapped LPIs). But we make sure that the
> property table is actually valid, as all redistributors might still
> be disabled at this point.
> Since write_itte_locked() now sees its first usage, we change the
> declaration to static.
> 
> Signed-off-by: Andre Przywara <andre.przywara@arm.com>
> ---
>   xen/arch/arm/gic-v3-its.c        |  27 ++++++++
>   xen/arch/arm/vgic-v3-its.c       | 138 ++++++++++++++++++++++++++++++++++++++-
>   xen/include/asm-arm/gic_v3_its.h |   3 +
>   3 files changed, 165 insertions(+), 3 deletions(-)
> 
> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
> index 8864e0b..41fff64 100644
> --- a/xen/arch/arm/gic-v3-its.c
> +++ b/xen/arch/arm/gic-v3-its.c
> @@ -876,6 +876,33 @@ int gicv3_remove_guest_event(struct domain *d, paddr_t vdoorbell_address,
>       return 0;
>   }
>   
> +/*
> + * Connects the event ID for an already assigned device to the given VCPU/vLPI
> + * pair. The corresponding physical LPI is already mapped on the host side
> + * (when assigning the physical device to the guest), so we just connect the
> + * target VCPU/vLPI pair to that interrupt to inject it properly if it fires.
> + * Returns a pointer to the already allocated struct pending_irq that is
> + * meant to be used by that event.
> + */
> +struct pending_irq *gicv3_assign_guest_event(struct domain *d,
> +                                             paddr_t vdoorbell_address,
> +                                             uint32_t vdevid, uint32_t eventid,
> +                                             uint32_t virt_lpi)
> +{
> +    struct pending_irq *pirq;
> +    uint32_t host_lpi = 0;
This should be INVALID_LPI and not 0.

[...]

> +/*
> + * For a given virtual LPI read the enabled bit and priority from the virtual
> + * property table and update the virtual IRQ's state in the given pending_irq.
> + * Must be called with the respective VGIC VCPU lock held.
> + */
> +static int update_lpi_property(struct domain *d, struct pending_irq *p)
> +{
> +    paddr_t addr;
> +    uint8_t property;
> +    int ret;
> +
> +    /*
> +     * If no redistributor has its LPIs enabled yet, we can't access the
> +     * property table. In this case we just can't update the properties,
> +     * but this should not be an error from an ITS point of view.
> +     */
> +    if ( !read_atomic(&d->arch.vgic.rdists_enabled) )
> +        return 0;

AFAICT, there are no other place where the property would be updated. 
Should we put a warning to let the user know that property will not be 
updated? More that you don't return an error so no easy way to know 
what's going on.

> +
> +    addr = d->arch.vgic.rdist_propbase & GENMASK(51, 12);
> +
> +    ret = vgic_access_guest_memory(d, addr + p->irq - LPI_OFFSET,
> +                                   &property, sizeof(property), false);
> +    if ( ret )
> +        return ret;
> +
> +    write_atomic(&p->lpi_priority, property & LPI_PROP_PRIO_MASK);
> +
> +    if ( property & LPI_PROP_ENABLED )
> +        set_bit(GIC_IRQ_GUEST_ENABLED, &p->status);
> +    else
> +        clear_bit(GIC_IRQ_GUEST_ENABLED, &p->status);
> +
> +    return 0;
> +}
> +
>   /* Must be called with the ITS lock held. */
>   static int its_discard_event(struct virt_its *its,
>                                uint32_t vdevid, uint32_t vevid)
> @@ -538,6 +574,98 @@ static int its_handle_mapd(struct virt_its *its, uint64_t *cmdptr)
>       return ret;
>   }
>   
> +static int its_handle_mapti(struct virt_its *its, uint64_t *cmdptr)
> +{

[...]

> +    /*
> +     * radix_tree_insert() returns an error either due to an internal
> +     * condition (like memory allocation failure) or because the LPI already
> +     * existed in the tree. We don't support the latter case, so we always
> +     * cleanup and return an error here in any case.
> +     */
> +out_remove_host_entry:
> +    gicv3_remove_guest_event(its->d, its->doorbell_address, devid, eventid);

Can gicv3_remove_guest_event fail? If yes, should we check the 
return/add comment? If not, then we should have an ASSERT(....).

Cheers,
Andre Przywara June 7, 2017, 5:49 p.m. UTC | #2
Hi,

On 02/06/17 18:12, Julien Grall wrote:
> Hi Andre,
> 
> On 05/26/2017 06:35 PM, Andre Przywara wrote:
>> The MAPTI commands associates a DeviceID/EventID pair with a LPI/CPU
>> pair and actually instantiates LPI interrupts. MAPI is just a variant
>> of this comment, where the LPI ID is the same as the event ID.
>> We connect the already allocated host LPI to this virtual LPI, so that
>> any triggering LPI on the host can be quickly forwarded to a guest.
>> Beside entering the domain and the virtual LPI number in the respective
>> host LPI entry, we also initialize and add the already allocated
>> struct pending_irq to our radix tree, so that we can now easily find it
>> by its virtual LPI number.
>> We also read the property table to update the enabled bit and the
>> priority for our new LPI, as we might have missed this during an earlier
>> INVALL call (which only checks mapped LPIs). But we make sure that the
>> property table is actually valid, as all redistributors might still
>> be disabled at this point.
>> Since write_itte_locked() now sees its first usage, we change the
>> declaration to static.
>>
>> Signed-off-by: Andre Przywara <andre.przywara@arm.com>
>> ---
>>   xen/arch/arm/gic-v3-its.c        |  27 ++++++++
>>   xen/arch/arm/vgic-v3-its.c       | 138
>> ++++++++++++++++++++++++++++++++++++++-
>>   xen/include/asm-arm/gic_v3_its.h |   3 +
>>   3 files changed, 165 insertions(+), 3 deletions(-)
>>
>> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
>> index 8864e0b..41fff64 100644
>> --- a/xen/arch/arm/gic-v3-its.c
>> +++ b/xen/arch/arm/gic-v3-its.c
>> @@ -876,6 +876,33 @@ int gicv3_remove_guest_event(struct domain *d,
>> paddr_t vdoorbell_address,
>>       return 0;
>>   }
>>   +/*
>> + * Connects the event ID for an already assigned device to the given
>> VCPU/vLPI
>> + * pair. The corresponding physical LPI is already mapped on the host
>> side
>> + * (when assigning the physical device to the guest), so we just
>> connect the
>> + * target VCPU/vLPI pair to that interrupt to inject it properly if
>> it fires.
>> + * Returns a pointer to the already allocated struct pending_irq that is
>> + * meant to be used by that event.
>> + */
>> +struct pending_irq *gicv3_assign_guest_event(struct domain *d,
>> +                                             paddr_t vdoorbell_address,
>> +                                             uint32_t vdevid,
>> uint32_t eventid,
>> +                                             uint32_t virt_lpi)
>> +{
>> +    struct pending_irq *pirq;
>> +    uint32_t host_lpi = 0;
> This should be INVALID_LPI and not 0.
> 
> [...]
> 
>> +/*
>> + * For a given virtual LPI read the enabled bit and priority from the
>> virtual
>> + * property table and update the virtual IRQ's state in the given
>> pending_irq.
>> + * Must be called with the respective VGIC VCPU lock held.
>> + */
>> +static int update_lpi_property(struct domain *d, struct pending_irq *p)
>> +{
>> +    paddr_t addr;
>> +    uint8_t property;
>> +    int ret;
>> +
>> +    /*
>> +     * If no redistributor has its LPIs enabled yet, we can't access the
>> +     * property table. In this case we just can't update the properties,
>> +     * but this should not be an error from an ITS point of view.
>> +     */
>> +    if ( !read_atomic(&d->arch.vgic.rdists_enabled) )
>> +        return 0;
> 
> AFAICT, there are no other place where the property would be updated.
> Should we put a warning to let the user know that property will not be
> updated? More that you don't return an error so no easy way to know
> what's going on.

So I thought about this again: If we handle an INV command while the
respective redistributor has LPIs off, even hardware can't do anything,
as having LPIs disabled means that there is no valid property table.
So a hardware redistributor would probably just ignore this request.

I see us only calling update_lpi_property during command handling, so I
think it is safe to just silently ignore it, as hardware would do the same?

>> +
>> +    addr = d->arch.vgic.rdist_propbase & GENMASK(51, 12);
>> +
>> +    ret = vgic_access_guest_memory(d, addr + p->irq - LPI_OFFSET,
>> +                                   &property, sizeof(property), false);
>> +    if ( ret )
>> +        return ret;
>> +
>> +    write_atomic(&p->lpi_priority, property & LPI_PROP_PRIO_MASK);
>> +
>> +    if ( property & LPI_PROP_ENABLED )
>> +        set_bit(GIC_IRQ_GUEST_ENABLED, &p->status);
>> +    else
>> +        clear_bit(GIC_IRQ_GUEST_ENABLED, &p->status);
>> +
>> +    return 0;
>> +}
>> +
>>   /* Must be called with the ITS lock held. */
>>   static int its_discard_event(struct virt_its *its,
>>                                uint32_t vdevid, uint32_t vevid)
>> @@ -538,6 +574,98 @@ static int its_handle_mapd(struct virt_its *its,
>> uint64_t *cmdptr)
>>       return ret;
>>   }
>>   +static int its_handle_mapti(struct virt_its *its, uint64_t *cmdptr)
>> +{
> 
> [...]
> 
>> +    /*
>> +     * radix_tree_insert() returns an error either due to an internal
>> +     * condition (like memory allocation failure) or because the LPI
>> already
>> +     * existed in the tree. We don't support the latter case, so we
>> always
>> +     * cleanup and return an error here in any case.
>> +     */
>> +out_remove_host_entry:
>> +    gicv3_remove_guest_event(its->d, its->doorbell_address, devid,
>> eventid);
> 
> Can gicv3_remove_guest_event fail? If yes, should we check the
> return/add comment? If not, then we should have an ASSERT(....).

Well, as we have an "if ( !ret ) return 0;" above, we only get here with
ret being != 0, so this is in an error path already.
I am not sure how we should react here then, I think reporting the first
error is probably more meaningful.

Cheers,
Andre.
Andre Przywara June 9, 2017, 11:17 a.m. UTC | #3
Hi,

On 02/06/17 18:12, Julien Grall wrote:
> Hi Andre,
> 
> On 05/26/2017 06:35 PM, Andre Przywara wrote:
>> The MAPTI commands associates a DeviceID/EventID pair with a LPI/CPU
>> pair and actually instantiates LPI interrupts. MAPI is just a variant
>> of this comment, where the LPI ID is the same as the event ID.
>> We connect the already allocated host LPI to this virtual LPI, so that
>> any triggering LPI on the host can be quickly forwarded to a guest.
>> Beside entering the domain and the virtual LPI number in the respective
>> host LPI entry, we also initialize and add the already allocated
>> struct pending_irq to our radix tree, so that we can now easily find it
>> by its virtual LPI number.
>> We also read the property table to update the enabled bit and the
>> priority for our new LPI, as we might have missed this during an earlier
>> INVALL call (which only checks mapped LPIs). But we make sure that the
>> property table is actually valid, as all redistributors might still
>> be disabled at this point.
>> Since write_itte_locked() now sees its first usage, we change the
>> declaration to static.
>>
>> Signed-off-by: Andre Przywara <andre.przywara@arm.com>
>> ---
>>   xen/arch/arm/gic-v3-its.c        |  27 ++++++++
>>   xen/arch/arm/vgic-v3-its.c       | 138
>> ++++++++++++++++++++++++++++++++++++++-
>>   xen/include/asm-arm/gic_v3_its.h |   3 +
>>   3 files changed, 165 insertions(+), 3 deletions(-)
>>
>> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
>> index 8864e0b..41fff64 100644
>> --- a/xen/arch/arm/gic-v3-its.c
>> +++ b/xen/arch/arm/gic-v3-its.c
>> @@ -876,6 +876,33 @@ int gicv3_remove_guest_event(struct domain *d,
>> paddr_t vdoorbell_address,
>>       return 0;
>>   }
>>   +/*
>> + * Connects the event ID for an already assigned device to the given
>> VCPU/vLPI
>> + * pair. The corresponding physical LPI is already mapped on the host
>> side
>> + * (when assigning the physical device to the guest), so we just
>> connect the
>> + * target VCPU/vLPI pair to that interrupt to inject it properly if
>> it fires.
>> + * Returns a pointer to the already allocated struct pending_irq that is
>> + * meant to be used by that event.
>> + */
>> +struct pending_irq *gicv3_assign_guest_event(struct domain *d,
>> +                                             paddr_t vdoorbell_address,
>> +                                             uint32_t vdevid,
>> uint32_t eventid,
>> +                                             uint32_t virt_lpi)
>> +{
>> +    struct pending_irq *pirq;
>> +    uint32_t host_lpi = 0;
> This should be INVALID_LPI and not 0.
> 
> [...]
> 
>> +/*
>> + * For a given virtual LPI read the enabled bit and priority from the
>> virtual
>> + * property table and update the virtual IRQ's state in the given
>> pending_irq.
>> + * Must be called with the respective VGIC VCPU lock held.
>> + */
>> +static int update_lpi_property(struct domain *d, struct pending_irq *p)
>> +{
>> +    paddr_t addr;
>> +    uint8_t property;
>> +    int ret;
>> +
>> +    /*
>> +     * If no redistributor has its LPIs enabled yet, we can't access the
>> +     * property table. In this case we just can't update the properties,
>> +     * but this should not be an error from an ITS point of view.
>> +     */
>> +    if ( !read_atomic(&d->arch.vgic.rdists_enabled) )
>> +        return 0;

I was just looking at rdists_enabled, and think that using read_atomic()
is a red herring.
First rdists_enabled is a bool, so I have a hard time to imagine how it
could be read non-atomically.
I think the intention of making this read "somewhat special" was to
cater for the fact that we write it under the domain lock, but read it
here without taking it. But I think for this case we don't need any
special read version, and anyway an *atomic* read would not help here.

What we want is to make sure that rdist_propbase is valid before we see
rdists_enabled gets true, this is what this check here is for. This
should be solved by a write barrier between the two on the other side.

Looking at Linux' memory_barriers.txt my understanding is that the
matching barrier on the read side does not necessarily need to be an
explicit barrier instruction, it could be a control flow dependency as
well. And here we have that: we check rdists_enabled and bail out if
it's not set, so neither the compiler nor the CPU can reorder this (as
this would violate program semantics).

Also rdists_enabled is a bit special in that it never gets reset once it
became true, very much like the LPI enablement in the GICv3 spec.

So I think we can really go with a normal read plus a comment.

Does that make sense?

Cheers,
Andre.
Stefano Stabellini June 9, 2017, 7:14 p.m. UTC | #4
On Fri, 9 Jun 2017, Andre Przywara wrote:
> On 02/06/17 18:12, Julien Grall wrote:
> > Hi Andre,
> > 
> > On 05/26/2017 06:35 PM, Andre Przywara wrote:
> >> The MAPTI commands associates a DeviceID/EventID pair with a LPI/CPU
> >> pair and actually instantiates LPI interrupts. MAPI is just a variant
> >> of this comment, where the LPI ID is the same as the event ID.
> >> We connect the already allocated host LPI to this virtual LPI, so that
> >> any triggering LPI on the host can be quickly forwarded to a guest.
> >> Beside entering the domain and the virtual LPI number in the respective
> >> host LPI entry, we also initialize and add the already allocated
> >> struct pending_irq to our radix tree, so that we can now easily find it
> >> by its virtual LPI number.
> >> We also read the property table to update the enabled bit and the
> >> priority for our new LPI, as we might have missed this during an earlier
> >> INVALL call (which only checks mapped LPIs). But we make sure that the
> >> property table is actually valid, as all redistributors might still
> >> be disabled at this point.
> >> Since write_itte_locked() now sees its first usage, we change the
> >> declaration to static.
> >>
> >> Signed-off-by: Andre Przywara <andre.przywara@arm.com>
> >> ---
> >>   xen/arch/arm/gic-v3-its.c        |  27 ++++++++
> >>   xen/arch/arm/vgic-v3-its.c       | 138
> >> ++++++++++++++++++++++++++++++++++++++-
> >>   xen/include/asm-arm/gic_v3_its.h |   3 +
> >>   3 files changed, 165 insertions(+), 3 deletions(-)
> >>
> >> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
> >> index 8864e0b..41fff64 100644
> >> --- a/xen/arch/arm/gic-v3-its.c
> >> +++ b/xen/arch/arm/gic-v3-its.c
> >> @@ -876,6 +876,33 @@ int gicv3_remove_guest_event(struct domain *d,
> >> paddr_t vdoorbell_address,
> >>       return 0;
> >>   }
> >>   +/*
> >> + * Connects the event ID for an already assigned device to the given
> >> VCPU/vLPI
> >> + * pair. The corresponding physical LPI is already mapped on the host
> >> side
> >> + * (when assigning the physical device to the guest), so we just
> >> connect the
> >> + * target VCPU/vLPI pair to that interrupt to inject it properly if
> >> it fires.
> >> + * Returns a pointer to the already allocated struct pending_irq that is
> >> + * meant to be used by that event.
> >> + */
> >> +struct pending_irq *gicv3_assign_guest_event(struct domain *d,
> >> +                                             paddr_t vdoorbell_address,
> >> +                                             uint32_t vdevid,
> >> uint32_t eventid,
> >> +                                             uint32_t virt_lpi)
> >> +{
> >> +    struct pending_irq *pirq;
> >> +    uint32_t host_lpi = 0;
> > This should be INVALID_LPI and not 0.
> > 
> > [...]
> > 
> >> +/*
> >> + * For a given virtual LPI read the enabled bit and priority from the
> >> virtual
> >> + * property table and update the virtual IRQ's state in the given
> >> pending_irq.
> >> + * Must be called with the respective VGIC VCPU lock held.
> >> + */
> >> +static int update_lpi_property(struct domain *d, struct pending_irq *p)
> >> +{
> >> +    paddr_t addr;
> >> +    uint8_t property;
> >> +    int ret;
> >> +
> >> +    /*
> >> +     * If no redistributor has its LPIs enabled yet, we can't access the
> >> +     * property table. In this case we just can't update the properties,
> >> +     * but this should not be an error from an ITS point of view.
> >> +     */
> >> +    if ( !read_atomic(&d->arch.vgic.rdists_enabled) )
> >> +        return 0;
> 
> I was just looking at rdists_enabled, and think that using read_atomic()
> is a red herring.
> First rdists_enabled is a bool, so I have a hard time to imagine how it
> could be read non-atomically.

This is not a good argument, because if we want the read to be atomic,
then we need to be using one of the _atomic functions regardless of the
type.


> I think the intention of making this read "somewhat special" was to
> cater for the fact that we write it under the domain lock, but read it
> here without taking it.

I haven't looked at the specific of rdists_enabled in this
implementaion, but be aware that in general writing a variable under a
lock, and reading it atomically is not safe. You either read and write
under a lock, or read and write atomically.


> But I think for this case we don't need any
> special read version, and anyway an *atomic* read would not help here.
> 
> What we want is to make sure that rdist_propbase is valid before we see
> rdists_enabled gets true, this is what this check here is for. This
> should be solved by a write barrier between the two on the other side.
> 
> Looking at Linux' memory_barriers.txt my understanding is that the
> matching barrier on the read side does not necessarily need to be an
> explicit barrier instruction, it could be a control flow dependency as
> well. And here we have that: we check rdists_enabled and bail out if
> it's not set, so neither the compiler nor the CPU can reorder this (as
> this would violate program semantics).

I think that this is true.


> Also rdists_enabled is a bit special in that it never gets reset once it
> became true, very much like the LPI enablement in the GICv3 spec.
> 
> So I think we can really go with a normal read plus a comment.
> 
> Does that make sense?

The first motivation isn't right, but I think that the latter
explanation makes sense.
Andre Przywara June 12, 2017, 4:10 p.m. UTC | #5
Hi,

On 09/06/17 20:14, Stefano Stabellini wrote:
> On Fri, 9 Jun 2017, Andre Przywara wrote:
>> On 02/06/17 18:12, Julien Grall wrote:
>>> Hi Andre,
>>>
>>> On 05/26/2017 06:35 PM, Andre Przywara wrote:
>>>> The MAPTI commands associates a DeviceID/EventID pair with a LPI/CPU
>>>> pair and actually instantiates LPI interrupts. MAPI is just a variant
>>>> of this comment, where the LPI ID is the same as the event ID.
>>>> We connect the already allocated host LPI to this virtual LPI, so that
>>>> any triggering LPI on the host can be quickly forwarded to a guest.
>>>> Beside entering the domain and the virtual LPI number in the respective
>>>> host LPI entry, we also initialize and add the already allocated
>>>> struct pending_irq to our radix tree, so that we can now easily find it
>>>> by its virtual LPI number.
>>>> We also read the property table to update the enabled bit and the
>>>> priority for our new LPI, as we might have missed this during an earlier
>>>> INVALL call (which only checks mapped LPIs). But we make sure that the
>>>> property table is actually valid, as all redistributors might still
>>>> be disabled at this point.
>>>> Since write_itte_locked() now sees its first usage, we change the
>>>> declaration to static.
>>>>
>>>> Signed-off-by: Andre Przywara <andre.przywara@arm.com>
>>>> ---
>>>>   xen/arch/arm/gic-v3-its.c        |  27 ++++++++
>>>>   xen/arch/arm/vgic-v3-its.c       | 138
>>>> ++++++++++++++++++++++++++++++++++++++-
>>>>   xen/include/asm-arm/gic_v3_its.h |   3 +
>>>>   3 files changed, 165 insertions(+), 3 deletions(-)
>>>>
>>>> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
>>>> index 8864e0b..41fff64 100644
>>>> --- a/xen/arch/arm/gic-v3-its.c
>>>> +++ b/xen/arch/arm/gic-v3-its.c
>>>> @@ -876,6 +876,33 @@ int gicv3_remove_guest_event(struct domain *d,
>>>> paddr_t vdoorbell_address,
>>>>       return 0;
>>>>   }
>>>>   +/*
>>>> + * Connects the event ID for an already assigned device to the given
>>>> VCPU/vLPI
>>>> + * pair. The corresponding physical LPI is already mapped on the host
>>>> side
>>>> + * (when assigning the physical device to the guest), so we just
>>>> connect the
>>>> + * target VCPU/vLPI pair to that interrupt to inject it properly if
>>>> it fires.
>>>> + * Returns a pointer to the already allocated struct pending_irq that is
>>>> + * meant to be used by that event.
>>>> + */
>>>> +struct pending_irq *gicv3_assign_guest_event(struct domain *d,
>>>> +                                             paddr_t vdoorbell_address,
>>>> +                                             uint32_t vdevid,
>>>> uint32_t eventid,
>>>> +                                             uint32_t virt_lpi)
>>>> +{
>>>> +    struct pending_irq *pirq;
>>>> +    uint32_t host_lpi = 0;
>>> This should be INVALID_LPI and not 0.
>>>
>>> [...]
>>>
>>>> +/*
>>>> + * For a given virtual LPI read the enabled bit and priority from the
>>>> virtual
>>>> + * property table and update the virtual IRQ's state in the given
>>>> pending_irq.
>>>> + * Must be called with the respective VGIC VCPU lock held.
>>>> + */
>>>> +static int update_lpi_property(struct domain *d, struct pending_irq *p)
>>>> +{
>>>> +    paddr_t addr;
>>>> +    uint8_t property;
>>>> +    int ret;
>>>> +
>>>> +    /*
>>>> +     * If no redistributor has its LPIs enabled yet, we can't access the
>>>> +     * property table. In this case we just can't update the properties,
>>>> +     * but this should not be an error from an ITS point of view.
>>>> +     */
>>>> +    if ( !read_atomic(&d->arch.vgic.rdists_enabled) )
>>>> +        return 0;
>>
>> I was just looking at rdists_enabled, and think that using read_atomic()
>> is a red herring.
>> First rdists_enabled is a bool, so I have a hard time to imagine how it
>> could be read non-atomically.
> 
> This is not a good argument, because if we want the read to be atomic,
> then we need to be using one of the _atomic functions regardless of the
> type.

OK, I see your point there. For the records (and to explain my ignorance
;-) I think it applies to a strict C standard point of view only. For
all practical means I think a read into a variable of a native data type
(especially that of a 1-byte sized bool) is always atomic on arm and
arm64 - especially with the load/store architecture of ARM. Also I have
a hard time to imagine intermediate values for a bool ;-), especially
since rdist_enabled only goes from false to true once in a domain's
lifetime.
But nevertheless I can see and agree that to be C standard compliant we
should use read_atomic().
Which makes me wonder if that would be true for other places in the code
as well ...

Another point of confusion may be that read_atomic() on its own does not
seem to be enough here, since we also need the barrier mechanism, maybe
even ACCESS_ONCE. But as I mentioned below this should be covered by the
control flow guarantee is this case.

I wonder if we should brainstorm if the usage of the atomic operations,
the barriers and ACCESS_ONCE is really correct in the current Xen code.
Comparing those to their Linux counterparts at least show some differences.

>> I think the intention of making this read "somewhat special" was to
>> cater for the fact that we write it under the domain lock, but read it
>> here without taking it.
> 
> I haven't looked at the specific of rdists_enabled in this
> implementaion, but be aware that in general writing a variable under a
> lock, and reading it atomically is not safe. You either read and write
> under a lock, or read and write atomically.

Agreed. rdists_enabled may be special here because it's a bool and only
goes from false (initialized value) to true once (there is only one
rdists_enabled assignment, which sets it to true).

>> But I think for this case we don't need any
>> special read version, and anyway an *atomic* read would not help here.
>>
>> What we want is to make sure that rdist_propbase is valid before we see
>> rdists_enabled gets true, this is what this check here is for. This
>> should be solved by a write barrier between the two on the other side.
>>
>> Looking at Linux' memory_barriers.txt my understanding is that the
>> matching barrier on the read side does not necessarily need to be an
>> explicit barrier instruction, it could be a control flow dependency as
>> well. And here we have that: we check rdists_enabled and bail out if
>> it's not set, so neither the compiler nor the CPU can reorder this (as
>> this would violate program semantics).
> 
> I think that this is true.
> 
> 
>> Also rdists_enabled is a bit special in that it never gets reset once it
>> became true, very much like the LPI enablement in the GICv3 spec.
>>
>> So I think we can really go with a normal read plus a comment.
>>
>> Does that make sense?
> 
> The first motivation isn't right, but I think that the latter
> explanation makes sense.

OK, I think I agree with that.

Cheers,
Andre.
Julien Grall June 12, 2017, 4:33 p.m. UTC | #6
Hi Andre,

On 07/06/17 18:49, Andre Przywara wrote:
> On 02/06/17 18:12, Julien Grall wrote:
>> On 05/26/2017 06:35 PM, Andre Przywara wrote:
>>> +    /*
>>> +     * radix_tree_insert() returns an error either due to an internal
>>> +     * condition (like memory allocation failure) or because the LPI
>>> already
>>> +     * existed in the tree. We don't support the latter case, so we
>>> always
>>> +     * cleanup and return an error here in any case.
>>> +     */
>>> +out_remove_host_entry:
>>> +    gicv3_remove_guest_event(its->d, its->doorbell_address, devid,
>>> eventid);
>>
>> Can gicv3_remove_guest_event fail? If yes, should we check the
>> return/add comment? If not, then we should have an ASSERT(....).
>
> Well, as we have an "if ( !ret ) return 0;" above, we only get here with
> ret being != 0, so this is in an error path already.
> I am not sure how we should react here then, I think reporting the first
> error is probably more meaningful.

Good point. I missed the if ( !ret ) return 0; in the code.

Cheers,
diff mbox

Patch

diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
index 8864e0b..41fff64 100644
--- a/xen/arch/arm/gic-v3-its.c
+++ b/xen/arch/arm/gic-v3-its.c
@@ -876,6 +876,33 @@  int gicv3_remove_guest_event(struct domain *d, paddr_t vdoorbell_address,
     return 0;
 }
 
+/*
+ * Connects the event ID for an already assigned device to the given VCPU/vLPI
+ * pair. The corresponding physical LPI is already mapped on the host side
+ * (when assigning the physical device to the guest), so we just connect the
+ * target VCPU/vLPI pair to that interrupt to inject it properly if it fires.
+ * Returns a pointer to the already allocated struct pending_irq that is
+ * meant to be used by that event.
+ */
+struct pending_irq *gicv3_assign_guest_event(struct domain *d,
+                                             paddr_t vdoorbell_address,
+                                             uint32_t vdevid, uint32_t eventid,
+                                             uint32_t virt_lpi)
+{
+    struct pending_irq *pirq;
+    uint32_t host_lpi = 0;
+
+    pirq = get_event_pending_irq(d, vdoorbell_address, vdevid, eventid,
+                                 &host_lpi);
+
+    if ( !pirq )
+        return NULL;
+
+    gicv3_lpi_update_host_entry(host_lpi, d->domain_id, virt_lpi);
+
+    return pirq;
+}
+
 /* 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 e9b1cb4..c350fa5 100644
--- a/xen/arch/arm/vgic-v3-its.c
+++ b/xen/arch/arm/vgic-v3-its.c
@@ -270,9 +270,9 @@  static bool read_itte(struct virt_its *its, uint32_t devid, uint32_t evid,
  * If vcpu_ptr is provided, returns the VCPU belonging to that collection.
  * Must be called with the ITS lock held.
  */
-bool write_itte_locked(struct virt_its *its, uint32_t devid,
-                       uint32_t evid, uint32_t collid, uint32_t vlpi,
-                       struct vcpu **vcpu_ptr)
+static bool write_itte_locked(struct virt_its *its, uint32_t devid,
+                              uint32_t evid, uint32_t collid, uint32_t vlpi,
+                              struct vcpu **vcpu_ptr)
 {
     paddr_t addr;
     struct vits_itte itte;
@@ -413,6 +413,42 @@  out_unlock:
     return ret;
 }
 
+/*
+ * For a given virtual LPI read the enabled bit and priority from the virtual
+ * property table and update the virtual IRQ's state in the given pending_irq.
+ * Must be called with the respective VGIC VCPU lock held.
+ */
+static int update_lpi_property(struct domain *d, struct pending_irq *p)
+{
+    paddr_t addr;
+    uint8_t property;
+    int ret;
+
+    /*
+     * If no redistributor has its LPIs enabled yet, we can't access the
+     * property table. In this case we just can't update the properties,
+     * but this should not be an error from an ITS point of view.
+     */
+    if ( !read_atomic(&d->arch.vgic.rdists_enabled) )
+        return 0;
+
+    addr = d->arch.vgic.rdist_propbase & GENMASK(51, 12);
+
+    ret = vgic_access_guest_memory(d, addr + p->irq - LPI_OFFSET,
+                                   &property, sizeof(property), false);
+    if ( ret )
+        return ret;
+
+    write_atomic(&p->lpi_priority, property & LPI_PROP_PRIO_MASK);
+
+    if ( property & LPI_PROP_ENABLED )
+        set_bit(GIC_IRQ_GUEST_ENABLED, &p->status);
+    else
+        clear_bit(GIC_IRQ_GUEST_ENABLED, &p->status);
+
+    return 0;
+}
+
 /* Must be called with the ITS lock held. */
 static int its_discard_event(struct virt_its *its,
                              uint32_t vdevid, uint32_t vevid)
@@ -538,6 +574,98 @@  static int its_handle_mapd(struct virt_its *its, uint64_t *cmdptr)
     return ret;
 }
 
+static int its_handle_mapti(struct virt_its *its, uint64_t *cmdptr)
+{
+    uint32_t devid = its_cmd_get_deviceid(cmdptr);
+    uint32_t eventid = its_cmd_get_id(cmdptr);
+    uint32_t intid = its_cmd_get_physical_id(cmdptr), _intid;
+    uint16_t collid = its_cmd_get_collection(cmdptr);
+    struct pending_irq *pirq;
+    struct vcpu *vcpu = NULL;
+    int ret = -1;
+
+    if ( its_cmd_get_command(cmdptr) == GITS_CMD_MAPI )
+        intid = eventid;
+
+    spin_lock(&its->its_lock);
+    /*
+     * Check whether there is a valid existing mapping. If yes, behavior is
+     * unpredictable, we choose to ignore this command here.
+     * This makes sure we start with a pristine pending_irq below.
+     */
+    if ( read_itte_locked(its, devid, eventid, &vcpu, &_intid) &&
+         _intid != INVALID_LPI )
+    {
+        spin_unlock(&its->its_lock);
+        return -1;
+    }
+
+    /* Enter the mapping in our virtual ITS tables. */
+    if ( !write_itte_locked(its, devid, eventid, collid, intid, &vcpu) )
+    {
+        spin_unlock(&its->its_lock);
+        return -1;
+    }
+
+    spin_unlock(&its->its_lock);
+
+    /*
+     * Connect this virtual LPI to the corresponding host LPI, which is
+     * determined by the same device ID and event ID on the host side.
+     * This returns us the corresponding, still unused pending_irq.
+     */
+    pirq = gicv3_assign_guest_event(its->d, its->doorbell_address,
+                                    devid, eventid, intid);
+    if ( !pirq )
+        goto out_remove_mapping;
+
+    vgic_init_pending_irq(pirq, intid);
+
+    /*
+     * Now read the guest's property table to initialize our cached state.
+     * We don't need the VGIC VCPU lock here, because the pending_irq isn't
+     * in the radix tree yet.
+     */
+    ret = update_lpi_property(its->d, pirq);
+    if ( ret )
+        goto out_remove_host_entry;
+
+    pirq->lpi_vcpu_id = vcpu->vcpu_id;
+    /*
+     * Mark this LPI as new, so any older (now unmapped) LPI in any LR
+     * can be easily recognised as such.
+     */
+    set_bit(GIC_IRQ_GUEST_PRISTINE_LPI, &pirq->status);
+
+    /*
+     * Now insert the pending_irq into the domain's LPI tree, so that
+     * it becomes live.
+     */
+    write_lock(&its->d->arch.vgic.pend_lpi_tree_lock);
+    ret = radix_tree_insert(&its->d->arch.vgic.pend_lpi_tree, intid, pirq);
+    write_unlock(&its->d->arch.vgic.pend_lpi_tree_lock);
+
+    if ( !ret )
+        return 0;
+
+    /*
+     * radix_tree_insert() returns an error either due to an internal
+     * condition (like memory allocation failure) or because the LPI already
+     * existed in the tree. We don't support the latter case, so we always
+     * cleanup and return an error here in any case.
+     */
+out_remove_host_entry:
+    gicv3_remove_guest_event(its->d, its->doorbell_address, devid, eventid);
+
+out_remove_mapping:
+    spin_lock(&its->its_lock);
+    write_itte_locked(its, devid, eventid,
+                      UNMAPPED_COLLECTION, INVALID_LPI, NULL);
+    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))
 
@@ -579,6 +707,10 @@  static int vgic_its_handle_cmds(struct domain *d, struct virt_its *its)
         case GITS_CMD_MAPD:
             ret = its_handle_mapd(its, command);
             break;
+        case GITS_CMD_MAPI:
+        case GITS_CMD_MAPTI:
+            ret = its_handle_mapti(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 e78dadf..c6404ea 100644
--- a/xen/include/asm-arm/gic_v3_its.h
+++ b/xen/include/asm-arm/gic_v3_its.h
@@ -175,6 +175,9 @@  struct pending_irq *gicv3_its_get_event_pending_irq(struct domain *d,
                                                     uint32_t veventid);
 int gicv3_remove_guest_event(struct domain *d, paddr_t vdoorbell_address,
                                      uint32_t vdevid, uint32_t veventid);
+struct pending_irq *gicv3_assign_guest_event(struct domain *d, paddr_t doorbell,
+                                             uint32_t devid, uint32_t eventid,
+                                             uint32_t virt_lpi);
 void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id,
                                  uint32_t virt_lpi);