On Mon, Feb 2, 2026 at 6:14 PM Mykyta Poturai <[email protected]> wrote:
>
> When GIC wants to inject a virtual interrupt to a VCPU that is not
> currently scheduled, it sends a kick for a hypervisor to schedule it. To
> receive such kicks, we need to set up a doorbell interrupt for each VPE.
>
> Add changes necessary to allocate, mask/unmask and handle doorbell
> interrupts for each VPE. When a doorbell interrupt is received, set the
> pending_last flag for the corresponding VPE and kick it, so that the
> hypervisor schedules the VCPU to handle pending VLPIs.
>
> Signed-off-by: Mykyta Poturai <[email protected]>
> ---
>  xen/arch/arm/gic-v3-its.c             |  13 ++-
>  xen/arch/arm/gic-v3-lpi.c             |  69 ++++++++++----
>  xen/arch/arm/gic-v4-its.c             | 127 ++++++++++++++++++++++++++
>  xen/arch/arm/include/asm/gic_v3_its.h |   6 +-
>  xen/arch/arm/vgic-v3-its.c            |  14 ++-
>  5 files changed, 203 insertions(+), 26 deletions(-)
>
> diff --git a/xen/arch/arm/gic-v3-its.c b/xen/arch/arm/gic-v3-its.c
> index be840fbc8f..fa5c1eb6d1 100644
> --- a/xen/arch/arm/gic-v3-its.c
> +++ b/xen/arch/arm/gic-v3-its.c
> @@ -1016,8 +1016,11 @@ int gicv3_its_map_guest_device(struct domain *d,
>
>      dev->guest_doorbell = guest_doorbell;
>      dev->guest_devid = guest_devid;
> -    dev->host_devid = host_devid;
> -    dev->eventids = nr_events;
> +
> +    #ifdef CONFIG_GICV4
> +       spin_lock_init(&dev->event_map.vlpi_lock);
> +       dev->event_map.nr_lpis = nr_events;
> +    #endif
>
>      rb_link_node(&dev->rbnode, parent, new);
>      rb_insert_color(&dev->rbnode, &d->arch.vgic.its_devices);
> @@ -1142,7 +1145,8 @@ int gicv3_remove_guest_event(struct domain *d, paddr_t 
> vdoorbell_address,
>      if ( host_lpi == INVALID_LPI )
>          return -EINVAL;
>
> -    gicv3_lpi_update_host_entry(host_lpi, d->domain_id, INVALID_LPI);
> +    gicv3_lpi_update_host_entry(host_lpi, d->domain_id, INVALID_LPI,
> +                                false, INVALID_VCPU_ID);
>
>      return 0;
>  }
> @@ -1169,7 +1173,8 @@ struct pending_irq *gicv3_assign_guest_event(struct 
> domain *d,
>      if ( !pirq )
>          return NULL;
>
> -    gicv3_lpi_update_host_entry(host_lpi, d->domain_id, virt_lpi);
> +    gicv3_lpi_update_host_entry(host_lpi, d->domain_id, virt_lpi,
> +                                false, INVALID_VCPU_ID);
>
>      return pirq;
>  }
> diff --git a/xen/arch/arm/gic-v3-lpi.c b/xen/arch/arm/gic-v3-lpi.c
> index 3c2649b695..37f1aa1064 100644
> --- a/xen/arch/arm/gic-v3-lpi.c
> +++ b/xen/arch/arm/gic-v3-lpi.c
> @@ -39,7 +39,7 @@ union host_lpi {
>      struct {
>          uint32_t virt_lpi;
>          uint16_t dom_id;
> -        uint16_t pad;
> +        uint16_t db_vcpu_id;
>      };
>  };
>
> @@ -161,24 +161,48 @@ void gicv3_do_LPI(unsigned int lpi)
>       * ignore them, as they have no further state and no-one can expect
>       * to see them if they have not been mapped.
>       */
> -    if ( hlpi.virt_lpi == INVALID_LPI )
> +    if ( hlpi.virt_lpi == INVALID_LPI && hlpi.db_vcpu_id == INVALID_VCPU_ID )
>          goto out;
>
>      d = rcu_lock_domain_by_id(hlpi.dom_id);
>      if ( !d )
>          goto out;
>
> -    /*
> -     * TODO: Investigate what to do here for potential interrupt storms.
> -     * As we keep all host LPIs enabled, for disabling LPIs we would need
> -     * to queue a ITS host command, which we avoid so far during a guest's
> -     * runtime. Also re-enabling would trigger a host command upon the
> -     * guest sending a command, which could be an attack vector for
> -     * hogging the host command queue.
> -     * See the thread around here for some background:
> -     * https://lists.xen.org/archives/html/xen-devel/2016-12/msg00003.html
> -     */
> -    vgic_vcpu_inject_lpi(d, hlpi.virt_lpi);
> +    /* It is a doorbell interrupt. */
> +    if ( hlpi.db_vcpu_id != INVALID_VCPU_ID )
> +    {
> +#ifdef CONFIG_GICV4
> +        struct vcpu *v = d->vcpu[hlpi.db_vcpu_id];
> +
> +        /* We got the message, no need to fire again */
> +        its_vpe_mask_db(v->arch.vgic.its_vpe);
> +
> +        /*
> +         * Update the pending_last flag that indicates that VLPIs are 
> pending.
> +         * And the corresponding vcpu is also kicked into action.
> +         */
> +        v->arch.vgic.its_vpe->pending_last = true;

This write is not synchronized. pending_last is touched from the LPI
handler path, so please use the appropriate vPE lock here, or make it
an atomic/WRITE_ONCE with a clear concurrency rationale. As-is this is
a data race.

> +
> +        vcpu_kick(v);
> +#else
> +        printk(XENLOG_WARNING
> +               "Doorbell LPI is only suooprted on GICV4\n");
> +#endif
> +    }
> +    else
> +    {
> +        /*
> +         * TODO: Investigate what to do here for potential interrupt storms.
> +         * As we keep all host LPIs enabled, for disabling LPIs we would need
> +         * to queue a ITS host command, which we avoid so far during a 
> guest's
> +         * runtime. Also re-enabling would trigger a host command upon the
> +         * guest sending a command, which could be an attack vector for
> +         * hogging the host command queue.
> +         * See the thread around here for some background:
> +         * 
> https://lists.xen.org/archives/html/xen-devel/2016-12/msg00003.html
> +         */
> +        vgic_vcpu_inject_lpi(d, hlpi.virt_lpi);
> +    }
>
>      rcu_unlock_domain(d);
>
> @@ -187,7 +211,8 @@ out:
>  }
>
>  void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id,
> -                                 uint32_t virt_lpi)
> +                                 uint32_t virt_lpi, bool is_db,
> +                                 uint16_t db_vcpu_id)
>  {
>      union host_lpi *hlpip, hlpi;
>
> @@ -197,8 +222,16 @@ void gicv3_lpi_update_host_entry(uint32_t host_lpi, int 
> domain_id,
>
>      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;
> +    if ( !is_db )
> +    {
> +        hlpi.virt_lpi = virt_lpi;
> +        hlpi.dom_id = domain_id;
> +    }
> +    else
> +    {
> +        hlpi.dom_id = domain_id;
> +        hlpi.db_vcpu_id = db_vcpu_id;
> +    }
>
>      write_u64_atomic(&hlpip->data, hlpi.data);
>  }
> @@ -595,6 +628,7 @@ int gicv3_allocate_host_lpi_block(struct domain *d, 
> uint32_t *first_lpi)
>           */
>          hlpi.virt_lpi = INVALID_LPI;
>          hlpi.dom_id = d->domain_id;
> +        hlpi.db_vcpu_id = INVALID_VCPU_ID;
>          write_u64_atomic(&lpi_data.host_lpis[chunk][lpi_idx + i].data,
>                           hlpi.data);
>
> @@ -602,7 +636,8 @@ int gicv3_allocate_host_lpi_block(struct domain *d, 
> uint32_t *first_lpi)
>           * Enable this host LPI, so we don't have to do this during the
>           * guest's runtime.
>           */
> -        lpi_data.lpi_property[lpi + i] |= LPI_PROP_ENABLED;
> +        lpi_write_config(lpi_data.lpi_property, lpi + i + LPI_OFFSET, 0xff,
> +                         LPI_PROP_ENABLED);
>      }
>
>      lpi_data.next_free_lpi = lpi + LPI_BLOCK;
> diff --git a/xen/arch/arm/gic-v4-its.c b/xen/arch/arm/gic-v4-its.c
> index 175fda7acb..0462976b93 100644
> --- a/xen/arch/arm/gic-v4-its.c
> +++ b/xen/arch/arm/gic-v4-its.c
> @@ -157,6 +157,9 @@ static int its_send_cmd_vmapp(struct host_its *its, 
> struct its_vpe *vpe,
>      cmd[3] = (vpt_addr & GENMASK(51, 16)) |
>               ((HOST_LPIS_NRBITS - 1) & GENMASK(4, 0));
>
> +    /* Default doorbell interrupt */
> +    cmd[1] |= (uint64_t)vpe->vpe_db_lpi;

Doorbell LPI is a GICv4.1 addition; for GICv4.0 this should not be set.

> +
>   out:
>      ret = its_send_command(its, cmd);
>
> @@ -296,6 +299,37 @@ static int its_send_cmd_vmovp(struct its_vpe *vpe)
>      return 0;
>  }
>
> +
> +static void its_vpe_send_inv_db(struct its_vpe *vpe)
> +{
> +    // struct its_device *dev = vpe_proxy.dev;
> +    // unsigned long flags;
> +
> +    // spin_lock_irqsave(&vpe_proxy.lock, flags);
> +    // gicv4_vpe_db_proxy_map_locked(vpe);
> +    // its_send_cmd_inv(dev->hw_its, dev->host_devid, vpe->vpe_proxy_event);
> +    // spin_unlock_irqrestore(&vpe_proxy.lock, flags);
> +}
> +
> +static void its_vpe_inv_db(struct its_vpe *vpe)
> +{
> +    its_vpe_send_inv_db(vpe);
> +}
> +
> +void its_vpe_mask_db(struct its_vpe *vpe)
> +{
> +    /* Only clear enable bit. */
> +    lpi_write_config(lpi_data.lpi_property, vpe->vpe_db_lpi, 
> LPI_PROP_ENABLED, 0);
> +    its_vpe_inv_db(vpe);
> +}
> +
> +static void its_vpe_unmask_db(struct its_vpe *vpe)
> +{
> +    /* Only set enable bit. */
> +    lpi_write_config(lpi_data.lpi_property, vpe->vpe_db_lpi, 0, 
> LPI_PROP_ENABLED);
> +    its_vpe_inv_db(vpe);
> +}
> +
>  static void __init its_vpe_teardown(struct its_vpe *vpe)
>  {
>      unsigned int order;
> @@ -309,6 +343,8 @@ static void __init its_vpe_teardown(struct its_vpe *vpe)
>  int vgic_v4_its_vm_init(struct domain *d)
>  {
>      unsigned int nr_vcpus = d->max_vcpus;
> +    unsigned int nr_db_lpis, nr_chunks, i = 0;
> +    uint32_t *db_lpi_bases;
>      int ret = -ENOMEM;
>
>      if ( !gicv3_its_host_has_its() )
> @@ -326,9 +362,31 @@ int vgic_v4_its_vm_init(struct domain *d)
>      d->arch.vgic.its_vm->vproptable = lpi_allocate_proptable();
>      if ( !d->arch.vgic.its_vm->vproptable )
>          goto fail_vprop;
> +    /* Allocate a doorbell interrupt for each VPE. */
> +    nr_db_lpis = d->arch.vgic.its_vm->nr_vpes;
> +    nr_chunks = DIV_ROUND_UP(nr_db_lpis, LPI_BLOCK);
> +    db_lpi_bases = xzalloc_array(uint32_t, nr_chunks);
> +    if ( !db_lpi_bases )
> +        goto fail_db_bases;
> +
> +    do {
> +        /* Allocate doorbell interrupts in chunks of LPI_BLOCK (=32). */
> +        ret = gicv3_allocate_host_lpi_block(d, &db_lpi_bases[i]);
> +        if ( ret )
> +            goto fail_db;
> +    } while ( ++i < nr_chunks );
> +
> +    d->arch.vgic.its_vm->db_lpi_bases = db_lpi_bases;
> +    d->arch.vgic.its_vm->nr_db_lpis = nr_db_lpis;
>
>      return 0;
>
> +fail_db:
> +    while ( --i >= 0 )

'i' is unsigned, so --i >= 0 is always true.
This will loop forever.

> +        gicv3_free_host_lpi_block(d->arch.vgic.its_vm->db_lpi_bases[i]);

The fail path frees via d->arch.vgic.its_vm->db_lpi_bases, but this
pointer is only assigned on the success path. On failure it can be
NULL/uninitialized. Please free via the local db_lpi_bases[] (and then
xfree(db_lpi_bases)), or assign its_vm->db_lpi_bases before entering the
allocation loop.

> +    xfree(db_lpi_bases);
> +fail_db_bases:
> +    lpi_free_proptable(d->arch.vgic.its_vm->vproptable);
>  fail_vprop:
>      xfree(d->arch.vgic.its_vm->vpes);
>   fail_vpes:
> @@ -340,8 +398,13 @@ fail_vprop:
>  void vgic_v4_free_its_vm(struct domain *d)
>  {
>      struct its_vm *its_vm = d->arch.vgic.its_vm;
> +    int nr_chunks = DIV_ROUND_UP(its_vm->nr_db_lpis, LPI_BLOCK);
>      if ( its_vm->vpes )
>          xfree(its_vm->vpes);
> +    while ( --nr_chunks >= 0 )
> +        gicv3_free_host_lpi_block(its_vm->db_lpi_bases[nr_chunks]);
> +    if ( its_vm->db_lpi_bases )
> +        xfree(its_vm->db_lpi_bases);
>      if ( its_vm->vproptable )
>          lpi_free_proptable(its_vm);
>  }
> @@ -357,14 +420,29 @@ int vgic_v4_its_vpe_init(struct vcpu *vcpu)
>          return -ENOMEM;
>
>      its_vm->vpes[vcpuid] = vcpu->arch.vgic.its_vpe;
> +    vcpu->arch.vgic.its_vpe = vcpu->arch.vgic.its_vpe;

This is a no-op self-assignment; please remove.

> +    vcpu->arch.vgic.its_vpe->vpe_db_lpi = its_vm->db_lpi_bases[vcpuid/32] + 
> (vcpuid % 32);
> +    /*
> +     * Sometimes vlpi gets firstly mapped before associated vpe
> +     * becoming resident, so in case missing the interrupt, we intend to
> +     * enable doorbell at the initialization stage
> +     */
> +
>      vcpu->arch.vgic.its_vpe->its_vm = its_vm;
>
> +    gicv3_lpi_update_host_entry(vcpu->arch.vgic.its_vpe->vpe_db_lpi,
> +                                vcpu->domain->domain_id, INVALID_LPI, true,
> +                                vcpu->vcpu_id);
> +
> +
>      ret = its_vpe_init(vcpu->arch.vgic.its_vpe);
>      if ( ret )
>      {
>          its_vpe_teardown(vcpu->arch.vgic.its_vpe);
>          return ret;
>      }
> +    its_vpe_unmask_db(vcpu->arch.vgic.its_vpe);
> +
>      return 0;
>  }
>
> @@ -800,6 +878,7 @@ void vgic_v4_load(struct vcpu *vcpu)
>       * corresponding to our current CPU expects us here
>       */
>      WARN_ON(gicv4_vpe_set_affinity(vcpu));
> +    its_vpe_mask_db(vpe);
>      its_make_vpe_resident(vpe, vcpu->processor);
>      vpe->resident = true;
>  }
> @@ -812,5 +891,53 @@ void vgic_v4_put(struct vcpu *vcpu, bool need_db)
>          return;
>
>      its_make_vpe_non_resident(vpe, vcpu->processor);
> +    if ( need_db )
> +        /* Enable the doorbell, as the guest is going to block */
> +        its_vpe_unmask_db(vpe);
>      vpe->resident = false;
>  }
> +
> +static int its_vlpi_set_doorbell(struct its_vlpi_map *map, bool enable)
> +{
> +    if (map->db_enabled == enable)
> +        return 0;
> +
> +    map->db_enabled = enable;
> +
> +    /*
> +     * Ideally, we'd issue a VMAPTI to set the doorbell to its LPI
> +     * value or to 1023, depending on the enable bit. But that
> +     * would be issuing a mapping for an /existing/ DevID+EventID
> +     * pair, which is UNPREDICTABLE. Instead, let's issue a VMOVI
> +     * to the /same/ vPE, using this opportunity to adjust the doorbell.
> +     */
> +    return its_send_cmd_vmovi(map->dev->hw_its, map);
> +}
> +
> +int its_vlpi_prop_update(struct pending_irq *pirq, uint8_t property,
> +                         bool needs_inv)
> +{
> +    struct its_vlpi_map *map;
> +    unsigned int cpu;
> +    int ret;
> +
> +    if ( !pirq->vlpi_map )
> +        return -EINVAL;
> +
> +    map = pirq->vlpi_map;
> +
> +    /* Cache the updated property and update the vproptable. */
> +    map->properties = property;
> +    lpi_write_config(map->vm->vproptable, pirq->irq, 0xff, property);
> +
> +    if ( needs_inv )
> +    {
> +        cpu = map->vm->vpes[map->vpe_idx]->col_idx;
> +        ret = its_inv_lpi(map->dev->hw_its, map->dev, map->eventid, cpu);
> +        if ( ret )
> +            return ret;
> +    }
> +
> +    return its_vlpi_set_doorbell(map, property & LPI_PROP_ENABLED);
> +}
> +
> diff --git a/xen/arch/arm/include/asm/gic_v3_its.h 
> b/xen/arch/arm/include/asm/gic_v3_its.h
> index f03a8fad47..dababe97cd 100644
> --- a/xen/arch/arm/include/asm/gic_v3_its.h
> +++ b/xen/arch/arm/include/asm/gic_v3_its.h
> @@ -295,7 +295,9 @@ struct pending_irq *gicv3_assign_guest_event(struct 
> domain *d,
>                                               uint32_t vdevid, uint32_t 
> eventid,
>                                               uint32_t virt_lpi);
>  void gicv3_lpi_update_host_entry(uint32_t host_lpi, int domain_id,
> -                                 uint32_t virt_lpi);
> +                                 uint32_t virt_lpi, bool is_db,
> +                                 uint16_t db_vcpu_id);
> +
>  struct its_baser *its_get_baser(struct host_its *hw_its, uint32_t type);
>  bool its_alloc_table_entry(struct its_baser *baser, uint32_t id);
>  struct page_info *lpi_allocate_pendtable(void);
> @@ -322,6 +324,8 @@ bool event_is_forwarded_to_vcpu(struct its_device *dev, 
> uint32_t eventid);
>  void its_vpe_mask_db(struct its_vpe *vpe);
>  #endif
>  int gicv4_its_vlpi_unmap(struct pending_irq *pirq);
> +int its_vlpi_prop_update(struct pending_irq *pirq, uint8_t property,
> +                         bool needs_inv);
>
>  /* ITS quirks handling. */
>  uint64_t gicv3_its_get_cacheability(void);
> diff --git a/xen/arch/arm/vgic-v3-its.c b/xen/arch/arm/vgic-v3-its.c
> index 94f7dd7d90..0a740ad68f 100644
> --- a/xen/arch/arm/vgic-v3-its.c
> +++ b/xen/arch/arm/vgic-v3-its.c
> @@ -387,7 +387,7 @@ out_unlock:
>   * 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)
> +int update_lpi_property(struct domain *d, struct pending_irq *p, bool 
> needs_inv)

