@samitouri / QOSamiQemu / commits / df7416d809

hw/intc/arm_gicv5: Implement gicv5_set_priority()

Implement the gicv5_set_priority() function, which is our equivalent of the Stream Protocol SetPriority command. This acts by looking the interrupt ID up in the Interrupt State Table and storing the new priority value into the table entry. The memory transaction has to have the right transaction attributes for the domain it is for; we precalculate these and keep them in the GICv5ISTConfig. The GIC has an optional software-error reporting mechanism via the IRS_SWERR_* registers; this does not report all failure cases, only those that would be annoying to detect and debug in some other way. We choose not to implement this, but include some comments for reportable error cases for future reference. Our LOG_GUEST_ERROR logging is a superset of this. At this point we implement only handling of SetPriority for LPIs; we will add SPI handling in a later commit. Virtual interrupts aren't supported by this initial EL1-only GICv5 implementation. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-19-peter.maydell@linaro.org

Peter Maydell committed Mar 27, 2026 at 11:16 UTC df7416d8098c6b8c80e6bf50784991a1cc069755
5 files changed +274
hw/intc/arm_gicv5.c
+233
@@ -9,6 +9,7 @@
9 #include "qemu/osdep.h"
10 #include "hw/core/registerfields.h"
11 #include "hw/intc/arm_gicv5.h"
12 +#include "hw/intc/arm_gicv5_stream.h"
13 #include "qapi/error.h"
14 #include "qemu/log.h"
15 #include "trace.h"
@@ -23,6 +24,25 @@ static const char *domain_name[] = {
24 [GICV5_ID_REALM] = "Realm",
25 };
26
27 +static const char *inttype_name(GICv5IntType t)
28 +{
29 + /*
30 + * We have to be more cautious with getting human readable names
31 + * for a GICv5IntType for trace strings than we do with the domain
32 + * enum, because here the value can come from a guest register
33 + * field.
34 + */
35 + static const char *names[] = {
36 + [GICV5_PPI] = "PPI",
37 + [GICV5_LPI] = "LPI",
38 + [GICV5_SPI] = "SPI",
39 + };
40 + if (t >= ARRAY_SIZE(names) || !names[t]) {
41 + return "RESERVED";
42 + }
43 + return names[t];
44 +}
45 +
46 REG32(IRS_IDR0, 0x0)
47 FIELD(IRS_IDR0, INT_DOM, 0, 2)
48 FIELD(IRS_IDR0, PA_RANGE, 2, 4)
@@ -265,6 +285,218 @@ REG64(IRS_SWERR_SYNDROMER0, 0x3c8)
285 REG64(IRS_SWERR_SYNDROMER1, 0x3d0)
286 FIELD(IRS_SWERR_SYNDROMER2, ADDR, 3, 53)
287
288 +FIELD(L1_ISTE, VALID, 0, 1)
289 +FIELD(L1_ISTE, L2_ADDR, 12, 44)
290 +
291 +FIELD(L2_ISTE, PENDING, 0, 1)
292 +FIELD(L2_ISTE, ACTIVE, 1, 1)
293 +FIELD(L2_ISTE, HM, 2, 1)
294 +FIELD(L2_ISTE, ENABLE, 3, 1)
295 +FIELD(L2_ISTE, IRM, 4, 1)
296 +FIELD(L2_ISTE, HWU, 9, 2)
297 +FIELD(L2_ISTE, PRIORITY, 11, 5)
298 +FIELD(L2_ISTE, IAFFID, 16, 16)
299 +
300 +static MemTxAttrs irs_txattrs(GICv5Common *cs, GICv5Domain domain)
301 +{
302 + /*
303 + * Return a MemTxAttrs to use for IRS memory accesses. IRS_CR1
304 + * has the usual Arm cacheability/shareability attributes, but
305 + * QEMU doesn't care about those. All we need to specify here is
306 + * the correct security attributes, which depend on the interrupt
307 + * domain. Conveniently, our GICv5Domain encoding matches the
308 + * ARMSecuritySpace one (because both follow an architecturally
309 + * specified field). The exception is that the EL3 domain must be
310 + * Secure instead of Root if we don't implement Realm.
311 + */
312 + if (domain == GICV5_ID_EL3 &&
313 + !gicv5_domain_implemented(cs, GICV5_ID_REALM)) {
314 + domain = GICV5_ID_S;
315 + }
316 + return (MemTxAttrs) {
317 + .space = domain,
318 + .secure = domain == GICV5_ID_S || domain == GICV5_ID_EL3,
319 + };
320 +}
321 +
322 +static hwaddr l1_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg,
323 + uint32_t id)
324 +{
325 + /*
326 + * In a 2-level IST configuration, return the address of the L1
327 + * IST entry for this interrupt ID. The bottom l2_idx_bits of the
328 + * ID value are the index into the L2 table, and the higher bits
329 + * of the ID index the L1 table.
330 + */
331 + uint32_t l1_index = id >> cfg->l2_idx_bits;
332 + return cfg->base + (l1_index * 8);
333 +}
334 +
335 +static bool get_l2_iste_addr(GICv5Common *cs, const GICv5ISTConfig *cfg,
336 + uint32_t id, hwaddr *l2_iste_addr)
337 +{
338 + /*
339 + * Get the address of the L2 interrupt state table entry for this
340 + * interrupt. On success, fill in l2_iste_addr and return true.
341 + * On failure, return false.
342 + */
343 + hwaddr l2_base;
344 +
345 + if (!cfg->valid) {
346 + return false;
347 + }
348 +
349 + if (id >= (1 << cfg->id_bits)) {
350 + return false;
351 + }
352 +
353 + if (cfg->structure) {
354 + /*
355 + * 2-level table: read the L1 IST. The bottom l2_idx_bits of
356 + * the ID value are the index into the L2 table, and the
357 + * higher bits of the ID index the L1 table. There is always
358 + * at least one L1 table entry.
359 + */
360 + hwaddr l1_addr = l1_iste_addr(cs, cfg, id);
361 + uint64_t l1_iste;
362 + MemTxResult res;
363 +
364 + l1_iste = address_space_ldq_le(&cs->dma_as, l1_addr,
365 + cfg->txattrs, &res);
366 + if (res != MEMTX_OK) {
367 + /* Reportable with EC=0x01 if sw error reporting implemented */
368 + qemu_log_mask(LOG_GUEST_ERROR, "L1 ISTE lookup failed for ID 0x%x"
369 + " at physical address 0x" HWADDR_FMT_plx "\n",
370 + id, l1_addr);
371 + return false;
372 + }
373 + if (!FIELD_EX64(l1_iste, L1_ISTE, VALID)) {
374 + return false;
375 + }
376 + l2_base = l1_iste & R_L1_ISTE_L2_ADDR_MASK;
377 + id = extract32(id, 0, cfg->l2_idx_bits);
378 + } else {
379 + /* 1-level table */
380 + l2_base = cfg->base;
381 + }
382 +
383 + *l2_iste_addr = l2_base + (id * cfg->istsz);
384 + return true;
385 +}
386 +
387 +static bool read_l2_iste_mem(GICv5Common *cs, const GICv5ISTConfig *cfg,
388 + hwaddr addr, uint32_t *l2_iste)
389 +{
390 + MemTxResult res;
391 +
392 + *l2_iste = address_space_ldl_le(&cs->dma_as, addr, cfg->txattrs, &res);
393 + if (res != MEMTX_OK) {
394 + /* Reportable with EC=0x02 if sw error reporting implemented */
395 + qemu_log_mask(LOG_GUEST_ERROR, "L2 ISTE read failed at physical "
396 + "address 0x" HWADDR_FMT_plx "\n", addr);
397 + }
398 + return res == MEMTX_OK;
399 +}
400 +
401 +static bool write_l2_iste_mem(GICv5Common *cs, const GICv5ISTConfig *cfg,
402 + hwaddr addr, uint32_t l2_iste)
403 +{
404 + MemTxResult res;
405 +
406 + address_space_stl_le(&cs->dma_as, addr, l2_iste, cfg->txattrs, &res);
407 + if (res != MEMTX_OK) {
408 + /* Reportable with EC=0x02 if sw error reporting implemented */
409 + qemu_log_mask(LOG_GUEST_ERROR, "L2 ISTE write failed at physical "
410 + "address 0x" HWADDR_FMT_plx "\n", addr);
411 + }
412 + return res == MEMTX_OK;
413 +}
414 +
415 +/*
416 + * This is returned by get_l2_iste() and has everything we need to do
417 + * the writeback of the L2 ISTE word in put_l2_iste(). Currently the
418 + * get/put functions always directly do guest memory reads and writes
419 + * to update the L2 ISTE. In a future commit we will add support for a
420 + * cache of some of the ISTE data in a local hashtable; the APIs are
421 + * designed with that in mind.
422 + */
423 +typedef struct L2_ISTE_Handle {
424 + hwaddr l2_iste_addr;
425 + uint32_t l2_iste;
426 +} L2_ISTE_Handle;
427 +
428 +static uint32_t *get_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg,
429 + uint32_t id, L2_ISTE_Handle *h)
430 +{
431 + /*
432 + * Find the L2 ISTE for the interrupt @id.
433 + *
434 + * We return a pointer to the ISTE: the caller can freely read and
435 + * modify the uint64_t pointed to to update the ISTE. If the
436 + * caller modifies the L2 ISTE word, it must call put_l2_iste(),
437 + * passing it @h, to write back the ISTE. If the caller is only
438 + * reading the L2 ISTE, it does not need to call put_l2_iste().
439 + *
440 + * We fill in @h with information needed for put_l2_iste().
441 + *
442 + * If the ISTE could not be read (typically because of a memory
443 + * error), return NULL.
444 + */
445 + if (!get_l2_iste_addr(cs, cfg, id, &h->l2_iste_addr) ||
446 + !read_l2_iste_mem(cs, cfg, h->l2_iste_addr, &h->l2_iste)) {
447 + return NULL;
448 + }
449 + return &h->l2_iste;
450 +}
451 +
452 +static void put_l2_iste(GICv5Common *cs, const GICv5ISTConfig *cfg,
453 + L2_ISTE_Handle *h)
454 +{
455 + /*
456 + * Write back the modified L2_ISTE word found with get_l2_iste().
457 + * Once this has been called the L2_ISTE_Handle @h and the pointer
458 + * to the L2 ISTE word are no longer valid.
459 + */
460 + write_l2_iste_mem(cs, cfg, h->l2_iste_addr, h->l2_iste);
461 +}
462 +
463 +void gicv5_set_priority(GICv5Common *cs, uint32_t id, uint8_t priority,
464 + GICv5Domain domain, GICv5IntType type, bool virtual)
465 +{
466 + GICv5 *s = ARM_GICV5(cs);
467 +
468 + trace_gicv5_set_priority(domain_name[domain], inttype_name(type), virtual,
469 + id, priority);
470 + /* We must ignore unimplemented low-order priority bits */
471 + priority &= MAKE_64BIT_MASK(5 - QEMU_GICV5_PRI_BITS, QEMU_GICV5_PRI_BITS);
472 +
473 + if (virtual) {
474 + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set "
475 + "priority of a virtual interrupt\n");
476 + return;
477 + }
478 +
479 + switch (type) {
480 + case GICV5_LPI:
481 + {
482 + const GICv5ISTConfig *cfg = &s->phys_lpi_config[domain];
483 + L2_ISTE_Handle h;
484 + uint32_t *l2_iste_p = get_l2_iste(cs, cfg, id, &h);
485 +
486 + if (!l2_iste_p) {
487 + return;
488 + }
489 + *l2_iste_p = FIELD_DP32(*l2_iste_p, L2_ISTE, PRIORITY, priority);
490 + put_l2_iste(cs, cfg, &h);
491 + break;
492 + }
493 + default:
494 + qemu_log_mask(LOG_GUEST_ERROR, "gicv5_set_priority: tried to set "
495 + "priority of bad interrupt type %d\n", type);
496 + return;
497 + }
498 +}
499 +
500 static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value)
501 {
502 GICv5Common *cs = ARM_GICV5_COMMON(s);
@@ -331,6 +563,7 @@ static void irs_ist_baser_write(GICv5 *s, GICv5Domain domain, uint64_t value)
563 */
564 l2_idx_bits = l2bits - istbits;
565 cfg->base = cs->irs_ist_baser[domain] & R_IRS_IST_BASER_ADDR_MASK;
566 + cfg->txattrs = irs_txattrs(cs, domain),
567 cfg->id_bits = id_bits;
568 cfg->istsz = 1 << istbits;
569 cfg->l2_idx_bits = l2_idx_bits;
hw/intc/trace-events
+1
@@ -235,6 +235,7 @@ gicv5_badwrite(const char *domain, uint64_t offset, uint64_t data, unsigned size
235 gicv5_spi(uint32_t id, int level) "GICv5 SPI ID %u asserted at level %d"
236 gicv5_ist_valid(const char *domain, uint64_t base, uint8_t id_bits, uint8_t l2_idx_bits, uint8_t istsz, bool structure) "GICv5 IRS %s IST now valid: base 0x%" PRIx64 " id_bits %u l2_idx_bits %u IST entry size %u 2-level %d"
237 gicv5_ist_invalid(const char *domain) "GICv5 IRS %s IST no longer valid"
238 +gicv5_set_priority(const char *domain, const char *type, bool virtual, uint32_t id, uint8_t priority) "GICv5 IRS SetPriority %s %s virtual:%d ID %u prio %u"
239
240 # arm_gicv5_common.c
241 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
+1
@@ -19,6 +19,7 @@ OBJECT_DECLARE_TYPE(GICv5, GICv5Class, ARM_GICV5)
19
20 typedef struct GICv5ISTConfig {
21 hwaddr base; /* Base address */
22 + MemTxAttrs txattrs; /* TX attrs to use for this table */
23 uint8_t id_bits; /* number of bits in an ID for this table */
24 uint8_t l2_idx_bits; /* number of ID bits that index into L2 table */
25 uint8_t istsz; /* L2 ISTE size in bytes */
include/hw/intc/arm_gicv5_stream.h
+29
@@ -12,6 +12,7 @@
12 #define HW_INTC_ARM_GICV5_STREAM_H
13
14 #include "target/arm/cpu-qom.h"
15 +#include "hw/intc/arm_gicv5_types.h"
16
17 typedef struct GICv5Common GICv5Common;
18
@@ -29,4 +30,32 @@ typedef struct GICv5Common GICv5Common;
30 */
31 bool gicv5_set_gicv5state(ARMCPU *cpu, GICv5Common *cs);
32
33 +/*
34 + * The architected Stream Protocol is asynchronous; commands can be
35 + * initiated both from the IRS and from the CPU interface, and some
36 + * require acknowledgement. For QEMU, we simplify this because we know
37 + * that in the CPU interface code we hold the BQL and so our IRS model
38 + * is not going to be busy; when we send commands from the CPUIF
39 + * ("upstream commands") we can model this as a synchronous function
40 + * call whose return corresponds to the acknowledgement of a completed
41 + * command.
42 + */
43 +
44 +/**
45 + * gicv5_set_priority
46 + * @cs: GIC IRS to send command to
47 + * @id: interrupt ID
48 + * @priority: priority to set
49 + * @domain: interrupt Domain to act on
50 + * @type: interrupt type (LPI or SPI)
51 + * @virtual: true if this is a virtual interrupt
52 + *
53 + * Set priority of an interrupt; matches stream interface SetPriority
54 + * command from CPUIF to IRS. There is no report back of
55 + * success/failure to the CPUIF in the protocol.
56 + */
57 +void gicv5_set_priority(GICv5Common *cs, uint32_t id,
58 + uint8_t priority, GICv5Domain domain,
59 + GICv5IntType type, bool virtual);
60 +
61 #endif
include/hw/intc/arm_gicv5_types.h
+10
@@ -45,4 +45,14 @@ typedef enum GICv5Domain {
45 #define GICV5_PPI_CNTP 30
46 #define GICV5_PPI_TRBIRQ 31
47
48 +/*
49 + * Type of the interrupt; these values match the 3-bit format
50 + * specified in the GICv5 spec R_GYVWB.
51 + */
52 +typedef enum GICv5IntType {
53 + GICV5_PPI = 1,
54 + GICV5_LPI = 2,
55 + GICV5_SPI = 3,
56 +} GICv5IntType;
57 +
58 #endif