Upstream: https://gitlab.com/qemu-project/qemu.git Upstream-Commit: d2e570cc0f97b936902a5b1b86b73c0f5998b475
216 lines
7.4 KiB
C
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);
|
|
}
|