I don’t see any out-of-file users of update_lpi_property().
If it’s still file-local, keep it static.

Best regards,
Mykola

>  {
>      paddr_t addr;
>      uint8_t property;
> @@ -417,6 +417,9 @@ static int update_lpi_property(struct domain *d, struct 
> pending_irq *p)
>      else
>          clear_bit(GIC_IRQ_GUEST_ENABLED, &p->status);
>
> +    if ( pirq_is_tied_to_hw(p) )
> +        return its_vlpi_prop_update(p, property, needs_inv);
> +
>      return 0;
>  }
>
> @@ -430,6 +433,9 @@ static int update_lpi_property(struct domain *d, struct 
> pending_irq *p)
>   */
>  static void update_lpi_vgic_status(struct vcpu *v, struct pending_irq *p)
>  {
> +    if ( pirq_is_tied_to_hw(p) )
> +        return;
> +
>      ASSERT(spin_is_locked(&v->arch.vgic.lock));
>
>      if ( test_bit(GIC_IRQ_GUEST_ENABLED, &p->status) )
> @@ -479,7 +485,7 @@ static int its_handle_inv(struct virt_its *its, uint64_t 
> *cmdptr)
>      spin_lock_irqsave(&vcpu->arch.vgic.lock, flags);
>
>      /* Read the property table and update our cached status. */
> -    if ( update_lpi_property(d, p) )
> +    if ( update_lpi_property(d, p, true) )
>          goto out_unlock;
>
>      /* Check whether the LPI needs to go on a VCPU. */
> @@ -552,7 +558,7 @@ static int its_handle_invall(struct virt_its *its, 
> uint64_t *cmdptr)
>
>              vlpi = pirqs[i]->irq;
>              /* If that fails for a single LPI, carry on to handle the rest. 
> */
> -            err = update_lpi_property(its->d, pirqs[i]);
> +            err = update_lpi_property(its->d, pirqs[i], false);
>              if ( !err )
>                  update_lpi_vgic_status(vcpu, pirqs[i]);
>              else
> @@ -785,7 +791,7 @@ static int its_handle_mapti(struct virt_its *its, 
> uint64_t *cmdptr)
>       * 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);
> +    ret = update_lpi_property(its->d, pirq, true);
>      if ( ret )
>          goto out_remove_host_entry;
>
> --
> 2.51.2

Reply via email to