@samitouri / QOSamiQemu / commits / 239aa45401

hw/intc/arm_gicv5: Implement skeleton code for IRS register frames

The GICv5 IRS has one mandatory register frame (the config frame) for each of up to four supported physical interrupt domains. Implement the skeleton of the code needed to create these as sysbus MMIO regions. The config frame has a mix of 32-bit and 64-bit registers, and it is valid to access the 64-bit registers with 32-bit accesses. In a similar way to the various GICv3 devices, we turn the MemoryRegionOps read_with_attrs and write_with_attrs calls into calls on functions specifically to read 32 or 64 bit values. (We can't trivially implement one in terms of the other because various registers have side effects on write which must only trigger when the "correct" half of the 64-bit register is written to.) Unlike the GICv3, we choose to expose a sysbus MMIO region for each interrupt domain even if the config of the GICv5 means that it doesn't implement that domain. This avoids having the config frame for a domain ending up at a different MMIO region index depending on the config of the GICv5. (This matters more for GICv5 because it supports Realm, and so there are more possible valid configurations.) gicv5_common_init_irqs_and_mmio() does not yet create any IRQs, but we name it this way to parallel the equivalent GICv3 function and to avoid having to rename it when we add the IRQ line creation in a subsequent commit. The arm_gicv5_types.h header is a little undermotivated at this point, but the aim is to have somewhere to put definitions that we want in both the GIC proper and the CPU interface. Signed-off-by: Peter Maydell <peter.maydell@linaro.org> Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com> Message-id: 20260327111700.795099-6-peter.maydell@linaro.org

Peter Maydell committed Mar 27, 2026 at 11:16 UTC 239aa454013a9b6cb7c723c62079789ec221d0d2
6 files changed +292
hw/intc/arm_gicv5.c
+172
@@ -8,9 +8,157 @@
8
9 #include "qemu/osdep.h"
10 #include "hw/intc/arm_gicv5.h"
11 +#include "qapi/error.h"
12 +#include "qemu/log.h"
13 +#include "trace.h"
14
15 OBJECT_DEFINE_TYPE(GICv5, gicv5, ARM_GICV5, ARM_GICV5_COMMON)
16
17 +static const char *domain_name[] = {
18 + [GICV5_ID_S] = "Secure",
19 + [GICV5_ID_NS] = "NonSecure",
20 + [GICV5_ID_EL3] = "EL3",
21 + [GICV5_ID_REALM] = "Realm",
22 +};
23 +
24 +static bool config_readl(GICv5 *s, GICv5Domain domain, hwaddr offset,
25 + uint64_t *data, MemTxAttrs attrs)
26 +{
27 + return false;
28 +}
29 +
30 +static bool config_writel(GICv5 *s, GICv5Domain domain, hwaddr offset,
31 + uint64_t data, MemTxAttrs attrs)
32 +{
33 + return false;
34 +}
35 +
36 +static bool config_readll(GICv5 *s, GICv5Domain domain, hwaddr offset,
37 + uint64_t *data, MemTxAttrs attrs)
38 +{
39 + return false;
40 +}
41 +
42 +static bool config_writell(GICv5 *s, GICv5Domain domain, hwaddr offset,
43 + uint64_t data, MemTxAttrs attrs)
44 +{
45 + return false;
46 +}
47 +
48 +static MemTxResult config_read(void *opaque, GICv5Domain domain, hwaddr offset,
49 + uint64_t *data, unsigned size,
50 + MemTxAttrs attrs)
51 +{
52 + GICv5 *s = ARM_GICV5(opaque);
53 + bool result;
54 +
55 + switch (size) {
56 + case 4:
57 + result = config_readl(s, domain, offset, data, attrs);
58 + break;
59 + case 8:
60 + result = config_readll(s, domain, offset, data, attrs);
61 + break;
62 + default:
63 + result = false;
64 + break;
65 + }
66 +
67 + if (!result) {
68 + qemu_log_mask(LOG_GUEST_ERROR,
69 + "%s: invalid guest read for IRS %s config frame "
70 + "at offset " HWADDR_FMT_plx
71 + " size %u\n", __func__, domain_name[domain],
72 + offset, size);
73 + trace_gicv5_badread(domain_name[domain], offset, size);
74 + /*
75 + * The spec requires that reserved registers are RAZ/WI; so we
76 + * log the error but return MEMTX_OK so we don't cause a
77 + * spurious data abort.
78 + */
79 + *data = 0;
80 + } else {
81 + trace_gicv5_read(domain_name[domain], offset, *data, size);
82 + }
83 +
84 + return MEMTX_OK;
85 +}
86 +
87 +static MemTxResult config_write(void *opaque, GICv5Domain domain,
88 + hwaddr offset, uint64_t data, unsigned size,
89 + MemTxAttrs attrs)
90 +{
91 + GICv5 *s = ARM_GICV5(opaque);
92 + bool result;
93 +
94 + switch (size) {
95 + case 4:
96 + result = config_writel(s, domain, offset, data, attrs);
97 + break;
98 + case 8:
99 + result = config_writell(s, domain, offset, data, attrs);
100 + break;
101 + default:
102 + result = false;
103 + break;
104 + }
105 +
106 + if (!result) {
107 + qemu_log_mask(LOG_GUEST_ERROR,
108 + "%s: invalid guest write for IRS %s config frame "
109 + "at offset " HWADDR_FMT_plx
110 + " size %u\n", __func__, domain_name[domain],
111 + offset, size);
112 + trace_gicv5_badwrite(domain_name[domain], offset, data, size);
113 + /*
114 + * The spec requires that reserved registers are RAZ/WI; so we
115 + * log the error but return MEMTX_OK so we don't cause a
116 + * spurious data abort.
117 + */
118 + } else {
119 + trace_gicv5_write(domain_name[domain], offset, data, size);
120 + }
121 +
122 + return MEMTX_OK;
123 +}
124 +
125 +#define DEFINE_READ_WRITE_WRAPPERS(NAME, DOMAIN) \
126 + static MemTxResult config_##NAME##_read(void *opaque, hwaddr offset, \
127 + uint64_t *data, unsigned size, \
128 + MemTxAttrs attrs) \
129 + { \
130 + return config_read(opaque, DOMAIN, offset, data, size, attrs); \
131 + } \
132 + static MemTxResult config_##NAME##_write(void *opaque, hwaddr offset, \
133 + uint64_t data, unsigned size, \
134 + MemTxAttrs attrs) \
135 + { \
136 + return config_write(opaque, DOMAIN, offset, data, size, attrs); \
137 + }
138 +
139 +DEFINE_READ_WRITE_WRAPPERS(ns, GICV5_ID_NS)
140 +DEFINE_READ_WRITE_WRAPPERS(realm, GICV5_ID_REALM)
141 +DEFINE_READ_WRITE_WRAPPERS(secure, GICV5_ID_S)
142 +DEFINE_READ_WRITE_WRAPPERS(el3, GICV5_ID_EL3)
143 +
144 +#define FRAME_OP_ENTRY(NAME, DOMAIN) \
145 + [DOMAIN] = { \
146 + .read_with_attrs = config_##NAME##_read, \
147 + .write_with_attrs = config_##NAME##_write, \
148 + .endianness = DEVICE_LITTLE_ENDIAN, \
149 + .valid.min_access_size = 4, \
150 + .valid.max_access_size = 8, \
151 + .impl.min_access_size = 4, \
152 + .impl.max_access_size = 8, \
153 + }
154 +
155 +static const MemoryRegionOps config_frame_ops[NUM_GICV5_DOMAINS] = {
156 + FRAME_OP_ENTRY(ns, GICV5_ID_NS),
157 + FRAME_OP_ENTRY(realm, GICV5_ID_REALM),
158 + FRAME_OP_ENTRY(secure, GICV5_ID_S),
159 + FRAME_OP_ENTRY(el3, GICV5_ID_EL3),
160 +};
161 +
162 static void gicv5_reset_hold(Object *obj, ResetType type)
163 {
164 GICv5 *s = ARM_GICV5(obj);
@@ -21,6 +169,28 @@ static void gicv5_reset_hold(Object *obj, ResetType type)
169 }
170 }
171
172 +static void gicv5_realize(DeviceState *dev, Error **errp)
173 +{
174 + GICv5Common *cs = ARM_GICV5_COMMON(dev);
175 + GICv5Class *gc = ARM_GICV5_GET_CLASS(dev);
176 +
177 + ERRP_GUARD();
178 +
179 + gc->parent_realize(dev, errp);
180 + if (*errp) {
181 + return;
182 + }
183 +
184 + /*
185 + * When we implement support for more than one interrupt domain,
186 + * we will provide some QOM properties so the board can configure
187 + * which domains are implemented. For now, we only implement the
188 + * NS domain.
189 + */
190 + cs->implemented_domains = (1 << GICV5_ID_NS);
191 + gicv5_common_init_irqs_and_mmio(cs, config_frame_ops);
192 +}
193 +
194 static void gicv5_init(Object *obj)
195 {
196 }
@@ -32,8 +202,10 @@ static void gicv5_finalize(Object *obj)
202 static void gicv5_class_init(ObjectClass *oc, const void *data)
203 {
204 ResettableClass *rc = RESETTABLE_CLASS(oc);
205 + DeviceClass *dc = DEVICE_CLASS(oc);
206 GICv5Class *gc = ARM_GICV5_CLASS(oc);
207
208 + device_class_set_parent_realize(dc, gicv5_realize, &gc->parent_realize);
209 resettable_class_set_parent_phases(rc, NULL, gicv5_reset_hold, NULL,
210 &gc->parent_phases);
211 }
hw/intc/arm_gicv5_common.c
+35
@@ -11,6 +11,41 @@
11
12 OBJECT_DEFINE_ABSTRACT_TYPE(GICv5Common, gicv5_common, ARM_GICV5_COMMON, SYS_BUS_DEVICE)
13
14 +static bool bad_frame_accepts(void *opaque, hwaddr addr, unsigned size,
15 + bool is_write, MemTxAttrs attrs)
16 +{
17 + return false;
18 +}
19 +
20 +/*
21 + * Used for the sysbus MMIO regions corresponding to IRS frames where
22 + * this IRS does not implement the interrupt domain. It's probably a
23 + * board/SoC error to create an IRS and try to wire up this MMIO
24 + * region, but if it does then the region will behave as unassigned
25 + * memory (generating a decode error). These frames are just here so
26 + * that changing which domains are implemented doesn't reorder which
27 + * sysbus MMIO region is which.
28 + */
29 +static const MemoryRegionOps bad_frame_ops = {
30 + .valid.accepts = bad_frame_accepts,
31 + .endianness = DEVICE_LITTLE_ENDIAN,
32 +};
33 +
34 +void gicv5_common_init_irqs_and_mmio(GICv5Common *cs,
35 + const MemoryRegionOps config_ops[NUM_GICV5_DOMAINS])
36 +{
37 + SysBusDevice *sbd = SYS_BUS_DEVICE(cs);
38 +
39 + for (int i = 0; i < NUM_GICV5_DOMAINS; i++) {
40 + g_autofree char *memname = g_strdup_printf("gicv5-irs-%d", i);
41 + const MemoryRegionOps *ops = gicv5_domain_implemented(cs, i) ?
42 + &config_ops[i] : &bad_frame_ops;
43 + memory_region_init_io(&cs->iomem[i], OBJECT(cs), ops, cs,
44 + memname, IRS_CONFIG_FRAME_SIZE);
45 + sysbus_init_mmio(sbd, &cs->iomem[i]);
46 + }
47 +}
48 +
49 static void gicv5_common_reset_hold(Object *obj, ResetType type)
50 {
51 }
hw/intc/trace-events
+6
@@ -227,6 +227,12 @@ gicv3_its_vte_read(uint32_t vpeid, int valid, uint32_t vptsize, uint64_t vptaddr
227 gicv3_its_vte_read_fault(uint32_t vpeid) "GICv3 ITS: vPE Table read for vPEID 0x%x: faulted"
228 gicv3_its_vte_write(uint32_t vpeid, int valid, uint32_t vptsize, uint64_t vptaddr, uint32_t rdbase) "GICv3 ITS: vPE Table write for vPEID 0x%x: valid %d VPTsize 0x%x VPTaddr 0x%" PRIx64 " RDbase 0x%x"
229
230 +# arm_gicv5.c
231 +gicv5_read(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
232 +gicv5_badread(const char *domain, uint64_t offset, unsigned size) "GICv5 IRS %s config frame read: offset 0x%" PRIx64 " size %u: error"
233 +gicv5_write(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u"
234 +gicv5_badwrite(const char *domain, uint64_t offset, uint64_t data, unsigned size) "GICv5 IRS %s config frame write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u: error"
235 +
236 # armv7m_nvic.c
237 nvic_recompute_state(int vectpending, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d vectpending_prio %d exception_prio %d"
238 nvic_recompute_state_secure(int vectpending, bool vectpending_is_s_banked, int vectpending_prio, int exception_prio) "NVIC state recomputed: vectpending %d is_s_banked %d vectpending_prio %d exception_prio %d"
include/hw/intc/arm_gicv5.h
+1
@@ -26,6 +26,7 @@ struct GICv5 {
26
27 struct GICv5Class {
28 GICv5CommonClass parent_class;
29 + DeviceRealize parent_realize;
30 ResettablePhases parent_phases;
31 };
32
include/hw/intc/arm_gicv5_common.h
+50
@@ -11,6 +11,26 @@
11
12 #include "qom/object.h"
13 #include "hw/core/sysbus.h"
14 +#include "hw/intc/arm_gicv5_types.h"
15 +
16 +/*
17 + * QEMU interface:
18 + *
19 + * sysbus MMIO regions (in order matching IRS_IDR0.INT_DOM encoding):
20 + * - IRS config frame for the Secure Interrupt Domain
21 + * - IRS config frame for the Non-secure Interrupt Domain
22 + * - IRS config frame for the EL3 Interrupt Domain
23 + * - IRS config frame for the Realm Interrupt Domain
24 + *
25 + * Note that even if this particular IRS does not implement all four
26 + * interrupt domains it will still expose four sysbus MMIO regions.
27 + * The regions corresponding to unimplemented domains will always fail
28 + * accesses with a decode error. Generally the SoC/board should
29 + * probably not map a region for a domain that it configured the IRS
30 + * to not implement; the regions are only exposed so that changing
31 + * which domains are implemented doesn't reorder which sysbus MMIO
32 + * region is which (e.g. NS will always be 1 and EL3 will always be 2).
33 + */
34
35 #define TYPE_ARM_GICV5_COMMON "arm-gicv5-common"
36
@@ -22,10 +42,40 @@ OBJECT_DECLARE_TYPE(GICv5Common, GICv5CommonClass, ARM_GICV5_COMMON)
42 */
43 struct GICv5Common {
44 SysBusDevice parent_obj;
45 +
46 + MemoryRegion iomem[NUM_GICV5_DOMAINS];
47 +
48 + /* Bits here are set for each physical interrupt domain implemented */
49 + uint8_t implemented_domains;
50 };
51
52 struct GICv5CommonClass {
53 SysBusDeviceClass parent_class;
54 };
55
56 +
57 +#define IRS_CONFIG_FRAME_SIZE 0x10000
58 +
59 +/**
60 + * gicv5_common_init_irqs_and_mmio: Create IRQs and MMIO regions for the GICv5
61 + * @s: GIC object
62 + * @ops: array of MemoryRegionOps that implement the config frames behaviour
63 + *
64 + * Subclasses of ARM_GICV5_COMMON should call this to create the sysbus
65 + * MemoryRegions for the IRS config frames, passing in a four element array
66 + * of MemoryRegionOps structs.
67 + */
68 +void gicv5_common_init_irqs_and_mmio(GICv5Common *cs,
69 + const MemoryRegionOps ops[NUM_GICV5_DOMAINS]);
70 +
71 +/**
72 + * gicv5_domain_implemented: Return true if this IRS implements this domain
73 + * @s: GIC object
74 + * @domain: domain to check
75 + */
76 +static inline bool gicv5_domain_implemented(GICv5Common *cs, GICv5Domain domain)
77 +{
78 + return cs->implemented_domains & (1 << domain);
79 +}
80 +
81 #endif
include/hw/intc/arm_gicv5_types.h new
+28
@@ -0,0 +1,28 @@
1 +/*
2 + * Type definitions for GICv5
3 + *
4 + * This file is for type definitions that we want to share between
5 + * the GIC proper and the CPU interface.
6 + *
7 + * Copyright (c) 2025 Linaro Limited
8 + *
9 + * SPDX-License-Identifier: GPL-2.0-or-later
10 + */
11 +
12 +#ifndef HW_INTC_ARM_GICv5_TYPES_H
13 +#define HW_INTC_ARM_GICv5_TYPES_H
14 +
15 +/*
16 + * The GICv5 has four physical Interrupt Domains. This numbering must
17 + * match the encoding used in IRS_IDR0.INT_DOM.
18 + */
19 +typedef enum GICv5Domain {
20 + GICV5_ID_S = 0,
21 + GICV5_ID_NS = 1,
22 + GICV5_ID_EL3 = 2,
23 + GICV5_ID_REALM = 3,
24 +} GICv5Domain;
25 +
26 +#define NUM_GICV5_DOMAINS 4
27 +
28 +#endif