Model the RP2040 system configuration registers, documented reset and write masks, and APB atomic aliases.
Route each peripheral interrupt to the normal IRQ input or NMI input of each Cortex-M0+ according to PROC0_NMI_MASK and PROC1_NMI_MASK. MEMPOWERDOWN retains its documented register state without inventing behavior for accesses to powered-down memory banks. Add focused qtests for reset values, write masks, and atomic aliases. Signed-off-by: Gilles Grimaud <[email protected]> --- hw/arm/rp2040.c | 57 +++++++- hw/misc/meson.build | 1 + hw/misc/rp2040_syscfg.c | 220 +++++++++++++++++++++++++++++++ include/hw/arm/rp2040.h | 7 + include/hw/misc/rp2040_syscfg.h | 41 ++++++ tests/qtest/meson.build | 2 +- tests/qtest/rp2040-syscfg-test.c | 97 ++++++++++++++ 7 files changed, 421 insertions(+), 4 deletions(-) create mode 100644 hw/misc/rp2040_syscfg.c create mode 100644 include/hw/misc/rp2040_syscfg.h create mode 100644 tests/qtest/rp2040-syscfg-test.c diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c index a6d1ff75f8..eac4dde9cd 100644 --- a/hw/arm/rp2040.c +++ b/hw/arm/rp2040.c @@ -13,6 +13,7 @@ #include "hw/core/loader.h" #include "hw/core/qdev-clock.h" #include "hw/core/qdev-properties.h" +#include "hw/core/irq.h" #include "hw/misc/unimp.h" #include "qemu/datadir.h" #include "target/arm/cpu-qom.h" @@ -49,7 +50,6 @@ static const struct { hwaddr base; hwaddr size; } rp2040_unimplemented[] = { - { "rp2040.syscfg", 0x40004000, 0x4000 }, { "rp2040.clocks", 0x40008000, 0x4000 }, { "rp2040.resets", 0x4000c000, 0x4000 }, { "rp2040.psm", 0x40010000, 0x4000 }, @@ -83,6 +83,40 @@ static const struct { { "rp2040.sio", 0xd0000000, 0x1000 }, }; +static void rp2040_update_nmi(RP2040State *s) +{ + unsigned core; + + for (core = 0; core < RP2040_NUM_CORES; core++) { + uint32_t nmi_mask = rp2040_syscfg_get_nmi_mask(&s->syscfg, core); + bool nmi_level = false; + int irq; + + for (irq = 0; irq < RP2040_NUM_IRQS; irq++) { + bool route_to_nmi = nmi_mask & BIT(irq); + + qemu_set_irq(s->cpu_irq[core][irq], + s->irq_level[irq] && !route_to_nmi); + nmi_level |= s->irq_level[irq] && route_to_nmi; + } + qemu_set_irq(s->nmi_irq[core], nmi_level); + } +} + +static void rp2040_syscfg_update(void *opaque) +{ + rp2040_update_nmi(opaque); +} + +static void rp2040_set_irq(void *opaque, int irq, int level) +{ + RP2040State *s = opaque; + + assert(irq >= 0 && irq < RP2040_NUM_IRQS); + s->irq_level[irq] = level; + rp2040_update_nmi(s); +} + static void rp2040_soc_init(Object *obj) { RP2040State *s = RP2040(obj); @@ -109,8 +143,11 @@ static void rp2040_soc_init(Object *obj) "chardev"); } + object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG); object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO); + s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS); + s->sysclk = clock_new(obj, "sysclk"); clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ); } @@ -221,8 +258,21 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp) if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m[i]), errp)) { return; } + + for (int irq = 0; irq < RP2040_NUM_IRQS; irq++) { + s->cpu_irq[i][irq] = + qdev_get_gpio_in(DEVICE(&s->armv7m[i]), irq); + } + s->nmi_irq[i] = qdev_get_gpio_in_named(DEVICE(&s->armv7m[i]), + "NMI", 0); } + if (!sysbus_realize(SYS_BUS_DEVICE(&s->syscfg), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->syscfg), 0, RP2040_SYSCFG_BASE); + rp2040_syscfg_set_update_callback(&s->syscfg, rp2040_syscfg_update, s); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysinfo), errp)) { return; } @@ -235,9 +285,10 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp) } sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]); sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0, - qdev_get_gpio_in(DEVICE(&s->armv7m[0]), - uart_irq[i])); + s->irq[uart_irq[i]]); } + + rp2040_update_nmi(s); } static const Property rp2040_soc_properties[] = { diff --git a/hw/misc/meson.build b/hw/misc/meson.build index cd5970bb84..baa2818d8a 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -98,6 +98,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files( 'bcm2835_cprman.c', 'bcm2835_powermgt.c', )) +system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_syscfg.c')) system_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040_sysinfo.c')) system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c')) system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c')) diff --git a/hw/misc/rp2040_syscfg.c b/hw/misc/rp2040_syscfg.c new file mode 100644 index 0000000000..9f907194ed --- /dev/null +++ b/hw/misc/rp2040_syscfg.c @@ -0,0 +1,220 @@ +/* + * RP2040 syscfg emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/rp2040.h" +#include "hw/misc/rp2040_syscfg.h" +#include "migration/vmstate.h" +#include "qemu/log.h" +#include "qemu/module.h" + +#define SYSCFG_PROC0_NMI_MASK 0x00 +#define SYSCFG_PROC1_NMI_MASK 0x04 +#define SYSCFG_PROC_CONFIG 0x08 +#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c +#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10 +#define SYSCFG_DBGFORCE 0x14 +#define SYSCFG_MEMPOWERDOWN 0x18 + +#define PROC_CONFIG_RW_MASK 0xff000000 +#define PROC_CONFIG_RESET 0x10000000 +#define PROC_IN_SYNC_BYPASS_MASK 0x3fffffff +#define PROC_IN_SYNC_BYPASS_HI_MASK 0x0000003f +#define DBGFORCE_RW_MASK 0x000000ee +#define DBGFORCE_RESET 0x00000066 +#define MEMPOWERDOWN_MASK 0x000000ff + +static void rp2040_syscfg_update(RP2040SysCfgState *s) +{ + if (s->update) { + s->update(s->update_opaque); + } +} + +void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s, + RP2040SysCfgUpdateFn update, + void *opaque) +{ + s->update = update; + s->update_opaque = opaque; +} + +uint32_t rp2040_syscfg_get_nmi_mask(RP2040SysCfgState *s, unsigned core) +{ + assert(core < 2); + return core ? s->proc1_nmi_mask : s->proc0_nmi_mask; +} + +static uint64_t rp2040_syscfg_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040SysCfgState *s = opaque; + hwaddr offset = addr & 0xfff; + uint64_t value; + + switch (offset) { + case SYSCFG_PROC0_NMI_MASK: + value = s->proc0_nmi_mask; + break; + case SYSCFG_PROC1_NMI_MASK: + value = s->proc1_nmi_mask; + break; + case SYSCFG_PROC_CONFIG: + value = s->proc_config; + break; + case SYSCFG_PROC_IN_SYNC_BYPASS: + value = s->proc_in_sync_bypass; + break; + case SYSCFG_PROC_IN_SYNC_BYPASS_HI: + value = s->proc_in_sync_bypass_hi; + break; + case SYSCFG_DBGFORCE: + value = s->dbgforce; + break; + case SYSCFG_MEMPOWERDOWN: + value = s->mempowerdown; + break; + default: + value = 0; + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented read at offset 0x%" + HWADDR_PRIx "\n", __func__, addr & 0xfff); + break; + } + + return value; +} + +static void rp2040_syscfg_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + RP2040SysCfgState *s = opaque; + hwaddr alias = addr & RP2040_ATOMIC_ALIAS_MASK; + hwaddr offset = addr & 0xfff; + uint32_t value = value64; + + switch (offset) { + case SYSCFG_PROC0_NMI_MASK: + s->proc0_nmi_mask = + rp2040_atomic_update(s->proc0_nmi_mask, value, alias); + rp2040_syscfg_update(s); + break; + case SYSCFG_PROC1_NMI_MASK: + s->proc1_nmi_mask = + rp2040_atomic_update(s->proc1_nmi_mask, value, alias); + rp2040_syscfg_update(s); + break; + case SYSCFG_PROC_CONFIG: + s->proc_config = + rp2040_atomic_update(s->proc_config, value, alias) & + PROC_CONFIG_RW_MASK; + break; + case SYSCFG_PROC_IN_SYNC_BYPASS: + s->proc_in_sync_bypass = + rp2040_atomic_update(s->proc_in_sync_bypass, value, alias) & + PROC_IN_SYNC_BYPASS_MASK; + break; + case SYSCFG_PROC_IN_SYNC_BYPASS_HI: + s->proc_in_sync_bypass_hi = + rp2040_atomic_update(s->proc_in_sync_bypass_hi, value, alias) & + PROC_IN_SYNC_BYPASS_HI_MASK; + break; + case SYSCFG_DBGFORCE: + s->dbgforce = + rp2040_atomic_update(s->dbgforce, value, alias) & + DBGFORCE_RW_MASK; + break; + case SYSCFG_MEMPOWERDOWN: + s->mempowerdown = + rp2040_atomic_update(s->mempowerdown, value, alias) & + MEMPOWERDOWN_MASK; + break; + default: + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented write at offset 0x%" + HWADDR_PRIx "\n", __func__, addr & 0xfff); + break; + } +} + +static const MemoryRegionOps rp2040_syscfg_ops = { + .read = rp2040_syscfg_read, + .write = rp2040_syscfg_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void rp2040_syscfg_reset(DeviceState *dev) +{ + RP2040SysCfgState *s = RP2040_SYSCFG(dev); + + s->proc0_nmi_mask = 0; + s->proc1_nmi_mask = 0; + s->proc_config = PROC_CONFIG_RESET; + s->proc_in_sync_bypass = 0; + s->proc_in_sync_bypass_hi = 0; + s->dbgforce = DBGFORCE_RESET; + s->mempowerdown = 0; + rp2040_syscfg_update(s); +} + +static int rp2040_syscfg_post_load(void *opaque, int version_id) +{ + RP2040SysCfgState *s = opaque; + + rp2040_syscfg_update(s); + return 0; +} + +static void rp2040_syscfg_init(Object *obj) +{ + RP2040SysCfgState *s = RP2040_SYSCFG(obj); + + memory_region_init_io(&s->iomem, obj, &rp2040_syscfg_ops, s, + "rp2040.syscfg", RP2040_SYSCFG_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static const VMStateDescription rp2040_syscfg_vmstate = { + .name = TYPE_RP2040_SYSCFG, + .version_id = 1, + .minimum_version_id = 1, + .post_load = rp2040_syscfg_post_load, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(proc0_nmi_mask, RP2040SysCfgState), + VMSTATE_UINT32(proc1_nmi_mask, RP2040SysCfgState), + VMSTATE_UINT32(proc_config, RP2040SysCfgState), + VMSTATE_UINT32(proc_in_sync_bypass, RP2040SysCfgState), + VMSTATE_UINT32(proc_in_sync_bypass_hi, RP2040SysCfgState), + VMSTATE_UINT32(dbgforce, RP2040SysCfgState), + VMSTATE_UINT32(mempowerdown, RP2040SysCfgState), + VMSTATE_END_OF_LIST() + } +}; + +static void rp2040_syscfg_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_syscfg_reset); + dc->vmsd = &rp2040_syscfg_vmstate; +} + +static const TypeInfo rp2040_syscfg_info = { + .name = TYPE_RP2040_SYSCFG, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RP2040SysCfgState), + .instance_init = rp2040_syscfg_init, + .class_init = rp2040_syscfg_class_init, +}; + +static void rp2040_syscfg_register_types(void) +{ + type_register_static(&rp2040_syscfg_info); +} +type_init(rp2040_syscfg_register_types) diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h index 7e7d8f5c95..f6050f255d 100644 --- a/include/hw/arm/rp2040.h +++ b/include/hw/arm/rp2040.h @@ -14,6 +14,7 @@ #include "hw/core/clock.h" #include "hw/core/sysbus.h" #include "hw/misc/rp2040_sysinfo.h" +#include "hw/misc/rp2040_syscfg.h" #include "qom/object.h" #define TYPE_RP2040 "rp2040" @@ -36,6 +37,7 @@ struct RP2040State { ARMv7MState armv7m[RP2040_NUM_CORES]; PL011State uart[2]; + RP2040SysCfgState syscfg; RP2040SysInfoState sysinfo; MemoryRegion *board_memory; @@ -45,6 +47,11 @@ struct RP2040State { MemoryRegion sram[6]; char *bootrom_file; + qemu_irq *irq; + qemu_irq cpu_irq[RP2040_NUM_CORES][RP2040_NUM_IRQS]; + qemu_irq nmi_irq[RP2040_NUM_CORES]; + bool irq_level[RP2040_NUM_IRQS]; + Clock *sysclk; }; diff --git a/include/hw/misc/rp2040_syscfg.h b/include/hw/misc/rp2040_syscfg.h new file mode 100644 index 0000000000..0e02e4ad81 --- /dev/null +++ b/include/hw/misc/rp2040_syscfg.h @@ -0,0 +1,41 @@ +/* + * RP2040 syscfg emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_RP2040_SYSCFG_H +#define HW_MISC_RP2040_SYSCFG_H + +#include "hw/core/sysbus.h" +#include "qom/object.h" + +#define TYPE_RP2040_SYSCFG "rp2040-syscfg" +OBJECT_DECLARE_SIMPLE_TYPE(RP2040SysCfgState, RP2040_SYSCFG) + +#define RP2040_SYSCFG_BASE 0x40004000 +#define RP2040_SYSCFG_SIZE 0x4000 + +typedef void (*RP2040SysCfgUpdateFn)(void *opaque); + +struct RP2040SysCfgState { + SysBusDevice parent_obj; + + MemoryRegion iomem; + RP2040SysCfgUpdateFn update; + void *update_opaque; + uint32_t proc0_nmi_mask; + uint32_t proc1_nmi_mask; + uint32_t proc_config; + uint32_t proc_in_sync_bypass; + uint32_t proc_in_sync_bypass_hi; + uint32_t dbgforce; + uint32_t mempowerdown; +}; + +void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s, + RP2040SysCfgUpdateFn update, + void *opaque); +uint32_t rp2040_syscfg_get_nmi_mask(RP2040SysCfgState *s, unsigned core); + +#endif diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 3cfda5a40d..fdc7dbc84b 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -249,7 +249,7 @@ qtests_stm32l4x5 = \ qtests_arm = \ (config_all_devices.has_key('CONFIG_RP2040') ? - ['rp2040-sysinfo-test'] : []) + \ + ['rp2040-syscfg-test', 'rp2040-sysinfo-test'] : []) + \ (config_all_devices.has_key('CONFIG_ARM_VIRT') and config_all_devices.has_key('CONFIG_PL011_C') ? ['pl011-test'] : []) + \ (config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \ diff --git a/tests/qtest/rp2040-syscfg-test.c b/tests/qtest/rp2040-syscfg-test.c new file mode 100644 index 0000000000..b1a0738bd2 --- /dev/null +++ b/tests/qtest/rp2040-syscfg-test.c @@ -0,0 +1,97 @@ +/* + * QTest testcase for the RP2040 SYSCFG block. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "hw/misc/rp2040.h" +#include "qemu/bitops.h" + +#define SYSCFG_BASE 0x40004000 +#define SYSCFG_PROC0_NMI_MASK 0x00 +#define SYSCFG_PROC1_NMI_MASK 0x04 +#define SYSCFG_PROC_CONFIG 0x08 +#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c +#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10 +#define SYSCFG_DBGFORCE 0x14 +#define SYSCFG_MEMPOWERDOWN 0x18 + +#define PROC_CONFIG_RESET 0x10000000 +#define DBGFORCE_RESET 0x00000066 + +static QTestState *rp2040_start(void) +{ + return qtest_init("-machine raspi-pico"); +} + +static void test_syscfg_reset_values(void) +{ + QTestState *qts = rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK), + ==, 0); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK), + ==, 0); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC_CONFIG), ==, + PROC_CONFIG_RESET); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_DBGFORCE), ==, + DBGFORCE_RESET); + + qtest_quit(qts); +} + +static void test_syscfg_rw_masks(void) +{ + QTestState *qts = rp2040_start(); + + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0xa5a5a5a5); + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK, 0x5a5a5a5a); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK), + ==, 0xa5a5a5a5); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK), + ==, 0x5a5a5a5a); + + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS, 0xffffffff); + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI, + 0xffffffff); + qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, + SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS), + ==, 0x3fffffff); + g_assert_cmphex(qtest_readl(qts, + SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI), + ==, 0x0000003f); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN), ==, + 0x000000ff); + + qtest_quit(qts); +} + +static void test_syscfg_atomic_aliases(void) +{ + QTestState *qts = rp2040_start(); + + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0); + qtest_writel(qts, SYSCFG_BASE + RP2040_ATOMIC_SET + + SYSCFG_PROC0_NMI_MASK, 0x0000000f); + qtest_writel(qts, SYSCFG_BASE + RP2040_ATOMIC_CLR + + SYSCFG_PROC0_NMI_MASK, 0x00000003); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK), + ==, 0x0000000c); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/rp2040-syscfg/reset-values", test_syscfg_reset_values); + qtest_add_func("/rp2040-syscfg/rw-masks", test_syscfg_rw_masks); + qtest_add_func("/rp2040-syscfg/atomic-aliases", + test_syscfg_atomic_aliases); + + return g_test_run(); +} -- 2.55.0
