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 <mykyta_poturai@epam.com>
---
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;
+
+ 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;
+
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 )
+ gicv3_free_host_lpi_block(d->arch.vgic.its_vm->db_lpi_bases[i]);
+ 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;
+ 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)
{
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
© 2016 - 2026 Red Hat, Inc.