On Mon, Feb 2, 2026 at 6:14 PM Mykyta Poturai <[email protected]> wrote:
>
> GICv4 needs to know which VCPU is currently scheduled to be able to
> deliver VLPIs. Implement switching of VPEs on VCPU context switch by
> extending the existing save/restore mechanism used for GICv2 and GICv3.
>
> Scheduling a VPE is done by setting up the VPENDBASER and VPROPBASER
> registers to the appropriate tables for the currently running VCPU. When
> scheduling out, preserve the IDAI and PendingLast bits from VPENDBASER.
>
> Signed-off-by: Mykyta Poturai <[email protected]>
> ---
>  xen/arch/arm/gic-v2.c                  |   2 +-
>  xen/arch/arm/gic-v3.c                  |   9 +-
>  xen/arch/arm/gic-v4-its.c              | 138 +++++++++++++++++++++++++
>  xen/arch/arm/gic-vgic.c                |   6 ++
>  xen/arch/arm/include/asm/gic.h         |   2 +-
>  xen/arch/arm/include/asm/gic_v3_defs.h |   6 ++
>  xen/arch/arm/include/asm/gic_v4_its.h  |   9 ++
>  xen/arch/arm/include/asm/vgic.h        |   2 +
>  8 files changed, 170 insertions(+), 4 deletions(-)
>
> diff --git a/xen/arch/arm/gic-v2.c b/xen/arch/arm/gic-v2.c
> index 0cd41eac12..c16fa5d67e 100644
> --- a/xen/arch/arm/gic-v2.c
> +++ b/xen/arch/arm/gic-v2.c
> @@ -181,7 +181,7 @@ static void gicv2_save_state(struct vcpu *v)
>      writel_gich(0, GICH_HCR);
>  }
>
> -static void gicv2_restore_state(const struct vcpu *v)
> +static void gicv2_restore_state(struct vcpu *v)
>  {
>      int i;
>
> diff --git a/xen/arch/arm/gic-v3.c b/xen/arch/arm/gic-v3.c
> index d4af332b0e..07736179db 100644
> --- a/xen/arch/arm/gic-v3.c
> +++ b/xen/arch/arm/gic-v3.c
> @@ -133,7 +133,7 @@ bool gic_is_gicv4(void)
>  #endif
>
>  /* per-cpu re-distributor base */
> -static DEFINE_PER_CPU(void __iomem*, rbase);
> +DEFINE_PER_CPU(void __iomem*, rbase);
>
>  #define GICD                   (gicv3.map_dbase)
>  #define GICD_RDIST_BASE        (this_cpu(rbase))
> @@ -475,13 +475,15 @@ static void gicv3_save_state(struct vcpu *v)
>       * are now visible to the system register interface
>       */
>      dsb(sy);
> +    if ( gic_is_gicv4() )
> +        vgic_v4_put(v, false);
>      gicv3_save_lrs(v);
>      save_aprn_regs(&v->arch.gic);
>      v->arch.gic.v3.vmcr = READ_SYSREG(ICH_VMCR_EL2);
>      v->arch.gic.v3.sre_el1 = READ_SYSREG(ICC_SRE_EL1);
>  }
>
> -static void gicv3_restore_state(const struct vcpu *v)
> +static void gicv3_restore_state(struct vcpu *v)
>  {
>      register_t val;
>
> @@ -510,6 +512,9 @@ static void gicv3_restore_state(const struct vcpu *v)
>      restore_aprn_regs(&v->arch.gic);
>      gicv3_restore_lrs(v);
>
> +    if ( gic_is_gicv4() )
> +        vgic_v4_load(v);
> +
>      /*
>       * Make sure all stores are visible the GIC
>       */
> diff --git a/xen/arch/arm/gic-v4-its.c b/xen/arch/arm/gic-v4-its.c
> index fac3b44a94..6a550a65b2 100644
> --- a/xen/arch/arm/gic-v4-its.c
> +++ b/xen/arch/arm/gic-v4-its.c
> @@ -18,6 +18,7 @@
>   * GNU General Public License for more details.
>   */
>
> +#include <xen/delay.h>
>  #include <xen/errno.h>
>  #include <xen/sched.h>
>  #include <xen/spinlock.h>
> @@ -44,6 +45,21 @@ void __init gicv4_its_vpeid_allocator_init(void)
>          panic("Could not allocate VPEID bitmap space\n");
>  }
>
> +static void __iomem *gic_data_rdist_vlpi_base(unsigned int cpu)
> +{
> +    /*
> +     * Each Redistributor defines two 64KB frames in the physical address 
> map.
> +     * In GICv4, there are two additional 64KB frames.
> +     * The frames for each Redistributor must be contiguous and must be
> +     * ordered as follows:
> +     * 1. RD_base
> +     * 2. SGI_base
> +     * 3. VLPI_base
> +     * 4. Reserved
> +     */
> +    return GICD_RDIST_BASE_CPU(cpu) + SZ_128K;
> +}
> +
>  static int __init its_alloc_vpeid(struct its_vpe *vpe)
>  {
>      int id;
> @@ -571,3 +587,125 @@ int its_send_cmd_vinv(struct host_its *its, struct 
> its_device *dev,
>
>      return gicv3_its_wait_commands(its);
>  }
> +
> +static uint64_t read_vpend_dirty_clean(void __iomem *vlpi_base,
> +                                       unsigned int count)
> +{
> +    uint64_t val;
> +    bool clean;
> +
> +    do {
> +        val = gits_read_vpendbaser(vlpi_base + GICR_VPENDBASER);
> +        /* Poll GICR_VPENDBASER.Dirty until it reads 0. */
> +        clean = !(val & GICR_VPENDBASER_Dirty);
> +        if ( !clean )
> +        {
> +            count--;
> +            cpu_relax();
> +            udelay(1);
> +        }
> +    } while ( !clean && count );
> +
> +    if ( !clean )
> +    {
> +        printk(XENLOG_WARNING "ITS virtual pending table not totally 
> parsed\n");
> +        val |= GICR_VPENDBASER_PendingLast;
> +    }
> +
> +    return val;
> +}
> +
> +/*
> + * When a vPE is made resident, the GIC starts parsing the virtual pending
> + * table to deliver pending interrupts. This takes place asynchronously,
> + * and can at times take a long while.
> + */
> +static void its_wait_vpt_parse_complete(void __iomem *vlpi_base)
> +{
> +    if ( !gic_support_vptValidDirty() )
> +        return;
> +
> +    read_vpend_dirty_clean(vlpi_base, 500);
> +}
> +
> +static uint64_t its_clear_vpend_valid(void __iomem *vlpi_base, uint64_t clr,
> +                                      uint64_t set)
> +{
> +    unsigned int count = 1000000;    /* 1s! */

The 1s timeout looks too large for a context‑switch path.
Consider a much smaller bound (or async handling) and warn on
timeout, rather than potentially stalling the scheduler.

> +    uint64_t val;
> +
> +    /*
> +     * Clearing the Valid bit informs the Redistributor that a context
> +     * switch is taking place.
> +     */
> +    val = gits_read_vpendbaser(vlpi_base + GICR_VPENDBASER);
> +    val &= ~GICR_VPENDBASER_Valid;
> +    val &= ~clr;
> +    val |= set;
> +    gits_write_vpendbaser(val, vlpi_base + GICR_VPENDBASER);
> +
> +    return read_vpend_dirty_clean(vlpi_base, count);
> +}
> +
> +static void its_make_vpe_resident(struct its_vpe *vpe, unsigned int cpu)
> +{
> +    void __iomem *vlpi_base = gic_data_rdist_vlpi_base(cpu);
> +    uint64_t val;
> +
> +    /* Switch in this VM's virtual property table. */
> +    val  = virt_to_maddr(vpe->its_vm->vproptable) & GENMASK(51, 12);
> +    val |= gicv3_its_get_cacheability() << 
> GICR_VPROPBASER_INNER_CACHEABILITY_SHIFT;
> +    val |= gicv3_its_get_shareability() << 
> GICR_VPROPBASER_SHAREABILITY_SHIFT;
> +    val |= GIC_BASER_CACHE_SameAsInner << 
> GICR_VPROPBASER_OUTER_CACHEABILITY_SHIFT;

Nit: cacheability/shareability are taken from a global ITS quirk.
If different ITSes can report different attributes, we would need
per‑ITS values here. Otherwise, a short note that Xen assumes
homogeneous ITS attributes would help.

> +    val |= (HOST_LPIS_NRBITS - 1) & GICR_VPROPBASER_IDBITS_MASK;
> +    gits_write_vpropbaser(val, vlpi_base + GICR_VPROPBASER);

The VPROPBASER value is constant per VM. We could precompute
and store it in struct its_vm at init, then just write the
cached value here instead of recomputing each time.

> +
> +    /* Switch in this VCPU's VPT. */
> +    val  = virt_to_maddr(vpe->vpendtable) & GENMASK(51, 16);
> +    val |= gicv3_its_get_cacheability() << 
> GICR_VPENDBASER_INNER_CACHEABILITY_SHIFT;
> +    val |= gicv3_its_get_shareability() << 
> GICR_VPENDBASER_SHAREABILITY_SHIFT;
> +    val |= GIC_BASER_CACHE_SameAsInner << 
> GICR_VPENDBASER_OUTER_CACHEABILITY_SHIFT;
> +    /*
> +     * When the GICR_VPENDBASER.Valid bit is written from 0 to 1,
> +     * this bit is RES1.
> +     */
> +    val |= GICR_VPENDBASER_PendingLast;
> +    val |= vpe->idai ? GICR_VPENDBASER_IDAI : 0;
> +    val |= GICR_VPENDBASER_Valid;
> +    gits_write_vpendbaser(val, vlpi_base + GICR_VPENDBASER);
> +
> +    its_wait_vpt_parse_complete(vlpi_base);
> +}
> +
> +static void its_make_vpe_non_resident(struct its_vpe *vpe, unsigned int cpu)
> +{
> +    void __iomem *vlpi_base = gic_data_rdist_vlpi_base(cpu);
> +    uint64_t val;
> +
> +    val = its_clear_vpend_valid(vlpi_base, 0, 0);
> +    vpe->idai = val & GICR_VPENDBASER_IDAI;
> +    vpe->pending_last = val & GICR_VPENDBASER_PendingLast;
> +}
> +
> +void vgic_v4_load(struct vcpu *vcpu)
> +{
> +    struct its_vpe *vpe = vcpu->arch.vgic.its_vpe;
> +
> +
> +    if ( vpe->resident )

gicv3_restore_state() call vgic_v4_load when
gic_is_gicv4() is true, even if its_vpe is NULL.

The same story about vgic_v4_put.

> +        return;
> +
> +    its_make_vpe_resident(vpe, vcpu->processor);
> +    vpe->resident = true;
> +}
> +
> +void vgic_v4_put(struct vcpu *vcpu, bool need_db)

nit: unused need_db

> +{
> +    struct its_vpe *vpe = vcpu->arch.vgic.its_vpe;
> +
> +    if ( !vpe->resident )
> +        return;
> +
> +    its_make_vpe_non_resident(vpe, vcpu->processor);
> +    vpe->resident = false;
> +}
> diff --git a/xen/arch/arm/gic-vgic.c b/xen/arch/arm/gic-vgic.c
> index ea48c5375a..44db142dbd 100644
> --- a/xen/arch/arm/gic-vgic.c
> +++ b/xen/arch/arm/gic-vgic.c
> @@ -377,6 +377,12 @@ int vgic_vcpu_pending_irq(struct vcpu *v)
>          }
>      }
>
> +#ifdef CONFIG_GICV4
> +    if ( gic_is_gicv4() )
> +        if ( v->arch.vgic.its_vpe->pending_last )

