From: gilles grimaud <[email protected]> Model the XIP windows, cache control registers, SSI interface and external NOR flash commands. Add raw-file persistence, configurable flash UID and focused qtests, and connect IO_QSPI chip-select overrides.
Update the synthetic boot path and existing TCG guests to execute a compiled second-stage boot block. The boot2 source is derived from the Raspberry Pi Pico SDK and therefore retains its BSD-3-Clause license. Signed-off-by: gilles grimaud <[email protected]> --- hw/arm/Kconfig | 1 + hw/arm/raspi_pico.c | 103 +- hw/arm/rp2040.c | 154 +- hw/misc/rp2040_ioqspi.c | 31 + hw/ssi/Kconfig | 3 + hw/ssi/meson.build | 1 + hw/ssi/rp2040_xip.c | 1348 +++++++++++++++++ hw/ssi/trace-events | 12 + include/hw/arm/rp2040.h | 5 + include/hw/misc/rp2040_ioqspi.h | 3 + include/hw/ssi/rp2040_xip.h | 92 ++ tests/qtest/meson.build | 3 +- tests/qtest/rp2040-xip-test.c | 293 ++++ tests/tcg/arm/Makefile.softmmu-target | 7 +- tests/tcg/arm/system/rp2040-boot2-w25q080.S | 178 +++ .../system/{rp2040-minimal.ld => rp2040.ld} | 11 +- 16 files changed, 2200 insertions(+), 45 deletions(-) create mode 100644 hw/ssi/rp2040_xip.c create mode 100644 include/hw/ssi/rp2040_xip.h create mode 100644 tests/qtest/rp2040-xip-test.c create mode 100644 tests/tcg/arm/system/rp2040-boot2-w25q080.S rename tests/tcg/arm/system/{rp2040-minimal.ld => rp2040.ld} (72%) diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 81cd84b642..dad91b368d 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -394,6 +394,7 @@ config RP2040 select RP2040_TIMER select RP2040_VREG select RP2040_WATCHDOG + select RP2040_XIP select RP2040_XOSC select UNIMP diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c index dd07c58801..2696a24951 100644 --- a/hw/arm/raspi_pico.c +++ b/hw/arm/raspi_pico.c @@ -22,19 +22,82 @@ #define TYPE_RASPI_PICO_MACHINE MACHINE_TYPE_NAME("raspi-pico") OBJECT_DECLARE_SIMPLE_TYPE(RaspiPicoMachineState, RASPI_PICO_MACHINE) -#define PICO_FLASH_SIZE (2 * MiB) - struct RaspiPicoMachineState { MachineState parent_obj; RP2040State soc; - MemoryRegion flash; + char *flash_file; + char *flash_uid; + uint64_t flash_uid_value; + bool flash_uid_set; char *rosc_random_seed; uint64_t rosc_random_seed_value; bool rosc_random_seed_set; bool strict_uart_pins; }; +static char *raspi_pico_get_flash_file(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj); + + return g_strdup(s->flash_file ?: ""); +} + +static void raspi_pico_set_flash_file(Object *obj, const char *value, + Error **errp) +{ + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj); + + g_free(s->flash_file); + s->flash_file = g_strdup(value); +} + +static char *raspi_pico_get_flash_uid(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj); + + return g_strdup(s->flash_uid ?: ""); +} + +static void raspi_pico_set_flash_uid(Object *obj, const char *value, + Error **errp) +{ + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj); + const char *p; + uint64_t uid; + int i; + + if (!value || !*value) { + g_free(s->flash_uid); + s->flash_uid = NULL; + s->flash_uid_value = 0; + s->flash_uid_set = false; + return; + } + + p = g_str_has_prefix(value, "0x") || g_str_has_prefix(value, "0X") ? + value + 2 : value; + if (strlen(p) != 16) { + error_setg(errp, "flash-uid must be exactly 16 hexadecimal digits"); + return; + } + for (i = 0; i < 16; i++) { + if (!g_ascii_isxdigit(p[i])) { + error_setg(errp, "invalid flash-uid '%s'", value); + return; + } + } + if (qemu_strtou64(p, NULL, 16, &uid) < 0) { + error_setg(errp, "invalid flash-uid '%s'", value); + return; + } + + g_free(s->flash_uid); + s->flash_uid = g_strdup(value); + s->flash_uid_value = uid; + s->flash_uid_set = true; +} + static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp) { RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj); @@ -82,6 +145,14 @@ static void raspi_pico_init(MachineState *machine) qdev_prop_set_bit(DEVICE(&s->soc.rosc), "random-seed-set", s->rosc_random_seed_set); qdev_prop_set_uint32(DEVICE(&s->soc.sio), "gpio-hi-in", 1u << 1); + if (s->flash_file) { + qdev_prop_set_string(DEVICE(&s->soc.xip), "flash-file", + s->flash_file); + } + if (s->flash_uid_set) { + qdev_prop_set_uint64(DEVICE(&s->soc.xip), "flash-uid", + s->flash_uid_value); + } if (machine->firmware) { qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file", machine->firmware); @@ -89,20 +160,21 @@ static void raspi_pico_init(MachineState *machine) object_property_set_link(OBJECT(&s->soc), "memory", OBJECT(system_memory), &error_fatal); - memory_region_init_rom(&s->flash, NULL, "raspi-pico.flash", - PICO_FLASH_SIZE, &error_fatal); - memory_region_add_subregion(system_memory, RP2040_XIP_BASE, &s->flash); - sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); - armv7m_load_kernel(s->soc.armv7m[0].cpu, machine->kernel_filename, - RP2040_XIP_BASE, PICO_FLASH_SIZE); + rp2040_xip_load_image(&s->soc.xip, machine->kernel_filename, + &error_fatal); + armv7m_load_kernel(s->soc.armv7m[0].cpu, NULL, + RP2040_XIP_BASE, 2 * MiB); + rp2040_xip_set_writable(&s->soc.xip, false); } static void raspi_pico_machine_finalize(Object *obj) { RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj); + g_free(s->flash_file); + g_free(s->flash_uid); g_free(s->rosc_random_seed); } @@ -142,6 +214,19 @@ static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data) mc->no_floppy = 1; mc->no_cdrom = 1; + object_class_property_add_str(oc, "flash-file", + raspi_pico_get_flash_file, + raspi_pico_set_flash_file); + object_class_property_set_description(oc, "flash-file", + "Load initial XIP flash contents " + "from a raw host file"); + object_class_property_add_str(oc, "flash-uid", + raspi_pico_get_flash_uid, + raspi_pico_set_flash_uid); + object_class_property_set_description(oc, "flash-uid", + "Set the emulated external flash " + "64-bit unique ID as 16 hex digits"); + object_class_property_add_str(oc, "rosc-random-seed", raspi_pico_get_rosc_random_seed, raspi_pico_set_rosc_random_seed); diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c index 79c7fda151..3a8c8f88af 100644 --- a/hw/arm/rp2040.c +++ b/hw/arm/rp2040.c @@ -16,6 +16,7 @@ #include "hw/core/irq.h" #include "hw/misc/unimp.h" #include "qemu/datadir.h" +#include "system/address-spaces.h" #include "target/arm/cpu.h" #include "target/arm/cpu-qom.h" @@ -29,37 +30,109 @@ #define RP2040_PROC1 1 /* - * Minimal synthetic ROM. Core 0 enters the XIP image while core 1 waits for - * the RP2040 ROM FIFO launch sequence: { 0, 0, 1, VTOR, SP, PC }. + * Temporary boot ROM used until a faithful RP2040 boot ROM is requested. It + * uses SIO_CPUID to split core behavior: core 0 copies the 256-byte XIP + * second-stage boot code into SRAM and branches to the SRAM copy; core 1 waits + * in ROM for the Pico SDK launch FIFO sequence, echoes the received words, + * installs VTOR/MSP, then branches to the received entry point. Real RP2040 + * mask ROM performs more checks, but boot2 expects to run from SRAM while it + * configures XIP. */ static const uint8_t rp2040_bootrom[] = { - 0x00, 0x20, 0x04, 0x20, 0x41, 0x00, 0x00, 0x00, - 0x97, 0x00, 0x00, 0x00, 0x97, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x1f, 0x4c, 0x20, 0x68, 0x00, 0x28, 0x05, 0xd1, - 0x1e, 0x48, 0x1f, 0x49, 0x01, 0x60, 0x1f, 0x48, - 0x01, 0x68, 0x08, 0x47, 0x1e, 0x4d, 0x00, 0x26, - 0x00, 0xf0, 0x1e, 0xf8, 0x03, 0x2e, 0x07, 0xd2, - 0xb1, 0x00, 0x6a, 0x58, 0x90, 0x42, 0x0c, 0xd0, - 0x00, 0x26, 0x00, 0xf0, 0x1c, 0xf8, 0xf3, 0xe7, - 0x03, 0x2e, 0x01, 0xd1, 0x07, 0x46, 0x04, 0xe0, - 0x04, 0x2e, 0x01, 0xd1, 0x03, 0x46, 0x00, 0xe0, - 0x05, 0x46, 0x00, 0xf0, 0x10, 0xf8, 0x01, 0x36, - 0x06, 0x2e, 0xe5, 0xd1, 0x0d, 0x49, 0x0f, 0x60, - 0x83, 0xf3, 0x08, 0x88, 0x28, 0x47, 0xfe, 0xe7, - 0x09, 0x4c, 0x20, 0x6d, 0x01, 0x21, 0x08, 0x42, - 0xfb, 0xd0, 0xa0, 0x6d, 0x70, 0x47, 0x06, 0x4c, - 0x21, 0x6d, 0x02, 0x22, 0x11, 0x42, 0xfb, 0xd0, - 0x60, 0x65, 0x70, 0x47, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0xd0, 0x08, 0xed, 0x00, 0xe0, - 0x00, 0x00, 0x00, 0x10, 0x04, 0x00, 0x00, 0x10, - 0xb4, 0x00, 0x00, 0x00, + 0x00, 0x20, 0x04, 0x20, /* initial SP: 0x20042000 */ + 0x41, 0x00, 0x00, 0x00, /* reset handler: 0x00000041 */ + 0xb3, 0x00, 0x00, 0x00, /* NMI handler: 0x000000b3 */ + 0xb3, 0x00, 0x00, 0x00, /* HardFault handler: 0x000000b3 */ + 0xb3, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* SVC handler: 0x000000b3 */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* PendSV handler: 0x000000b3 */ + 0xb3, 0x00, 0x00, 0x00, /* SysTick handler: 0x000000b3 */ + 0x26, 0x4c, /* ldr r4, [pc, #152] ; SIO_BASE */ + 0x20, 0x68, /* ldr r0, [r4] ; SIO_CPUID */ + 0x00, 0x28, /* cmp r0, #0 */ + 0x13, 0xd1, /* bne core1 path */ + 0x25, 0x48, /* ldr r0, [pc, #148] ; 0x10000000 */ + 0x26, 0x49, /* ldr r1, [pc, #152] ; 0x20041f00 */ + 0x40, 0x22, /* movs r2, #64 */ + 0x03, 0x68, /* ldr r3, [r0] */ + 0x0b, 0x60, /* str r3, [r1] */ + 0x04, 0x30, /* adds r0, #4 */ + 0x04, 0x31, /* adds r1, #4 */ + 0x01, 0x3a, /* subs r2, #1 */ + 0xf9, 0xd1, /* bne copy loop */ + 0x23, 0x4b, /* ldr r3, [pc, #140] ; launch entry */ + 0x9e, 0x46, /* mov lr, r3 */ + 0x23, 0x48, /* ldr r0, [pc, #140] ; 0x20041f01 */ + 0x00, 0x47, /* bx r0 */ + 0x23, 0x48, /* ldr r0, [pc, #140] ; 0x10000100 */ + 0x23, 0x49, /* ldr r1, [pc, #140] ; VTOR */ + 0x08, 0x60, /* str r0, [r1] */ + 0x06, 0xc8, /* ldm r0!, {r1, r2} */ + 0x81, 0xf3, 0x08, 0x88, /* msr msp, r1 */ + 0x10, 0x47, /* bx r2 */ + 0x21, 0x4d, /* ldr r5, [pc, #132] ; sequence */ + 0x00, 0x26, /* movs r6, #0 */ + 0x00, 0xf0, 0x1e, 0xf8, /* bl fifo_pop */ + 0x03, 0x2e, /* cmp r6, #3 */ + 0x07, 0xd2, /* bhs echo */ + 0xb1, 0x00, /* lsls r1, r6, #2 */ + 0x6a, 0x58, /* ldr r2, [r5, r1] */ + 0x90, 0x42, /* cmp r0, r2 */ + 0x0c, 0xd0, /* beq echo */ + 0x00, 0x26, /* movs r6, #0 */ + 0x00, 0xf0, 0x1c, 0xf8, /* bl fifo_push */ + 0xf3, 0xe7, /* b core1 loop */ + 0x03, 0x2e, /* cmp r6, #3 */ + 0x01, 0xd1, /* bne maybe SP */ + 0x07, 0x46, /* mov r7, r0 */ + 0x04, 0xe0, /* b echo */ + 0x04, 0x2e, /* cmp r6, #4 */ + 0x01, 0xd1, /* bne save PC */ + 0x03, 0x46, /* mov r3, r0 */ + 0x00, 0xe0, /* b echo */ + 0x05, 0x46, /* mov r5, r0 */ + 0x00, 0xf0, 0x10, 0xf8, /* bl fifo_push */ + 0x01, 0x36, /* adds r6, #1 */ + 0x06, 0x2e, /* cmp r6, #6 */ + 0xe5, 0xd1, /* bne core1 loop */ + 0x12, 0x49, /* ldr r1, [pc, #72] ; VTOR */ + 0x0f, 0x60, /* str r7, [r1] */ + 0x83, 0xf3, 0x08, 0x88, /* msr msp, r3 */ + 0x28, 0x47, /* bx r5 */ + 0xfe, 0xe7, /* hang */ + 0x09, 0x4c, /* ldr r4, [pc, #36] ; SIO_BASE */ + 0x20, 0x6d, /* ldr r0, [r4, #0x50] */ + 0x01, 0x21, /* movs r1, #1 */ + 0x08, 0x42, /* tst r0, r1 */ + 0xfb, 0xd0, /* beq fifo_pop */ + 0xa0, 0x6d, /* ldr r0, [r4, #0x58] */ + 0x70, 0x47, /* bx lr */ + 0x06, 0x4c, /* ldr r4, [pc, #24] ; SIO_BASE */ + 0x21, 0x6d, /* ldr r1, [r4, #0x50] */ + 0x02, 0x22, /* movs r2, #2 */ + 0x11, 0x42, /* tst r1, r2 */ + 0xfb, 0xd0, /* beq fifo_push */ + 0x60, 0x65, /* str r0, [r4, #0x54] */ + 0x70, 0x47, /* bx lr */ + 0x00, 0x00, 0x00, 0x00, /* core1 sequence[0] */ + 0x00, 0x00, 0x00, 0x00, /* core1 sequence[1] */ + 0x01, 0x00, 0x00, 0x00, /* core1 sequence[2] */ + 0x00, 0x00, 0x00, 0xd0, /* SIO_BASE */ + 0x00, 0x00, 0x00, 0x10, /* boot2 source: 0x10000000 */ + 0x00, 0x1f, 0x04, 0x20, /* boot2 SRAM copy: 0x20041f00 */ + 0x63, 0x00, 0x00, 0x00, /* post-boot2 launch entry: 0x00000063 */ + 0x01, 0x1f, 0x04, 0x20, /* boot2 SRAM entry: 0x20041f01 */ + 0x00, 0x01, 0x00, 0x10, /* application vectors: 0x10000100 */ + 0x08, 0xed, 0x00, 0xe0, /* VTOR: 0xe000ed08 */ + 0xd0, 0x00, 0x00, 0x00, /* core1 sequence table */ }; static const struct { @@ -293,6 +366,8 @@ static void rp2040_soc_init(Object *obj) "chardev"); } + object_initialize_child(obj, "xip", &s->xip, TYPE_RP2040_XIP); + object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS); object_initialize_child(obj, "iobank0", &s->iobank0, TYPE_RP2040_IOBANK0); @@ -439,6 +514,21 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp) return; } + if (!sysbus_realize(SYS_BUS_DEVICE(&s->xip), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->xip), 0, RP2040_XIP_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->xip), 1, RP2040_XIP_CTRL_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->xip), 2, RP2040_XIP_SSI_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->xip), 3, RP2040_XIP_AUX_BASE); + memory_region_add_subregion(get_system_memory(), RP2040_XIP_NOALLOC_BASE, + &s->xip.xip_noalloc); + memory_region_add_subregion(get_system_memory(), RP2040_XIP_NOCACHE_BASE, + &s->xip.xip_nocache); + memory_region_add_subregion(get_system_memory(), + RP2040_XIP_NOCACHE_NOALLOC_BASE, + &s->xip.xip_nocache_noalloc); + for (i = 0; i < ARRAY_SIZE(rp2040_unimplemented); i++) { create_unimplemented_device(rp2040_unimplemented[i].name, rp2040_unimplemented[i].base, @@ -541,6 +631,12 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->sio), 1, s->cpu_irq[RP2040_PROC1][RP2040_SIO_IRQ_PROC1]); + object_property_set_link(OBJECT(&s->ioqspi), "xip", OBJECT(&s->xip), + &err); + if (err) { + error_propagate(errp, err); + return; + } if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) { return; } diff --git a/hw/misc/rp2040_ioqspi.c b/hw/misc/rp2040_ioqspi.c index eafd5e9eef..8356b7aea0 100644 --- a/hw/misc/rp2040_ioqspi.c +++ b/hw/misc/rp2040_ioqspi.c @@ -5,8 +5,10 @@ */ #include "qemu/osdep.h" +#include "hw/core/qdev-properties.h" #include "hw/misc/rp2040_nyi.h" #include "hw/misc/rp2040_ioqspi.h" +#include "hw/ssi/rp2040_xip.h" #include "migration/vmstate.h" #include "qemu/log.h" #include "qemu/module.h" @@ -24,6 +26,10 @@ #define IOQSPI_CTRL_RESET 0x1f #define IOQSPI_CTRL_RW_MASK 0x33333f +#define IOQSPI_CTRL_OUTOVER_MASK 0x300 +#define IOQSPI_CTRL_OUTOVER_LOW 0x200 +#define IOQSPI_CTRL_OUTOVER_HIGH 0x300 +#define IOQSPI_SS_INDEX 1 #define IOQSPI_INTR_EDGE_MASK 0x00cccccc #define IOQSPI_IRQ_MASK 0x00ffffff @@ -68,6 +74,22 @@ static uint32_t rp2040_ioqspi_ints(uint32_t intr, uint32_t inte, return (intr & inte) | intf; } +static void rp2040_ioqspi_update_ss(RP2040IoQspiState *s) +{ + uint32_t outover; + + if (!s->xip) { + return; + } + + outover = s->ctrl[IOQSPI_SS_INDEX] & IOQSPI_CTRL_OUTOVER_MASK; + if (outover == IOQSPI_CTRL_OUTOVER_LOW) { + rp2040_xip_qspi_cs(s->xip, false); + } else if (outover == IOQSPI_CTRL_OUTOVER_HIGH) { + rp2040_xip_qspi_cs(s->xip, true); + } +} + static uint64_t rp2040_ioqspi_read(void *opaque, hwaddr addr, unsigned size) { RP2040IoQspiState *s = opaque; @@ -139,6 +161,9 @@ static void rp2040_ioqspi_write(void *opaque, hwaddr addr, s->ctrl[index] = rp2040_ioqspi_apply_alias(s->ctrl[index], value, alias) & IOQSPI_CTRL_RW_MASK; + if (index == IOQSPI_SS_INDEX) { + rp2040_ioqspi_update_ss(s); + } } else { switch (offset) { case IOQSPI_INTR: @@ -187,6 +212,11 @@ static void rp2040_ioqspi_write(void *opaque, hwaddr addr, } } +static const Property rp2040_ioqspi_properties[] = { + DEFINE_PROP_LINK("xip", RP2040IoQspiState, xip, TYPE_RP2040_XIP, + RP2040XipState *), +}; + static const MemoryRegionOps rp2040_ioqspi_ops = { .read = rp2040_ioqspi_read, .write = rp2040_ioqspi_write, @@ -245,6 +275,7 @@ static void rp2040_ioqspi_class_init(ObjectClass *klass, const void *data) DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, rp2040_ioqspi_reset); + device_class_set_props(dc, rp2040_ioqspi_properties); dc->vmsd = &rp2040_ioqspi_vmstate; } diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig index 1bd56463c1..c8d0787dee 100644 --- a/hw/ssi/Kconfig +++ b/hw/ssi/Kconfig @@ -9,6 +9,9 @@ config SIFIVE_SPI config SSI bool +config RP2040_XIP + bool + config XILINX_SPI bool select SSI diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build index 6afb1ea200..dc93ed5def 100644 --- a/hw/ssi/meson.build +++ b/hw/ssi/meson.build @@ -13,3 +13,4 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c')) system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c')) system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c')) system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c')) +system_ss.add(when: 'CONFIG_RP2040_XIP', if_true: files('rp2040_xip.c')) diff --git a/hw/ssi/rp2040_xip.c b/hw/ssi/rp2040_xip.c new file mode 100644 index 0000000000..d517df41ff --- /dev/null +++ b/hw/ssi/rp2040_xip.c @@ -0,0 +1,1348 @@ +/* + * RP2040 XIP/SSI flash controller emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "qapi/error.h" +#include "elf.h" +#include "exec/memattrs.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/loader.h" +#include "hw/misc/rp2040_nyi.h" +#include "hw/ssi/rp2040_xip.h" +#include "qemu/log.h" +#include "system/address-spaces.h" +#include "trace.h" + +#define RP2040_XIP_CTRL_EN 0x1 +#define RP2040_XIP_CTRL_ERR_BADWRITE 0x2 +#define RP2040_XIP_STAT_FLUSH_READY 0x1 +#define RP2040_XIP_STAT_FIFO_EMPTY 0x2 +#define RP2040_XIP_STAT_FIFO_FULL 0x4 +#define RP2040_XIP_FLASH_BASE 0x10000000 +#define RP2040_XIP_CTRL 0x00 +#define RP2040_XIP_FLUSH 0x04 +#define RP2040_XIP_STAT 0x08 +#define RP2040_XIP_CTR_HIT 0x0c +#define RP2040_XIP_CTR_ACC 0x10 +#define RP2040_XIP_STREAM_ADDR 0x14 +#define RP2040_XIP_STREAM_CTR 0x18 +#define RP2040_XIP_STREAM_FIFO 0x1c +#define RP2040_XIP_STREAM_CTR_MASK 0x003fffff + +#define RP2040_SSI_CTRLR0 0x00 +#define RP2040_SSI_CTRLR1 0x04 +#define RP2040_SSI_SSIENR 0x08 +#define RP2040_SSI_SER 0x10 +#define RP2040_SSI_BAUDR 0x14 +#define RP2040_SSI_TXFTLR 0x18 +#define RP2040_SSI_RXFTLR 0x1c +#define RP2040_SSI_TXFLR 0x20 +#define RP2040_SSI_RXFLR 0x24 +#define RP2040_SSI_SR 0x28 +#define RP2040_SSI_IMR 0x2c +#define RP2040_SSI_ISR 0x30 +#define RP2040_SSI_RISR 0x34 +#define RP2040_SSI_TXOICR 0x38 +#define RP2040_SSI_RXOICR 0x3c +#define RP2040_SSI_RXUICR 0x40 +#define RP2040_SSI_MSTICR 0x44 +#define RP2040_SSI_ICR 0x48 +#define RP2040_SSI_DMACR 0x4c +#define RP2040_SSI_DMATDLR 0x50 +#define RP2040_SSI_DMARDLR 0x54 +#define RP2040_SSI_IDR 0x58 +#define RP2040_SSI_VERSION_ID 0x5c +#define RP2040_SSI_DR0 0x60 +#define RP2040_SSI_DR_END 0xec +#define RP2040_SSI_RX_SAMPLE_DLY 0xf0 +#define RP2040_SSI_SPI_CTRLR0 0xf4 + +#define RP2040_SSI_SR_BUSY 0x01 +#define RP2040_SSI_SR_TFNF 0x02 +#define RP2040_SSI_SR_TFE 0x04 +#define RP2040_SSI_SR_RFNE 0x08 +#define RP2040_SSI_SR_RFF 0x10 + +#define FLASH_CMD_READ 0x03 +#define FLASH_CMD_WRITE_STATUS 0x01 +#define FLASH_CMD_PAGE_PROGRAM 0x02 +#define FLASH_CMD_READ_STATUS 0x05 +#define FLASH_CMD_READ_STATUS2 0x35 +#define FLASH_CMD_WRITE_ENABLE 0x06 +#define FLASH_CMD_READ_UNIQUE_ID 0x4b +#define FLASH_CMD_SECTOR_ERASE 0x20 +#define FLASH_CMD_QUAD_IO_READ 0xeb +#define FLASH_CMD_CONTINUATION_READ 0xa0 + +#define RP2040_SSI_DMACR_TDMAE BIT(1) +#define RP2040_SSI_DMACR_RDMAE BIT(0) + +#define FLASH_UNIQUE_ID_SIZE 8 +#define FLASH_UNIQUE_ID_DUMMY_BYTES 4 +#define FLASH_UID_DEFAULT 0x3eb8a7493fcc0608ull + +#define FLASH_STATUS_WIP 0x01 +#define FLASH_STATUS_WEL 0x02 +#define FLASH_PAGE_SIZE 256 +#define FLASH_SECTOR_SIZE 4096 + +#define ATOMIC_ALIAS_MASK 0x3000 +#define ATOMIC_XOR 0x1000 +#define ATOMIC_SET 0x2000 +#define ATOMIC_CLR 0x3000 + +#define RP2040_BOOT2_SIZE 256 +#define RP2040_BOOT2_CRC_SIZE 252 +#define RP2040_BOOT2_CRC_INIT 0xffffffff +#define RP2040_BOOT2_CRC_POLY 0x04c11db7 +#define RP2040_SRAM_BASE 0x20000000 +#define RP2040_SRAM_END 0x20042000 + +static uint32_t rp2040_xip_apply_alias(uint32_t old, uint32_t value, + hwaddr alias) +{ + switch (alias) { + case ATOMIC_XOR: + return old ^ value; + case ATOMIC_SET: + return old | value; + case ATOMIC_CLR: + return old & ~value; + default: + return value; + } +} + +static void rp2040_xip_rx_clear(RP2040XipState *s) +{ + s->rx_len = 0; + s->rx_pos = 0; +} + +static void rp2040_xip_rx_push(RP2040XipState *s, uint8_t value) +{ + if (s->rx_len == ARRAY_SIZE(s->rx) && s->rx_pos > 0) { + memmove(s->rx, s->rx + s->rx_pos, s->rx_len - s->rx_pos); + s->rx_len -= s->rx_pos; + s->rx_pos = 0; + } + + if (s->rx_len < ARRAY_SIZE(s->rx)) { + s->rx[s->rx_len++] = value; + qemu_irq_pulse(s->dreq_rx); + } +} + +static bool rp2040_xip_rx_compact(RP2040XipState *s) +{ + if (s->rx_len == s->rx_pos) { + rp2040_xip_rx_clear(s); + return true; + } + if (s->rx_pos > 0) { + memmove(s->rx, s->rx + s->rx_pos, s->rx_len - s->rx_pos); + s->rx_len -= s->rx_pos; + s->rx_pos = 0; + return true; + } + return false; +} + +static bool rp2040_xip_flash_word(RP2040XipState *s, uint32_t addr, + uint32_t *value) +{ + if (s->flash_size < sizeof(uint32_t) || + addr > s->flash_size - sizeof(uint32_t)) { + *value = 0xffffffff; + return false; + } + + *value = ldl_le_p(s->storage + addr); + return true; +} + +static void rp2040_xip_rx_push_ssi_word(RP2040XipState *s, uint32_t value) +{ + if (ARRAY_SIZE(s->rx) - (s->rx_len - s->rx_pos) < sizeof(uint32_t)) { + rp2040_xip_rx_compact(s); + } + if (ARRAY_SIZE(s->rx) - s->rx_len < sizeof(uint32_t)) { + return; + } + + /* + * Non-XIP 32-bit SSI reads deliver the serial flash byte stream in the + * opposite byte order expected by the RP2040 system bus; SDK users enable + * DMA BSWAP when copying words from SSI_DR0. + */ + s->rx[s->rx_len++] = extract32(value, 24, 8); + s->rx[s->rx_len++] = extract32(value, 16, 8); + s->rx[s->rx_len++] = extract32(value, 8, 8); + s->rx[s->rx_len++] = extract32(value, 0, 8); + qemu_irq_pulse(s->dreq_rx); +} + +static void rp2040_xip_ssi_bulk_fill(RP2040XipState *s) +{ + while (s->ssi_bulk_remaining > 0 && + ARRAY_SIZE(s->rx) - (s->rx_len - s->rx_pos) >= + sizeof(uint32_t)) { + uint32_t value; + + rp2040_xip_rx_compact(s); + rp2040_xip_flash_word(s, s->ssi_bulk_addr, &value); + rp2040_xip_rx_push_ssi_word(s, value); + s->ssi_bulk_addr += sizeof(uint32_t); + s->ssi_bulk_remaining--; + } +} + +static void rp2040_xip_ssi_bulk_start(RP2040XipState *s, uint32_t addr, + uint32_t words) +{ + rp2040_xip_rx_clear(s); + s->ssi_bulk_addr = addr; + s->ssi_bulk_remaining = words; + rp2040_xip_ssi_bulk_fill(s); +} + +static void rp2040_xip_update_stream_dreq(RP2040XipState *s) +{ + qemu_set_irq(s->dreq_stream, s->stream_fifo_len > s->stream_fifo_pos); +} + +static void rp2040_xip_stream_clear(RP2040XipState *s) +{ + s->stream_fifo_len = 0; + s->stream_fifo_pos = 0; + rp2040_xip_update_stream_dreq(s); +} + +static uint32_t rp2040_xip_stream_word(RP2040XipState *s) +{ + uint32_t off; + + if (s->flash_size < sizeof(uint32_t)) { + return 0xffffffff; + } + if (s->stream_addr < RP2040_XIP_FLASH_BASE || + s->stream_addr - RP2040_XIP_FLASH_BASE > + s->flash_size - sizeof(uint32_t)) { + return 0xffffffff; + } + + off = s->stream_addr - RP2040_XIP_FLASH_BASE; + return ldl_le_p(s->storage + off); +} + +static void rp2040_xip_stream_fill(RP2040XipState *s) +{ + if (s->stream_fifo_pos == s->stream_fifo_len) { + s->stream_fifo_pos = 0; + s->stream_fifo_len = 0; + } + + while (s->stream_ctr > 0 && + s->stream_fifo_len < ARRAY_SIZE(s->stream_fifo)) { + s->stream_fifo[s->stream_fifo_len++] = rp2040_xip_stream_word(s); + s->stream_addr += 4; + s->stream_ctr--; + } + + rp2040_xip_update_stream_dreq(s); +} + +static uint32_t rp2040_xip_stream_pop(RP2040XipState *s) +{ + uint32_t value = 0; + + rp2040_xip_stream_fill(s); + if (s->stream_fifo_pos < s->stream_fifo_len) { + value = s->stream_fifo[s->stream_fifo_pos++]; + } + rp2040_xip_stream_fill(s); + return value; +} + +static uint32_t rp2040_xip_stat(RP2040XipState *s) +{ + uint32_t stat = RP2040_XIP_STAT_FLUSH_READY; + + rp2040_xip_stream_fill(s); + if (s->stream_fifo_pos == s->stream_fifo_len) { + stat |= RP2040_XIP_STAT_FIFO_EMPTY; + } + if (s->stream_fifo_len - s->stream_fifo_pos == + ARRAY_SIZE(s->stream_fifo)) { + stat |= RP2040_XIP_STAT_FIFO_FULL; + } + + return stat; +} + +static uint8_t rp2040_xip_status(RP2040XipState *s) +{ + uint8_t status = 0; + + if (s->busy) { + status |= FLASH_STATUS_WIP; + } + if (s->write_enable) { + status |= FLASH_STATUS_WEL; + } + + return status; +} + +static uint32_t rp2040_xip_tx_addr(RP2040XipState *s) +{ + return (uint32_t)s->tx[1] << 16 | s->tx[2] << 8 | s->tx[3]; +} + +static uint32_t rp2040_xip_quad_io_addr(RP2040XipState *s) +{ + return (uint32_t)s->tx[1] << 16 | s->tx[2] << 8 | s->tx[3]; +} + +static uint8_t rp2040_xip_flash_uid_byte(RP2040XipState *s, unsigned index) +{ + return extract64(s->flash_uid, (FLASH_UNIQUE_ID_SIZE - 1 - index) * 8, 8); +} + +static void rp2040_xip_finish_busy(RP2040XipState *s) +{ + s->busy = false; +} + +static void rp2040_xip_reset_tx(RP2040XipState *s) +{ + s->tx_len = 0; + s->tx_unsupported_logged = false; + s->ssi_bulk_remaining = 0; +} + +static bool rp2040_xip_writeback(RP2040XipState *s, Error **errp) +{ + g_autoptr(GError) gerr = NULL; + + if (!s->flash_file || !*s->flash_file) { + return true; + } + + if (!g_file_set_contents(s->flash_file, (const char *)s->storage, + s->flash_size, &gerr)) { + error_setg(errp, "could not write flash file '%s': %s", + s->flash_file, gerr->message); + return false; + } + + return true; +} + +static void rp2040_xip_writeback_or_warn(RP2040XipState *s) +{ + Error *local_err = NULL; + + if (!rp2040_xip_writeback(s, &local_err)) { + warn_report_err(local_err); + } +} + +static uint32_t rp2040_xip_boot2_crc(const uint8_t *data) +{ + uint32_t crc = RP2040_BOOT2_CRC_INIT; + int i; + int bit; + + for (i = 0; i < RP2040_BOOT2_CRC_SIZE; i++) { + crc ^= (uint32_t)data[i] << 24; + for (bit = 0; bit < 8; bit++) { + if (crc & BIT(31)) { + crc = (crc << 1) ^ RP2040_BOOT2_CRC_POLY; + } else { + crc <<= 1; + } + } + } + + return crc; +} + +static bool rp2040_xip_boot2_empty(const uint8_t *data) +{ + int i; + + for (i = 0; i < RP2040_BOOT2_SIZE; i++) { + if (data[i] != 0xff) { + return false; + } + } + + return true; +} + +static bool rp2040_xip_boot2_looks_like_vector_table(const uint8_t *data) +{ + uint32_t initial_sp = ldl_le_p(data); + uint32_t reset = ldl_le_p(data + 4); + + return initial_sp >= RP2040_SRAM_BASE && + initial_sp <= RP2040_SRAM_END && + reset >= RP2040_XIP_FLASH_BASE && + reset < RP2040_XIP_FLASH_BASE + MiB && + (reset & 1); +} + +static bool rp2040_xip_fixup_boot2(RP2040XipState *s, const char *filename, + Error **errp) +{ + uint32_t crc; + + if (s->flash_size < RP2040_BOOT2_SIZE) { + error_setg(errp, "flash is too small for an RP2040 boot2 block"); + return false; + } + + if (rp2040_xip_boot2_empty(s->storage)) { + error_setg(errp, "image '%s' does not contain RP2040 boot2 at " + "0x%08x", filename, RP2040_XIP_FLASH_BASE); + return false; + } + + if (rp2040_xip_boot2_looks_like_vector_table(s->storage)) { + error_setg(errp, "image '%s' starts with an application vector table, " + "not RP2040 boot2; use a Pico SDK UF2 or an ELF with " + "boot2 linked at 0x%08x", filename, RP2040_XIP_FLASH_BASE); + return false; + } + + crc = rp2040_xip_boot2_crc(s->storage); + stl_le_p(s->storage + RP2040_BOOT2_CRC_SIZE, crc); + + return true; +} + +static void rp2040_xip_program(RP2040XipState *s) +{ + uint32_t addr; + uint32_t page_end; + unsigned data_len; + unsigned i; + + trace_rp2040_xip_program(s->tx_len >= 4 ? rp2040_xip_tx_addr(s) : 0, + s->tx_len > 4 ? s->tx_len - 4 : 0, + s->write_enable); + + if (!s->write_enable) { + return; + } + + s->write_enable = false; + + if (s->tx_len <= 4) { + return; + } + + addr = rp2040_xip_tx_addr(s); + if (addr >= s->flash_size) { + return; + } + + page_end = ROUND_UP(addr + 1, FLASH_PAGE_SIZE); + data_len = MIN(s->tx_len - 4, page_end - addr); + data_len = MIN(data_len, s->flash_size - addr); + + for (i = 0; i < data_len; i++) { + s->storage[addr + i] &= s->tx[4 + i]; + } + + rp2040_xip_writeback_or_warn(s); + s->busy = true; +} + +static void rp2040_xip_erase(RP2040XipState *s) +{ + uint32_t addr; + uint32_t base; + + trace_rp2040_xip_erase(s->tx_len >= 4 ? rp2040_xip_tx_addr(s) : 0, + s->write_enable); + + if (!s->write_enable) { + return; + } + + s->write_enable = false; + + if (s->tx_len < 4) { + return; + } + + addr = rp2040_xip_tx_addr(s); + base = QEMU_ALIGN_DOWN(addr, FLASH_SECTOR_SIZE); + if (base >= s->flash_size) { + return; + } + + memset(&s->storage[base], 0xff, MIN(FLASH_SECTOR_SIZE, + s->flash_size - base)); + rp2040_xip_writeback_or_warn(s); + s->busy = true; +} + +static void rp2040_xip_finish_command(RP2040XipState *s) +{ + if (s->tx_len == 0) { + return; + } + + trace_rp2040_xip_finish_command(s->tx[0], s->tx_len); + + switch (s->tx[0]) { + case FLASH_CMD_WRITE_STATUS: + s->write_enable = false; + break; + case FLASH_CMD_PAGE_PROGRAM: + rp2040_xip_program(s); + break; + case FLASH_CMD_SECTOR_ERASE: + rp2040_xip_erase(s); + break; + default: + break; + } + + rp2040_xip_reset_tx(s); +} + +static void rp2040_xip_dr_write(RP2040XipState *s, uint8_t value) +{ + uint32_t addr; + + if (s->tx_len < ARRAY_SIZE(s->tx)) { + s->tx[s->tx_len++] = value; + } + + switch (s->tx[0]) { + case FLASH_CMD_WRITE_STATUS: + s->write_enable = false; + rp2040_xip_rx_push(s, 0); + break; + case FLASH_CMD_PAGE_PROGRAM: + case FLASH_CMD_SECTOR_ERASE: + rp2040_xip_rx_push(s, 0); + break; + case FLASH_CMD_WRITE_ENABLE: + s->write_enable = true; + rp2040_xip_rx_push(s, 0); + rp2040_xip_reset_tx(s); + break; + case FLASH_CMD_READ_STATUS: + rp2040_xip_rx_push(s, rp2040_xip_status(s)); + rp2040_xip_finish_busy(s); + rp2040_xip_reset_tx(s); + break; + case FLASH_CMD_READ_STATUS2: + rp2040_xip_rx_push(s, 0); + rp2040_xip_finish_busy(s); + rp2040_xip_reset_tx(s); + break; + case FLASH_CMD_READ_UNIQUE_ID: + if (s->tx_len <= 1 + FLASH_UNIQUE_ID_DUMMY_BYTES) { + rp2040_xip_rx_push(s, 0); + } else { + unsigned index = s->tx_len - 2 - FLASH_UNIQUE_ID_DUMMY_BYTES; + + rp2040_xip_rx_push(s, index < FLASH_UNIQUE_ID_SIZE ? + rp2040_xip_flash_uid_byte(s, index) : 0); + } + break; + case FLASH_CMD_READ: + if (s->tx_len <= 4) { + rp2040_xip_rx_push(s, 0); + } else { + addr = rp2040_xip_tx_addr(s) + s->tx_len - 5; + rp2040_xip_rx_push(s, addr < s->flash_size ? + s->storage[addr] : 0xff); + } + break; + case FLASH_CMD_QUAD_IO_READ: + /* + * Minimal 0xeb fast-read support for the RP2040 mask ROM path. + * The ROM clocks opcode, 24-bit address and mode/dummy bytes before + * consuming data. We do not model bus width or wait-cycle timing here. + */ + if (s->tx_len <= 5) { + rp2040_xip_rx_push(s, 0); + } else { + addr = rp2040_xip_quad_io_addr(s) + s->tx_len - 6; + rp2040_xip_rx_push(s, addr < s->flash_size ? + s->storage[addr] : 0xff); + } + break; + case 0x00: + rp2040_xip_rx_push(s, 0); + break; + default: + /* + * The RP2040 boot ROM performs small full-duplex SSI transactions + * while probing the flash path. Even for commands we do not model yet, + * a transmitted byte clocks one receive byte back from the bus. + */ + if (!s->tx_unsupported_logged) { + g_autofree char *detail = g_strdup_printf("opcode 0x%02x", + s->tx[0]); + + rp2040_log_nyi("xip.ssi", "flash command", detail); + s->tx_unsupported_logged = true; + } + rp2040_xip_rx_push(s, 0); + break; + } +} + +MemTxResult rp2040_xip_read_data(RP2040XipState *s, hwaddr addr, + uint64_t *data, unsigned size) +{ + uint64_t value = 0; + unsigned i; + + if (s->busy || addr + size > s->flash_size) { + return MEMTX_ERROR; + } + + for (i = 0; i < size; i++) { + hwaddr cur = addr + i; + uint8_t byte = s->storage[cur]; + + value |= (uint64_t)byte << (i * 8); + } + *data = value; + return MEMTX_OK; +} + +static MemTxResult rp2040_xip_read(void *opaque, hwaddr addr, uint64_t *data, + unsigned size, MemTxAttrs attrs) +{ + return rp2040_xip_read_data(opaque, addr, data, size); +} + +static MemTxResult rp2040_xip_alias_read(void *opaque, hwaddr addr, + uint64_t *data, unsigned size, + MemTxAttrs attrs) +{ + return rp2040_xip_read_data(opaque, addr, data, size); +} + +static MemTxResult rp2040_xip_write(void *opaque, hwaddr addr, uint64_t data, + unsigned size, MemTxAttrs attrs) +{ + RP2040XipState *s = opaque; + unsigned i; + + if (!s->xip_writable) { + return MEMTX_ERROR; + } + if (addr + size > s->flash_size) { + return MEMTX_ERROR; + } + + for (i = 0; i < size; i++) { + s->storage[addr + i] = extract64(data, i * 8, 8); + } + return MEMTX_OK; +} + +static uint64_t rp2040_xip_ctrl_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040XipState *s = opaque; + hwaddr offset = addr & 0xfff; + uint64_t value; + + switch (offset) { + case RP2040_XIP_CTRL: + value = s->xip_ctrl; + break; + case RP2040_XIP_FLUSH: + case RP2040_XIP_CTR_HIT: + case RP2040_XIP_CTR_ACC: + value = 0; + break; + case RP2040_XIP_STAT: + value = rp2040_xip_stat(s); + break; + case RP2040_XIP_STREAM_ADDR: + value = s->stream_addr; + break; + case RP2040_XIP_STREAM_CTR: + value = s->stream_ctr; + break; + case RP2040_XIP_STREAM_FIFO: + value = rp2040_xip_stream_pop(s); + break; + default: + value = 0; + qemu_log_mask(LOG_UNIMP, "rp2040.xip.ctrl: unimplemented read " + "(size %d, addr 0x%08" HWADDR_PRIx + ", offset 0x%04" HWADDR_PRIx + ") -> 0x%0*" PRIx64 "\n", + size, RP2040_XIP_CTRL_BASE + addr, offset, + size << 1, value); + break; + } + + return value; +} + +static void rp2040_xip_ctrl_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + RP2040XipState *s = opaque; + hwaddr alias = addr & ATOMIC_ALIAS_MASK; + hwaddr offset = addr & 0xfff; + uint32_t new_value; + + switch (offset) { + case RP2040_XIP_CTRL: + new_value = rp2040_xip_apply_alias(s->xip_ctrl, value, alias); + s->xip_ctrl = new_value & (RP2040_XIP_CTRL_EN | + RP2040_XIP_CTRL_ERR_BADWRITE); + break; + case RP2040_XIP_FLUSH: + rp2040_xip_stream_clear(s); + break; + case RP2040_XIP_CTR_HIT: + case RP2040_XIP_CTR_ACC: + break; + case RP2040_XIP_STREAM_ADDR: + s->stream_addr = value & ~3u; + break; + case RP2040_XIP_STREAM_CTR: + s->stream_ctr = value & RP2040_XIP_STREAM_CTR_MASK; + if (s->stream_ctr == 0) { + rp2040_xip_stream_clear(s); + } else { + rp2040_xip_stream_fill(s); + } + break; + default: + qemu_log_mask(LOG_UNIMP, "rp2040.xip.ctrl: unimplemented write " + "(size %d, addr 0x%08" HWADDR_PRIx + ", offset 0x%04" HWADDR_PRIx + ", value 0x%0*" PRIx64 ")\n", + size, RP2040_XIP_CTRL_BASE + addr, offset, + size << 1, value); + break; + } +} + +static uint64_t rp2040_xip_aux_read(void *opaque, hwaddr addr, unsigned size) +{ + return rp2040_xip_stream_pop(opaque); +} + +static void rp2040_xip_aux_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + rp2040_log_nyi("xip.aux", "write", + "XIP auxiliary stream FIFO is read-only"); +} + +static uint64_t rp2040_xip_ssi_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040XipState *s = opaque; + hwaddr offset = addr & 0xfff; + uint8_t value; + uint32_t risr = s->rx_len > s->rx_pos ? 0 : 1; + uint64_t ret; + + if (offset >= RP2040_SSI_DR0 && offset <= RP2040_SSI_DR_END) { + unsigned i; + + if (s->tx_len > 0 && s->ssi_bulk_remaining == 0) { + if (s->rx_pos < s->rx_len) { + value = s->rx[s->rx_pos++]; + } else { + value = 0; + } + if (s->rx_pos == s->rx_len) { + rp2040_xip_rx_clear(s); + } + return value; + } + + ret = 0; + for (i = 0; i < size; i++) { + if (s->rx_pos == s->rx_len) { + rp2040_xip_rx_clear(s); + rp2040_xip_ssi_bulk_fill(s); + } + if (s->rx_pos < s->rx_len) { + value = s->rx[s->rx_pos++]; + } else { + value = 0; + } + ret |= (uint64_t)value << (i * 8); + } + if (s->rx_pos == s->rx_len) { + rp2040_xip_rx_clear(s); + rp2040_xip_ssi_bulk_fill(s); + } + return ret; + } + + switch (offset) { + case RP2040_SSI_CTRLR0: + ret = s->ctrlr0; + break; + case RP2040_SSI_CTRLR1: + ret = s->ctrlr1; + break; + case RP2040_SSI_SSIENR: + ret = s->ssienr; + break; + case RP2040_SSI_SER: + ret = s->ser; + break; + case RP2040_SSI_BAUDR: + ret = s->baudr; + break; + case RP2040_SSI_TXFTLR: + ret = s->txftlr; + break; + case RP2040_SSI_RXFTLR: + ret = s->rxftlr; + break; + case RP2040_SSI_TXFLR: + ret = 0; + break; + case RP2040_SSI_RXFLR: + ret = s->rx_len - s->rx_pos; + break; + case RP2040_SSI_SR: + ret = RP2040_SSI_SR_TFE | RP2040_SSI_SR_TFNF | + (s->busy ? RP2040_SSI_SR_BUSY : 0) | + (s->rx_len > s->rx_pos ? RP2040_SSI_SR_RFNE : 0) | + (s->rx_len - s->rx_pos == ARRAY_SIZE(s->rx) ? + RP2040_SSI_SR_RFF : 0); + break; + case RP2040_SSI_IMR: + ret = s->imr; + break; + case RP2040_SSI_ISR: + case RP2040_SSI_RISR: + ret = risr; + break; + case RP2040_SSI_TXOICR: + case RP2040_SSI_RXOICR: + case RP2040_SSI_RXUICR: + case RP2040_SSI_MSTICR: + case RP2040_SSI_ICR: + ret = 0; + break; + case RP2040_SSI_DMACR: + ret = s->dmacr; + break; + case RP2040_SSI_DMATDLR: + ret = s->dmatdlr; + break; + case RP2040_SSI_DMARDLR: + ret = s->dmardlr; + break; + case RP2040_SSI_IDR: + ret = 0; + break; + case RP2040_SSI_VERSION_ID: + ret = 0x3430312a; + break; + case RP2040_SSI_RX_SAMPLE_DLY: + ret = s->rx_sample_dly; + break; + case RP2040_SSI_SPI_CTRLR0: + ret = s->spi_ctrlr0; + break; + default: + ret = 0; + qemu_log_mask(LOG_UNIMP, "rp2040.xip.ssi: unimplemented read " + "(size %d, addr 0x%08" HWADDR_PRIx + ", offset 0x%04" HWADDR_PRIx + ") -> 0x%0*" PRIx64 "\n", + size, RP2040_XIP_SSI_BASE + addr, offset, + size << 1, ret); + break; + } + + return ret; +} + +static void rp2040_xip_ssi_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + RP2040XipState *s = opaque; + hwaddr alias = addr & ATOMIC_ALIAS_MASK; + hwaddr offset = addr & 0xfff; + uint32_t old_ssienr = s->ssienr; + uint32_t old_ser = s->ser; + uint32_t new_value; + + if (offset >= RP2040_SSI_DR0 && offset <= RP2040_SSI_DR_END) { + if (s->tx_len < 8 || (s->tx_len & 0x3f) == 0) { + trace_rp2040_xip_dr_write(value, size, s->tx_len, s->ctrlr0, + s->ctrlr1, s->spi_ctrlr0); + } + if (size == 4 && (value & 0xff) == FLASH_CMD_CONTINUATION_READ) { + rp2040_xip_ssi_bulk_start(s, value >> 8, s->ctrlr1 + 1); + return; + } + rp2040_xip_dr_write(s, value & 0xff); + return; + } + + switch (offset) { + case RP2040_SSI_CTRLR0: + s->ctrlr0 = rp2040_xip_apply_alias(s->ctrlr0, value, alias); + trace_rp2040_xip_ctrlr0(s->ctrlr0); + break; + case RP2040_SSI_CTRLR1: + s->ctrlr1 = rp2040_xip_apply_alias(s->ctrlr1, value, alias); + trace_rp2040_xip_ctrlr1(s->ctrlr1); + break; + case RP2040_SSI_SSIENR: + new_value = rp2040_xip_apply_alias(s->ssienr, value, alias); + s->ssienr = new_value & 1; + trace_rp2040_xip_ssienr(old_ssienr, s->ssienr, s->tx_len); + if ((old_ssienr & 1) && !s->ssienr) { + rp2040_xip_finish_command(s); + } + if (!s->ssienr) { + rp2040_xip_rx_clear(s); + rp2040_xip_reset_tx(s); + } + break; + case RP2040_SSI_SER: + new_value = rp2040_xip_apply_alias(s->ser, value, alias); + s->ser = new_value & 1; + trace_rp2040_xip_ser(old_ser, s->ser, s->tx_len); + if ((old_ser & 1) && !s->ser) { + rp2040_xip_finish_command(s); + } + break; + case RP2040_SSI_BAUDR: + new_value = rp2040_xip_apply_alias(s->baudr, value, alias); + s->baudr = new_value & 0xffff; + break; + case RP2040_SSI_TXFTLR: + new_value = rp2040_xip_apply_alias(s->txftlr, value, alias); + s->txftlr = new_value & 0xff; + break; + case RP2040_SSI_RXFTLR: + new_value = rp2040_xip_apply_alias(s->rxftlr, value, alias); + s->rxftlr = new_value & 0xff; + break; + case RP2040_SSI_IMR: + new_value = rp2040_xip_apply_alias(s->imr, value, alias); + s->imr = new_value & 0x3f; + break; + case RP2040_SSI_DMACR: + new_value = rp2040_xip_apply_alias(s->dmacr, value, alias); + s->dmacr = new_value & (RP2040_SSI_DMACR_TDMAE | + RP2040_SSI_DMACR_RDMAE); + break; + case RP2040_SSI_DMATDLR: + s->dmatdlr = rp2040_xip_apply_alias(s->dmatdlr, value, alias) & 0xff; + break; + case RP2040_SSI_DMARDLR: + s->dmardlr = rp2040_xip_apply_alias(s->dmardlr, value, alias) & 0xff; + break; + case RP2040_SSI_RX_SAMPLE_DLY: + s->rx_sample_dly = rp2040_xip_apply_alias(s->rx_sample_dly, value, + alias) & 0xff; + break; + case RP2040_SSI_SPI_CTRLR0: + s->spi_ctrlr0 = rp2040_xip_apply_alias(s->spi_ctrlr0, value, alias); + trace_rp2040_xip_spi_ctrlr0(s->spi_ctrlr0); + break; + default: + qemu_log_mask(LOG_UNIMP, "rp2040.xip.ssi: unimplemented write " + "(size %d, addr 0x%08" HWADDR_PRIx + ", offset 0x%04" HWADDR_PRIx + ", value 0x%0*" PRIx64 ")\n", + size, RP2040_XIP_SSI_BASE + addr, offset, + size << 1, value); + break; + } +} + +static const MemoryRegionOps rp2040_xip_ops = { + .read_with_attrs = rp2040_xip_read, + .write_with_attrs = rp2040_xip_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static const MemoryRegionOps rp2040_xip_alias_ops = { + .read_with_attrs = rp2040_xip_alias_read, + .write_with_attrs = rp2040_xip_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static const MemoryRegionOps rp2040_xip_ctrl_ops = { + .read = rp2040_xip_ctrl_read, + .write = rp2040_xip_ctrl_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static const MemoryRegionOps rp2040_xip_ssi_ops = { + .read = rp2040_xip_ssi_read, + .write = rp2040_xip_ssi_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 4, + .unaligned = true, + }, +}; + +static const MemoryRegionOps rp2040_xip_aux_ops = { + .read = rp2040_xip_aux_read, + .write = rp2040_xip_aux_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +void rp2040_xip_set_writable(RP2040XipState *s, bool writable) +{ + s->xip_writable = writable; +} + +void rp2040_xip_qspi_cs(RP2040XipState *s, bool high) +{ + if (s->qspi_cs_high == high) { + return; + } + + trace_rp2040_xip_qspi_cs(high, s->tx_len); + s->qspi_cs_high = high; + + if (high) { + rp2040_xip_finish_command(s); + } else { + rp2040_xip_rx_clear(s); + rp2040_xip_reset_tx(s); + } +} + +static bool rp2040_xip_load_elf(RP2040XipState *s, const char *filename, + Error **errp) +{ + g_autofree gchar *contents = NULL; + gsize len; + const Elf32_Ehdr *ehdr; + const Elf32_Phdr *phdr; + int i; + + if (!g_file_get_contents(filename, &contents, &len, NULL)) { + error_setg(errp, "could not load flash image '%s'", filename); + return true; + } + + if (len < sizeof(*ehdr)) { + return false; + } + + ehdr = (const Elf32_Ehdr *)contents; + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) != 0) { + return false; + } + if (ehdr->e_ident[EI_CLASS] != ELFCLASS32 || + ehdr->e_ident[EI_DATA] != ELFDATA2LSB || + le16_to_cpu(ehdr->e_machine) != EM_ARM) { + error_setg(errp, "unsupported flash ELF image '%s'", filename); + return true; + } + if (le32_to_cpu(ehdr->e_phoff) > len || + le16_to_cpu(ehdr->e_phentsize) != sizeof(*phdr) || + le16_to_cpu(ehdr->e_phnum) > + (len - le32_to_cpu(ehdr->e_phoff)) / sizeof(*phdr)) { + error_setg(errp, "invalid flash ELF image '%s'", filename); + return true; + } + + phdr = (const Elf32_Phdr *)(contents + le32_to_cpu(ehdr->e_phoff)); + for (i = 0; i < le16_to_cpu(ehdr->e_phnum); i++) { + uint32_t paddr = le32_to_cpu(phdr[i].p_paddr); + uint32_t filesz = le32_to_cpu(phdr[i].p_filesz); + uint32_t memsz = le32_to_cpu(phdr[i].p_memsz); + uint32_t off = le32_to_cpu(phdr[i].p_offset); + uint32_t xip_off; + + if (le32_to_cpu(phdr[i].p_type) != PT_LOAD) { + continue; + } + + if (filesz == 0) { + continue; + } + + if (paddr < RP2040_XIP_FLASH_BASE || + paddr - RP2040_XIP_FLASH_BASE > s->flash_size || + filesz > memsz || + filesz > s->flash_size - (paddr - RP2040_XIP_FLASH_BASE) || + off > len || + filesz > len - off) { + error_setg(errp, "flash ELF segment is outside XIP storage"); + return true; + } + + xip_off = paddr - RP2040_XIP_FLASH_BASE; + memcpy(s->storage + xip_off, contents + off, filesz); + } + + return true; +} + +void rp2040_xip_load_image(RP2040XipState *s, const char *filename, + Error **errp) +{ + Error *local_err = NULL; + ssize_t image_size; + + if (!filename) { + return; + } + + if (rp2040_xip_load_elf(s, filename, &local_err)) { + if (local_err) { + error_propagate(errp, local_err); + return; + } + if (!rp2040_xip_fixup_boot2(s, filename, errp)) { + return; + } + rp2040_xip_writeback(s, errp); + return; + } + + image_size = load_image_size(filename, s->storage, s->flash_size); + if (image_size < 0) { + error_setg(errp, "could not load flash image '%s'", filename); + return; + } + if (!rp2040_xip_fixup_boot2(s, filename, errp)) { + return; + } + rp2040_xip_writeback(s, errp); +} + +bool rp2040_xip_flash_range_erase(RP2040XipState *s, uint32_t flash_offs, + uint32_t count, uint32_t block_size, + uint8_t block_cmd, Error **errp) +{ + if (!QEMU_IS_ALIGNED(flash_offs, FLASH_SECTOR_SIZE) || + !QEMU_IS_ALIGNED(count, FLASH_SECTOR_SIZE)) { + error_setg(errp, "flash erase range is not sector-aligned"); + return false; + } + if (flash_offs > s->flash_size || count > s->flash_size - flash_offs) { + error_setg(errp, "flash erase range is outside XIP storage"); + return false; + } + if (block_size && block_size != 64 * KiB) { + g_autofree char *detail = g_strdup_printf("block size %" PRIu32, + block_size); + + rp2040_log_nyi("bootrom", "flash_range_erase block size", detail); + } + if (block_cmd != 0x20 && block_cmd != 0xd8) { + g_autofree char *detail = g_strdup_printf("erase command 0x%02x", + block_cmd); + + rp2040_log_nyi("bootrom", "flash_range_erase command", detail); + } + + memset(s->storage + flash_offs, 0xff, count); + return rp2040_xip_writeback(s, errp); +} + +bool rp2040_xip_flash_range_program(RP2040XipState *s, uint32_t flash_offs, + uint32_t data_addr, uint32_t count, + Error **errp) +{ + g_autofree uint8_t *buf = NULL; + uint32_t i; + + if (!QEMU_IS_ALIGNED(flash_offs, FLASH_PAGE_SIZE) || + !QEMU_IS_ALIGNED(count, FLASH_PAGE_SIZE)) { + error_setg(errp, "flash program range is not page-aligned"); + return false; + } + if (flash_offs > s->flash_size || count > s->flash_size - flash_offs) { + error_setg(errp, "flash program range is outside XIP storage"); + return false; + } + + buf = g_malloc(count); + if (address_space_read(&address_space_memory, data_addr, + MEMTXATTRS_UNSPECIFIED, buf, count) != MEMTX_OK) { + error_setg(errp, "could not read flash program buffer at 0x%08" PRIx32, + data_addr); + return false; + } + + for (i = 0; i < count; i++) { + s->storage[flash_offs + i] &= buf[i]; + } + + return rp2040_xip_writeback(s, errp); +} + +static void rp2040_xip_realize(DeviceState *dev, Error **errp) +{ + RP2040XipState *s = RP2040_XIP(dev); + g_autofree gchar *contents = NULL; + gsize contents_len = 0; + + if (s->flash_size == 0) { + error_setg(errp, "flash-size must be non-zero"); + return; + } + + s->xip_writable = true; + s->storage = g_malloc0(s->flash_size); + memset(s->storage, 0xff, s->flash_size); + + if (s->flash_file) { + if (!g_file_get_contents(s->flash_file, &contents, &contents_len, + NULL)) { + error_setg(errp, "could not load flash file '%s'", + s->flash_file); + return; + } + if (contents_len > s->flash_size) { + error_setg(errp, "flash file '%s' is %" G_GSIZE_FORMAT + " bytes, larger than %" G_GSIZE_FORMAT + " byte Pico flash", + s->flash_file, contents_len, (gsize)s->flash_size); + return; + } + memcpy(s->storage, contents, contents_len); + } + + memory_region_init_io(&s->xip, OBJECT(dev), &rp2040_xip_ops, s, + "rp2040.xip", s->flash_size); + memory_region_init_io(&s->xip_noalloc, OBJECT(dev), + &rp2040_xip_alias_ops, s, "rp2040.xip.noalloc", + s->flash_size); + memory_region_init_io(&s->xip_nocache, OBJECT(dev), + &rp2040_xip_alias_ops, s, "rp2040.xip.nocache", + s->flash_size); + memory_region_init_io(&s->xip_nocache_noalloc, OBJECT(dev), + &rp2040_xip_alias_ops, s, + "rp2040.xip.nocache-noalloc", s->flash_size); + memory_region_init_io(&s->ctrl, OBJECT(dev), &rp2040_xip_ctrl_ops, s, + "rp2040.xip.ctrl", RP2040_XIP_CTRL_SIZE); + memory_region_init_io(&s->ssi, OBJECT(dev), &rp2040_xip_ssi_ops, s, + "rp2040.xip.ssi", RP2040_XIP_SSI_SIZE); + memory_region_init_io(&s->aux, OBJECT(dev), &rp2040_xip_aux_ops, s, + "rp2040.xip.aux", RP2040_XIP_AUX_SIZE); + + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->xip); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->ctrl); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->ssi); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->aux); +} + +static void rp2040_xip_reset(DeviceState *dev) +{ + RP2040XipState *s = RP2040_XIP(dev); + + s->xip_ctrl = RP2040_XIP_CTRL_EN | RP2040_XIP_CTRL_ERR_BADWRITE; + s->ctrlr0 = 0; + s->ctrlr1 = 0; + s->ssienr = 0; + s->ser = 0; + s->baudr = 0; + s->txftlr = 0; + s->rxftlr = 0; + s->imr = 0; + s->dmacr = 0; + s->dmatdlr = 0; + s->dmardlr = 4; + s->rx_sample_dly = 0; + s->spi_ctrlr0 = 0; + s->write_enable = false; + s->busy = false; + s->qspi_cs_high = true; + s->stream_addr = 0; + s->stream_ctr = 0; + rp2040_xip_stream_clear(s); + rp2040_xip_reset_tx(s); + rp2040_xip_rx_clear(s); +} + +static void rp2040_xip_finalize(Object *obj) +{ + RP2040XipState *s = RP2040_XIP(obj); + + g_free(s->flash_file); + g_free(s->storage); +} + +static void rp2040_xip_init(Object *obj) +{ + RP2040XipState *s = RP2040_XIP(obj); + + qdev_init_gpio_out_named(DEVICE(obj), &s->dreq_rx, "dreq-rx", 1); + qdev_init_gpio_out_named(DEVICE(obj), &s->dreq_stream, "dreq-stream", 1); +} + +static const Property rp2040_xip_properties[] = { + DEFINE_PROP_UINT32("flash-size", RP2040XipState, flash_size, 2 * MiB), + DEFINE_PROP_STRING("flash-file", RP2040XipState, flash_file), + DEFINE_PROP_UINT64("flash-uid", RP2040XipState, flash_uid, + FLASH_UID_DEFAULT), +}; + +static void rp2040_xip_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = rp2040_xip_realize; + device_class_set_legacy_reset(dc, rp2040_xip_reset); + device_class_set_props(dc, rp2040_xip_properties); +} + +static const TypeInfo rp2040_xip_info = { + .name = TYPE_RP2040_XIP, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(RP2040XipState), + .instance_init = rp2040_xip_init, + .instance_finalize = rp2040_xip_finalize, + .class_init = rp2040_xip_class_init, +}; + +static void rp2040_xip_register_types(void) +{ + type_register_static(&rp2040_xip_info); +} +type_init(rp2040_xip_register_types) diff --git a/hw/ssi/trace-events b/hw/ssi/trace-events index b9d8648297..e94dfc1d7b 100644 --- a/hw/ssi/trace-events +++ b/hw/ssi/trace-events @@ -32,6 +32,18 @@ ibex_spi_host_transfer(uint32_t tx_data, uint32_t rx_data) "tx_data: 0x%" PRIx32 ibex_spi_host_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PRIx64 " size %u: 0x%" PRIx64 ibex_spi_host_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size %u:" +# rp2040_xip.c +rp2040_xip_finish_command(uint8_t cmd, unsigned tx_len) "cmd 0x%02x tx_len %u" +rp2040_xip_program(uint32_t addr, unsigned len, bool write_enable) "addr 0x%06x len %u wel %u" +rp2040_xip_erase(uint32_t addr, bool write_enable) "addr 0x%06x wel %u" +rp2040_xip_dr_write(uint64_t value, unsigned size, unsigned tx_len, uint32_t ctrlr0, uint32_t ctrlr1, uint32_t spi_ctrlr0) "value 0x%" PRIx64 " size %u tx_len %u ctrlr0 0x%08x ctrlr1 0x%08x spi_ctrlr0 0x%08x" +rp2040_xip_ctrlr0(uint32_t value) "value 0x%08x" +rp2040_xip_ctrlr1(uint32_t value) "value 0x%08x" +rp2040_xip_spi_ctrlr0(uint32_t value) "value 0x%08x" +rp2040_xip_qspi_cs(bool high, unsigned tx_len) "high %u tx_len %u" +rp2040_xip_ssienr(uint32_t old_value, uint32_t new_value, unsigned tx_len) "old 0x%x new 0x%x tx_len %u" +rp2040_xip_ser(uint32_t old_value, uint32_t new_value, unsigned tx_len) "old 0x%x new 0x%x tx_len %u" + #pnv_spi.c pnv_spi_read(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PRIx64 pnv_spi_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PRIx64 diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h index 16e5ca8846..cb108bd5f1 100644 --- a/include/hw/arm/rp2040.h +++ b/include/hw/arm/rp2040.h @@ -29,6 +29,7 @@ #include "hw/misc/rp2040_vreg.h" #include "hw/misc/rp2040_watchdog.h" #include "hw/misc/rp2040_xosc.h" +#include "hw/ssi/rp2040_xip.h" #include "qom/object.h" #define TYPE_RP2040 "rp2040" @@ -37,6 +38,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040) #define RP2040_ROM_BASE 0x00000000 #define RP2040_ROM_SIZE (16 * KiB) #define RP2040_XIP_BASE 0x10000000 +#define RP2040_XIP_NOALLOC_BASE 0x11000000 +#define RP2040_XIP_NOCACHE_BASE 0x12000000 +#define RP2040_XIP_NOCACHE_NOALLOC_BASE 0x13000000 #define RP2040_SRAM_BASE 0x20000000 #define RP2040_SRAM_BANK_SIZE (64 * KiB) #define RP2040_SRAM4_BASE 0x20040000 @@ -69,6 +73,7 @@ struct RP2040State { RP2040VregState vreg; RP2040WatchdogState watchdog; RP2040XoscState xosc; + RP2040XipState xip; MemoryRegion *board_memory; MemoryRegion cpu_memory[RP2040_NUM_CORES]; diff --git a/include/hw/misc/rp2040_ioqspi.h b/include/hw/misc/rp2040_ioqspi.h index e671d2a1b5..eae63588e6 100644 --- a/include/hw/misc/rp2040_ioqspi.h +++ b/include/hw/misc/rp2040_ioqspi.h @@ -13,6 +13,8 @@ #define TYPE_RP2040_IOQSPI "rp2040-ioqspi" OBJECT_DECLARE_SIMPLE_TYPE(RP2040IoQspiState, RP2040_IOQSPI) +typedef struct RP2040XipState RP2040XipState; + #define RP2040_IOQSPI_BASE 0x40018000 #define RP2040_IOQSPI_SIZE 0x4000 @@ -28,6 +30,7 @@ struct RP2040IoQspiState { uint32_t proc1_intf; uint32_t dormant_wake_inte; uint32_t dormant_wake_intf; + RP2040XipState *xip; }; #endif diff --git a/include/hw/ssi/rp2040_xip.h b/include/hw/ssi/rp2040_xip.h new file mode 100644 index 0000000000..53d91f88e9 --- /dev/null +++ b/include/hw/ssi/rp2040_xip.h @@ -0,0 +1,92 @@ +/* + * RP2040 XIP/SSI flash controller emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SSI_RP2040_XIP_H +#define HW_SSI_RP2040_XIP_H + +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "qom/object.h" + +#define TYPE_RP2040_XIP "rp2040-xip" +OBJECT_DECLARE_SIMPLE_TYPE(RP2040XipState, RP2040_XIP) + +#define RP2040_XIP_CTRL_BASE 0x14000000 +#define RP2040_XIP_SSI_BASE 0x18000000 +#define RP2040_XIP_AUX_BASE 0x50400000 +#define RP2040_XIP_CTRL_SIZE 0x4000 +#define RP2040_XIP_SSI_SIZE 0x4000 +#define RP2040_XIP_AUX_SIZE 0x4000 +#define RP2040_XIP_STREAM_FIFO_DEPTH 4 + +struct RP2040XipState { + SysBusDevice parent_obj; + + MemoryRegion xip; + MemoryRegion xip_noalloc; + MemoryRegion xip_nocache; + MemoryRegion xip_nocache_noalloc; + MemoryRegion ctrl; + MemoryRegion ssi; + MemoryRegion aux; + qemu_irq dreq_rx; + qemu_irq dreq_stream; + + uint32_t flash_size; + char *flash_file; + uint64_t flash_uid; + uint8_t *storage; + bool xip_writable; + + uint32_t xip_ctrl; + + uint32_t ctrlr0; + uint32_t ctrlr1; + uint32_t ssienr; + uint32_t ser; + uint32_t baudr; + uint32_t txftlr; + uint32_t rxftlr; + uint32_t imr; + uint32_t dmacr; + uint32_t dmatdlr; + uint32_t dmardlr; + uint32_t rx_sample_dly; + uint32_t spi_ctrlr0; + + bool write_enable; + bool busy; + bool qspi_cs_high; + uint8_t tx[260]; + unsigned tx_len; + bool tx_unsupported_logged; + uint8_t rx[16]; + unsigned rx_len; + unsigned rx_pos; + uint32_t ssi_bulk_addr; + uint32_t ssi_bulk_remaining; + + uint32_t stream_addr; + uint32_t stream_ctr; + uint32_t stream_fifo[RP2040_XIP_STREAM_FIFO_DEPTH]; + unsigned stream_fifo_len; + unsigned stream_fifo_pos; +}; + +void rp2040_xip_set_writable(RP2040XipState *s, bool writable); +MemTxResult rp2040_xip_read_data(RP2040XipState *s, hwaddr addr, + uint64_t *data, unsigned size); +void rp2040_xip_load_image(RP2040XipState *s, const char *filename, + Error **errp); +void rp2040_xip_qspi_cs(RP2040XipState *s, bool high); +bool rp2040_xip_flash_range_erase(RP2040XipState *s, uint32_t flash_offs, + uint32_t count, uint32_t block_size, + uint8_t block_cmd, Error **errp); +bool rp2040_xip_flash_range_program(RP2040XipState *s, uint32_t flash_offs, + uint32_t data_addr, uint32_t count, + Error **errp); + +#endif diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index f0ffa84dd1..9bc7540aee 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -268,7 +268,8 @@ qtests_arm = \ 'rp2040-timer-test', 'rp2040-uart-test', 'rp2040-vreg-test', - 'rp2040-watchdog-test'] : []) + \ + 'rp2040-watchdog-test', + 'rp2040-xip-test'] : []) + \ (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \ (config_all_devices.has_key('CONFIG_FSI_APB2OPB_ASPEED') ? ['aspeed_fsi-test'] : []) + \ (config_all_devices.has_key('CONFIG_CAN_FLEXCAN') ? ['flexcan-test'] : []) + \ diff --git a/tests/qtest/rp2040-xip-test.c b/tests/qtest/rp2040-xip-test.c new file mode 100644 index 0000000000..8e5b719332 --- /dev/null +++ b/tests/qtest/rp2040-xip-test.c @@ -0,0 +1,293 @@ +/* + * QTest testcase for the RP2040 XIP/SSI block. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest.h" +#include "qemu/bitops.h" +#include "qemu/bswap.h" + +#define XIP_BASE 0x10000000 +#define XIP_NOCACHE_NOALLOC_BASE 0x13000000 +#define XIP_CTRL_BASE 0x14000000 +#define XIP_STAT 0x08 +#define XIP_STREAM_ADDR 0x14 +#define XIP_STREAM_CTR 0x18 +#define XIP_STREAM_FIFO 0x1c +#define XIP_SSI_BASE 0x18000000 +#define SSI_CTRLR1 0x04 +#define SSI_SSIENR 0x08 +#define SSI_DR0 0x60 +#define SSI_SER 0x10 + +#define IOQSPI_BASE 0x40018000 +#define IOQSPI_SS_CTRL 0x0c +#define IOQSPI_OUT_LOW 0x200 +#define IOQSPI_OUT_HIGH 0x300 + +#define XIP_STAT_FIFO_FULL BIT(2) +#define XIP_STAT_FIFO_EMPTY BIT(1) +#define XIP_STAT_FLUSH_READY BIT(0) + +static QTestState *rp2040_start(const char *machine_args) +{ + if (machine_args) { + return qtest_initf("-machine raspi-pico,%s", machine_args); + } + return qtest_init("-machine raspi-pico"); +} + +static void read_flash_uid(QTestState *qts, uint8_t *uid) +{ + int i; + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x4b); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + for (i = 0; i < 4; i++) { + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + } + for (i = 0; i < 8; i++) { + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0); + uid[i] = qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + } +} + +static void program_flash_bytes(QTestState *qts, uint32_t off, + const uint8_t *buf, size_t len) +{ + size_t i; + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x06); + + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 1); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x02); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 16, 8)); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 8, 8)); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 0, 8)); + for (i = 0; i < len; i++) { + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, buf[i]); + } + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 0); + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x05); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); +} + +static void program_flash_bytes_without_wel(QTestState *qts, uint32_t off, + const uint8_t *buf, size_t len) +{ + size_t i; + + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 1); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x02); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 16, 8)); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 8, 8)); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 0, 8)); + for (i = 0; i < len; i++) { + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, buf[i]); + } + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 0); +} + +static void erase_flash_sector(QTestState *qts, uint32_t off) +{ + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x06); + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 1); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x20); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 16, 8)); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 8, 8)); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, extract32(off, 0, 8)); + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 0); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x05); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); +} + +static void test_flash_uid_default(void) +{ + static const uint8_t expected[] = { + 0x3e, 0xb8, 0xa7, 0x49, 0x3f, 0xcc, 0x06, 0x08, + }; + QTestState *qts = rp2040_start(NULL); + uint8_t uid[8]; + + read_flash_uid(qts, uid); + g_assert_cmpmem(uid, sizeof(uid), expected, sizeof(expected)); + + qtest_quit(qts); +} + +static void test_flash_uid_machine_option(void) +{ + static const uint8_t expected[] = { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + }; + QTestState *qts = rp2040_start("flash-uid=0011223344556677"); + uint8_t uid[8]; + + read_flash_uid(qts, uid); + g_assert_cmpmem(uid, sizeof(uid), expected, sizeof(expected)); + + qtest_quit(qts); +} + +static void test_flash_program_erase(void) +{ + static const uint8_t first[] = { 0x0f, 0x55, 0xaa, 0xf0 }; + static const uint8_t second[] = { 0xf0, 0xaa, 0x55, 0x0f }; + static const uint8_t combined[] = { 0x00, 0x00, 0x00, 0x00 }; + static const uint8_t erased[] = { 0xff, 0xff, 0xff, 0xff }; + uint8_t actual[sizeof(first)]; + QTestState *qts = rp2040_start(NULL); + + program_flash_bytes_without_wel(qts, 0x1000, first, sizeof(first)); + qtest_memread(qts, XIP_BASE + 0x1000, actual, sizeof(actual)); + g_assert_cmpmem(actual, sizeof(actual), erased, sizeof(erased)); + + program_flash_bytes(qts, 0x1000, first, sizeof(first)); + program_flash_bytes(qts, 0x1000, second, sizeof(second)); + qtest_memread(qts, XIP_BASE + 0x1000, actual, sizeof(actual)); + g_assert_cmpmem(actual, sizeof(actual), combined, sizeof(combined)); + + erase_flash_sector(qts, 0x1000); + qtest_memread(qts, XIP_BASE + 0x1000, actual, sizeof(actual)); + g_assert_cmpmem(actual, sizeof(actual), erased, sizeof(erased)); + + qtest_quit(qts); +} + +static void test_flash_persistence(void) +{ + static const uint8_t expected[] = { 0x12, 0x34, 0x56, 0x78 }; + g_autoptr(GError) error = NULL; + g_autofree char *path = NULL; + g_autofree char *machine_args = NULL; + uint8_t initial[4096]; + uint8_t actual[sizeof(expected)]; + QTestState *qts; + int fd; + + memset(initial, 0xff, sizeof(initial)); + fd = g_file_open_tmp("qemu-rp2040-flash-XXXXXX", &path, &error); + g_assert_no_error(error); + g_assert_cmpint(fd, >=, 0); + close(fd); + g_assert_true(g_file_set_contents(path, (const char *)initial, + sizeof(initial), &error)); + g_assert_no_error(error); + + machine_args = g_strdup_printf("flash-file=%s", path); + qts = rp2040_start(machine_args); + program_flash_bytes(qts, 0x800, expected, sizeof(expected)); + qtest_quit(qts); + + qts = rp2040_start(machine_args); + qtest_memread(qts, XIP_BASE + 0x800, actual, sizeof(actual)); + g_assert_cmpmem(actual, sizeof(actual), expected, sizeof(expected)); + qtest_quit(qts); + + unlink(path); +} + +static void test_ioqspi_chip_select(void) +{ + static const uint8_t expected[] = { 0x5a, 0xa5 }; + uint8_t actual[sizeof(expected)]; + QTestState *qts = rp2040_start(NULL); + size_t i; + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x06); + qtest_writel(qts, IOQSPI_BASE + IOQSPI_SS_CTRL, IOQSPI_OUT_LOW); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x02); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x00); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x20); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x00); + for (i = 0; i < ARRAY_SIZE(expected); i++) { + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, expected[i]); + } + qtest_writel(qts, IOQSPI_BASE + IOQSPI_SS_CTRL, IOQSPI_OUT_HIGH); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x05); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + + qtest_memread(qts, XIP_BASE + 0x2000, actual, sizeof(actual)); + g_assert_cmpmem(actual, sizeof(actual), expected, sizeof(expected)); + qtest_quit(qts); +} + +static void test_stream_fifo(void) +{ + static const uint32_t expected[] = { + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, + }; + QTestState *qts = rp2040_start(NULL); + size_t i; + + program_flash_bytes(qts, 0, (const uint8_t *)expected, sizeof(expected)); + + qtest_writel(qts, XIP_CTRL_BASE + XIP_STREAM_ADDR, XIP_BASE); + qtest_writel(qts, XIP_CTRL_BASE + XIP_STREAM_CTR, ARRAY_SIZE(expected)); + + g_assert_cmphex(qtest_readl(qts, XIP_CTRL_BASE + XIP_STAT) & + (XIP_STAT_FLUSH_READY | XIP_STAT_FIFO_FULL), ==, + XIP_STAT_FLUSH_READY | XIP_STAT_FIFO_FULL); + for (i = 0; i < ARRAY_SIZE(expected); i++) { + g_assert_cmphex(qtest_readl(qts, + XIP_CTRL_BASE + XIP_STREAM_FIFO), ==, + expected[i]); + } + g_assert_cmphex(qtest_readl(qts, XIP_CTRL_BASE + XIP_STREAM_CTR), ==, 0); + g_assert_cmphex(qtest_readl(qts, XIP_CTRL_BASE + XIP_STAT) & + XIP_STAT_FIFO_EMPTY, ==, XIP_STAT_FIFO_EMPTY); + + qtest_quit(qts); +} + +static void test_ssi_bulk_read(void) +{ + static const uint32_t expected[] = { + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, + }; + QTestState *qts = rp2040_start(NULL); + size_t i; + + program_flash_bytes(qts, 0, (const uint8_t *)expected, sizeof(expected)); + g_assert_cmphex(qtest_readl(qts, XIP_NOCACHE_NOALLOC_BASE), ==, + expected[0]); + + qtest_writel(qts, XIP_SSI_BASE + SSI_SSIENR, 0); + qtest_writel(qts, XIP_SSI_BASE + SSI_CTRLR1, ARRAY_SIZE(expected) - 1); + qtest_writel(qts, XIP_SSI_BASE + SSI_SSIENR, 1); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0xa0); + + for (i = 0; i < ARRAY_SIZE(expected); i++) { + g_assert_cmphex(bswap32(qtest_readl(qts, + XIP_SSI_BASE + SSI_DR0)), ==, + expected[i]); + } + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/rp2040-xip/flash-uid-default", + test_flash_uid_default); + qtest_add_func("/rp2040-xip/flash-uid-machine-option", + test_flash_uid_machine_option); + qtest_add_func("/rp2040-xip/flash-program-erase", + test_flash_program_erase); + qtest_add_func("/rp2040-xip/flash-persistence", + test_flash_persistence); + qtest_add_func("/rp2040-xip/ioqspi-chip-select", + test_ioqspi_chip_select); + qtest_add_func("/rp2040-xip/stream-fifo", test_stream_fifo); + qtest_add_func("/rp2040-xip/ssi-bulk-read", test_ssi_bulk_read); + + return g_test_run(); +} diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target index f3efe619d1..a07264fd89 100644 --- a/tests/tcg/arm/Makefile.softmmu-target +++ b/tests/tcg/arm/Makefile.softmmu-target @@ -24,11 +24,12 @@ RP2040_TESTS=rp2040-boot rp2040-uart rp2040-mpu \ rp2040-sio-fifo rp2040-sio-divider rp2040-psm-proc1 \ rp2040-core1-launch rp2040-timer -$(RP2040_TESTS): %: %.S rp2040-minimal.ld +$(RP2040_TESTS): %: %.S rp2040-boot2-w25q080.S rp2040.ld $(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \ -Wl,--build-id=none -x assembler-with-cpp \ - $< -o $@ -nostdlib -static \ - -T $(ARM_SRC)/rp2040-minimal.ld + $(ARM_SRC)/rp2040-boot2-w25q080.S $< \ + -o $@ -nostdlib -static \ + -T $(ARM_SRC)/rp2040.ld run-rp2040-boot: QEMU_OPTS=-M raspi-pico \ -semihosting-config enable=on,target=native -kernel diff --git a/tests/tcg/arm/system/rp2040-boot2-w25q080.S b/tests/tcg/arm/system/rp2040-boot2-w25q080.S new file mode 100644 index 0000000000..d128b98421 --- /dev/null +++ b/tests/tcg/arm/system/rp2040-boot2-w25q080.S @@ -0,0 +1,178 @@ +/* + * RP2040 second-stage boot code for the Winbond W25Q080. + * + * Derived from boot2_w25q080.S in the Raspberry Pi Pico SDK. + * Copyright (c) 2019-2021 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +.syntax unified +.cpu cortex-m0plus +.thumb + +#define PADS_QSPI_BASE 0x40020000 +#define XIP_SSI_BASE 0x18000000 +#define XIP_BASE 0x10000000 +#define VTOR 0xe000ed08 + +#define PADS_QSPI_SCLK 0x04 +#define PADS_QSPI_SD0 0x08 +#define PADS_QSPI_SD1 0x0c +#define PADS_QSPI_SD2 0x10 +#define PADS_QSPI_SD3 0x14 +#define PADS_QSPI_SCHMITT 0x02 +#define PADS_QSPI_SCLK_8MA_FAST 0x21 + +#define SSI_CTRLR0 0x00 +#define SSI_CTRLR1 0x04 +#define SSI_SSIENR 0x08 +#define SSI_BAUDR 0x14 +#define SSI_SR 0x28 +#define SSI_DR0 0x60 +#define SSI_RX_SAMPLE_DLY 0xf0 +#define SSI_SPI_CTRLR0 0xf4 + +#define SSI_SR_BUSY 0x01 +#define SSI_SR_TFE 0x04 + +#define CTRL0_SPI_TXRX 0x00070000 +#define CTRLR0_ENTER_XIP 0x005f0300 +#define SPI_CTRLR0_ENTER_XIP 0x00002221 +#define SPI_CTRLR0_XIP 0xa0002022 + +#define CMD_WRITE_STATUS 0x01 +#define CMD_READ_STATUS 0x05 +#define CMD_WRITE_ENABLE 0x06 +#define CMD_READ_STATUS2 0x35 +#define CMD_QUAD_IO_READ 0xeb +#define STATUS2_QUAD_ENABLE 0x02 +#define MODE_CONTINUOUS_READ 0xa0 + +.section .boot2, "ax", %progbits +.global boot2 +.type boot2, %function +.thumb_func +boot2: + push {lr} + + ldr r3, =PADS_QSPI_BASE + movs r0, PADS_QSPI_SCLK_8MA_FAST + str r0, [r3, PADS_QSPI_SCLK] + ldr r0, [r3, PADS_QSPI_SD0] + movs r1, PADS_QSPI_SCHMITT + bics r0, r1 + str r0, [r3, PADS_QSPI_SD0] + str r0, [r3, PADS_QSPI_SD1] + str r0, [r3, PADS_QSPI_SD2] + str r0, [r3, PADS_QSPI_SD3] + + ldr r3, =XIP_SSI_BASE + movs r1, 0 + str r1, [r3, SSI_SSIENR] + movs r1, 2 + str r1, [r3, SSI_BAUDR] + movs r1, 1 + movs r2, SSI_RX_SAMPLE_DLY + str r1, [r3, r2] + + ldr r1, =CTRL0_SPI_TXRX + str r1, [r3, SSI_CTRLR0] + movs r1, 1 + str r1, [r3, SSI_SSIENR] + + movs r0, CMD_READ_STATUS2 + bl read_flash_sreg + movs r2, STATUS2_QUAD_ENABLE + cmp r0, r2 + beq skip_status_programming + + movs r1, CMD_WRITE_ENABLE + str r1, [r3, SSI_DR0] + bl wait_ssi_ready + ldr r1, [r3, SSI_DR0] + + movs r1, CMD_WRITE_STATUS + str r1, [r3, SSI_DR0] + movs r0, 0 + str r0, [r3, SSI_DR0] + str r2, [r3, SSI_DR0] + bl wait_ssi_ready + ldr r1, [r3, SSI_DR0] + ldr r1, [r3, SSI_DR0] + ldr r1, [r3, SSI_DR0] + +1: + movs r0, CMD_READ_STATUS + bl read_flash_sreg + movs r1, 1 + tst r0, r1 + bne 1b + +skip_status_programming: + movs r1, 0 + str r1, [r3, SSI_SSIENR] + + ldr r1, =CTRLR0_ENTER_XIP + str r1, [r3, SSI_CTRLR0] + movs r1, 0 + str r1, [r3, SSI_CTRLR1] + ldr r1, =SPI_CTRLR0_ENTER_XIP + ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0) + str r1, [r0] + movs r1, 1 + str r1, [r3, SSI_SSIENR] + movs r1, CMD_QUAD_IO_READ + str r1, [r3, SSI_DR0] + movs r1, MODE_CONTINUOUS_READ + str r1, [r3, SSI_DR0] + bl wait_ssi_ready + + movs r1, 0 + str r1, [r3, SSI_SSIENR] + ldr r1, =SPI_CTRLR0_XIP + ldr r0, =(XIP_SSI_BASE + SSI_SPI_CTRLR0) + str r1, [r0] + movs r1, 1 + str r1, [r3, SSI_SSIENR] + + pop {r0} + cmp r0, 0 + beq vector_into_flash + bx r0 + +vector_into_flash: + ldr r0, =(XIP_BASE + 0x100) + ldr r1, =VTOR + str r0, [r1] + ldmia r0, {r0, r1} + msr msp, r0 + bx r1 + +wait_ssi_ready: + push {r0, r1, lr} +2: + ldr r1, [r3, SSI_SR] + movs r0, SSI_SR_TFE + tst r1, r0 + beq 2b + movs r0, SSI_SR_BUSY + tst r1, r0 + bne 2b + pop {r0, r1, pc} + +read_flash_sreg: + push {r1, lr} + str r0, [r3, SSI_DR0] + str r0, [r3, SSI_DR0] + bl wait_ssi_ready + ldr r0, [r3, SSI_DR0] + ldr r0, [r3, SSI_DR0] + pop {r1, pc} + +.align 2 +.ltorg + + .space 252 - (. - boot2), 0 + .word 0 +.size boot2, . - boot2 diff --git a/tests/tcg/arm/system/rp2040-minimal.ld b/tests/tcg/arm/system/rp2040.ld similarity index 72% rename from tests/tcg/arm/system/rp2040-minimal.ld rename to tests/tcg/arm/system/rp2040.ld index ebfbac8cf2..9d902bf2b9 100644 --- a/tests/tcg/arm/system/rp2040-minimal.ld +++ b/tests/tcg/arm/system/rp2040.ld @@ -1,5 +1,3 @@ -/* SPDX-License-Identifier: GPL-2.0-or-later */ - ENTRY(reset_handler) MEMORY @@ -10,7 +8,14 @@ MEMORY SECTIONS { - .vectors ORIGIN(FLASH) : + .boot2 ORIGIN(FLASH) : + { + KEEP(*(.boot2)) + } > FLASH + + ASSERT(SIZEOF(.boot2) == 256, "RP2040 boot2 must be exactly 256 bytes") + + .vectors ORIGIN(FLASH) + 0x100 : { KEEP(*(.vectors)) } > FLASH -- 2.55.0
