Files
qemu/hw/intc/arm_gicv5_common.c
Peter Maydell 829ce41fbe target/arm: GICv5 cpuif: Implement ICC_IAFFIDR_EL1
The CPU system register ICC_IAFFIDR_EL1 is a read-only register that
tells the guest what the affinity ID of that CPU is.

Implement this register.  In real hardware using the stream protocol,
the IRS tells the CPU its IAFFID using a DownstreamControl command as
part of the handshake process when the IRS-CPU link is brought
online.  Our analogue of this is to pass the IAFFID as an extra
argument to gicv5_set_gicv5state().  (We could have the CPU call into
the GIC every time to ask for the value, but this would mean we had
to search the cpus[] array for the right CPU to return its IAFFID.)

Note that we don't put the IAFFID into the gicv5_cpuif sub-struct,
because that part of the CPU struct is zeroed on reset, and we must
keep the IAFFID across reset (we only set it up when the GIC device
is created).

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Message-id: 20260327111700.795099-35-peter.maydell@linaro.org
2026-05-07 15:13:47 +01:00

216 lines
7.4 KiB
C

/*
* Common base class for GICv5 IRS
*
* Copyright (c) 2025 Linaro Limited
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/intc/arm_gicv5_common.h"
#include "hw/intc/arm_gicv5_stream.h"
#include "hw/core/qdev-properties.h"
#include "qapi/error.h"
#include "trace.h"
OBJECT_DEFINE_ABSTRACT_TYPE(GICv5Common, gicv5_common, ARM_GICV5_COMMON, SYS_BUS_DEVICE)
/* Any value > 2^24 is out of the valid range for this property */
#define GICV5_SPI_IRS_RANGE_NOT_SET 0xffffffff
static bool bad_frame_accepts(void *opaque, hwaddr addr, unsigned size,
bool is_write, MemTxAttrs attrs)
{
return false;
}
/*
* Used for the sysbus MMIO regions corresponding to IRS frames where
* this IRS does not implement the interrupt domain. It's probably a
* board/SoC error to create an IRS and try to wire up this MMIO
* region, but if it does then the region will behave as unassigned
* memory (generating a decode error). These frames are just here so
* that changing which domains are implemented doesn't reorder which
* sysbus MMIO region is which.
*/
static const MemoryRegionOps bad_frame_ops = {
.valid.accepts = bad_frame_accepts,
.endianness = DEVICE_LITTLE_ENDIAN,
};
void gicv5_common_init_irqs_and_mmio(GICv5Common *cs,
qemu_irq_handler handler,
const MemoryRegionOps config_ops[NUM_GICV5_DOMAINS])
{
SysBusDevice *sbd = SYS_BUS_DEVICE(cs);
if (cs->spi_irs_range) {
qdev_init_gpio_in(DEVICE(cs), handler, cs->spi_irs_range);
}
for (int i = 0; i < NUM_GICV5_DOMAINS; i++) {
g_autofree char *memname = g_strdup_printf("gicv5-irs-%d", i);
const MemoryRegionOps *ops = gicv5_domain_implemented(cs, i) ?
&config_ops[i] : &bad_frame_ops;
memory_region_init_io(&cs->iomem[i], OBJECT(cs), ops, cs,
memname, IRS_CONFIG_FRAME_SIZE);
sysbus_init_mmio(sbd, &cs->iomem[i]);
}
}
static void gicv5_common_reset_hold(Object *obj, ResetType type)
{
GICv5Common *cs = ARM_GICV5_COMMON(obj);
memset(cs->irs_ist_baser, 0, sizeof(cs->irs_ist_baser));
memset(cs->irs_ist_cfgr, 0, sizeof(cs->irs_ist_cfgr));
memset(cs->irs_cr0, 0, sizeof(cs->irs_cr0));
memset(cs->irs_cr1, 0, sizeof(cs->irs_cr1));
memset(cs->irs_pe_selr, 0, sizeof(cs->irs_pe_selr));
if (cs->spi) {
GICv5Domain mp_domain;
/*
* D_YGLYC, D_TVVRZ: SPIs reset to edge-triggered, inactive,
* idle, disabled, targeted routing mode, not assigned to a
* VM, and assigned to the most-privileged interrupt domain.
* Other state is UNKNOWN: we choose to zero it.
*/
memset(cs->spi, 0, cs->spi_irs_range * sizeof(*cs->spi));
/*
* The most-privileged interrupt domain is effectively the
* first in the list (EL3, S, NS) that we implement.
*/
if (gicv5_domain_implemented(cs, GICV5_ID_EL3)) {
mp_domain = GICV5_ID_EL3;
} else if (gicv5_domain_implemented(cs, GICV5_ID_S)) {
mp_domain = GICV5_ID_S;
} else {
mp_domain = GICV5_ID_NS;
}
for (int i = 0; i < cs->spi_irs_range; i++) {
cs->spi[i].domain = mp_domain;
}
}
for (int i = 0; i < NUM_GICV5_DOMAINS; i++) {
/*
* We reset irs_spi_selr to an invalid value so that our reset
* value for IRS_SPI_STATUSR.V is correctly 0. The guest can
* never read IRS_SPI_SELR directly.
*/
cs->irs_spi_selr[i] = cs->spi_base + cs->spi_irs_range;
}
}
static void gicv5_common_init(Object *obj)
{
}
static void gicv5_common_finalize(Object *obj)
{
}
static void gicv5_common_realize(DeviceState *dev, Error **errp)
{
GICv5Common *cs = ARM_GICV5_COMMON(dev);
if (cs->num_cpus == 0) {
error_setg(errp, "The cpus array property must have at least one CPU");
return;
}
if (cs->num_cpus >= (1 << 16)) {
/* We'll hit other QEMU limits long before this one :-) */
error_setg(errp, "Number of CPUs exceeds GICv5 architectural maximum");
return;
}
if (cs->num_cpus != cs->num_cpu_iaffids) {
error_setg(errp, "The cpu-iaffids array property must be the same size "
"as the cpus array property");
return;
}
if (cs->irsid >= (1 << 16)) {
error_setg(errp, "irsid (%u) is more than 2^16-1", cs->irsid);
return;
}
if (cs->spi_range > (1 << 24)) {
/*
* Note that IRS_IDR5.SPI_RANGE is a 25 bit field but the largest
* architecturally permitted value is 2^24 (not 2^25-1), hence
* use of > in the range check.
*/
error_setg(errp, "spi-range (%u) is more than 2^24", cs->spi_range);
return;
}
if (cs->spi_irs_range == GICV5_SPI_IRS_RANGE_NOT_SET) {
/* spi-irs-range defaults to same as spi-range */
cs->spi_irs_range = cs->spi_range;
}
if (cs->spi_irs_range > (1 << 24)) {
/* Similarly IRS_IDR6.SPI_IRS_RANGE */
error_setg(errp, "spi-irs-range (%u) is more than 2^24",
cs->spi_irs_range);
return;
}
if (cs->spi_base >= (1 << 24)) {
/* IRS_IDR7.SPI_BASE is a 24-bit field, so range check is >= */
error_setg(errp, "spi-base (%u) is more than 2^24-1", cs->spi_base);
return;
}
/* range checks above mean we know this addition won't overflow */
if (cs->spi_base + cs->spi_irs_range > cs->spi_range) {
error_setg(errp, "spi-base (%u) + spi-irs-range (%u) is "
"more than spi-range (%u)",
cs->spi_base, cs->spi_irs_range, cs->spi_range);
return;
}
if (!cs->dma) {
error_setg(errp, "sysmem link property not set");
return;
}
for (int i = 0; i < cs->num_cpus; i++) {
if (!gicv5_set_gicv5state(cs->cpus[i], cs, cs->cpu_iaffids[i])) {
error_setg(errp,
"CPU %d (IAFFID 0x%x) does not implement GICv5 CPU interface",
i, cs->cpu_iaffids[i]);
return;
}
}
address_space_init(&cs->dma_as, cs->dma, "gicv5-sysmem");
cs->spi = g_new0(GICv5SPIState, cs->spi_irs_range);
trace_gicv5_common_realize(cs->irsid, cs->num_cpus,
cs->spi_base, cs->spi_irs_range, cs->spi_range);
}
static const Property arm_gicv5_common_properties[] = {
DEFINE_PROP_LINK_ARRAY("cpus", GICv5Common, num_cpus,
cpus, TYPE_ARM_CPU, ARMCPU *),
DEFINE_PROP_ARRAY("cpu-iaffids", GICv5Common, num_cpu_iaffids,
cpu_iaffids, qdev_prop_uint32, uint32_t),
DEFINE_PROP_UINT32("irsid", GICv5Common, irsid, 0),
DEFINE_PROP_UINT32("spi-range", GICv5Common, spi_range, 0),
DEFINE_PROP_UINT32("spi-base", GICv5Common, spi_base, 0),
DEFINE_PROP_UINT32("spi-irs-range", GICv5Common, spi_irs_range,
GICV5_SPI_IRS_RANGE_NOT_SET),
DEFINE_PROP_LINK("sysmem", GICv5Common, dma, TYPE_MEMORY_REGION,
MemoryRegion *),
};
static void gicv5_common_class_init(ObjectClass *oc, const void *data)
{
ResettableClass *rc = RESETTABLE_CLASS(oc);
DeviceClass *dc = DEVICE_CLASS(oc);
rc->phases.hold = gicv5_common_reset_hold;
dc->realize = gicv5_common_realize;
device_class_set_props(dc, arm_gicv5_common_properties);
}