Should we check its_vpe too?

> +            rc = 1;
> +#endif
> +
>  out:
>      spin_unlock_irqrestore(&v->arch.vgic.lock, flags);
>      return rc;
> diff --git a/xen/arch/arm/include/asm/gic.h b/xen/arch/arm/include/asm/gic.h
> index afb1cc3751..04a20bdca5 100644
> --- a/xen/arch/arm/include/asm/gic.h
> +++ b/xen/arch/arm/include/asm/gic.h
> @@ -362,7 +362,7 @@ struct gic_hw_operations {
>      /* Save GIC registers */
>      void (*save_state)(struct vcpu *v);
>      /* Restore GIC registers */
> -    void (*restore_state)(const struct vcpu *v);
> +    void (*restore_state)(struct vcpu *v);
>      /* Dump GIC LR register information */
>      void (*dump_state)(const struct vcpu *v);
>
> diff --git a/xen/arch/arm/include/asm/gic_v3_defs.h 
> b/xen/arch/arm/include/asm/gic_v3_defs.h
> index 3a7d18ef59..0db75309cf 100644
> --- a/xen/arch/arm/include/asm/gic_v3_defs.h
> +++ b/xen/arch/arm/include/asm/gic_v3_defs.h
> @@ -257,6 +257,12 @@ struct rdist_region {
>      bool single_rdist;
>  };
>
> +/* per-cpu re-distributor base */
> +DECLARE_PER_CPU(void __iomem*, rbase);
> +
> +#define GICD_RDIST_BASE             (this_cpu(rbase))

looks like possible macro redefinition, see gic-v3.c

Best regards,
Mykola


> +#define GICD_RDIST_BASE_CPU(cpu)    (per_cpu(rbase, cpu))
> +
>  #endif /* __ASM_ARM_GIC_V3_DEFS_H__ */
>
>  /*
> diff --git a/xen/arch/arm/include/asm/gic_v4_its.h 
> b/xen/arch/arm/include/asm/gic_v4_its.h
> index ba81b25bde..37b6b92f0c 100644
> --- a/xen/arch/arm/include/asm/gic_v4_its.h
> +++ b/xen/arch/arm/include/asm/gic_v4_its.h
> @@ -56,6 +56,15 @@ void gicv4_its_vpeid_allocator_init(void);
>  #define GICR_VPROPBASER                              0x0070
>  #define GICR_VPENDBASER                              0x0078
>
> +#define GICR_VPROPBASER_OUTER_CACHEABILITY_SHIFT         56
> +#define GICR_VPROPBASER_SHAREABILITY_SHIFT               10
> +#define GICR_VPROPBASER_SHAREABILITY_MASK                \
> +        (3UL << GICR_VPROPBASER_SHAREABILITY_SHIFT)
> +#define GICR_VPROPBASER_INNER_CACHEABILITY_SHIFT          7
> +#define GICR_VPROPBASER_INNER_CACHEABILITY_MASK           \
> +        (7UL << GICR_VPROPBASER_INNER_CACHEABILITY_SHIFT)
> +#define GICR_VPROPBASER_IDBITS_MASK                    0x1f
> +
>  #define GICR_VPENDBASER_Dirty                   (1UL << 60)
>  #define GICR_VPENDBASER_PendingLast             (1UL << 61)
>  #define GICR_VPENDBASER_IDAI                    (1UL << 62)
> diff --git a/xen/arch/arm/include/asm/vgic.h b/xen/arch/arm/include/asm/vgic.h
> index 580310fec4..9ef667decb 100644
> --- a/xen/arch/arm/include/asm/vgic.h
> +++ b/xen/arch/arm/include/asm/vgic.h
> @@ -417,6 +417,8 @@ bool gic_is_gicv4(void);
>  int vgic_v4_its_vm_init(struct domain *d);
>  void vgic_v4_free_its_vm(struct domain *d);
>  int vgic_v4_its_vpe_init(struct vcpu *vcpu);
> +void vgic_v4_load(struct vcpu *vcpu);
> +void vgic_v4_put(struct vcpu *vcpu, bool need_db);
>  #endif /* !CONFIG_NEW_VGIC */
>
>  /*** Common VGIC functions used by Xen arch code ****/
> --
> 2.51.2

Reply via email to