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.
Back the executable XIP windows with a ROM device so TCG can map guest code directly. Disable ROMD while the flash is busy to route accesses through the device callbacks and preserve XIP faults. Flush translated mappings after flash contents change, and implement the alternate XIP views as aliases of the same ROM device. 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/raspi_pico.c | 111 +- hw/arm/rp2040.c | 154 +- hw/misc/rp2040_ioqspi.c | 31 + hw/ssi/meson.build | 1 + hw/ssi/rp2040_xip.c | 1368 +++++++++++++++++ hw/ssi/trace-events | 12 + include/hw/arm/rp2040.h | 5 + include/hw/misc/rp2040_ioqspi.h | 3 + include/hw/ssi/rp2040_xip.h | 94 ++ tests/qtest/meson.build | 3 +- tests/qtest/rp2040-xip-test.c | 366 +++++ tests/tcg/arm/system/meson.build | 3 +- tests/tcg/arm/system/rp2040-boot2-w25q080.S | 178 +++ .../system/{rp2040-minimal.ld => rp2040.ld} | 11 +- 14 files changed, 2297 insertions(+), 43 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/raspi_pico.c b/hw/arm/raspi_pico.c index 71cd5817a6..c99ff2660e 100644 --- a/hw/arm/raspi_pico.c +++ b/hw/arm/raspi_pico.c @@ -14,6 +14,7 @@ #include "hw/arm/rp2040.h" #include "hw/core/boards.h" #include "hw/core/qdev-properties.h" +#include "qemu/cutils.h" #include "system/address-spaces.h" #include "system/system.h" #include "qom/object.h" @@ -21,18 +22,81 @@ #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; uint64_t rosc_random_seed; 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 void raspi_pico_get_rosc_random_seed(Object *obj, Visitor *v, const char *name, void *opaque, Error **errp) @@ -73,6 +137,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); @@ -80,14 +152,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); } static bool raspi_pico_get_strict_uart_pins(Object *obj, Error **errp) @@ -126,6 +205,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(oc, "rosc-random-seed", "uint64", raspi_pico_get_rosc_random_seed, raspi_pico_set_rosc_random_seed, NULL, NULL); @@ -147,6 +239,7 @@ static const TypeInfo raspi_pico_machine_info = { .parent = TYPE_MACHINE, .instance_size = sizeof(RaspiPicoMachineState), .instance_init = raspi_pico_machine_initfn, + .instance_finalize = raspi_pico_machine_finalize, .class_init = raspi_pico_machine_class_init, }; diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c index 86bf259439..6b3e31e551 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 { @@ -251,6 +324,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); @@ -368,6 +443,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, @@ -481,6 +571,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 f4d1a1f329..d8fd98934c 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.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 @@ -48,6 +54,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; @@ -119,6 +141,9 @@ static void rp2040_ioqspi_write(void *opaque, hwaddr addr, s->ctrl[index] = rp2040_atomic_update(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: @@ -167,6 +192,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, @@ -225,6 +255,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/meson.build b/hw/ssi/meson.build index 6afb1ea200..eaeff03ffa 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', 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..fdd48b0f91 --- /dev/null +++ b/hw/ssi/rp2040_xip.c @@ -0,0 +1,1368 @@ +/* + * 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.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 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 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_set_busy(RP2040XipState *s, bool busy) +{ + s->busy = busy; + memory_region_rom_device_set_romd(&s->xip, !busy); +} + +static void rp2040_xip_changed(RP2040XipState *s, hwaddr addr, hwaddr size) +{ + memory_region_flush_rom_device(&s->xip, addr, size); +} + +static void rp2040_xip_finish_busy(RP2040XipState *s) +{ + rp2040_xip_set_busy(s, 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_changed(s, addr, data_len); + rp2040_xip_writeback_or_warn(s); + rp2040_xip_set_busy(s, true); +} + +static void rp2040_xip_erase(RP2040XipState *s) +{ + uint32_t addr; + uint32_t base; + uint32_t erase_len; + + 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; + } + + erase_len = MIN(FLASH_SECTOR_SIZE, s->flash_size - base); + memset(&s->storage[base], 0xff, erase_len); + rp2040_xip_changed(s, base, erase_len); + rp2040_xip_writeback_or_warn(s); + rp2040_xip_set_busy(s, 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) { + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented flash command 0x%02x\n", + __func__, s->tx[0]); + s->tx_unsupported_logged = true; + } + rp2040_xip_rx_push(s, 0); + break; + } +} + +static bool rp2040_xip_ssi_transfer_active(RP2040XipState *s) +{ + return s->ssienr && (s->ser || !s->qspi_cs_high); +} + +static void rp2040_xip_ssi_defer_write(RP2040XipState *s, uint8_t value) +{ + if (s->deferred_tx_len < ARRAY_SIZE(s->deferred_tx)) { + s->deferred_tx[s->deferred_tx_len++] = value; + } +} + +static void rp2040_xip_ssi_drain_deferred(RP2040XipState *s) +{ + unsigned len; + unsigned i; + + if (!rp2040_xip_ssi_transfer_active(s)) { + return; + } + + len = s->deferred_tx_len; + s->deferred_tx_len = 0; + for (i = 0; i < len; i++) { + rp2040_xip_dr_write(s, s->deferred_tx[i]); + } +} + +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_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); + } + rp2040_xip_changed(s, addr, size); + 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 & RP2040_ATOMIC_ALIAS_MASK; + hwaddr offset = addr & 0xfff; + uint32_t new_value; + + switch (offset) { + case RP2040_XIP_CTRL: + new_value = rp2040_atomic_update(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) +{ + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write to read-only auxiliary stream FIFO\n", + __func__); +} + +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 & RP2040_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 (!rp2040_xip_ssi_transfer_active(s)) { + rp2040_xip_ssi_defer_write(s, value); + return; + } + rp2040_xip_ssi_drain_deferred(s); + 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_atomic_update(s->ctrlr0, value, alias); + trace_rp2040_xip_ctrlr0(s->ctrlr0); + break; + case RP2040_SSI_CTRLR1: + s->ctrlr1 = rp2040_atomic_update(s->ctrlr1, value, alias); + trace_rp2040_xip_ctrlr1(s->ctrlr1); + break; + case RP2040_SSI_SSIENR: + new_value = rp2040_atomic_update(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); + s->deferred_tx_len = 0; + } else { + rp2040_xip_ssi_drain_deferred(s); + } + break; + case RP2040_SSI_SER: + new_value = rp2040_atomic_update(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); + } + rp2040_xip_ssi_drain_deferred(s); + break; + case RP2040_SSI_BAUDR: + new_value = rp2040_atomic_update(s->baudr, value, alias); + s->baudr = new_value & 0xffff; + break; + case RP2040_SSI_TXFTLR: + new_value = rp2040_atomic_update(s->txftlr, value, alias); + s->txftlr = new_value & 0xff; + break; + case RP2040_SSI_RXFTLR: + new_value = rp2040_atomic_update(s->rxftlr, value, alias); + s->rxftlr = new_value & 0xff; + break; + case RP2040_SSI_IMR: + new_value = rp2040_atomic_update(s->imr, value, alias); + s->imr = new_value & 0x3f; + break; + case RP2040_SSI_DMACR: + new_value = rp2040_atomic_update(s->dmacr, value, alias); + s->dmacr = new_value & (RP2040_SSI_DMACR_TDMAE | + RP2040_SSI_DMACR_RDMAE); + break; + case RP2040_SSI_DMATDLR: + s->dmatdlr = rp2040_atomic_update(s->dmatdlr, value, alias) & 0xff; + break; + case RP2040_SSI_DMARDLR: + s->dmardlr = rp2040_atomic_update(s->dmardlr, value, alias) & 0xff; + break; + case RP2040_SSI_RX_SAMPLE_DLY: + s->rx_sample_dly = rp2040_atomic_update(s->rx_sample_dly, value, + alias) & 0xff; + break; + case RP2040_SSI_SPI_CTRLR0: + s->spi_ctrlr0 = rp2040_atomic_update(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_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); + rp2040_xip_ssi_drain_deferred(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_changed(s, 0, s->flash_size); + 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_changed(s, 0, s->flash_size); + 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) { + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented block size %" PRIu32 "\n", + __func__, block_size); + } + if (block_cmd != 0x20 && block_cmd != 0xd8) { + qemu_log_mask(LOG_UNIMP, + "%s: unimplemented erase command 0x%02x\n", + __func__, block_cmd); + } + + memset(s->storage + flash_offs, 0xff, count); + rp2040_xip_changed(s, flash_offs, 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]; + } + + rp2040_xip_changed(s, flash_offs, count); + 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; + if (!memory_region_init_rom_device(&s->xip, OBJECT(dev), + &rp2040_xip_ops, s, "rp2040.xip", + s->flash_size, errp)) { + return; + } + s->storage = memory_region_get_ram_ptr(&s->xip); + 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_alias(&s->xip_noalloc, OBJECT(dev), + "rp2040.xip.noalloc", &s->xip, 0, + s->flash_size); + memory_region_init_alias(&s->xip_nocache, OBJECT(dev), + "rp2040.xip.nocache", &s->xip, 0, + s->flash_size); + memory_region_init_alias(&s->xip_nocache_noalloc, OBJECT(dev), + "rp2040.xip.nocache-noalloc", &s->xip, 0, + 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; + rp2040_xip_set_busy(s, false); + s->qspi_cs_high = true; + s->stream_addr = 0; + s->stream_ctr = 0; + rp2040_xip_stream_clear(s); + rp2040_xip_reset_tx(s); + s->deferred_tx_len = 0; + rp2040_xip_rx_clear(s); +} + +static void rp2040_xip_finalize(Object *obj) +{ + RP2040XipState *s = RP2040_XIP(obj); + + g_free(s->flash_file); +} + +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 788b821d0e..bb59e80e08 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..f132ec1ba5 --- /dev/null +++ b/include/hw/ssi/rp2040_xip.h @@ -0,0 +1,94 @@ +/* + * 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; + uint8_t deferred_tx[260]; + unsigned deferred_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 e2ffb99b1f..a663809225 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -277,7 +277,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..bc1b47841d --- /dev/null +++ b/tests/qtest/rp2040-xip-test.c @@ -0,0 +1,366 @@ +/* + * 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 SSI_RXFLR 0x24 + +#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 ssi_enable(QTestState *qts) +{ + qtest_writel(qts, XIP_SSI_BASE + SSI_SSIENR, 1); +} + +static void ssi_disable(QTestState *qts) +{ + qtest_writel(qts, XIP_SSI_BASE + SSI_SSIENR, 0); +} + +static void ssi_select(QTestState *qts) +{ + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 1); +} + +static void ssi_deselect(QTestState *qts) +{ + qtest_writel(qts, XIP_SSI_BASE + SSI_SER, 0); +} + +static void write_enable(QTestState *qts) +{ + ssi_select(qts); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x06); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + ssi_deselect(qts); +} + +static void finish_busy(QTestState *qts) +{ + ssi_select(qts); + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x05); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + ssi_deselect(qts); +} + +static void read_flash_uid(QTestState *qts, uint8_t *uid) +{ + int i; + + ssi_enable(qts); + ssi_select(qts); + 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); + } + ssi_deselect(qts); +} + +static void program_flash_bytes(QTestState *qts, uint32_t off, + const uint8_t *buf, size_t len) +{ + size_t i; + + ssi_enable(qts); + write_enable(qts); + ssi_select(qts); + 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]); + } + ssi_deselect(qts); + finish_busy(qts); +} + +static void program_flash_bytes_without_wel(QTestState *qts, uint32_t off, + const uint8_t *buf, size_t len) +{ + size_t i; + + ssi_enable(qts); + ssi_select(qts); + 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]); + } + ssi_deselect(qts); +} + +static void erase_flash_sector(QTestState *qts, uint32_t off) +{ + ssi_enable(qts); + write_enable(qts); + ssi_select(qts); + 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)); + ssi_deselect(qts); + finish_busy(qts); +} + +static void test_ssi_defers_unselected_data(void) +{ + QTestState *qts = rp2040_start(NULL); + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x05); + g_assert_cmphex(qtest_readl(qts, XIP_SSI_BASE + SSI_RXFLR), ==, 0); + + ssi_enable(qts); + g_assert_cmphex(qtest_readl(qts, XIP_SSI_BASE + SSI_RXFLR), ==, 0); + ssi_select(qts); + g_assert_cmphex(qtest_readl(qts, XIP_SSI_BASE + SSI_RXFLR), ==, 1); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + ssi_deselect(qts); + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x05); + g_assert_cmphex(qtest_readl(qts, XIP_SSI_BASE + SSI_RXFLR), ==, 0); + ssi_disable(qts); + ssi_enable(qts); + ssi_select(qts); + g_assert_cmphex(qtest_readl(qts, XIP_SSI_BASE + SSI_RXFLR), ==, 0); + ssi_deselect(qts); + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0x05); + g_assert_cmphex(qtest_readl(qts, XIP_SSI_BASE + SSI_RXFLR), ==, 0); + ssi_select(qts); + g_assert_cmphex(qtest_readl(qts, XIP_SSI_BASE + SSI_RXFLR), ==, 1); + + qtest_quit(qts); +} + +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; + + ssi_enable(qts); + write_enable(qts); + 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); + finish_busy(qts); + + 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); + ssi_select(qts); + 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]); + } + ssi_deselect(qts); + + 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/ssi-defers-unselected-data", + test_ssi_defers_unselected_data); + 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/system/meson.build b/tests/tcg/arm/system/meson.build index d39359419b..8e7286e390 100644 --- a/tests/tcg/arm/system/meson.build +++ b/tests/tcg/arm/system/meson.build @@ -17,7 +17,8 @@ qemu_base_args = ['-display', 'none', qemu_def_args = ['-M', 'virt', '-cpu', 'max', qemu_base_args] rp2040_cflags = ['-mcpu=cortex-m0plus', '-mthumb', '-mfloat-abi=soft', '-nostdlib', '-Wl,--build-id=none', - '-T', files('rp2040-minimal.ld')] + files('rp2040-boot2-w25q080.S'), + '-T', files('rp2040.ld')] rp2040_qemu_args = ['-M', 'raspi-pico', '-monitor', 'none', qemu_base_args] rp2040_uart_qemu_args = ['-M', 'raspi-pico,strict-uart-pins=off', 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
