@samitouri / QOSamiQemu / commits / a0562ada75

hw/intc/arm_gicv5: Calculate HPPI in the IRS

The IRS is required to present the highest priority pending interrupt that it has for each domain for each cpu interface. We implement this in the irs_recalc_hppi() function, which we call at every point where some relevant IRS state changes. This function calls gicv5_forward_interrupt() to do the equivalent of the GICv5 stream protocol Forward and Recall commands. For the moment we simply record the HPPI on the CPU interface side without trying to process it; the handling of the HPPI in the cpuif will be added in subsequent commits. There are some cases where we could skip doing the full HPPI recalculation, e.g. when the guest changes the config of an interrupt that is disabled; we expect that the guest will only do interrupt config at startup, so we don't attempt to optimise this. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-44-peter.maydell@linaro.org

Peter Maydell committed Mar 27, 2026 at 11:16 UTC a0562ada75ed4b7544f97a3dea14fbb67a819a29
5 files changed +240
hw/intc/arm_gicv5.c
+202
@@ -376,6 +376,157 @@ static MemTxAttrs irs_txattrs(GICv5Common *cs, GICv5Domain domain)
376 };
377 }
378
379 +/* Data we need to pass through to lpi_cache_get_hppi() */
380 +typedef struct GetHPPIUserData {
381 + GICv5PendingIrq *best;
382 + uint32_t iaffid;
383 +} GetHPPIUserData;
384 +
385 +static void lpi_cache_get_hppi(gpointer key, gpointer value, gpointer user_data)
386 +{
387 + uint64_t id = GPOINTER_TO_INT(key);
388 + uint64_t l2_iste = *(uint64_t *)value;
389 + uint32_t prio, iaffid;
390 + GetHPPIUserData *ud = user_data;
391 +
392 + if ((l2_iste & (R_L2_ISTE_PENDING_MASK | R_L2_ISTE_ACTIVE_MASK | R_L2_ISTE_ENABLE_MASK))
393 + != (R_L2_ISTE_PENDING_MASK | R_L2_ISTE_ENABLE_MASK)) {
394 + return;
395 + }
396 + prio = FIELD_EX32(l2_iste, L2_ISTE, PRIORITY);
397 + iaffid = FIELD_EX32(l2_iste, L2_ISTE, IAFFID);
398 + if (iaffid == ud->iaffid && prio < ud->best->prio) {
399 + id = FIELD_DP32(id, INTID, TYPE, GICV5_LPI);
400 + ud->best->intid = id;
401 + ud->best->prio = prio;
402 + }
403 +}
404 +
405 +static int irs_cpuidx_from_iaffid(GICv5Common *cs, uint32_t iaffid)
406 +{
407 + for (int i = 0; i < cs->num_cpus; i++) {
408 + if (cs->cpu_iaffids[i] == iaffid) {
409 + return i;
410 + }
411 + }
412 + return -1;
413 +}
414 +
415 +static void irs_recalc_hppi(GICv5 *s, GICv5Domain domain, uint32_t iaffid)
416 +{
417 + /*
418 + * Recalculate the highest priority pending interrupt for the
419 + * specified domain and cpuif. HPPI candidates must be pending,
420 + * inactive and enabled.
421 + */
422 + GICv5Common *cs = ARM_GICV5_COMMON(s);
423 + int cpuidx = irs_cpuidx_from_iaffid(cs, iaffid);
424 + ARMCPU *cpu = cpuidx >= 0 ? cs->cpus[cpuidx] : NULL;
425 + GICv5PendingIrq best;
426 +
427 + best.intid = 0;
428 + best.prio = PRIO_IDLE;
429 +
430 + if (!cpu) {
431 + /* Nothing happens for iaffids targeting nonexistent CPUs */
432 + trace_gicv5_irs_recalc_hppi_fail(domain_name[domain], iaffid,
433 + "IAFFID doesn't match any CPU");
434 + return;
435 + }
436 +
437 + if (!FIELD_EX32(cs->irs_cr0[domain], IRS_CR0, IRSEN)) {
438 + /* When the IRS is disabled we don't forward HPPIs */
439 + trace_gicv5_irs_recalc_hppi_fail(domain_name[domain], iaffid,
440 + "IRS_CR0.IRSEN is zero");
441 + return;
442 + }
443 +
444 + if (s->phys_lpi_config[domain].valid) {
445 + GetHPPIUserData ud;
446 +
447 + ud.best = &best;
448 + ud.iaffid = iaffid;
449 + g_hash_table_foreach(s->phys_lpi_config[domain].lpi_cache,
450 + lpi_cache_get_hppi, &ud);
451 + }
452 +
453 + /*
454 + * OPT: consider also caching the SPI interrupt information,
455 + * similarly to how we handle LPIs, if iterating through the whole
456 + * SPI array every time is too expensive.
457 + */
458 + for (int i = 0; i < cs->spi_irs_range; i++) {
459 + GICv5SPIState *spi = &cs->spi[i];
460 +
461 + if (spi->active || !spi->pending || !spi->enabled) {
462 + continue;
463 + }
464 + if (spi->domain != domain || spi->iaffid != iaffid) {
465 + continue;
466 + }
467 + if (spi->priority < best.prio) {
468 + uint32_t intid = 0;
469 + intid = FIELD_DP32(intid, INTID, ID, i);
470 + intid = FIELD_DP32(intid, INTID, TYPE, GICV5_SPI);
471 + best.intid = intid;
472 + best.prio = spi->priority;
473 + }
474 + }
475 +
476 + trace_gicv5_irs_recalc_hppi(domain_name[domain], iaffid,
477 + best.intid, best.prio);
478 +
479 + s->hppi[domain][cpuidx] = best;
480 + /*
481 + * Now present the HPPI to the cpuif. In the real hardware stream
482 + * protocol, the connection between IRS and cpuif is asynchronous,
483 + * and so both ends track their idea of the current HPPI, with a
484 + * back-and-forth sequence so they stay in sync and more
485 + * interaction when the cpuif resets. For QEMU, we are strictly
486 + * synchronous and the cpuif asking the IRS for data is a cheap
487 + * function call, so we simplify this:
488 + * - the IRS knows what the current HPPI is
489 + * - s->hppi[][] is a cache we can recalculate
490 + * - the IRS merely tells the cpuif "something changed", and
491 + * the cpuif asks for the current HPPI when it needs it
492 + * - the cpuif does not cache the HPPI on its end
493 + */
494 + gicv5_forward_interrupt(cpu, domain);
495 +}
496 +
497 +static void irs_recalc_hppi_all_cpus(GICv5 *s, GICv5Domain domain)
498 +{
499 + /*
500 + * Recalculate the HPPI for every CPU for this domain. This is
501 + * not as efficient as it could be because we will scan through
502 + * the LPI cached hash table and the SPI array for each CPU rather
503 + * than doing a single combined scan, but we only need to do this
504 + * very rarely, when the guest enables or disables the IST, so we
505 + * implement this the simple way.
506 + */
507 + GICv5Common *cs = ARM_GICV5_COMMON(s);
508 + for (int i = 0; i < cs->num_cpus; i++) {
509 + irs_recalc_hppi(s, domain, cs->cpu_iaffids[i]);
510 + }
511 +}
512 +
513 +static void irs_recall_hppis(GICv5 *s, GICv5Domain domain)
514 +{
515 + /*
516 + * The IRS was just disabled -- we must recall any pending HPPIs
517 + * we have sent to the CPU interfaces. For us this means that we
518 + * clear our cached HPPI data and tell the cpuif that it has
519 + * changed.
520 + */
521 + GICv5Common *cs = ARM_GICV5_COMMON(s);
522 +
523 + for (int i = 0; i < cs->num_cpus; i++) {
524 + s->hppi[domain][i].intid = 0;
525 + s->hppi[domain][i].prio = PRIO_IDLE;
526 + gicv5_forward_interrupt(cs->cpus[i], domain);
527 + }
528 +}
529 +
530 static hwaddr l1_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg,
531 uint32_t id)
532 {
@@ -575,6 +726,7 @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority,
726 GICv5Domain domain, GICv5IntType type, bool virtual)
727 {
728 GICv5 *s = ARM_GICV5(cs);
729 + uint32_t iaffid;
730
731 trace_gicv5_set_priority(domain_name[domain], inttype_name(type), virtual,
732 id, priority);
@@ -598,6 +750,7 @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority,
750 return;
751 }
752 *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PRIORITY, priority);
753 + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
754 put_l2_iste(cs, cfg, &h);
755 break;
756 }
@@ -612,6 +765,7 @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority,
765 }
766
767 spi->priority = priority;
768 + iaffid = spi->iaffid;
769 break;
770 }
771 default:
@@ -619,12 +773,15 @@ void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority,
773 "priority of bad interrupt type %d\n", type);
774 return;
775 }
776 +
777 + irs_recalc_hppi(s, domain, iaffid);
778 }
779
780 void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled,
781 GICv5Domain domain, GICv5IntType type, bool virtual)
782 {
783 GICv5 *s = ARM_GICV5(cs);
784 + uint32_t iaffid;
785
786 trace_gicv5_set_enabled(domain_name[domain], inttype_name(type), virtual,
787 id, enabled);
@@ -645,6 +802,7 @@ void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled,
802 return;
803 }
804 *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, ENABLE, enabled);
805 + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
806 put_l2_iste(cs, cfg, &h);
807 break;
808 }
@@ -659,6 +817,7 @@ void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled,
817 }
818
819 spi->enabled = true;
820 + iaffid = spi->iaffid;
821 break;
822 }
823 default:
@@ -666,12 +825,15 @@ void gicv5_set_enabled(GICv5Common *cs, uint32_t id, bool enabled,
825 "enable state of bad interrupt type %d\n", type);
826 return;
827 }
828 +
829 + irs_recalc_hppi(s, domain, iaffid);
830 }
831
832 void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending,
833 GICv5Domain domain, GICv5IntType type, bool virtual)
834 {
835 GICv5 *s = ARM_GICV5(cs);
836 + uint32_t iaffid;
837
838 trace_gicv5_set_pending(domain_name[domain], inttype_name(type), virtual,
839 id, pending);
@@ -692,6 +854,7 @@ void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending,
854 return;
855 }
856 *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PENDING, pending);
857 + iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
858 put_l2_iste(cs, cfg, &h);
859 break;
860 }
@@ -706,6 +869,7 @@ void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending,
869 }
870
871 spi->pending = true;
872 + iaffid = spi->iaffid;
873 break;
874 }
875 default:
@@ -713,6 +877,8 @@ void gicv5_set_pending(GICv5Common *cs, uint32_t id, bool pending,
877 "pending state of bad interrupt type %d\n", type);
878 return;
879 }
880 +
881 + irs_recalc_hppi(s, domain, iaffid);
882 }
883
884 void gicv5_set_handling(GICv5Common *cs, uint32_t id,
@@ -767,6 +933,7 @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid,
933 GICv5IntType type, bool virtual)
934 {
935 GICv5 *s = ARM_GICV5(cs);
936 + uint32_t old_iaffid;
937
938 trace_gicv5_set_target(domain_name[domain], inttype_name(type), virtual,
939 id, iaffid, irm);
@@ -800,6 +967,7 @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid,
967 * L2_ISTE.IRM is RES0. We never read it, and we can skip
968 * explicitly writing it to zero here.
969 */
970 + old_iaffid = FIELD_EX32(*l2_iste_p, L2_ISTE, IAFFID);
971 *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, IAFFID, iaffid);
972 put_l2_iste(cs, cfg, &h);
973 break;
@@ -814,6 +982,7 @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid,
982 return;
983 }
984
985 + old_iaffid = spi->iaffid;
986 spi->iaffid = iaffid;
987 break;
988 }
@@ -822,6 +991,9 @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid,
991 "target of bad interrupt type %d\n", type);
992 return;
993 }
994 +
995 + irs_recalc_hppi(s, domain, old_iaffid);
996 + irs_recalc_hppi(s, domain, iaffid);
997 }
998
999 static uint64_t l2_iste_to_icsr(GICv5Common *cs, const GICv5ISTConfig *cfg,
@@ -942,6 +1114,12 @@ static void irs_map_l2_istr_write(GICv5 *s, GICv5Domain domain, uint64_t value)
1114 if (res != MEMTX_OK) {
1115 goto txfail;
1116 }
1117 + /*
1118 + * It's CONSTRAINED UNPREDICTABLE to make an L2 IST valid when
1119 + * some of its entries have Pending already set, so we don't need
1120 + * to go through looking for Pending bits and pulling them into
1121 + * the cache, and we don't need to recalc our HPPI.
1122 + */
1123 return;
1124
1125 txfail:
@@ -999,6 +1177,7 @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value)
1177 IRS_IST_BASER, VALID, valid);
1178 s->phys_lpi_config[domain].valid = false;
1179 trace_gicv5_ist_invalid(domain_name[domain]);
1180 + irs_recalc_hppi_all_cpus(s, domain);
1181 return;
1182 }
1183 cs->irs_ist_baser[domain] = value;
@@ -1068,6 +1247,7 @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value)
1247 cfg->valid = true;
1248 trace_gicv5_ist_valid(domain_name[domain], cfg->base, cfg->id_bits,
1249 cfg->l2_idx_bits, cfg->istsz, cfg->structure);
1250 + irs_recalc_hppi_all_cpus(s, domain);
1251 }
1252 }
1253
@@ -1223,6 +1403,11 @@ static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset,
1403 case A_IRS_CR0:
1404 /* Enabling is instantaneous for us so IDLE is always 1 */
1405 *data = cs->irs_cr0[domain] | R_IRS_CR0_IDLE_MASK;
1406 + if (FIELD_EX32(cs->irs_cr0[domain], IRS_CR0, IRSEN)) {
1407 + irs_recalc_hppi_all_cpus(s, domain);
1408 + } else {
1409 + irs_recall_hppis(s, domain);
1410 + }
1411 return true;
1412 case A_IRS_CR1:
1413 *data = cs->irs_cr1[domain];
@@ -1311,6 +1496,7 @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset,
1496 } else if (spi->level) {
1497 spi->pending = false;
1498 }
1499 + irs_recalc_hppi(s, spi->domain, spi->iaffid);
1500 }
1501 }
1502 return true;
@@ -1320,7 +1506,12 @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset,
1506 /* this is RAZ/WI except for the EL3 domain */
1507 GICv5SPIState *spi = spi_for_selr(cs, domain);
1508 if (spi) {
1509 + GICv5Domain old_domain = spi->domain;
1510 spi->domain = FIELD_EX32(data, IRS_SPI_DOMAINR, DOMAIN);
1511 + if (spi->domain != old_domain) {
1512 + irs_recalc_hppi(s, old_domain, spi->iaffid);
1513 + irs_recalc_hppi(s, spi->domain, spi->iaffid);
1514 + }
1515 }
1516 }
1517 return true;
@@ -1331,6 +1522,7 @@ static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset,
1522
1523 if (spi) {
1524 spi_sample(spi);
1525 + irs_recalc_hppi(s, spi->domain, spi->iaffid);
1526 }
1527 trace_gicv5_spi_state(id, spi->level, spi->pending, spi->active);
1528 return true;
@@ -1499,6 +1691,7 @@ static void gicv5_set_spi(void *opaque, int irq, int level)
1691 {
1692 /* These irqs are all SPIs; the INTID is irq + s->spi_base */
1693 GICv5Common *cs = ARM_GICV5_COMMON(opaque);
1694 + GICv5 *s = ARM_GICV5(cs);
1695 uint32_t spi_id = irq + cs->spi_base;
1696 GICv5SPIState *spi = gicv5_raw_spi_state(cs, spi_id);
1697
@@ -1511,6 +1704,8 @@ static void gicv5_set_spi(void *opaque, int irq, int level)
1704 spi->level = level;
1705 spi_sample(spi);
1706 trace_gicv5_spi_state(spi_id, spi->level, spi->pending, spi->active);
1707 +
1708 + irs_recalc_hppi(s, spi->domain, spi->iaffid);
1709 }
1710
1711 static void gicv5_reset_hold(Object *obj, ResetType type)
@@ -1591,6 +1786,7 @@ static void gicv5_set_idregs(GICv5Common *cs)
1786
1787 static void gicv5_realize(DeviceState *dev, Error **errp)
1788 {
1789 + GICv5 *s = ARM_GICV5(dev);
1790 GICv5Common *cs = ARM_GICV5_COMMON(dev);
1791 GICv5Class *gc = ARM_GICV5_GET_CLASS(dev);
1792 Error *migration_blocker = NULL;
@@ -1618,6 +1814,12 @@ static void gicv5_realize(DeviceState *dev, Error **errp)
1814
1815 gicv5_set_idregs(cs);
1816 gicv5_common_init_irqs_and_mmio(cs, gicv5_set_spi, config_frame_ops);
1817 +
1818 + for (int i = 0; i < NUM_GICV5_DOMAINS; i++) {
1819 + if (gicv5_domain_implemented(cs, i)) {
1820 + s->hppi[i] = g_new0(GICv5PendingIrq, cs->num_cpus);
1821 + }
1822 + }
1823 }
1824
1825 static void gicv5_init(Object *obj)
hw/intc/trace-events
+2
@@ -242,6 +242,8 @@ gicv5_set_handling(const char *domain, const char *type, bool virtual, uint32_t
242 gicv5_set_target(const char *domain, const char *type, bool virtual, uint32_t id, uint32_t iaffid, int irm) "GICv5 IRS SetTarget %s %s virtual:%d ID %u IAFFID %u routingmode %d"
243 gicv5_request_config(const char *domain, const char *type, bool virtual, uint32_t id, uint64_t icsr) "GICv5 IRS RequestConfig %s %s virtual:%d ID %u ICSR 0x%" PRIx64
244 gicv5_spi_state(uint32_t spi_id, bool level, bool pending, bool active) "GICv5 IRS SPI ID %u now level %d pending %d active %d"
245 +gicv5_irs_recalc_hppi_fail(const char *domain, uint32_t iaffid, const char *reason) "GICv5 IRS %s IAFFID %u: no HPPI: %s"
246 +gicv5_irs_recalc_hppi(const char *domain, uint32_t iaffid, uint32_t id, uint8_t prio) "GICv5 IRS %s IAFFID %u: new HPPI ID 0x%x prio %u"
247
248 # arm_gicv5_common.c
249 gicv5_common_realize(uint32_t irsid, uint32_t num_cpus, uint32_t spi_base, uint32_t spi_irs_range, uint32_t spi_range) "GICv5 IRS realized: IRS ID %u, %u CPUs, SPI base %u, SPI IRS range %u, SPI range %u"
include/hw/intc/arm_gicv5.h
+3
@@ -37,6 +37,9 @@ struct GICv5 {
37
38 /* This is the info from IRS_IST_BASER and IRS_IST_CFGR */
39 GICv5ISTConfig phys_lpi_config[NUM_GICV5_DOMAINS];
40 +
41 + /* We cache the HPPI for each CPU for each domain here */
42 + GICv5PendingIrq *hppi[NUM_GICV5_DOMAINS];
43 };
44
45 struct GICv5Class {
include/hw/intc/arm_gicv5_stream.h
+24
@@ -151,4 +151,28 @@ void gicv5_set_target(GICv5Common *cs, uint32_t id, uint32_t iaffid,
151 uint64_t gicv5_request_config(GICv5Common *cs, uint32_t id, GICv5Domain domain,
152 GICv5IntType type, bool virtual);
153
154 +/**
155 + * gicv5_forward_interrupt
156 + * @cpu: CPU interface to forward interrupt to
157 + * @domain: domain this interrupt is for
158 + *
159 + * Tell the CPU interface that the highest priority pending interrupt
160 + * that the IRS has available for it has changed. This is the
161 + * equivalent of the stream protocol's Forward packet, and also of its
162 + * Recall packet.
163 + *
164 + * The stream protocol makes this asynchronous, allowing two Forward
165 + * packets to be in flight and requiring an acknowledge, because the
166 + * cpuif might be about to activate the previous forwarded interrupt
167 + * while we are trying to tell it about a new one. But for QEMU we
168 + * hold the BQL, so we know the vcpu might be executing guest code but
169 + * it cannot be in the middle of changing cpuif state. So we can just
170 + * synchronously tell it that a new HPPI exists (which might cause it
171 + * to assert IRQ or FIQ to itself); this works as if the cpuif gave us
172 + * a Release for the old HPPI. The cpuif will ask the IRS for the
173 + * HPPI info via a function call, so we do not need to pass it across
174 + * here.
175 + */
176 +void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain);
177 +
178 #endif
target/arm/tcg/gicv5-cpuif.c
+9
@@ -157,6 +157,15 @@ static void gic_recalc_ppi_hppi(CPUARMState *env)
157 }
158 }
159
160 +void gicv5_forward_interrupt(ARMCPU *cpu, GICv5Domain domain)
161 +{
162 + /*
163 + * For now, we do nothing. Later we will recalculate the overall
164 + * HPPI by combining the IRS HPPI with the PPI HPPI, and possibly
165 + * signal IRQ/FIQ.
166 + */
167 +}
168 +
169 static void gic_cddis_write(CPUARMState *env, const ARMCPRegInfo *ri,
170 uint64_t value)
171 {