hw/arm/armsse: Add framework for data-driven device placement

The SSE-300 is mostly the same as the SSE-200, but it has moved some
of the devices in the memory map and uses different device types in
some cases.  To accommodate this, add a framework where the placement
and wiring of some devices can be specified in a data table.

This commit adds the framework for this data-driven device placement,
and makes the CMSDK APB timer devices use it.  Subsequent commits
will convert the other devices which differ between SSE-200 and
SSE-300.

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
Message-id: 20210219144617.4782-24-peter.maydell@linaro.org
master
Peter Maydell 2021-02-19 14:45:56 +00:00
parent 3378873802
commit e94d7723b5
2 changed files with 125 additions and 25 deletions

View File

@ -24,6 +24,27 @@
#include "hw/irq.h"
#include "hw/qdev-clock.h"
/*
* The SSE-300 puts some devices in different places to the
* SSE-200 (and original IoTKit). We use an array of these structs
* to define how each variant lays out these devices. (Parts of the
* SoC that are the same for all variants aren't handled via these
* data structures.)
*/
#define NO_IRQ -1
#define NO_PPC -1
typedef struct ARMSSEDeviceInfo {
const char *name; /* name to use for the QOM object; NULL terminates list */
const char *type; /* QOM type name */
unsigned int index; /* Which of the N devices of this type is this ? */
hwaddr addr;
int ppc; /* Index of APB PPC this device is wired up to, or NO_PPC */
int ppc_port; /* Port number of this device on the PPC */
int irq; /* NO_IRQ, or 0..NUM_SSE_IRQS-1 */
} ARMSSEDeviceInfo;
struct ARMSSEInfo {
const char *name;
uint32_t sse_version;
@ -38,6 +59,7 @@ struct ARMSSEInfo {
bool has_cpusecctrl;
bool has_cpuid;
Property *props;
const ARMSSEDeviceInfo *devinfo;
};
static Property iotkit_properties[] = {
@ -64,6 +86,30 @@ static Property armsse_properties[] = {
DEFINE_PROP_END_OF_LIST()
};
static const ARMSSEDeviceInfo sse200_devices[] = {
{
.name = "timer0",
.type = TYPE_CMSDK_APB_TIMER,
.index = 0,
.addr = 0x40000000,
.ppc = 0,
.ppc_port = 0,
.irq = 3,
},
{
.name = "timer1",
.type = TYPE_CMSDK_APB_TIMER,
.index = 1,
.addr = 0x40001000,
.ppc = 0,
.ppc_port = 1,
.irq = 4,
},
{
.name = NULL,
}
};
static const ARMSSEInfo armsse_variants[] = {
{
.name = TYPE_IOTKIT,
@ -79,6 +125,7 @@ static const ARMSSEInfo armsse_variants[] = {
.has_cpusecctrl = false,
.has_cpuid = false,
.props = iotkit_properties,
.devinfo = sse200_devices,
},
{
.name = TYPE_SSE200,
@ -94,6 +141,7 @@ static const ARMSSEInfo armsse_variants[] = {
.has_cpusecctrl = true,
.has_cpuid = true,
.props = armsse_properties,
.devinfo = sse200_devices,
},
};
@ -250,6 +298,7 @@ static void armsse_init(Object *obj)
ARMSSE *s = ARM_SSE(obj);
ARMSSEClass *asc = ARM_SSE_GET_CLASS(obj);
const ARMSSEInfo *info = asc->info;
const ARMSSEDeviceInfo *devinfo;
int i;
assert(info->sram_banks <= MAX_SRAM_BANKS);
@ -290,6 +339,18 @@ static void armsse_init(Object *obj)
}
}
for (devinfo = info->devinfo; devinfo->name; devinfo++) {
assert(devinfo->ppc == NO_PPC || devinfo->ppc < ARRAY_SIZE(s->apb_ppc));
if (!strcmp(devinfo->type, TYPE_CMSDK_APB_TIMER)) {
assert(devinfo->index < ARRAY_SIZE(s->timer));
object_initialize_child(obj, devinfo->name,
&s->timer[devinfo->index],
TYPE_CMSDK_APB_TIMER);
} else {
g_assert_not_reached();
}
}
object_initialize_child(obj, "secctl", &s->secctl, TYPE_IOTKIT_SECCTL);
for (i = 0; i < ARRAY_SIZE(s->apb_ppc); i++) {
@ -312,8 +373,6 @@ static void armsse_init(Object *obj)
object_initialize_child(obj, name, splitter, TYPE_SPLIT_IRQ);
g_free(name);
}
object_initialize_child(obj, "timer0", &s->timer0, TYPE_CMSDK_APB_TIMER);
object_initialize_child(obj, "timer1", &s->timer1, TYPE_CMSDK_APB_TIMER);
object_initialize_child(obj, "s32ktimer", &s->s32ktimer,
TYPE_CMSDK_APB_TIMER);
object_initialize_child(obj, "dualtimer", &s->dualtimer,
@ -453,6 +512,7 @@ static void armsse_realize(DeviceState *dev, Error **errp)
ARMSSE *s = ARM_SSE(dev);
ARMSSEClass *asc = ARM_SSE_GET_CLASS(dev);
const ARMSSEInfo *info = asc->info;
const ARMSSEDeviceInfo *devinfo;
int i;
MemoryRegion *mr;
Error *err = NULL;
@ -736,25 +796,53 @@ static void armsse_realize(DeviceState *dev, Error **errp)
* it to the appropriate PPC port; then we can realize the PPC and
* map its upstream ends to the right place in the container.
*/
qdev_connect_clock_in(DEVICE(&s->timer0), "pclk", s->mainclk);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer0), errp)) {
return;
}
sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer0), 0,
armsse_get_common_irq_in(s, 3));
mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->timer0), 0);
object_property_set_link(OBJECT(&s->apb_ppc[0]), "port[0]", OBJECT(mr),
&error_abort);
for (devinfo = info->devinfo; devinfo->name; devinfo++) {
SysBusDevice *sbd;
qemu_irq irq;
qdev_connect_clock_in(DEVICE(&s->timer1), "pclk", s->mainclk);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer1), errp)) {
return;
if (!strcmp(devinfo->type, TYPE_CMSDK_APB_TIMER)) {
sbd = SYS_BUS_DEVICE(&s->timer[devinfo->index]);
qdev_connect_clock_in(DEVICE(sbd), "pclk", s->mainclk);
if (!sysbus_realize(sbd, errp)) {
return;
}
mr = sysbus_mmio_get_region(sbd, 0);
} else {
g_assert_not_reached();
}
switch (devinfo->irq) {
case NO_IRQ:
irq = NULL;
break;
case 0 ... NUM_SSE_IRQS - 1:
irq = armsse_get_common_irq_in(s, devinfo->irq);
break;
default:
g_assert_not_reached();
}
if (irq) {
sysbus_connect_irq(sbd, 0, irq);
}
/*
* Devices connected to a PPC are connected to the port here;
* we will map the upstream end of that port to the right address
* in the container later after the PPC has been realized.
* Devices not connected to a PPC can be mapped immediately.
*/
if (devinfo->ppc != NO_PPC) {
TZPPC *ppc = &s->apb_ppc[devinfo->ppc];
g_autofree char *portname = g_strdup_printf("port[%d]",
devinfo->ppc_port);
object_property_set_link(OBJECT(ppc), portname, OBJECT(mr),
&error_abort);
} else {
memory_region_add_subregion(&s->container, devinfo->addr, mr);
}
}
sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer1), 0,
armsse_get_common_irq_in(s, 4));
mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->timer1), 0);
object_property_set_link(OBJECT(&s->apb_ppc[0]), "port[1]", OBJECT(mr),
&error_abort);
qdev_connect_clock_in(DEVICE(&s->dualtimer), "TIMCLK", s->mainclk);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->dualtimer), errp)) {
@ -813,10 +901,6 @@ static void armsse_realize(DeviceState *dev, Error **errp)
sbd_apb_ppc0 = SYS_BUS_DEVICE(&s->apb_ppc[0]);
dev_apb_ppc0 = DEVICE(&s->apb_ppc[0]);
mr = sysbus_mmio_get_region(sbd_apb_ppc0, 0);
memory_region_add_subregion(&s->container, 0x40000000, mr);
mr = sysbus_mmio_get_region(sbd_apb_ppc0, 1);
memory_region_add_subregion(&s->container, 0x40001000, mr);
mr = sysbus_mmio_get_region(sbd_apb_ppc0, 2);
memory_region_add_subregion(&s->container, 0x40002000, mr);
if (info->has_mhus) {
@ -947,6 +1031,23 @@ static void armsse_realize(DeviceState *dev, Error **errp)
qdev_get_gpio_in_named(dev_apb_ppc1,
"cfg_sec_resp", 0));
/*
* Now both PPCs are realized we can map the upstream ends of
* ports which correspond to entries in the devinfo array.
* The ports which are connected to non-devinfo devices have
* already been mapped.
*/
for (devinfo = info->devinfo; devinfo->name; devinfo++) {
SysBusDevice *ppc_sbd;
if (devinfo->ppc == NO_PPC) {
continue;
}
ppc_sbd = SYS_BUS_DEVICE(&s->apb_ppc[devinfo->ppc]);
mr = sysbus_mmio_get_region(ppc_sbd, devinfo->ppc_port);
memory_region_add_subregion(&s->container, devinfo->addr, mr);
}
if (!object_property_set_int(OBJECT(&s->sysinfo), "SYS_VERSION",
info->sys_version, errp)) {
return;

View File

@ -158,8 +158,7 @@ struct ARMSSE {
IoTKitSecCtl secctl;
TZPPC apb_ppc[NUM_INTERNAL_PPCS];
TZMPC mpc[IOTS_NUM_MPC];
CMSDKAPBTimer timer0;
CMSDKAPBTimer timer1;
CMSDKAPBTimer timer[2];
CMSDKAPBTimer s32ktimer;
qemu_or_irq ppc_irq_orgate;
SplitIRQ sec_resp_splitter;