* [PATCH RFC v2 01/30] target/arm: support Cortex-M0+ MPU
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-01 17:52 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 02/30] hw/char/pl011: expose DMA request outputs Gilles Grimaud
` (29 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, gilles grimaud, Peter Maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Cortex-M0+ implementations may provide an Armv6 PMSA MPU with eight regions. QEMU already marks M-profile CPUs as PMSA, but only exposes and allocates the existing region model for Armv7 CPUs. This prevents a Cortex-M0+ board from enabling its optional MPU.
Use QEMU's PMSAv7 state and translation path for the compatible Armv6-M MPU register layout. Permit CONTROL.nPRIV changes when the optional MPU is present and clear the region state on reset.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/intc/armv7m_nvic.c | 3 +++
target/arm/cpu.c | 12 +++++++++---
target/arm/ptw.c | 3 ++-
target/arm/tcg/m_helper.c | 10 +++++++++-
4 files changed, 23 insertions(+), 5 deletions(-)
diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
index a7651f831e..3140599ac1 100644
--- a/hw/intc/armv7m_nvic.c
+++ b/hw/intc/armv7m_nvic.c
@@ -1626,6 +1626,9 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
break;
case 0xd08: /* Vector Table Offset. */
cpu->env.v7m.vecbase[attrs.secure] = value & 0xffffff80;
+ if (!arm_feature(&cpu->env, ARM_FEATURE_M_SECURITY)) {
+ cpu->env.v7m.vecbase[!attrs.secure] = value & 0xffffff80;
+ }
break;
case 0xd0c: /* Application Interrupt/Reset Control (AIRCR) */
if ((value >> R_V7M_AIRCR_VECTKEY_SHIFT) == 0x05fa) {
diff --git a/target/arm/cpu.c b/target/arm/cpu.c
index 77aa78f00e..f58a1db843 100644
--- a/target/arm/cpu.c
+++ b/target/arm/cpu.c
@@ -617,7 +617,8 @@ static void arm_cpu_reset_hold(Object *obj, ResetType type)
sizeof(*env->pmsav8.rlar[M_REG_S])
* cpu->pmsav7_dregion);
}
- } else if (arm_feature(env, ARM_FEATURE_V7)) {
+ } else if (arm_feature(env, ARM_FEATURE_V7) ||
+ arm_feature(env, ARM_FEATURE_M)) {
memset(env->pmsav7.drbar, 0,
sizeof(*env->pmsav7.drbar) * cpu->pmsav7_dregion);
memset(env->pmsav7.drsr, 0,
@@ -1656,7 +1657,11 @@ static void arm_cpu_post_init(Object *obj)
#ifndef CONFIG_USER_ONLY
if (arm_feature(&cpu->env, ARM_FEATURE_PMSA)) {
qdev_property_add_static(DEVICE(obj), &arm_cpu_has_mpu_property);
- if (arm_feature(&cpu->env, ARM_FEATURE_V7)) {
+ /*
+ * QEMU's PMSAv7 state also models the Armv6-M MPU register layout.
+ */
+ if (arm_feature(&cpu->env, ARM_FEATURE_V7) ||
+ arm_feature(&cpu->env, ARM_FEATURE_M)) {
qdev_property_add_static(DEVICE(obj),
&arm_cpu_pmsav7_dregion_property);
}
@@ -2332,7 +2337,8 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
}
if (arm_feature(env, ARM_FEATURE_PMSA) &&
- arm_feature(env, ARM_FEATURE_V7)) {
+ (arm_feature(env, ARM_FEATURE_V7) ||
+ arm_feature(env, ARM_FEATURE_M))) {
uint32_t nr = cpu->pmsav7_dregion;
if (nr > 0xff) {
diff --git a/target/arm/ptw.c b/target/arm/ptw.c
index a29de0385f..b366a8d3dd 100644
--- a/target/arm/ptw.c
+++ b/target/arm/ptw.c
@@ -3901,7 +3901,8 @@ static bool get_phys_addr_nogpc(CPUARMState *env, S1Translate *ptw,
/* PMSAv8 */
ret = get_phys_addr_pmsav8(env, ptw, address, access_type,
result, fi);
- } else if (arm_feature(env, ARM_FEATURE_V7)) {
+ } else if (arm_feature(env, ARM_FEATURE_V7) ||
+ arm_feature(env, ARM_FEATURE_M)) {
/* PMSAv7 */
ret = get_phys_addr_pmsav7(env, ptw, address, access_type,
result, fi);
diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c
index f4ba93b291..667b2a3cbd 100644
--- a/target/arm/tcg/m_helper.c
+++ b/target/arm/tcg/m_helper.c
@@ -16,6 +16,7 @@
#include "qemu/bitops.h"
#include "qemu/log.h"
#include "exec/page-protection.h"
+#include "exec/cputlb.h"
#ifdef CONFIG_TCG
#include "accel/tcg/cpu-ldst-common.h"
#include "semihosting/common-semi.h"
@@ -2775,9 +2776,16 @@ void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
!arm_v7m_is_handler_mode(env))) {
write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
}
- if (cur_el > 0 && arm_feature(env, ARM_FEATURE_M_MAIN)) {
+ if (cur_el > 0 && (arm_feature(env, ARM_FEATURE_M_MAIN) ||
+ env_archcpu(env)->has_mpu)) {
+ uint32_t old_control = env->v7m.control[env->v7m.secure];
+
env->v7m.control[env->v7m.secure] &= ~R_V7M_CONTROL_NPRIV_MASK;
env->v7m.control[env->v7m.secure] |= val & R_V7M_CONTROL_NPRIV_MASK;
+ if ((old_control ^ env->v7m.control[env->v7m.secure]) &
+ R_V7M_CONTROL_NPRIV_MASK) {
+ tlb_flush(env_cpu(env));
+ }
}
if (cpu_isar_feature(aa32_vfp_simd, env_archcpu(env))) {
/*
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 01/30] target/arm: support Cortex-M0+ MPU
2026-08-29 23:42 ` [PATCH RFC v2 01/30] target/arm: support Cortex-M0+ MPU Gilles Grimaud
@ 2026-09-01 17:52 ` Alex Bennée
2026-09-01 19:23 ` Peter Maydell
0 siblings, 1 reply; 49+ messages in thread
From: Alex Bennée @ 2026-09-01 17:52 UTC (permalink / raw)
To: Gilles Grimaud; +Cc: qemu-devel, qemu-arm, Peter Maydell
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Cortex-M0+ implementations may provide an Armv6 PMSA MPU with eight regions. QEMU already marks M-profile CPUs as PMSA, but only exposes and allocates the existing region model for Armv7 CPUs. This prevents a Cortex-M0+ board from enabling its optional MPU.
>
Just a comment for all patches, you should ensure your editor wraps at
80 columns (72 for title) as patches are often read on terminals.
It's good habit to run through checkpatch.pl to pick up the minor
oft-forgotten stuff.
> Use QEMU's PMSAv7 state and translation path for the compatible
> Armv6-M MPU register layout. Permit CONTROL.nPRIV changes when the
> optional MPU is present and clear the region state on reset.
Hmm will need to think about this - the file is explicitly targeting the
armv7_nvic so we might want to consider renaming if indeed special
casing the differences is the right thing to do.
That does make the spread of changes about both nvic and cpu hard to
review. Is env->pmsav7.drbar related to env.v7m.vecbase[!attrs.secure]
for example.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> hw/intc/armv7m_nvic.c | 3 +++
> target/arm/cpu.c | 12 +++++++++---
> target/arm/ptw.c | 3 ++-
> target/arm/tcg/m_helper.c | 10 +++++++++-
> 4 files changed, 23 insertions(+), 5 deletions(-)
>
> diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
> index a7651f831e..3140599ac1 100644
> --- a/hw/intc/armv7m_nvic.c
> +++ b/hw/intc/armv7m_nvic.c
> @@ -1626,6 +1626,9 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
> break;
> case 0xd08: /* Vector Table Offset. */
> cpu->env.v7m.vecbase[attrs.secure] = value & 0xffffff80;
> + if (!arm_feature(&cpu->env, ARM_FEATURE_M_SECURITY)) {
> + cpu->env.v7m.vecbase[!attrs.secure] = value & 0xffffff80;
> + }
> break;
> case 0xd0c: /* Application Interrupt/Reset Control (AIRCR) */
> if ((value >> R_V7M_AIRCR_VECTKEY_SHIFT) == 0x05fa) {
> diff --git a/target/arm/cpu.c b/target/arm/cpu.c
> index 77aa78f00e..f58a1db843 100644
> --- a/target/arm/cpu.c
> +++ b/target/arm/cpu.c
> @@ -617,7 +617,8 @@ static void arm_cpu_reset_hold(Object *obj, ResetType type)
> sizeof(*env->pmsav8.rlar[M_REG_S])
> * cpu->pmsav7_dregion);
> }
> - } else if (arm_feature(env, ARM_FEATURE_V7)) {
> + } else if (arm_feature(env, ARM_FEATURE_V7) ||
> + arm_feature(env, ARM_FEATURE_M)) {
> memset(env->pmsav7.drbar, 0,
> sizeof(*env->pmsav7.drbar) * cpu->pmsav7_dregion);
> memset(env->pmsav7.drsr, 0,
> @@ -1656,7 +1657,11 @@ static void arm_cpu_post_init(Object *obj)
> #ifndef CONFIG_USER_ONLY
> if (arm_feature(&cpu->env, ARM_FEATURE_PMSA)) {
> qdev_property_add_static(DEVICE(obj), &arm_cpu_has_mpu_property);
> - if (arm_feature(&cpu->env, ARM_FEATURE_V7)) {
> + /*
> + * QEMU's PMSAv7 state also models the Armv6-M MPU register layout.
> + */
> + if (arm_feature(&cpu->env, ARM_FEATURE_V7) ||
> + arm_feature(&cpu->env, ARM_FEATURE_M)) {
> qdev_property_add_static(DEVICE(obj),
> &arm_cpu_pmsav7_dregion_property);
> }
> @@ -2332,7 +2337,8 @@ static void arm_cpu_realizefn(DeviceState *dev, Error **errp)
> }
>
> if (arm_feature(env, ARM_FEATURE_PMSA) &&
> - arm_feature(env, ARM_FEATURE_V7)) {
> + (arm_feature(env, ARM_FEATURE_V7) ||
> + arm_feature(env, ARM_FEATURE_M))) {
> uint32_t nr = cpu->pmsav7_dregion;
>
> if (nr > 0xff) {
> diff --git a/target/arm/ptw.c b/target/arm/ptw.c
> index a29de0385f..b366a8d3dd 100644
> --- a/target/arm/ptw.c
> +++ b/target/arm/ptw.c
> @@ -3901,7 +3901,8 @@ static bool get_phys_addr_nogpc(CPUARMState *env, S1Translate *ptw,
> /* PMSAv8 */
> ret = get_phys_addr_pmsav8(env, ptw, address, access_type,
> result, fi);
> - } else if (arm_feature(env, ARM_FEATURE_V7)) {
> + } else if (arm_feature(env, ARM_FEATURE_V7) ||
> + arm_feature(env, ARM_FEATURE_M)) {
> /* PMSAv7 */
And again by definition this is PMSAv7, not v6 - you'll note we have
plenty of places where we do check against ARM_FEATURE_V6 (or V6K).
> ret = get_phys_addr_pmsav7(env, ptw, address, access_type,
> result, fi);
> diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c
> index f4ba93b291..667b2a3cbd 100644
> --- a/target/arm/tcg/m_helper.c
> +++ b/target/arm/tcg/m_helper.c
> @@ -16,6 +16,7 @@
> #include "qemu/bitops.h"
> #include "qemu/log.h"
> #include "exec/page-protection.h"
> +#include "exec/cputlb.h"
> #ifdef CONFIG_TCG
> #include "accel/tcg/cpu-ldst-common.h"
> #include "semihosting/common-semi.h"
> @@ -2775,9 +2776,16 @@ void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
> !arm_v7m_is_handler_mode(env))) {
> write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
> }
> - if (cur_el > 0 && arm_feature(env, ARM_FEATURE_M_MAIN)) {
> + if (cur_el > 0 && (arm_feature(env, ARM_FEATURE_M_MAIN) ||
> + env_archcpu(env)->has_mpu)) {
> + uint32_t old_control = env->v7m.control[env->v7m.secure];
> +
> env->v7m.control[env->v7m.secure] &= ~R_V7M_CONTROL_NPRIV_MASK;
> env->v7m.control[env->v7m.secure] |= val & R_V7M_CONTROL_NPRIV_MASK;
> + if ((old_control ^ env->v7m.control[env->v7m.secure]) &
> + R_V7M_CONTROL_NPRIV_MASK) {
> + tlb_flush(env_cpu(env));
> + }
> }
> if (cpu_isar_feature(aa32_vfp_simd, env_archcpu(env))) {
> /*
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 01/30] target/arm: support Cortex-M0+ MPU
2026-09-01 17:52 ` Alex Bennée
@ 2026-09-01 19:23 ` Peter Maydell
0 siblings, 0 replies; 49+ messages in thread
From: Peter Maydell @ 2026-09-01 19:23 UTC (permalink / raw)
To: Alex Bennée; +Cc: Gilles Grimaud, qemu-devel, qemu-arm
On Tue, 1 Sept 2026 at 18:52, Alex Bennée <alex.bennee@linaro.org> wrote:
>
> Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
>
> > From: gilles grimaud <gilles.grimaud@univ-lille.fr>
> >
> > Cortex-M0+ implementations may provide an Armv6 PMSA MPU with eight regions. QEMU already marks M-profile CPUs as PMSA, but only exposes and allocates the existing region model for Armv7 CPUs. This prevents a Cortex-M0+ board from enabling its optional MPU.
> >
>
> Just a comment for all patches, you should ensure your editor wraps at
> 80 columns (72 for title) as patches are often read on terminals.
>
> It's good habit to run through checkpatch.pl to pick up the minor
> oft-forgotten stuff.
>
> > Use QEMU's PMSAv7 state and translation path for the compatible
> > Armv6-M MPU register layout. Permit CONTROL.nPRIV changes when the
> > optional MPU is present and clear the region state on reset.
>
> Hmm will need to think about this - the file is explicitly targeting the
> armv7_nvic so we might want to consider renaming if indeed special
> casing the differences is the right thing to do.
All the M-profile code is shot through with "v7m" naming for historical
reasons. It is all intended to cover v6M, v7M and v8M.
v6M is awkward because its naming is more marketing driven than
related to the progression of A-profile versions. Really v6M is
more like "v7M without the Main Extension" -- it's a cut-down
v7M, not a predecessor to it. v8M tidies up the naming here
by making "is cut-down" orthogonal to the architectural version
number.
It is tempting to wonder if we should model v6M CPUs by having
them actually set ARM_FEATURE_M + ARM_FEATURE_V7 but not
ARM_FEATURE_M_MAIN. But that could quite easily have just as
many annoying corner cases only the other way around...
> > Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> > ---
> > hw/intc/armv7m_nvic.c | 3 +++
> > target/arm/cpu.c | 12 +++++++++---
> > target/arm/ptw.c | 3 ++-
> > target/arm/tcg/m_helper.c | 10 +++++++++-
> > 4 files changed, 23 insertions(+), 5 deletions(-)
> >
> > diff --git a/hw/intc/armv7m_nvic.c b/hw/intc/armv7m_nvic.c
> > index a7651f831e..3140599ac1 100644
> > --- a/hw/intc/armv7m_nvic.c
> > +++ b/hw/intc/armv7m_nvic.c
> > @@ -1626,6 +1626,9 @@ static void nvic_writel(NVICState *s, uint32_t offset, uint32_t value,
> > break;
> > case 0xd08: /* Vector Table Offset. */
> > cpu->env.v7m.vecbase[attrs.secure] = value & 0xffffff80;
> > + if (!arm_feature(&cpu->env, ARM_FEATURE_M_SECURITY)) {
> > + cpu->env.v7m.vecbase[!attrs.secure] = value & 0xffffff80;
> > + }
This looks dubious and also unrelated to what the commit message
says the change is doing. The VTOR exists whether the CPU has
the Security extension or not.
> > diff --git a/target/arm/tcg/m_helper.c b/target/arm/tcg/m_helper.c
> > index f4ba93b291..667b2a3cbd 100644
> > --- a/target/arm/tcg/m_helper.c
> > +++ b/target/arm/tcg/m_helper.c
> > @@ -16,6 +16,7 @@
> > #include "qemu/bitops.h"
> > #include "qemu/log.h"
> > #include "exec/page-protection.h"
> > +#include "exec/cputlb.h"
> > #ifdef CONFIG_TCG
> > #include "accel/tcg/cpu-ldst-common.h"
> > #include "semihosting/common-semi.h"
> > @@ -2775,9 +2776,16 @@ void HELPER(v7m_msr)(CPUARMState *env, uint32_t maskreg, uint32_t val)
> > !arm_v7m_is_handler_mode(env))) {
> > write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) != 0);
> > }
> > - if (cur_el > 0 && arm_feature(env, ARM_FEATURE_M_MAIN)) {
> > + if (cur_el > 0 && (arm_feature(env, ARM_FEATURE_M_MAIN) ||
> > + env_archcpu(env)->has_mpu)) {
Why are we testing against has_mpu here ?
> > + uint32_t old_control = env->v7m.control[env->v7m.secure];
> > +
> > env->v7m.control[env->v7m.secure] &= ~R_V7M_CONTROL_NPRIV_MASK;
> > env->v7m.control[env->v7m.secure] |= val & R_V7M_CONTROL_NPRIV_MASK;
> > + if ((old_control ^ env->v7m.control[env->v7m.secure]) &
> > + R_V7M_CONTROL_NPRIV_MASK) {
> > + tlb_flush(env_cpu(env));
Where has this tlb_flush() appeared from, and why?
> > + }
> > }
> > if (cpu_isar_feature(aa32_vfp_simd, env_archcpu(env))) {
> > /*
-- PMM
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 02/30] hw/char/pl011: expose DMA request outputs
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 01/30] target/arm: support Cortex-M0+ MPU Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 03/30] hw/misc: add RP2040 diagnostic helpers Gilles Grimaud
` (28 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Peter Maydell,
Marc-André Lureau, Paolo Bonzini, Fabiano Rosas,
Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Expose named TX and RX DMA request GPIO outputs from the PL011 model. Update them when UARTDMACR or FIFO state changes so SoC DMA controllers can consume PL011 pacing without device-specific callbacks.
Add a qtest covering TX and RX request assertion and deassertion.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
MAINTAINERS | 1 +
hw/char/pl011.c | 29 ++++++++++--
include/hw/char/pl011.h | 2 +
tests/qtest/meson.build | 2 +
tests/qtest/pl011-test.c | 95 ++++++++++++++++++++++++++++++++++++++++
5 files changed, 125 insertions(+), 4 deletions(-)
create mode 100644 tests/qtest/pl011-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 4a49a40294..55f6f2e3c0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -763,6 +763,7 @@ L: qemu-arm@nongnu.org
S: Maintained
F: hw/char/pl011.c
F: include/hw/char/pl011.h
+F: tests/qtest/pl011-test.c
F: hw/display/pl110*
F: hw/dma/pl080.c
F: include/hw/dma/pl080.h
diff --git a/hw/char/pl011.c b/hw/char/pl011.c
index cb12c3e224..77fa29ae28 100644
--- a/hw/char/pl011.c
+++ b/hw/char/pl011.c
@@ -80,6 +80,11 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr)
#define LCR_FEN (1 << 4)
#define LCR_BRK (1 << 0)
+/* DMA Control Register, UARTDMACR */
+#define DMACR_RXDMAE (1 << 0)
+#define DMACR_TXDMAE (1 << 1)
+#define DMACR_DMAONERR (1 << 2)
+
/* Control Register, UARTCR */
#define CR_OUT2 (1 << 13)
#define CR_OUT1 (1 << 12)
@@ -157,6 +162,15 @@ static inline unsigned pl011_get_fifo_depth(PL011State *s)
return pl011_is_fifo_enabled(s) ? PL011_FIFO_DEPTH : 1;
}
+static void pl011_update_dreq(PL011State *s)
+{
+ qemu_set_irq(s->dreq_tx,
+ (s->dmacr & DMACR_TXDMAE) &&
+ !(s->flags & PL011_FLAG_TXFF));
+ qemu_set_irq(s->dreq_rx,
+ (s->dmacr & DMACR_RXDMAE) && s->read_count > 0);
+}
+
static inline void pl011_reset_rx_fifo(PL011State *s)
{
s->read_count = 0;
@@ -190,6 +204,7 @@ static void pl011_fifo_rx_put(void *opaque, uint32_t value)
trace_pl011_fifo_rx_full();
s->flags |= PL011_FLAG_RXFF;
}
+ pl011_update_dreq(s);
if (s->read_count == s->read_trigger) {
s->int_level |= INT_RX;
pl011_update(s);
@@ -258,6 +273,7 @@ static void pl011_write_txdata(PL011State *s, uint8_t data)
pl011_loopback_tx(s, data);
s->int_level |= INT_TX;
pl011_update(s);
+ pl011_update_dreq(s);
}
static uint32_t pl011_read_rxdata(PL011State *s)
@@ -280,6 +296,7 @@ static uint32_t pl011_read_rxdata(PL011State *s)
trace_pl011_read_fifo(s->read_count, fifo_depth);
s->rsr = c >> 8;
pl011_update(s);
+ pl011_update_dreq(s);
qemu_chr_fe_accept_input(&s->chr);
return c;
}
@@ -469,6 +486,7 @@ static void pl011_write(void *opaque, hwaddr offset,
}
s->lcr = value;
pl011_set_read_trigger(s);
+ pl011_update_dreq(s);
break;
case 12: /* UARTCR */
/* ??? Need to implement the enable bit. */
@@ -492,10 +510,8 @@ static void pl011_write(void *opaque, hwaddr offset,
pl011_update(s);
break;
case 18: /* UARTDMACR */
- s->dmacr = value;
- if (value & 3) {
- qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n");
- }
+ s->dmacr = value & (DMACR_RXDMAE | DMACR_TXDMAE | DMACR_DMAONERR);
+ pl011_update_dreq(s);
break;
default:
qemu_log_mask(LOG_GUEST_ERROR,
@@ -603,6 +619,8 @@ static int pl011_post_load(void *opaque, int version_id)
s->ibrd &= IBRD_MASK;
s->fbrd &= FBRD_MASK;
+ s->dmacr &= DMACR_RXDMAE | DMACR_TXDMAE | DMACR_DMAONERR;
+ pl011_update_dreq(s);
return 0;
}
@@ -653,6 +671,8 @@ static void pl011_init(Object *obj)
for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
sysbus_init_irq(sbd, &s->irq[i]);
}
+ qdev_init_gpio_out_named(DEVICE(obj), &s->dreq_tx, "dreq-tx", 1);
+ qdev_init_gpio_out_named(DEVICE(obj), &s->dreq_rx, "dreq-rx", 1);
s->clk = qdev_init_clock_in(DEVICE(obj), "clk", pl011_clock_update, s,
ClockUpdate);
@@ -687,6 +707,7 @@ static void pl011_reset(DeviceState *dev)
s->logged_disabled_uart = false;
pl011_reset_rx_fifo(s);
pl011_reset_tx_fifo(s);
+ pl011_update_dreq(s);
}
static void pl011_class_init(ObjectClass *oc, const void *data)
diff --git a/include/hw/char/pl011.h b/include/hw/char/pl011.h
index 5695787650..c7b5a3a2b3 100644
--- a/include/hw/char/pl011.h
+++ b/include/hw/char/pl011.h
@@ -49,6 +49,8 @@ struct PL011State {
int read_trigger;
CharFrontend chr;
qemu_irq irq[6];
+ qemu_irq dreq_tx;
+ qemu_irq dreq_rx;
Clock *clk;
bool migrate_clk;
bool logged_disabled_uart;
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index cbdef5a545..5555ce4eae 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -240,6 +240,8 @@ qtests_stm32l4x5 = \
'stm32l4x5_usart-test']
qtests_arm = \
+ (config_all_devices.has_key('CONFIG_ARM_VIRT') and
+ config_all_devices.has_key('CONFIG_PL011_C') ? ['pl011-test'] : []) + \
(config_all_devices.has_key('CONFIG_MPS2') ? ['sse-timer-test'] : []) + \
(config_all_devices.has_key('CONFIG_CMSDK_APB_DUALTIMER') ? ['cmsdk-apb-dualtimer-test'] : []) + \
(config_all_devices.has_key('CONFIG_CMSDK_APB_TIMER') ? ['cmsdk-apb-timer-test'] : []) + \
diff --git a/tests/qtest/pl011-test.c b/tests/qtest/pl011-test.c
new file mode 100644
index 0000000000..30722cf9e2
--- /dev/null
+++ b/tests/qtest/pl011-test.c
@@ -0,0 +1,95 @@
+/*
+ * QTest testcase for the Arm PL011 UART DMA request outputs.
+ *
+ * Copyright (c) 2026 Gilles Grimaud
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qobject/qdict.h"
+#include "qobject/qlist.h"
+#include "libqtest.h"
+
+#define PL011_BASE 0x09000000
+#define PL011_DR 0x000
+#define PL011_CR 0x030
+#define PL011_DMACR 0x048
+
+#define CR_RXE (1 << 9)
+#define CR_TXE (1 << 8)
+#define CR_LBE (1 << 7)
+#define CR_UARTEN (1 << 0)
+
+#define DMACR_RXDMAE (1 << 0)
+#define DMACR_TXDMAE (1 << 1)
+
+static char *find_pl011_path(QTestState *qts)
+{
+ g_autoptr(QDict) response = NULL;
+ QListEntry *entry;
+ QList *children;
+
+ response = qtest_qmp(qts,
+ "{ 'execute': 'qom-list',"
+ " 'arguments': { 'path': '/machine/unattached' } }");
+ children = qdict_get_qlist(response, "return");
+
+ QLIST_FOREACH_ENTRY(children, entry) {
+ QDict *child = qobject_to(QDict, qlist_entry_obj(entry));
+
+ if (g_str_equal(qdict_get_str(child, "type"), "child<pl011>")) {
+ return g_strdup_printf("/machine/unattached/%s",
+ qdict_get_str(child, "name"));
+ }
+ }
+
+ g_assert_not_reached();
+}
+
+static void test_tx_dreq(void)
+{
+ QTestState *qts = qtest_init("-M virt");
+ g_autofree char *path = find_pl011_path(qts);
+
+ qtest_irq_intercept_out_named(qts, path, "dreq-tx");
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ qtest_writel(qts, PL011_BASE + PL011_DMACR, DMACR_TXDMAE);
+ g_assert_true(qtest_get_irq(qts, 0));
+ g_assert_cmphex(qtest_readl(qts, PL011_BASE + PL011_DMACR), ==,
+ DMACR_TXDMAE);
+
+ qtest_writel(qts, PL011_BASE + PL011_DMACR, 0);
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ qtest_quit(qts);
+}
+
+static void test_rx_dreq(void)
+{
+ QTestState *qts = qtest_init("-M virt");
+ g_autofree char *path = find_pl011_path(qts);
+
+ qtest_irq_intercept_out_named(qts, path, "dreq-rx");
+ qtest_writel(qts, PL011_BASE + PL011_CR,
+ CR_RXE | CR_TXE | CR_LBE | CR_UARTEN);
+ qtest_writel(qts, PL011_BASE + PL011_DMACR, DMACR_RXDMAE);
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ qtest_writel(qts, PL011_BASE + PL011_DR, 'x');
+ g_assert_true(qtest_get_irq(qts, 0));
+ g_assert_cmphex(qtest_readl(qts, PL011_BASE + PL011_DR), ==, 'x');
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/pl011/dreq/tx", test_tx_dreq);
+ qtest_add_func("/pl011/dreq/rx", test_rx_dreq);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 03/30] hw/misc: add RP2040 diagnostic helpers
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 01/30] target/arm: support Cortex-M0+ MPU Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 02/30] hw/char/pl011: expose DMA request outputs Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-01 18:30 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 04/30] hw/arm: add RP2040 SoC and Raspberry Pi Pico machine Gilles Grimaud
` (27 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Add shared helpers for reporting unimplemented RP2040 features and register accesses. Keeping the formatting in one place makes shallow peripheral models explicit and gives their diagnostics a consistent component, address, offset and value format.
Add a dedicated Kconfig symbol so the helpers remain disabled until selected by the RP2040 SoC.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
MAINTAINERS | 7 ++++++
hw/misc/Kconfig | 3 +++
hw/misc/meson.build | 1 +
hw/misc/rp2040_nyi.c | 47 ++++++++++++++++++++++++++++++++++++
include/hw/misc/rp2040_nyi.h | 19 +++++++++++++++
5 files changed, 77 insertions(+)
create mode 100644 hw/misc/rp2040_nyi.c
create mode 100644 include/hw/misc/rp2040_nyi.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 55f6f2e3c0..db641d9c4c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1035,6 +1035,13 @@ F: docs/system/arm/raspi.rst
F: tests/functional/arm/test_raspi2.py
F: tests/functional/aarch64/test_raspi*.py
+Raspberry Pi Pico / RP2040
+M: Gilles Grimaud <gilles.grimaud@univ-lille.fr>
+L: qemu-arm@nongnu.org
+S: Maintained
+F: hw/*/rp2040*
+F: include/hw/*/rp2040*
+
Real View
M: Peter Maydell <peter.maydell@linaro.org>
L: qemu-arm@nongnu.org
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 46e3c03cc8..de7a0f56fa 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -101,6 +101,9 @@ config FSL_IMX8MP_ANALOG
config FSL_IMX8MP_CCM
bool
+config RP2040_NYI
+ bool
+
config STM32_RCC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 54e07aacda..8611d59437 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -98,6 +98,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_cprman.c',
'bcm2835_powermgt.c',
))
+system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
diff --git a/hw/misc/rp2040_nyi.c b/hw/misc/rp2040_nyi.c
new file mode 100644
index 0000000000..7e9e4c09a1
--- /dev/null
+++ b/hw/misc/rp2040_nyi.c
@@ -0,0 +1,47 @@
+/*
+ * RP2040 "not yet implemented" diagnostics
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "qemu/log.h"
+
+void rp2040_log_nyi(const char *component, const char *feature,
+ const char *detail)
+{
+ if (g_str_has_prefix(component, "rp2040.")) {
+ component += strlen("rp2040.");
+ }
+
+ qemu_log_mask(LOG_UNIMP, "Not yet implemented: rp2040.%s: %s%s%s\n",
+ component, feature, detail ? ": " : "",
+ detail ? detail : "");
+}
+
+void rp2040_log_unimplemented_read(const char *component, unsigned size,
+ uint64_t addr, uint64_t offset,
+ uint64_t value)
+{
+ char detail[128];
+
+ snprintf(detail, sizeof(detail),
+ "size %u, addr 0x%08" PRIx64 ", offset 0x%04" PRIx64
+ " -> 0x%0*" PRIx64,
+ size, addr, offset, size << 1, value);
+ rp2040_log_nyi(component, "unimplemented read", detail);
+}
+
+void rp2040_log_unimplemented_write(const char *component, unsigned size,
+ uint64_t addr, uint64_t offset,
+ uint64_t value)
+{
+ char detail[128];
+
+ snprintf(detail, sizeof(detail),
+ "size %u, addr 0x%08" PRIx64 ", offset 0x%04" PRIx64
+ ", value 0x%0*" PRIx64,
+ size, addr, offset, size << 1, value);
+ rp2040_log_nyi(component, "unimplemented write", detail);
+}
diff --git a/include/hw/misc/rp2040_nyi.h b/include/hw/misc/rp2040_nyi.h
new file mode 100644
index 0000000000..c23805973b
--- /dev/null
+++ b/include/hw/misc/rp2040_nyi.h
@@ -0,0 +1,19 @@
+/*
+ * RP2040 "not yet implemented" diagnostics
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_NYI_H
+#define HW_MISC_RP2040_NYI_H
+
+void rp2040_log_nyi(const char *component, const char *feature,
+ const char *detail);
+void rp2040_log_unimplemented_read(const char *component, unsigned size,
+ uint64_t addr, uint64_t offset,
+ uint64_t value);
+void rp2040_log_unimplemented_write(const char *component, unsigned size,
+ uint64_t addr, uint64_t offset,
+ uint64_t value);
+
+#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 03/30] hw/misc: add RP2040 diagnostic helpers
2026-08-29 23:42 ` [PATCH RFC v2 03/30] hw/misc: add RP2040 diagnostic helpers Gilles Grimaud
@ 2026-09-01 18:30 ` Alex Bennée
2026-09-01 22:12 ` gilles grimaud
0 siblings, 1 reply; 49+ messages in thread
From: Alex Bennée @ 2026-09-01 18:30 UTC (permalink / raw)
To: Gilles Grimaud; +Cc: qemu-devel, qemu-arm, Paolo Bonzini
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Add shared helpers for reporting unimplemented RP2040 features and register accesses. Keeping the formatting in one place makes shallow peripheral models explicit and gives their diagnostics a consistent component, address, offset and value format.
>
> Add a dedicated Kconfig symbol so the helpers remain disabled until
> selected by the RP2040 SoC.
This seems over-engineered. What is wrong with:
qemu_log_mask(LOG_UNIMP, "%s: ....", __func__, ...);
In the various call sites. Most of the funcs seem to be prefixed anyway.
In the meantime you can instantiate TYPE_UNIMPLEMENTED_DEVICE for any
memory regions you have no implementation for.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> MAINTAINERS | 7 ++++++
> hw/misc/Kconfig | 3 +++
> hw/misc/meson.build | 1 +
> hw/misc/rp2040_nyi.c | 47 ++++++++++++++++++++++++++++++++++++
> include/hw/misc/rp2040_nyi.h | 19 +++++++++++++++
> 5 files changed, 77 insertions(+)
> create mode 100644 hw/misc/rp2040_nyi.c
> create mode 100644 include/hw/misc/rp2040_nyi.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 55f6f2e3c0..db641d9c4c 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1035,6 +1035,13 @@ F: docs/system/arm/raspi.rst
> F: tests/functional/arm/test_raspi2.py
> F: tests/functional/aarch64/test_raspi*.py
>
> +Raspberry Pi Pico / RP2040
> +M: Gilles Grimaud <gilles.grimaud@univ-lille.fr>
> +L: qemu-arm@nongnu.org
> +S: Maintained
> +F: hw/*/rp2040*
> +F: include/hw/*/rp2040*
> +
> Real View
> M: Peter Maydell <peter.maydell@linaro.org>
> L: qemu-arm@nongnu.org
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 46e3c03cc8..de7a0f56fa 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -101,6 +101,9 @@ config FSL_IMX8MP_ANALOG
> config FSL_IMX8MP_CCM
> bool
>
> +config RP2040_NYI
> + bool
> +
> config STM32_RCC
> bool
>
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 54e07aacda..8611d59437 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -98,6 +98,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
> 'bcm2835_cprman.c',
> 'bcm2835_powermgt.c',
> ))
> +system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
> system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
> system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
> system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
> diff --git a/hw/misc/rp2040_nyi.c b/hw/misc/rp2040_nyi.c
> new file mode 100644
> index 0000000000..7e9e4c09a1
> --- /dev/null
> +++ b/hw/misc/rp2040_nyi.c
> @@ -0,0 +1,47 @@
> +/*
> + * RP2040 "not yet implemented" diagnostics
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/misc/rp2040_nyi.h"
> +#include "qemu/log.h"
> +
> +void rp2040_log_nyi(const char *component, const char *feature,
> + const char *detail)
> +{
> + if (g_str_has_prefix(component, "rp2040.")) {
> + component += strlen("rp2040.");
> + }
> +
> + qemu_log_mask(LOG_UNIMP, "Not yet implemented: rp2040.%s: %s%s%s\n",
> + component, feature, detail ? ": " : "",
> + detail ? detail : "");
> +}
> +
> +void rp2040_log_unimplemented_read(const char *component, unsigned size,
> + uint64_t addr, uint64_t offset,
> + uint64_t value)
> +{
> + char detail[128];
> +
> + snprintf(detail, sizeof(detail),
> + "size %u, addr 0x%08" PRIx64 ", offset 0x%04" PRIx64
> + " -> 0x%0*" PRIx64,
> + size, addr, offset, size << 1, value);
> + rp2040_log_nyi(component, "unimplemented read", detail);
> +}
> +
> +void rp2040_log_unimplemented_write(const char *component, unsigned size,
> + uint64_t addr, uint64_t offset,
> + uint64_t value)
> +{
> + char detail[128];
> +
> + snprintf(detail, sizeof(detail),
> + "size %u, addr 0x%08" PRIx64 ", offset 0x%04" PRIx64
> + ", value 0x%0*" PRIx64,
> + size, addr, offset, size << 1, value);
> + rp2040_log_nyi(component, "unimplemented write", detail);
> +}
> diff --git a/include/hw/misc/rp2040_nyi.h b/include/hw/misc/rp2040_nyi.h
> new file mode 100644
> index 0000000000..c23805973b
> --- /dev/null
> +++ b/include/hw/misc/rp2040_nyi.h
> @@ -0,0 +1,19 @@
> +/*
> + * RP2040 "not yet implemented" diagnostics
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_RP2040_NYI_H
> +#define HW_MISC_RP2040_NYI_H
> +
> +void rp2040_log_nyi(const char *component, const char *feature,
> + const char *detail);
> +void rp2040_log_unimplemented_read(const char *component, unsigned size,
> + uint64_t addr, uint64_t offset,
> + uint64_t value);
> +void rp2040_log_unimplemented_write(const char *component, unsigned size,
> + uint64_t addr, uint64_t offset,
> + uint64_t value);
> +
> +#endif
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 03/30] hw/misc: add RP2040 diagnostic helpers
2026-09-01 18:30 ` Alex Bennée
@ 2026-09-01 22:12 ` gilles grimaud
0 siblings, 0 replies; 49+ messages in thread
From: gilles grimaud @ 2026-09-01 22:12 UTC (permalink / raw)
To: Alex Bennée; +Cc: qemu-devel, qemu-arm, Paolo Bonzini
[-- Attachment #1: Type: text/plain, Size: 6371 bytes --]
Hi Alex,
Thanks, I agree that the shared diagnostic layer is unnecessary.
I will drop this patch, including its Kconfig symbol. Completely
unimplemented MMIO regions will continue to use
TYPE_UNIMPLEMENTED_DEVICE, while partial device models will use local
qemu_log_mask(LOG_UNIMP, ...) calls where a diagnostic remains useful.
Those diagnostics will therefore be introduced together with the device
that owns the corresponding register or behavior.
Thanks,
--
Gilles Grimaud - 2XS/SISE - CRIStAL/IRCICA - CNRS/Univ. Lille
> Le 1 sept. 2026 à 20:30, Alex Bennée <alex.bennee@linaro.org> a écrit :
>
> Gilles Grimaud <gilles.grimaud@univ-lille.fr <mailto:gilles.grimaud@univ-lille.fr>> writes:
>
>> From: gilles grimaud <gilles.grimaud@univ-lille.fr <mailto:gilles.grimaud@univ-lille.fr>>
>>
>> Add shared helpers for reporting unimplemented RP2040 features and register accesses. Keeping the formatting in one place makes shallow peripheral models explicit and gives their diagnostics a consistent component, address, offset and value format.
>>
>> Add a dedicated Kconfig symbol so the helpers remain disabled until
>> selected by the RP2040 SoC.
>
> This seems over-engineered. What is wrong with:
>
> qemu_log_mask(LOG_UNIMP, "%s: ....", __func__, ...);
>
> In the various call sites. Most of the funcs seem to be prefixed anyway.
>
> In the meantime you can instantiate TYPE_UNIMPLEMENTED_DEVICE for any
> memory regions you have no implementation for.
>
>>
>> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
>> ---
>> MAINTAINERS | 7 ++++++
>> hw/misc/Kconfig | 3 +++
>> hw/misc/meson.build | 1 +
>> hw/misc/rp2040_nyi.c | 47 ++++++++++++++++++++++++++++++++++++
>> include/hw/misc/rp2040_nyi.h | 19 +++++++++++++++
>> 5 files changed, 77 insertions(+)
>> create mode 100644 hw/misc/rp2040_nyi.c
>> create mode 100644 include/hw/misc/rp2040_nyi.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index 55f6f2e3c0..db641d9c4c 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -1035,6 +1035,13 @@ F: docs/system/arm/raspi.rst
>> F: tests/functional/arm/test_raspi2.py
>> F: tests/functional/aarch64/test_raspi*.py
>>
>> +Raspberry Pi Pico / RP2040
>> +M: Gilles Grimaud <gilles.grimaud@univ-lille.fr>
>> +L: qemu-arm@nongnu.org
>> +S: Maintained
>> +F: hw/*/rp2040*
>> +F: include/hw/*/rp2040*
>> +
>> Real View
>> M: Peter Maydell <peter.maydell@linaro.org>
>> L: qemu-arm@nongnu.org
>> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
>> index 46e3c03cc8..de7a0f56fa 100644
>> --- a/hw/misc/Kconfig
>> +++ b/hw/misc/Kconfig
>> @@ -101,6 +101,9 @@ config FSL_IMX8MP_ANALOG
>> config FSL_IMX8MP_CCM
>> bool
>>
>> +config RP2040_NYI
>> + bool
>> +
>> config STM32_RCC
>> bool
>>
>> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
>> index 54e07aacda..8611d59437 100644
>> --- a/hw/misc/meson.build
>> +++ b/hw/misc/meson.build
>> @@ -98,6 +98,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
>> 'bcm2835_cprman.c',
>> 'bcm2835_powermgt.c',
>> ))
>> +system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
>> system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
>> system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
>> system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
>> diff --git a/hw/misc/rp2040_nyi.c b/hw/misc/rp2040_nyi.c
>> new file mode 100644
>> index 0000000000..7e9e4c09a1
>> --- /dev/null
>> +++ b/hw/misc/rp2040_nyi.c
>> @@ -0,0 +1,47 @@
>> +/*
>> + * RP2040 "not yet implemented" diagnostics
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "hw/misc/rp2040_nyi.h"
>> +#include "qemu/log.h"
>> +
>> +void rp2040_log_nyi(const char *component, const char *feature,
>> + const char *detail)
>> +{
>> + if (g_str_has_prefix(component, "rp2040.")) {
>> + component += strlen("rp2040.");
>> + }
>> +
>> + qemu_log_mask(LOG_UNIMP, "Not yet implemented: rp2040.%s: %s%s%s\n",
>> + component, feature, detail ? ": " : "",
>> + detail ? detail : "");
>> +}
>> +
>> +void rp2040_log_unimplemented_read(const char *component, unsigned size,
>> + uint64_t addr, uint64_t offset,
>> + uint64_t value)
>> +{
>> + char detail[128];
>> +
>> + snprintf(detail, sizeof(detail),
>> + "size %u, addr 0x%08" PRIx64 ", offset 0x%04" PRIx64
>> + " -> 0x%0*" PRIx64,
>> + size, addr, offset, size << 1, value);
>> + rp2040_log_nyi(component, "unimplemented read", detail);
>> +}
>> +
>> +void rp2040_log_unimplemented_write(const char *component, unsigned size,
>> + uint64_t addr, uint64_t offset,
>> + uint64_t value)
>> +{
>> + char detail[128];
>> +
>> + snprintf(detail, sizeof(detail),
>> + "size %u, addr 0x%08" PRIx64 ", offset 0x%04" PRIx64
>> + ", value 0x%0*" PRIx64,
>> + size, addr, offset, size << 1, value);
>> + rp2040_log_nyi(component, "unimplemented write", detail);
>> +}
>> diff --git a/include/hw/misc/rp2040_nyi.h b/include/hw/misc/rp2040_nyi.h
>> new file mode 100644
>> index 0000000000..c23805973b
>> --- /dev/null
>> +++ b/include/hw/misc/rp2040_nyi.h
>> @@ -0,0 +1,19 @@
>> +/*
>> + * RP2040 "not yet implemented" diagnostics
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_MISC_RP2040_NYI_H
>> +#define HW_MISC_RP2040_NYI_H
>> +
>> +void rp2040_log_nyi(const char *component, const char *feature,
>> + const char *detail);
>> +void rp2040_log_unimplemented_read(const char *component, unsigned size,
>> + uint64_t addr, uint64_t offset,
>> + uint64_t value);
>> +void rp2040_log_unimplemented_write(const char *component, unsigned size,
>> + uint64_t addr, uint64_t offset,
>> + uint64_t value);
>> +
>> +#endif
>
> --
[-- Attachment #2: Type: text/html, Size: 19574 bytes --]
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 04/30] hw/arm: add RP2040 SoC and Raspberry Pi Pico machine
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (2 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 03/30] hw/misc: add RP2040 diagnostic helpers Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-01 18:33 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 05/30] tests/tcg/arm: add Raspberry Pi Pico smoke tests Gilles Grimaud
` (26 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Philippe Mathieu-Daudé
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Continue the Raspberry Pi Pico machine model introduced by Alex Bennée's 2022 RFC on current QEMU APIs. Add the RP2040 QOM skeleton, internal ROM and SRAM map, the board XIP window, two PL011-backed UARTs, and explicit unimplemented-device placeholders for the remaining MMIO blocks.\n\nKeep the model single-core for now. Core 1 startup and the SIO launch protocol are introduced later with the multicore support.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
MAINTAINERS | 1 +
configs/devices/arm-softmmu/default.mak | 1 +
hw/arm/Kconfig | 13 ++
hw/arm/meson.build | 2 +
hw/arm/raspi_pico.c | 85 +++++++++
hw/arm/rp2040.c | 242 ++++++++++++++++++++++++
include/hw/arm/rp2040.h | 46 +++++
7 files changed, 390 insertions(+)
create mode 100644 hw/arm/raspi_pico.c
create mode 100644 hw/arm/rp2040.c
create mode 100644 include/hw/arm/rp2040.h
diff --git a/MAINTAINERS b/MAINTAINERS
index db641d9c4c..8fd7385d83 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1041,6 +1041,7 @@ L: qemu-arm@nongnu.org
S: Maintained
F: hw/*/rp2040*
F: include/hw/*/rp2040*
+F: hw/arm/raspi_pico.c
Real View
M: Peter Maydell <peter.maydell@linaro.org>
diff --git a/configs/devices/arm-softmmu/default.mak b/configs/devices/arm-softmmu/default.mak
index 71cf164511..e7f815dd0d 100644
--- a/configs/devices/arm-softmmu/default.mak
+++ b/configs/devices/arm-softmmu/default.mak
@@ -33,6 +33,7 @@
# CONFIG_OLIMEX_STM32_H405=n
# CONFIG_MPS2=n
# CONFIG_RASPI=n
+CONFIG_RASPI_PICO=y
# CONFIG_DIGIC=n
# CONFIG_SABRELITE=n
# CONFIG_EMCRAFT_SF2=n
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 260d2f0751..d93303ab80 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -368,6 +368,19 @@ config RASPI
select BCM2835_SPI
select BCM2835_I2C
+config RASPI_PICO
+ bool
+ default y
+ depends on TCG && ARM
+ select RP2040
+
+config RP2040
+ bool
+ select ARM_V7M
+ select PL011
+ select RP2040_NYI
+ select UNIMP
+
config STM32F100_SOC
bool
select ARM_V7M
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 8ee5307a91..3240ef6637 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -32,6 +32,8 @@ arm_common_ss.add(when: 'CONFIG_RASPI', if_true: files(
'raspi.c',
'raspi4b.c'
))
+arm_common_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040.c'))
+arm_common_ss.add(when: 'CONFIG_RASPI_PICO', if_true: files('raspi_pico.c'))
arm_common_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'))
arm_common_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c'))
arm_common_ss.add(when: 'CONFIG_STM32F405_SOC', if_true: files('stm32f405_soc.c'))
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
new file mode 100644
index 0000000000..2bb7d4e511
--- /dev/null
+++ b/hw/arm/raspi_pico.c
@@ -0,0 +1,85 @@
+/*
+ * Raspberry Pi Pico machine
+ *
+ * Copyright (c) 2021 Linaro Ltd
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/arm/boot.h"
+#include "hw/arm/machines-qom.h"
+#include "hw/arm/rp2040.h"
+#include "hw/core/boards.h"
+#include "hw/core/qdev-properties.h"
+#include "system/address-spaces.h"
+#include "system/system.h"
+#include "qom/object.h"
+
+#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;
+};
+
+static void raspi_pico_init(MachineState *machine)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(machine);
+ MemoryRegion *system_memory = get_system_memory();
+
+ object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
+ qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
+ qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
+ if (machine->firmware) {
+ qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
+ machine->firmware);
+ }
+ 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.cpu, machine->kernel_filename,
+ RP2040_XIP_BASE, PICO_FLASH_SIZE);
+}
+
+static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
+{
+ MachineClass *mc = MACHINE_CLASS(oc);
+
+ mc->desc = "Raspberry Pi Pico (Cortex-M0+)";
+ mc->init = raspi_pico_init;
+ mc->default_cpus = 1;
+ mc->min_cpus = 1;
+ mc->max_cpus = 1;
+ mc->default_ram_size = 0;
+ mc->no_parallel = 1;
+ mc->no_floppy = 1;
+ mc->no_cdrom = 1;
+}
+
+static const TypeInfo raspi_pico_machine_info = {
+ .name = TYPE_RASPI_PICO_MACHINE,
+ .parent = TYPE_MACHINE,
+ .instance_size = sizeof(RaspiPicoMachineState),
+ .class_init = raspi_pico_machine_class_init,
+ .interfaces = arm_machine_interfaces,
+};
+
+static void raspi_pico_machine_init(void)
+{
+ type_register_static(&raspi_pico_machine_info);
+}
+type_init(raspi_pico_machine_init)
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
new file mode 100644
index 0000000000..2196bad18d
--- /dev/null
+++ b/hw/arm/rp2040.c
@@ -0,0 +1,242 @@
+/*
+ * RP2040 SoC emulation
+ *
+ * Copyright (c) 2021 Linaro Ltd
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/arm/rp2040.h"
+#include "hw/core/loader.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/unimp.h"
+#include "qemu/datadir.h"
+#include "target/arm/cpu-qom.h"
+
+#define RP2040_SYSCLK_FRQ 125000000
+
+#define RP2040_UART0_BASE 0x40034000
+#define RP2040_UART0_IRQ 20
+#define RP2040_UART1_BASE 0x40038000
+#define RP2040_UART1_IRQ 21
+
+/*
+ * Temporary boot ROM used until the synthetic ROM is introduced. It loads
+ * the reset handler from a vector table at the base of the XIP window.
+ */
+static const uint8_t rp2040_bootrom[] = {
+ 0x00, 0x20, 0x04, 0x20, /* initial SP: 0x20042000 */
+ 0x09, 0x00, 0x00, 0x00, /* reset handler: 0x00000009 */
+ 0x03, 0x48, /* ldr r0, vtor */
+ 0x04, 0x49, /* ldr r1, xip_base */
+ 0x01, 0x60, /* str r1, [r0] */
+ 0x04, 0x48, /* ldr r0, xip_reset_vector */
+ 0x01, 0x68, /* ldr r1, [r0] */
+ 0x08, 0x47, /* bx r1 */
+ 0xfe, 0xe7, /* b . */
+ 0xc0, 0x46, /* nop */
+ 0x08, 0xed, 0x00, 0xe0, /* VTOR: 0xe000ed08 */
+ 0x00, 0x00, 0x00, 0x10, /* XIP base: 0x10000000 */
+ 0x04, 0x00, 0x00, 0x10, /* XIP reset vector: 0x10000004 */
+};
+
+static const struct {
+ const char *name;
+ hwaddr base;
+ hwaddr size;
+} rp2040_unimplemented[] = {
+ { "rp2040.sysinfo", 0x40000000, 0x4000 },
+ { "rp2040.syscfg", 0x40004000, 0x4000 },
+ { "rp2040.clocks", 0x40008000, 0x4000 },
+ { "rp2040.resets", 0x4000c000, 0x4000 },
+ { "rp2040.psm", 0x40010000, 0x4000 },
+ { "rp2040.iobank0", 0x40014000, 0x4000 },
+ { "rp2040.ioqspi", 0x40018000, 0x4000 },
+ { "rp2040.padsbank0", 0x4001c000, 0x4000 },
+ { "rp2040.padsqspi", 0x40020000, 0x4000 },
+ { "rp2040.xosc", 0x40024000, 0x4000 },
+ { "rp2040.pll_sys", 0x40028000, 0x4000 },
+ { "rp2040.pll_usb", 0x4002c000, 0x4000 },
+ { "rp2040.busctrl", 0x40030000, 0x4000 },
+ { "rp2040.uart0_aliases", 0x40035000, 0x3000 },
+ { "rp2040.uart1_aliases", 0x40039000, 0x3000 },
+ { "rp2040.spi0", 0x4003c000, 0x4000 },
+ { "rp2040.spi1", 0x40040000, 0x4000 },
+ { "rp2040.i2c0", 0x40044000, 0x4000 },
+ { "rp2040.i2c1", 0x40048000, 0x4000 },
+ { "rp2040.adc", 0x4004c000, 0x4000 },
+ { "rp2040.pwm", 0x40050000, 0x4000 },
+ { "rp2040.timer", 0x40054000, 0x4000 },
+ { "rp2040.watchdog", 0x40058000, 0x4000 },
+ { "rp2040.rtc", 0x4005c000, 0x4000 },
+ { "rp2040.rosc", 0x40060000, 0x4000 },
+ { "rp2040.vreg_and_chip_reset", 0x40064000, 0x4000 },
+ { "rp2040.tbman", 0x4006c000, 0x4000 },
+ { "rp2040.dma", 0x50000000, 0x1000 },
+ { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
+ { "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
+ { "rp2040.pio0", 0x50200000, 0x10000 },
+ { "rp2040.pio1", 0x50300000, 0x10000 },
+ { "rp2040.sio", 0xd0000000, 0x1000 },
+};
+
+static void rp2040_soc_init(Object *obj)
+{
+ RP2040State *s = RP2040(obj);
+ int i;
+
+ object_initialize_child(obj, "proc0", &s->armv7m, TYPE_ARMV7M);
+ qdev_prop_set_string(DEVICE(&s->armv7m), "cpu-type",
+ ARM_CPU_TYPE_NAME("cortex-m0"));
+ qdev_prop_set_uint32(DEVICE(&s->armv7m), "num-irq", RP2040_NUM_IRQS);
+ qdev_prop_set_uint32(DEVICE(&s->armv7m), "mpu-ns-regions", 8);
+
+ for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
+ g_autofree char *name = g_strdup_printf("uart%d", i);
+ g_autofree char *property = g_strdup_printf("serial%d", i);
+
+ object_initialize_child(obj, name, &s->uart[i], TYPE_PL011);
+ object_property_add_alias(obj, property, OBJECT(&s->uart[i]),
+ "chardev");
+ }
+
+ s->sysclk = clock_new(obj, "sysclk");
+ clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ);
+}
+
+static bool rp2040_init_memory(RP2040State *s, Error **errp)
+{
+ int i;
+
+ if (!memory_region_init_rom(&s->rom, OBJECT(s), "rp2040.rom",
+ RP2040_ROM_SIZE, errp)) {
+ return false;
+ }
+ memory_region_add_subregion(s->board_memory, RP2040_ROM_BASE, &s->rom);
+
+ for (i = 0; i < 4; i++) {
+ g_autofree char *name = g_strdup_printf("rp2040.sram%d", i);
+
+ if (!memory_region_init_ram(&s->sram[i], OBJECT(s), name,
+ RP2040_SRAM_BANK_SIZE, errp)) {
+ return false;
+ }
+ memory_region_add_subregion(s->board_memory,
+ RP2040_SRAM_BASE +
+ i * RP2040_SRAM_BANK_SIZE,
+ &s->sram[i]);
+ }
+
+ if (!memory_region_init_ram(&s->sram[4], OBJECT(s), "rp2040.sram4",
+ RP2040_SRAM_HI_SIZE, errp)) {
+ return false;
+ }
+ memory_region_add_subregion(s->board_memory, RP2040_SRAM4_BASE,
+ &s->sram[4]);
+
+ if (!memory_region_init_ram(&s->sram[5], OBJECT(s), "rp2040.sram5",
+ RP2040_SRAM_HI_SIZE, errp)) {
+ return false;
+ }
+ memory_region_add_subregion(s->board_memory, RP2040_SRAM5_BASE,
+ &s->sram[5]);
+
+ return true;
+}
+
+static bool rp2040_load_bootrom(RP2040State *s, Error **errp)
+{
+ g_autofree char *filename = NULL;
+
+ if (!s->bootrom_file) {
+ rom_add_blob_fixed("rp2040.bootrom", rp2040_bootrom,
+ sizeof(rp2040_bootrom), RP2040_ROM_BASE);
+ return true;
+ }
+
+ filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->bootrom_file);
+ if (!filename) {
+ error_setg(errp, "could not find RP2040 boot ROM image '%s'",
+ s->bootrom_file);
+ return false;
+ }
+
+ return load_image_targphys(filename, RP2040_ROM_BASE,
+ RP2040_ROM_SIZE, errp) >= 0;
+}
+
+static void rp2040_soc_realize(DeviceState *dev, Error **errp)
+{
+ RP2040State *s = RP2040(dev);
+ static const hwaddr uart_base[] = { RP2040_UART0_BASE, RP2040_UART1_BASE };
+ static const int uart_irq[] = { RP2040_UART0_IRQ, RP2040_UART1_IRQ };
+ Error *err = NULL;
+ int i;
+
+ if (!s->board_memory) {
+ error_setg(errp, "memory property was not set");
+ return;
+ }
+ if (!rp2040_init_memory(s, errp) || !rp2040_load_bootrom(s, errp)) {
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(rp2040_unimplemented); i++) {
+ create_unimplemented_device(rp2040_unimplemented[i].name,
+ rp2040_unimplemented[i].base,
+ rp2040_unimplemented[i].size);
+ }
+
+ qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk);
+ object_property_set_link(OBJECT(&s->armv7m), "memory",
+ OBJECT(s->board_memory), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
+ return;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
+ qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
+ qdev_get_gpio_in(DEVICE(&s->armv7m), uart_irq[i]));
+ }
+}
+
+static const Property rp2040_soc_properties[] = {
+ DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGION,
+ MemoryRegion *),
+ DEFINE_PROP_STRING("bootrom-file", RP2040State, bootrom_file),
+};
+
+static void rp2040_soc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = rp2040_soc_realize;
+ device_class_set_props(dc, rp2040_soc_properties);
+}
+
+static const TypeInfo rp2040_soc_info = {
+ .name = TYPE_RP2040,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040State),
+ .instance_init = rp2040_soc_init,
+ .class_init = rp2040_soc_class_init,
+};
+
+static void rp2040_soc_types(void)
+{
+ type_register_static(&rp2040_soc_info);
+}
+type_init(rp2040_soc_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
new file mode 100644
index 0000000000..4e5cd06add
--- /dev/null
+++ b/include/hw/arm/rp2040.h
@@ -0,0 +1,46 @@
+/*
+ * RP2040 SoC emulation
+ *
+ * Copyright (c) 2021 Linaro Ltd
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_ARM_RP2040_H
+#define HW_ARM_RP2040_H
+
+#include "hw/arm/armv7m.h"
+#include "hw/char/pl011.h"
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040 "rp2040"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
+
+#define RP2040_ROM_BASE 0x00000000
+#define RP2040_ROM_SIZE (16 * KiB)
+#define RP2040_XIP_BASE 0x10000000
+#define RP2040_SRAM_BASE 0x20000000
+#define RP2040_SRAM_BANK_SIZE (64 * KiB)
+#define RP2040_SRAM4_BASE 0x20040000
+#define RP2040_SRAM5_BASE 0x20041000
+#define RP2040_SRAM_HI_SIZE (4 * KiB)
+
+#define RP2040_NUM_IRQS 32
+
+struct RP2040State {
+ SysBusDevice parent_obj;
+
+ ARMv7MState armv7m;
+ PL011State uart[2];
+
+ MemoryRegion *board_memory;
+ MemoryRegion rom;
+ MemoryRegion sram[6];
+ char *bootrom_file;
+
+ Clock *sysclk;
+};
+
+#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 04/30] hw/arm: add RP2040 SoC and Raspberry Pi Pico machine
2026-08-29 23:42 ` [PATCH RFC v2 04/30] hw/arm: add RP2040 SoC and Raspberry Pi Pico machine Gilles Grimaud
@ 2026-09-01 18:33 ` Alex Bennée
2026-09-01 22:33 ` gilles grimaud
0 siblings, 1 reply; 49+ messages in thread
From: Alex Bennée @ 2026-09-01 18:33 UTC (permalink / raw)
To: Gilles Grimaud
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell,
Philippe Mathieu-Daudé
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Continue the Raspberry Pi Pico machine model introduced by Alex Bennée's 2022 RFC on current QEMU APIs. Add the RP2040 QOM skeleton, internal ROM and SRAM map, the board XIP window, two PL011-backed UARTs, and explicit unimplemented-device placeholders for the remaining MMIO blocks.\n\nKeep the model single-core for now. Core 1 startup and the SIO launch protocol are introduced later with the multicore support.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> MAINTAINERS | 1 +
> configs/devices/arm-softmmu/default.mak | 1 +
> hw/arm/Kconfig | 13 ++
> hw/arm/meson.build | 2 +
> hw/arm/raspi_pico.c | 85 +++++++++
> hw/arm/rp2040.c | 242 ++++++++++++++++++++++++
> include/hw/arm/rp2040.h | 46 +++++
> 7 files changed, 390 insertions(+)
> create mode 100644 hw/arm/raspi_pico.c
> create mode 100644 hw/arm/rp2040.c
> create mode 100644 include/hw/arm/rp2040.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index db641d9c4c..8fd7385d83 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1041,6 +1041,7 @@ L: qemu-arm@nongnu.org
> S: Maintained
> F: hw/*/rp2040*
> F: include/hw/*/rp2040*
> +F: hw/arm/raspi_pico.c
>
> Real View
> M: Peter Maydell <peter.maydell@linaro.org>
> diff --git a/configs/devices/arm-softmmu/default.mak b/configs/devices/arm-softmmu/default.mak
> index 71cf164511..e7f815dd0d 100644
> --- a/configs/devices/arm-softmmu/default.mak
> +++ b/configs/devices/arm-softmmu/default.mak
> @@ -33,6 +33,7 @@
> # CONFIG_OLIMEX_STM32_H405=n
> # CONFIG_MPS2=n
> # CONFIG_RASPI=n
> +CONFIG_RASPI_PICO=y
> # CONFIG_DIGIC=n
> # CONFIG_SABRELITE=n
> # CONFIG_EMCRAFT_SF2=n
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 260d2f0751..d93303ab80 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -368,6 +368,19 @@ config RASPI
> select BCM2835_SPI
> select BCM2835_I2C
>
> +config RASPI_PICO
> + bool
> + default y
> + depends on TCG && ARM
> + select RP2040
> +
> +config RP2040
> + bool
> + select ARM_V7M
> + select PL011
> + select RP2040_NYI
> + select UNIMP
> +
> config STM32F100_SOC
> bool
> select ARM_V7M
> diff --git a/hw/arm/meson.build b/hw/arm/meson.build
> index 8ee5307a91..3240ef6637 100644
> --- a/hw/arm/meson.build
> +++ b/hw/arm/meson.build
> @@ -32,6 +32,8 @@ arm_common_ss.add(when: 'CONFIG_RASPI', if_true: files(
> 'raspi.c',
> 'raspi4b.c'
> ))
> +arm_common_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040.c'))
> +arm_common_ss.add(when: 'CONFIG_RASPI_PICO', if_true: files('raspi_pico.c'))
> arm_common_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'))
> arm_common_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c'))
> arm_common_ss.add(when: 'CONFIG_STM32F405_SOC', if_true: files('stm32f405_soc.c'))
> diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
> new file mode 100644
> index 0000000000..2bb7d4e511
> --- /dev/null
> +++ b/hw/arm/raspi_pico.c
> @@ -0,0 +1,85 @@
> +/*
> + * Raspberry Pi Pico machine
> + *
> + * Copyright (c) 2021 Linaro Ltd
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/units.h"
> +#include "qapi/error.h"
> +#include "hw/arm/boot.h"
> +#include "hw/arm/machines-qom.h"
> +#include "hw/arm/rp2040.h"
> +#include "hw/core/boards.h"
> +#include "hw/core/qdev-properties.h"
> +#include "system/address-spaces.h"
> +#include "system/system.h"
> +#include "qom/object.h"
> +
> +#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;
> +};
> +
> +static void raspi_pico_init(MachineState *machine)
> +{
> + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(machine);
> + MemoryRegion *system_memory = get_system_memory();
> +
> + object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
> + qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
> + qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
> + if (machine->firmware) {
> + qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
> + machine->firmware);
> + }
> + 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.cpu, machine->kernel_filename,
> + RP2040_XIP_BASE, PICO_FLASH_SIZE);
> +}
> +
> +static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
> +{
> + MachineClass *mc = MACHINE_CLASS(oc);
> +
> + mc->desc = "Raspberry Pi Pico (Cortex-M0+)";
> + mc->init = raspi_pico_init;
> + mc->default_cpus = 1;
> + mc->min_cpus = 1;
> + mc->max_cpus = 1;
picos are duel core aren't they?
> + mc->default_ram_size = 0;
> + mc->no_parallel = 1;
> + mc->no_floppy = 1;
> + mc->no_cdrom = 1;
> +}
> +
> +static const TypeInfo raspi_pico_machine_info = {
> + .name = TYPE_RASPI_PICO_MACHINE,
> + .parent = TYPE_MACHINE,
> + .instance_size = sizeof(RaspiPicoMachineState),
> + .class_init = raspi_pico_machine_class_init,
> + .interfaces = arm_machine_interfaces,
> +};
> +
> +static void raspi_pico_machine_init(void)
> +{
> + type_register_static(&raspi_pico_machine_info);
> +}
> +type_init(raspi_pico_machine_init)
> diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
> new file mode 100644
> index 0000000000..2196bad18d
> --- /dev/null
> +++ b/hw/arm/rp2040.c
> @@ -0,0 +1,242 @@
> +/*
> + * RP2040 SoC emulation
> + *
> + * Copyright (c) 2021 Linaro Ltd
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/units.h"
> +#include "qapi/error.h"
> +#include "hw/arm/rp2040.h"
> +#include "hw/core/loader.h"
> +#include "hw/core/qdev-clock.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/misc/unimp.h"
> +#include "qemu/datadir.h"
> +#include "target/arm/cpu-qom.h"
> +
> +#define RP2040_SYSCLK_FRQ 125000000
> +
> +#define RP2040_UART0_BASE 0x40034000
> +#define RP2040_UART0_IRQ 20
> +#define RP2040_UART1_BASE 0x40038000
> +#define RP2040_UART1_IRQ 21
> +
> +/*
> + * Temporary boot ROM used until the synthetic ROM is introduced. It loads
> + * the reset handler from a vector table at the base of the XIP window.
> + */
> +static const uint8_t rp2040_bootrom[] = {
> + 0x00, 0x20, 0x04, 0x20, /* initial SP: 0x20042000 */
> + 0x09, 0x00, 0x00, 0x00, /* reset handler: 0x00000009 */
> + 0x03, 0x48, /* ldr r0, vtor */
> + 0x04, 0x49, /* ldr r1, xip_base */
> + 0x01, 0x60, /* str r1, [r0] */
> + 0x04, 0x48, /* ldr r0, xip_reset_vector */
> + 0x01, 0x68, /* ldr r1, [r0] */
> + 0x08, 0x47, /* bx r1 */
> + 0xfe, 0xe7, /* b . */
> + 0xc0, 0x46, /* nop */
> + 0x08, 0xed, 0x00, 0xe0, /* VTOR: 0xe000ed08 */
> + 0x00, 0x00, 0x00, 0x10, /* XIP base: 0x10000000 */
> + 0x04, 0x00, 0x00, 0x10, /* XIP reset vector: 0x10000004 */
> +};
> +
> +static const struct {
> + const char *name;
> + hwaddr base;
> + hwaddr size;
> +} rp2040_unimplemented[] = {
> + { "rp2040.sysinfo", 0x40000000, 0x4000 },
> + { "rp2040.syscfg", 0x40004000, 0x4000 },
> + { "rp2040.clocks", 0x40008000, 0x4000 },
> + { "rp2040.resets", 0x4000c000, 0x4000 },
> + { "rp2040.psm", 0x40010000, 0x4000 },
> + { "rp2040.iobank0", 0x40014000, 0x4000 },
> + { "rp2040.ioqspi", 0x40018000, 0x4000 },
> + { "rp2040.padsbank0", 0x4001c000, 0x4000 },
> + { "rp2040.padsqspi", 0x40020000, 0x4000 },
> + { "rp2040.xosc", 0x40024000, 0x4000 },
> + { "rp2040.pll_sys", 0x40028000, 0x4000 },
> + { "rp2040.pll_usb", 0x4002c000, 0x4000 },
> + { "rp2040.busctrl", 0x40030000, 0x4000 },
> + { "rp2040.uart0_aliases", 0x40035000, 0x3000 },
> + { "rp2040.uart1_aliases", 0x40039000, 0x3000 },
> + { "rp2040.spi0", 0x4003c000, 0x4000 },
> + { "rp2040.spi1", 0x40040000, 0x4000 },
> + { "rp2040.i2c0", 0x40044000, 0x4000 },
> + { "rp2040.i2c1", 0x40048000, 0x4000 },
> + { "rp2040.adc", 0x4004c000, 0x4000 },
> + { "rp2040.pwm", 0x40050000, 0x4000 },
> + { "rp2040.timer", 0x40054000, 0x4000 },
> + { "rp2040.watchdog", 0x40058000, 0x4000 },
> + { "rp2040.rtc", 0x4005c000, 0x4000 },
> + { "rp2040.rosc", 0x40060000, 0x4000 },
> + { "rp2040.vreg_and_chip_reset", 0x40064000, 0x4000 },
> + { "rp2040.tbman", 0x4006c000, 0x4000 },
> + { "rp2040.dma", 0x50000000, 0x1000 },
> + { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
> + { "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
> + { "rp2040.pio0", 0x50200000, 0x10000 },
> + { "rp2040.pio1", 0x50300000, 0x10000 },
> + { "rp2040.sio", 0xd0000000, 0x1000 },
> +};
> +
> +static void rp2040_soc_init(Object *obj)
> +{
> + RP2040State *s = RP2040(obj);
> + int i;
> +
> + object_initialize_child(obj, "proc0", &s->armv7m, TYPE_ARMV7M);
> + qdev_prop_set_string(DEVICE(&s->armv7m), "cpu-type",
> + ARM_CPU_TYPE_NAME("cortex-m0"));
> + qdev_prop_set_uint32(DEVICE(&s->armv7m), "num-irq", RP2040_NUM_IRQS);
> + qdev_prop_set_uint32(DEVICE(&s->armv7m), "mpu-ns-regions", 8);
> +
> + for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
> + g_autofree char *name = g_strdup_printf("uart%d", i);
> + g_autofree char *property = g_strdup_printf("serial%d", i);
> +
> + object_initialize_child(obj, name, &s->uart[i], TYPE_PL011);
> + object_property_add_alias(obj, property, OBJECT(&s->uart[i]),
> + "chardev");
> + }
> +
> + s->sysclk = clock_new(obj, "sysclk");
> + clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ);
> +}
> +
> +static bool rp2040_init_memory(RP2040State *s, Error **errp)
> +{
> + int i;
> +
> + if (!memory_region_init_rom(&s->rom, OBJECT(s), "rp2040.rom",
> + RP2040_ROM_SIZE, errp)) {
> + return false;
> + }
> + memory_region_add_subregion(s->board_memory, RP2040_ROM_BASE, &s->rom);
> +
> + for (i = 0; i < 4; i++) {
> + g_autofree char *name = g_strdup_printf("rp2040.sram%d", i);
> +
> + if (!memory_region_init_ram(&s->sram[i], OBJECT(s), name,
> + RP2040_SRAM_BANK_SIZE, errp)) {
> + return false;
> + }
> + memory_region_add_subregion(s->board_memory,
> + RP2040_SRAM_BASE +
> + i * RP2040_SRAM_BANK_SIZE,
> + &s->sram[i]);
> + }
> +
> + if (!memory_region_init_ram(&s->sram[4], OBJECT(s), "rp2040.sram4",
> + RP2040_SRAM_HI_SIZE, errp)) {
> + return false;
> + }
> + memory_region_add_subregion(s->board_memory, RP2040_SRAM4_BASE,
> + &s->sram[4]);
> +
> + if (!memory_region_init_ram(&s->sram[5], OBJECT(s), "rp2040.sram5",
> + RP2040_SRAM_HI_SIZE, errp)) {
> + return false;
> + }
> + memory_region_add_subregion(s->board_memory, RP2040_SRAM5_BASE,
> + &s->sram[5]);
I think you need multiple addresses spaces for the last two as they are
banked per-CPU right?
> +
> + return true;
> +}
> +
> +static bool rp2040_load_bootrom(RP2040State *s, Error **errp)
> +{
> + g_autofree char *filename = NULL;
> +
> + if (!s->bootrom_file) {
> + rom_add_blob_fixed("rp2040.bootrom", rp2040_bootrom,
> + sizeof(rp2040_bootrom), RP2040_ROM_BASE);
> + return true;
> + }
> +
> + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->bootrom_file);
> + if (!filename) {
> + error_setg(errp, "could not find RP2040 boot ROM image '%s'",
> + s->bootrom_file);
> + return false;
> + }
> +
> + return load_image_targphys(filename, RP2040_ROM_BASE,
> + RP2040_ROM_SIZE, errp) >= 0;
> +}
> +
> +static void rp2040_soc_realize(DeviceState *dev, Error **errp)
> +{
> + RP2040State *s = RP2040(dev);
> + static const hwaddr uart_base[] = { RP2040_UART0_BASE, RP2040_UART1_BASE };
> + static const int uart_irq[] = { RP2040_UART0_IRQ, RP2040_UART1_IRQ };
> + Error *err = NULL;
> + int i;
> +
> + if (!s->board_memory) {
> + error_setg(errp, "memory property was not set");
> + return;
> + }
> + if (!rp2040_init_memory(s, errp) || !rp2040_load_bootrom(s, errp)) {
> + return;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(rp2040_unimplemented); i++) {
> + create_unimplemented_device(rp2040_unimplemented[i].name,
> + rp2040_unimplemented[i].base,
> + rp2040_unimplemented[i].size);
> + }
> +
> + qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk);
> + object_property_set_link(OBJECT(&s->armv7m), "memory",
> + OBJECT(s->board_memory), &err);
> + if (err) {
> + error_propagate(errp, err);
> + return;
> + }
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
> + return;
> + }
> +
> + for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
> + qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
> + return;
> + }
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
> + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
> + qdev_get_gpio_in(DEVICE(&s->armv7m), uart_irq[i]));
> + }
> +}
> +
> +static const Property rp2040_soc_properties[] = {
> + DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGION,
> + MemoryRegion *),
> + DEFINE_PROP_STRING("bootrom-file", RP2040State, bootrom_file),
> +};
> +
> +static void rp2040_soc_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + dc->realize = rp2040_soc_realize;
> + device_class_set_props(dc, rp2040_soc_properties);
> +}
> +
> +static const TypeInfo rp2040_soc_info = {
> + .name = TYPE_RP2040,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(RP2040State),
> + .instance_init = rp2040_soc_init,
> + .class_init = rp2040_soc_class_init,
> +};
> +
> +static void rp2040_soc_types(void)
> +{
> + type_register_static(&rp2040_soc_info);
> +}
> +type_init(rp2040_soc_types)
> diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
> new file mode 100644
> index 0000000000..4e5cd06add
> --- /dev/null
> +++ b/include/hw/arm/rp2040.h
> @@ -0,0 +1,46 @@
> +/*
> + * RP2040 SoC emulation
> + *
> + * Copyright (c) 2021 Linaro Ltd
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_ARM_RP2040_H
> +#define HW_ARM_RP2040_H
> +
> +#include "hw/arm/armv7m.h"
> +#include "hw/char/pl011.h"
> +#include "hw/core/clock.h"
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RP2040 "rp2040"
> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
> +
> +#define RP2040_ROM_BASE 0x00000000
> +#define RP2040_ROM_SIZE (16 * KiB)
> +#define RP2040_XIP_BASE 0x10000000
> +#define RP2040_SRAM_BASE 0x20000000
> +#define RP2040_SRAM_BANK_SIZE (64 * KiB)
> +#define RP2040_SRAM4_BASE 0x20040000
> +#define RP2040_SRAM5_BASE 0x20041000
> +#define RP2040_SRAM_HI_SIZE (4 * KiB)
> +
> +#define RP2040_NUM_IRQS 32
> +
> +struct RP2040State {
> + SysBusDevice parent_obj;
> +
> + ARMv7MState armv7m;
> + PL011State uart[2];
> +
> + MemoryRegion *board_memory;
> + MemoryRegion rom;
> + MemoryRegion sram[6];
> + char *bootrom_file;
> +
> + Clock *sysclk;
> +};
> +
> +#endif
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 04/30] hw/arm: add RP2040 SoC and Raspberry Pi Pico machine
2026-09-01 18:33 ` Alex Bennée
@ 2026-09-01 22:33 ` gilles grimaud
0 siblings, 0 replies; 49+ messages in thread
From: gilles grimaud @ 2026-09-01 22:33 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell,
Philippe Mathieu-Daudé
[-- Attachment #1: Type: text/plain, Size: 18581 bytes --]
Hi Alex,
Yes. The v2 patch introduced the machine as single-core and deferred core 1
to a later patch. That was the wrong split.
For v3 I will instantiate both Cortex-M0+ cores in the initial SoC patch.
Core 1 may remain parked until the later SIO and boot ROM patches introduce
its launch protocol, but the machine topology will be dual-core from the
start.
I assume that by the last two regions you mean SIO and PPB. I will give each
core a separate memory container and address space. Each ARMv7M instance will
therefore retain its own PPB, NVIC, and SysTick mapping. SIO will expose
per-core MMIO views over the shared SIO state, instead of inferring the
accessing core from current_cpu.
Thanks,
--
Gilles Grimaud - 2XS/SISE - CRIStAL/IRCICA - CNRS/Univ. Lille
> Le 1 sept. 2026 à 20:33, Alex Bennée <alex.bennee@linaro.org> a écrit :
>
> Gilles Grimaud <gilles.grimaud@univ-lille.fr <mailto:gilles.grimaud@univ-lille.fr>> writes:
>
>> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>>
>> Continue the Raspberry Pi Pico machine model introduced by Alex Bennée's 2022 RFC on current QEMU APIs. Add the RP2040 QOM skeleton, internal ROM and SRAM map, the board XIP window, two PL011-backed UARTs, and explicit unimplemented-device placeholders for the remaining MMIO blocks.\n\nKeep the model single-core for now. Core 1 startup and the SIO launch protocol are introduced later with the multicore support.
>>
>> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
>> ---
>> MAINTAINERS | 1 +
>> configs/devices/arm-softmmu/default.mak | 1 +
>> hw/arm/Kconfig | 13 ++
>> hw/arm/meson.build | 2 +
>> hw/arm/raspi_pico.c | 85 +++++++++
>> hw/arm/rp2040.c | 242 ++++++++++++++++++++++++
>> include/hw/arm/rp2040.h | 46 +++++
>> 7 files changed, 390 insertions(+)
>> create mode 100644 hw/arm/raspi_pico.c
>> create mode 100644 hw/arm/rp2040.c
>> create mode 100644 include/hw/arm/rp2040.h
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index db641d9c4c..8fd7385d83 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -1041,6 +1041,7 @@ L: qemu-arm@nongnu.org
>> S: Maintained
>> F: hw/*/rp2040*
>> F: include/hw/*/rp2040*
>> +F: hw/arm/raspi_pico.c
>>
>> Real View
>> M: Peter Maydell <peter.maydell@linaro.org>
>> diff --git a/configs/devices/arm-softmmu/default.mak b/configs/devices/arm-softmmu/default.mak
>> index 71cf164511..e7f815dd0d 100644
>> --- a/configs/devices/arm-softmmu/default.mak
>> +++ b/configs/devices/arm-softmmu/default.mak
>> @@ -33,6 +33,7 @@
>> # CONFIG_OLIMEX_STM32_H405=n
>> # CONFIG_MPS2=n
>> # CONFIG_RASPI=n
>> +CONFIG_RASPI_PICO=y
>> # CONFIG_DIGIC=n
>> # CONFIG_SABRELITE=n
>> # CONFIG_EMCRAFT_SF2=n
>> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
>> index 260d2f0751..d93303ab80 100644
>> --- a/hw/arm/Kconfig
>> +++ b/hw/arm/Kconfig
>> @@ -368,6 +368,19 @@ config RASPI
>> select BCM2835_SPI
>> select BCM2835_I2C
>>
>> +config RASPI_PICO
>> + bool
>> + default y
>> + depends on TCG && ARM
>> + select RP2040
>> +
>> +config RP2040
>> + bool
>> + select ARM_V7M
>> + select PL011
>> + select RP2040_NYI
>> + select UNIMP
>> +
>> config STM32F100_SOC
>> bool
>> select ARM_V7M
>> diff --git a/hw/arm/meson.build b/hw/arm/meson.build
>> index 8ee5307a91..3240ef6637 100644
>> --- a/hw/arm/meson.build
>> +++ b/hw/arm/meson.build
>> @@ -32,6 +32,8 @@ arm_common_ss.add(when: 'CONFIG_RASPI', if_true: files(
>> 'raspi.c',
>> 'raspi4b.c'
>> ))
>> +arm_common_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040.c'))
>> +arm_common_ss.add(when: 'CONFIG_RASPI_PICO', if_true: files('raspi_pico.c'))
>> arm_common_ss.add(when: 'CONFIG_STM32F100_SOC', if_true: files('stm32f100_soc.c'))
>> arm_common_ss.add(when: 'CONFIG_STM32F205_SOC', if_true: files('stm32f205_soc.c'))
>> arm_common_ss.add(when: 'CONFIG_STM32F405_SOC', if_true: files('stm32f405_soc.c'))
>> diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
>> new file mode 100644
>> index 0000000000..2bb7d4e511
>> --- /dev/null
>> +++ b/hw/arm/raspi_pico.c
>> @@ -0,0 +1,85 @@
>> +/*
>> + * Raspberry Pi Pico machine
>> + *
>> + * Copyright (c) 2021 Linaro Ltd
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qemu/units.h"
>> +#include "qapi/error.h"
>> +#include "hw/arm/boot.h"
>> +#include "hw/arm/machines-qom.h"
>> +#include "hw/arm/rp2040.h"
>> +#include "hw/core/boards.h"
>> +#include "hw/core/qdev-properties.h"
>> +#include "system/address-spaces.h"
>> +#include "system/system.h"
>> +#include "qom/object.h"
>> +
>> +#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;
>> +};
>> +
>> +static void raspi_pico_init(MachineState *machine)
>> +{
>> + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(machine);
>> + MemoryRegion *system_memory = get_system_memory();
>> +
>> + object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
>> + qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
>> + qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
>> + if (machine->firmware) {
>> + qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
>> + machine->firmware);
>> + }
>> + 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.cpu, machine->kernel_filename,
>> + RP2040_XIP_BASE, PICO_FLASH_SIZE);
>> +}
>> +
>> +static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
>> +{
>> + MachineClass *mc = MACHINE_CLASS(oc);
>> +
>> + mc->desc = "Raspberry Pi Pico (Cortex-M0+)";
>> + mc->init = raspi_pico_init;
>> + mc->default_cpus = 1;
>> + mc->min_cpus = 1;
>> + mc->max_cpus = 1;
>
> picos are duel core aren't they?
>
>> + mc->default_ram_size = 0;
>> + mc->no_parallel = 1;
>> + mc->no_floppy = 1;
>> + mc->no_cdrom = 1;
>> +}
>> +
>> +static const TypeInfo raspi_pico_machine_info = {
>> + .name = TYPE_RASPI_PICO_MACHINE,
>> + .parent = TYPE_MACHINE,
>> + .instance_size = sizeof(RaspiPicoMachineState),
>> + .class_init = raspi_pico_machine_class_init,
>> + .interfaces = arm_machine_interfaces,
>> +};
>> +
>> +static void raspi_pico_machine_init(void)
>> +{
>> + type_register_static(&raspi_pico_machine_info);
>> +}
>> +type_init(raspi_pico_machine_init)
>> diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
>> new file mode 100644
>> index 0000000000..2196bad18d
>> --- /dev/null
>> +++ b/hw/arm/rp2040.c
>> @@ -0,0 +1,242 @@
>> +/*
>> + * RP2040 SoC emulation
>> + *
>> + * Copyright (c) 2021 Linaro Ltd
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "qemu/units.h"
>> +#include "qapi/error.h"
>> +#include "hw/arm/rp2040.h"
>> +#include "hw/core/loader.h"
>> +#include "hw/core/qdev-clock.h"
>> +#include "hw/core/qdev-properties.h"
>> +#include "hw/misc/unimp.h"
>> +#include "qemu/datadir.h"
>> +#include "target/arm/cpu-qom.h"
>> +
>> +#define RP2040_SYSCLK_FRQ 125000000
>> +
>> +#define RP2040_UART0_BASE 0x40034000
>> +#define RP2040_UART0_IRQ 20
>> +#define RP2040_UART1_BASE 0x40038000
>> +#define RP2040_UART1_IRQ 21
>> +
>> +/*
>> + * Temporary boot ROM used until the synthetic ROM is introduced. It loads
>> + * the reset handler from a vector table at the base of the XIP window.
>> + */
>> +static const uint8_t rp2040_bootrom[] = {
>> + 0x00, 0x20, 0x04, 0x20, /* initial SP: 0x20042000 */
>> + 0x09, 0x00, 0x00, 0x00, /* reset handler: 0x00000009 */
>> + 0x03, 0x48, /* ldr r0, vtor */
>> + 0x04, 0x49, /* ldr r1, xip_base */
>> + 0x01, 0x60, /* str r1, [r0] */
>> + 0x04, 0x48, /* ldr r0, xip_reset_vector */
>> + 0x01, 0x68, /* ldr r1, [r0] */
>> + 0x08, 0x47, /* bx r1 */
>> + 0xfe, 0xe7, /* b . */
>> + 0xc0, 0x46, /* nop */
>> + 0x08, 0xed, 0x00, 0xe0, /* VTOR: 0xe000ed08 */
>> + 0x00, 0x00, 0x00, 0x10, /* XIP base: 0x10000000 */
>> + 0x04, 0x00, 0x00, 0x10, /* XIP reset vector: 0x10000004 */
>> +};
>> +
>> +static const struct {
>> + const char *name;
>> + hwaddr base;
>> + hwaddr size;
>> +} rp2040_unimplemented[] = {
>> + { "rp2040.sysinfo", 0x40000000, 0x4000 },
>> + { "rp2040.syscfg", 0x40004000, 0x4000 },
>> + { "rp2040.clocks", 0x40008000, 0x4000 },
>> + { "rp2040.resets", 0x4000c000, 0x4000 },
>> + { "rp2040.psm", 0x40010000, 0x4000 },
>> + { "rp2040.iobank0", 0x40014000, 0x4000 },
>> + { "rp2040.ioqspi", 0x40018000, 0x4000 },
>> + { "rp2040.padsbank0", 0x4001c000, 0x4000 },
>> + { "rp2040.padsqspi", 0x40020000, 0x4000 },
>> + { "rp2040.xosc", 0x40024000, 0x4000 },
>> + { "rp2040.pll_sys", 0x40028000, 0x4000 },
>> + { "rp2040.pll_usb", 0x4002c000, 0x4000 },
>> + { "rp2040.busctrl", 0x40030000, 0x4000 },
>> + { "rp2040.uart0_aliases", 0x40035000, 0x3000 },
>> + { "rp2040.uart1_aliases", 0x40039000, 0x3000 },
>> + { "rp2040.spi0", 0x4003c000, 0x4000 },
>> + { "rp2040.spi1", 0x40040000, 0x4000 },
>> + { "rp2040.i2c0", 0x40044000, 0x4000 },
>> + { "rp2040.i2c1", 0x40048000, 0x4000 },
>> + { "rp2040.adc", 0x4004c000, 0x4000 },
>> + { "rp2040.pwm", 0x40050000, 0x4000 },
>> + { "rp2040.timer", 0x40054000, 0x4000 },
>> + { "rp2040.watchdog", 0x40058000, 0x4000 },
>> + { "rp2040.rtc", 0x4005c000, 0x4000 },
>> + { "rp2040.rosc", 0x40060000, 0x4000 },
>> + { "rp2040.vreg_and_chip_reset", 0x40064000, 0x4000 },
>> + { "rp2040.tbman", 0x4006c000, 0x4000 },
>> + { "rp2040.dma", 0x50000000, 0x1000 },
>> + { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
>> + { "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
>> + { "rp2040.pio0", 0x50200000, 0x10000 },
>> + { "rp2040.pio1", 0x50300000, 0x10000 },
>> + { "rp2040.sio", 0xd0000000, 0x1000 },
>> +};
>> +
>> +static void rp2040_soc_init(Object *obj)
>> +{
>> + RP2040State *s = RP2040(obj);
>> + int i;
>> +
>> + object_initialize_child(obj, "proc0", &s->armv7m, TYPE_ARMV7M);
>> + qdev_prop_set_string(DEVICE(&s->armv7m), "cpu-type",
>> + ARM_CPU_TYPE_NAME("cortex-m0"));
>> + qdev_prop_set_uint32(DEVICE(&s->armv7m), "num-irq", RP2040_NUM_IRQS);
>> + qdev_prop_set_uint32(DEVICE(&s->armv7m), "mpu-ns-regions", 8);
>> +
>> + for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
>> + g_autofree char *name = g_strdup_printf("uart%d", i);
>> + g_autofree char *property = g_strdup_printf("serial%d", i);
>> +
>> + object_initialize_child(obj, name, &s->uart[i], TYPE_PL011);
>> + object_property_add_alias(obj, property, OBJECT(&s->uart[i]),
>> + "chardev");
>> + }
>> +
>> + s->sysclk = clock_new(obj, "sysclk");
>> + clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ);
>> +}
>> +
>> +static bool rp2040_init_memory(RP2040State *s, Error **errp)
>> +{
>> + int i;
>> +
>> + if (!memory_region_init_rom(&s->rom, OBJECT(s), "rp2040.rom",
>> + RP2040_ROM_SIZE, errp)) {
>> + return false;
>> + }
>> + memory_region_add_subregion(s->board_memory, RP2040_ROM_BASE, &s->rom);
>> +
>> + for (i = 0; i < 4; i++) {
>> + g_autofree char *name = g_strdup_printf("rp2040.sram%d", i);
>> +
>> + if (!memory_region_init_ram(&s->sram[i], OBJECT(s), name,
>> + RP2040_SRAM_BANK_SIZE, errp)) {
>> + return false;
>> + }
>> + memory_region_add_subregion(s->board_memory,
>> + RP2040_SRAM_BASE +
>> + i * RP2040_SRAM_BANK_SIZE,
>> + &s->sram[i]);
>> + }
>> +
>> + if (!memory_region_init_ram(&s->sram[4], OBJECT(s), "rp2040.sram4",
>> + RP2040_SRAM_HI_SIZE, errp)) {
>> + return false;
>> + }
>> + memory_region_add_subregion(s->board_memory, RP2040_SRAM4_BASE,
>> + &s->sram[4]);
>> +
>> + if (!memory_region_init_ram(&s->sram[5], OBJECT(s), "rp2040.sram5",
>> + RP2040_SRAM_HI_SIZE, errp)) {
>> + return false;
>> + }
>> + memory_region_add_subregion(s->board_memory, RP2040_SRAM5_BASE,
>> + &s->sram[5]);
>
> I think you need multiple addresses spaces for the last two as they are
> banked per-CPU right?
>
>> +
>> + return true;
>> +}
>> +
>> +static bool rp2040_load_bootrom(RP2040State *s, Error **errp)
>> +{
>> + g_autofree char *filename = NULL;
>> +
>> + if (!s->bootrom_file) {
>> + rom_add_blob_fixed("rp2040.bootrom", rp2040_bootrom,
>> + sizeof(rp2040_bootrom), RP2040_ROM_BASE);
>> + return true;
>> + }
>> +
>> + filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, s->bootrom_file);
>> + if (!filename) {
>> + error_setg(errp, "could not find RP2040 boot ROM image '%s'",
>> + s->bootrom_file);
>> + return false;
>> + }
>> +
>> + return load_image_targphys(filename, RP2040_ROM_BASE,
>> + RP2040_ROM_SIZE, errp) >= 0;
>> +}
>> +
>> +static void rp2040_soc_realize(DeviceState *dev, Error **errp)
>> +{
>> + RP2040State *s = RP2040(dev);
>> + static const hwaddr uart_base[] = { RP2040_UART0_BASE, RP2040_UART1_BASE };
>> + static const int uart_irq[] = { RP2040_UART0_IRQ, RP2040_UART1_IRQ };
>> + Error *err = NULL;
>> + int i;
>> +
>> + if (!s->board_memory) {
>> + error_setg(errp, "memory property was not set");
>> + return;
>> + }
>> + if (!rp2040_init_memory(s, errp) || !rp2040_load_bootrom(s, errp)) {
>> + return;
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(rp2040_unimplemented); i++) {
>> + create_unimplemented_device(rp2040_unimplemented[i].name,
>> + rp2040_unimplemented[i].base,
>> + rp2040_unimplemented[i].size);
>> + }
>> +
>> + qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk);
>> + object_property_set_link(OBJECT(&s->armv7m), "memory",
>> + OBJECT(s->board_memory), &err);
>> + if (err) {
>> + error_propagate(errp, err);
>> + return;
>> + }
>> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
>> + return;
>> + }
>> +
>> + for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
>> + qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
>> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
>> + return;
>> + }
>> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
>> + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
>> + qdev_get_gpio_in(DEVICE(&s->armv7m), uart_irq[i]));
>> + }
>> +}
>> +
>> +static const Property rp2040_soc_properties[] = {
>> + DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGION,
>> + MemoryRegion *),
>> + DEFINE_PROP_STRING("bootrom-file", RP2040State, bootrom_file),
>> +};
>> +
>> +static void rp2040_soc_class_init(ObjectClass *klass, const void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> + dc->realize = rp2040_soc_realize;
>> + device_class_set_props(dc, rp2040_soc_properties);
>> +}
>> +
>> +static const TypeInfo rp2040_soc_info = {
>> + .name = TYPE_RP2040,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(RP2040State),
>> + .instance_init = rp2040_soc_init,
>> + .class_init = rp2040_soc_class_init,
>> +};
>> +
>> +static void rp2040_soc_types(void)
>> +{
>> + type_register_static(&rp2040_soc_info);
>> +}
>> +type_init(rp2040_soc_types)
>> diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
>> new file mode 100644
>> index 0000000000..4e5cd06add
>> --- /dev/null
>> +++ b/include/hw/arm/rp2040.h
>> @@ -0,0 +1,46 @@
>> +/*
>> + * RP2040 SoC emulation
>> + *
>> + * Copyright (c) 2021 Linaro Ltd
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_ARM_RP2040_H
>> +#define HW_ARM_RP2040_H
>> +
>> +#include "hw/arm/armv7m.h"
>> +#include "hw/char/pl011.h"
>> +#include "hw/core/clock.h"
>> +#include "hw/core/sysbus.h"
>> +#include "qom/object.h"
>> +
>> +#define TYPE_RP2040 "rp2040"
>> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
>> +
>> +#define RP2040_ROM_BASE 0x00000000
>> +#define RP2040_ROM_SIZE (16 * KiB)
>> +#define RP2040_XIP_BASE 0x10000000
>> +#define RP2040_SRAM_BASE 0x20000000
>> +#define RP2040_SRAM_BANK_SIZE (64 * KiB)
>> +#define RP2040_SRAM4_BASE 0x20040000
>> +#define RP2040_SRAM5_BASE 0x20041000
>> +#define RP2040_SRAM_HI_SIZE (4 * KiB)
>> +
>> +#define RP2040_NUM_IRQS 32
>> +
>> +struct RP2040State {
>> + SysBusDevice parent_obj;
>> +
>> + ARMv7MState armv7m;
>> + PL011State uart[2];
>> +
>> + MemoryRegion *board_memory;
>> + MemoryRegion rom;
>> + MemoryRegion sram[6];
>> + char *bootrom_file;
>> +
>> + Clock *sysclk;
>> +};
>> +
>> +#endif
>
> --
> Alex Bennée
> Virtualisation Tech Lead @ Linaro
[-- Attachment #2: Type: text/html, Size: 37515 bytes --]
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 05/30] tests/tcg/arm: add Raspberry Pi Pico smoke tests
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (3 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 04/30] hw/arm: add RP2040 SoC and Raspberry Pi Pico machine Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-01 18:35 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG Gilles Grimaud
` (25 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, gilles grimaud, Peter Maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Build Cortex-M0+ guest programs from assembly sources to exercise the minimal Pico boot path, UART0 output, and the Cortex-M0+ MPU. Keep the vector table at the XIP base so these tests match the initial synthetic boot stub without depending on boot2 or the later flash controller model.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
MAINTAINERS | 1 +
tests/tcg/arm/Makefile.softmmu-target | 24 ++++
tests/tcg/arm/system/rp2040-boot.S | 30 ++++
tests/tcg/arm/system/rp2040-minimal.ld | 40 ++++++
tests/tcg/arm/system/rp2040-mpu.S | 183 +++++++++++++++++++++++++
tests/tcg/arm/system/rp2040-mpu.ref | 5 +
tests/tcg/arm/system/rp2040-uart.S | 53 +++++++
tests/tcg/arm/system/rp2040-uart.ref | 1 +
8 files changed, 337 insertions(+)
create mode 100644 tests/tcg/arm/system/rp2040-boot.S
create mode 100644 tests/tcg/arm/system/rp2040-minimal.ld
create mode 100644 tests/tcg/arm/system/rp2040-mpu.S
create mode 100644 tests/tcg/arm/system/rp2040-mpu.ref
create mode 100644 tests/tcg/arm/system/rp2040-uart.S
create mode 100644 tests/tcg/arm/system/rp2040-uart.ref
diff --git a/MAINTAINERS b/MAINTAINERS
index 8fd7385d83..b000b3287d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1042,6 +1042,7 @@ S: Maintained
F: hw/*/rp2040*
F: include/hw/*/rp2040*
F: hw/arm/raspi_pico.c
+F: tests/tcg/arm/system/rp2040*
Real View
M: Peter Maydell <peter.maydell@linaro.org>
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index 3e8a9b4073..429416ca58 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -20,6 +20,30 @@ run-test-armv6m-undef: QEMU_OPTS=-semihosting-config enable=on,target=native,cha
ARM_TESTS+=test-armv6m-undef
+RP2040_TESTS=rp2040-boot rp2040-uart rp2040-mpu
+
+$(RP2040_TESTS): %: %.S rp2040-minimal.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
+
+run-rp2040-boot: QEMU_OPTS=-M raspi-pico \
+ -semihosting-config enable=on,target=native -kernel
+
+run-rp2040-uart run-rp2040-mpu: QEMU_OPTS=-M raspi-pico \
+ -serial chardev:output \
+ -semihosting-config enable=on,target=native -kernel
+
+run-rp2040-uart run-rp2040-mpu: run-rp2040-%: rp2040-%
+ $(call run-test, $<, \
+ $(QEMU) -monitor none -display none \
+ -chardev file$(COMMA)path=$<.out$(COMMA)id=output \
+ $(QEMU_OPTS) $<)
+ $(call diff-out,$<,$(ARM_SRC)/$<.ref)
+
+ARM_TESTS+=$(RP2040_TESTS)
+
# These objects provide the basic boot code and helper functions for all tests
CRT_OBJS=boot.o
diff --git a/tests/tcg/arm/system/rp2040-boot.S b/tests/tcg/arm/system/rp2040-boot.S
new file mode 100644
index 0000000000..0537a54d44
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-boot.S
@@ -0,0 +1,30 @@
+/*
+ * Raspberry Pi Pico boot smoke test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+
+.section .vectors, "a", %progbits
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
diff --git a/tests/tcg/arm/system/rp2040-minimal.ld b/tests/tcg/arm/system/rp2040-minimal.ld
new file mode 100644
index 0000000000..ebfbac8cf2
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-minimal.ld
@@ -0,0 +1,40 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+ENTRY(reset_handler)
+
+MEMORY
+{
+ FLASH (rx) : ORIGIN = 0x10000000, LENGTH = 2M
+ SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 264K
+}
+
+SECTIONS
+{
+ .vectors ORIGIN(FLASH) :
+ {
+ KEEP(*(.vectors))
+ } > FLASH
+
+ .text :
+ {
+ *(.text*)
+ *(.rodata*)
+ } > FLASH
+
+ .data :
+ {
+ *(.data*)
+ } > SRAM AT > FLASH
+
+ .bss (NOLOAD) :
+ {
+ *(.bss*)
+ *(COMMON)
+ } > SRAM
+
+ /DISCARD/ :
+ {
+ *(.ARM.attributes)
+ *(.comment)
+ }
+}
diff --git a/tests/tcg/arm/system/rp2040-mpu.S b/tests/tcg/arm/system/rp2040-mpu.S
new file mode 100644
index 0000000000..a3aef55f5f
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-mpu.S
@@ -0,0 +1,183 @@
+/*
+ * Raspberry Pi Pico Cortex-M0+ MPU test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define TEST_ADDRESS 0x20001000
+#define TEST_VALUE 0x12345678
+#define UART0_DR 0x40034000
+#define MPU_TYPE 0xe000ed90
+#define MPU_CTRL 0xe000ed94
+#define MPU_RNR 0xe000ed98
+#define MPU_RBAR 0xe000ed9c
+#define MPU_RASR 0xe000eda0
+#define MPU_TYPE_DREGION_MASK 0x0000ff00
+#define MPU_TYPE_DREGION_SHIFT 8
+#define MPU_CTRL_ENABLE_PRIVDEFENA 0x05
+#define MPU_RASR_SRAM_FULL_ACCESS 0x03000025
+#define MPU_RASR_XIP_READ_ONLY 0x06000039
+#define MPU_RASR_TEST_PRIV_RW 0x0100000f
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word hardfault_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ ldr r0, =MPU_TYPE
+ ldr r0, [r0]
+ ldr r1, =MPU_TYPE_DREGION_MASK
+ ands r0, r1
+ lsrs r0, r0, MPU_TYPE_DREGION_SHIFT
+ cmp r0, 8
+ bne fail_type
+
+ ldr r0, =type_message
+ bl puts
+
+ ldr r4, =TEST_ADDRESS
+ ldr r5, =TEST_VALUE
+ str r5, [r4]
+ ldr r0, [r4]
+ cmp r0, r5
+ bne fail_plain
+
+ ldr r0, =plain_message
+ bl puts
+
+ movs r0, 0
+ ldr r1, =0x20000000
+ ldr r2, =MPU_RASR_SRAM_FULL_ACCESS
+ bl configure_region
+
+ movs r0, 1
+ movs r1, 0
+ ldr r2, =MPU_RASR_XIP_READ_ONLY
+ bl configure_region
+
+ movs r0, 2
+ ldr r1, =TEST_ADDRESS
+ ldr r2, =MPU_RASR_TEST_PRIV_RW
+ bl configure_region
+
+ ldr r0, =MPU_CTRL
+ movs r1, MPU_CTRL_ENABLE_PRIVDEFENA
+ str r1, [r0]
+ dsb
+ isb
+
+ ldr r0, [r4]
+ cmp r0, r5
+ bne fail_privileged
+
+ ldr r0, =privileged_message
+ bl puts
+
+ movs r0, 1
+ msr control, r0
+ isb
+
+ ldr r0, [r4]
+ ldr r0, =fail_no_fault_message
+ b test_failed
+
+.thumb_func
+configure_region:
+ ldr r3, =MPU_RNR
+ str r0, [r3]
+ ldr r3, =MPU_RBAR
+ str r1, [r3]
+ ldr r3, =MPU_RASR
+ str r2, [r3]
+ bx lr
+
+fail_type:
+ ldr r0, =fail_type_message
+ b test_failed
+
+fail_plain:
+ ldr r0, =fail_plain_message
+ b test_failed
+
+fail_privileged:
+ ldr r0, =fail_privileged_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+1:
+ b 1b
+
+.thumb_func
+hardfault_handler:
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG MPU START\n"
+type_message:
+ .asciz "PICO TCG MPU TYPE\n"
+plain_message:
+ .asciz "PICO TCG MPU PLAIN\n"
+privileged_message:
+ .asciz "PICO TCG MPU PRIV\n"
+ok_message:
+ .asciz "PICO TCG MPU OK\n"
+fail_type_message:
+ .asciz "PICO TCG MPU FAIL: TYPE\n"
+fail_plain_message:
+ .asciz "PICO TCG MPU FAIL: PLAIN\n"
+fail_privileged_message:
+ .asciz "PICO TCG MPU FAIL: PRIV\n"
+fail_no_fault_message:
+ .asciz "PICO TCG MPU FAIL: NOFAULT\n"
+fail_exception_message:
+ .asciz "PICO TCG MPU FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-mpu.ref b/tests/tcg/arm/system/rp2040-mpu.ref
new file mode 100644
index 0000000000..e55590ea16
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-mpu.ref
@@ -0,0 +1,5 @@
+PICO TCG MPU START
+PICO TCG MPU TYPE
+PICO TCG MPU PLAIN
+PICO TCG MPU PRIV
+PICO TCG MPU OK
diff --git a/tests/tcg/arm/system/rp2040-uart.S b/tests/tcg/arm/system/rp2040-uart.S
new file mode 100644
index 0000000000..bdb58cc7f2
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-uart.S
@@ -0,0 +1,53 @@
+/*
+ * Raspberry Pi Pico UART boot smoke test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+
+.section .vectors, "a", %progbits
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =UART0_DR
+ ldr r1, =message
+
+1:
+ ldrb r2, [r1]
+ cmp r2, 0
+ beq 2f
+ str r2, [r0]
+ adds r1, 1
+ b 1b
+
+2:
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+3:
+ b 3b
+
+.thumb_func
+default_handler:
+ b default_handler
+
+.section .rodata, "a", %progbits
+message:
+ .asciz "PICO TCG UART OK\n"
diff --git a/tests/tcg/arm/system/rp2040-uart.ref b/tests/tcg/arm/system/rp2040-uart.ref
new file mode 100644
index 0000000000..1ef16e8299
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-uart.ref
@@ -0,0 +1 @@
+PICO TCG UART OK
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 05/30] tests/tcg/arm: add Raspberry Pi Pico smoke tests
2026-08-29 23:42 ` [PATCH RFC v2 05/30] tests/tcg/arm: add Raspberry Pi Pico smoke tests Gilles Grimaud
@ 2026-09-01 18:35 ` Alex Bennée
0 siblings, 0 replies; 49+ messages in thread
From: Alex Bennée @ 2026-09-01 18:35 UTC (permalink / raw)
To: Gilles Grimaud; +Cc: qemu-devel, qemu-arm, Peter Maydell
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Build Cortex-M0+ guest programs from assembly sources to exercise the
> minimal Pico boot path, UART0 output, and the Cortex-M0+ MPU. Keep the
> vector table at the XIP base so these tests match the initial
> synthetic boot stub without depending on boot2 or the later flash
> controller model.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> MAINTAINERS | 1 +
> tests/tcg/arm/Makefile.softmmu-target | 24 ++++
Just FYI there is an update coming to tests/tcg that converts the build
to meson. The change should be relatively mechanical to make.
> tests/tcg/arm/system/rp2040-boot.S | 30 ++++
> tests/tcg/arm/system/rp2040-minimal.ld | 40 ++++++
> tests/tcg/arm/system/rp2040-mpu.S | 183 +++++++++++++++++++++++++
> tests/tcg/arm/system/rp2040-mpu.ref | 5 +
> tests/tcg/arm/system/rp2040-uart.S | 53 +++++++
> tests/tcg/arm/system/rp2040-uart.ref | 1 +
> 8 files changed, 337 insertions(+)
> create mode 100644 tests/tcg/arm/system/rp2040-boot.S
> create mode 100644 tests/tcg/arm/system/rp2040-minimal.ld
> create mode 100644 tests/tcg/arm/system/rp2040-mpu.S
> create mode 100644 tests/tcg/arm/system/rp2040-mpu.ref
> create mode 100644 tests/tcg/arm/system/rp2040-uart.S
> create mode 100644 tests/tcg/arm/system/rp2040-uart.ref
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 8fd7385d83..b000b3287d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1042,6 +1042,7 @@ S: Maintained
> F: hw/*/rp2040*
> F: include/hw/*/rp2040*
> F: hw/arm/raspi_pico.c
> +F: tests/tcg/arm/system/rp2040*
>
> Real View
> M: Peter Maydell <peter.maydell@linaro.org>
> diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
> index 3e8a9b4073..429416ca58 100644
> --- a/tests/tcg/arm/Makefile.softmmu-target
> +++ b/tests/tcg/arm/Makefile.softmmu-target
> @@ -20,6 +20,30 @@ run-test-armv6m-undef: QEMU_OPTS=-semihosting-config enable=on,target=native,cha
>
> ARM_TESTS+=test-armv6m-undef
>
> +RP2040_TESTS=rp2040-boot rp2040-uart rp2040-mpu
> +
> +$(RP2040_TESTS): %: %.S rp2040-minimal.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
> +
> +run-rp2040-boot: QEMU_OPTS=-M raspi-pico \
> + -semihosting-config enable=on,target=native -kernel
> +
> +run-rp2040-uart run-rp2040-mpu: QEMU_OPTS=-M raspi-pico \
> + -serial chardev:output \
> + -semihosting-config enable=on,target=native -kernel
> +
> +run-rp2040-uart run-rp2040-mpu: run-rp2040-%: rp2040-%
> + $(call run-test, $<, \
> + $(QEMU) -monitor none -display none \
> + -chardev file$(COMMA)path=$<.out$(COMMA)id=output \
> + $(QEMU_OPTS) $<)
> + $(call diff-out,$<,$(ARM_SRC)/$<.ref)
> +
> +ARM_TESTS+=$(RP2040_TESTS)
> +
> # These objects provide the basic boot code and helper functions for all tests
> CRT_OBJS=boot.o
>
> diff --git a/tests/tcg/arm/system/rp2040-boot.S b/tests/tcg/arm/system/rp2040-boot.S
> new file mode 100644
> index 0000000000..0537a54d44
> --- /dev/null
> +++ b/tests/tcg/arm/system/rp2040-boot.S
> @@ -0,0 +1,30 @@
> +/*
> + * Raspberry Pi Pico boot smoke test.
> + *
> + * Copyright 2026 Gilles Grimaud
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +.syntax unified
> +.cpu cortex-m0plus
> +.thumb
> +
> +#define SRAM_END 0x20042000
> +#define SYS_EXIT 0x18
> +#define ADP_STOPPED_APPLICATION_EXIT 0x20026
> +
> +.section .vectors, "a", %progbits
> +vector_table:
> + .word SRAM_END
> + .word reset_handler + 1
> +
> +.section .text, "ax", %progbits
> +.thumb_func
> +.global reset_handler
> +reset_handler:
> + movs r0, SYS_EXIT
> + ldr r1, =ADP_STOPPED_APPLICATION_EXIT
> + bkpt 0xab
> +
> +1:
> + b 1b
> diff --git a/tests/tcg/arm/system/rp2040-minimal.ld b/tests/tcg/arm/system/rp2040-minimal.ld
> new file mode 100644
> index 0000000000..ebfbac8cf2
> --- /dev/null
> +++ b/tests/tcg/arm/system/rp2040-minimal.ld
> @@ -0,0 +1,40 @@
> +/* SPDX-License-Identifier: GPL-2.0-or-later */
> +
> +ENTRY(reset_handler)
> +
> +MEMORY
> +{
> + FLASH (rx) : ORIGIN = 0x10000000, LENGTH = 2M
> + SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 264K
> +}
> +
> +SECTIONS
> +{
> + .vectors ORIGIN(FLASH) :
> + {
> + KEEP(*(.vectors))
> + } > FLASH
> +
> + .text :
> + {
> + *(.text*)
> + *(.rodata*)
> + } > FLASH
> +
> + .data :
> + {
> + *(.data*)
> + } > SRAM AT > FLASH
> +
> + .bss (NOLOAD) :
> + {
> + *(.bss*)
> + *(COMMON)
> + } > SRAM
> +
> + /DISCARD/ :
> + {
> + *(.ARM.attributes)
> + *(.comment)
> + }
> +}
> diff --git a/tests/tcg/arm/system/rp2040-mpu.S b/tests/tcg/arm/system/rp2040-mpu.S
> new file mode 100644
> index 0000000000..a3aef55f5f
> --- /dev/null
> +++ b/tests/tcg/arm/system/rp2040-mpu.S
> @@ -0,0 +1,183 @@
> +/*
> + * Raspberry Pi Pico Cortex-M0+ MPU test.
> + *
> + * Copyright 2026 Gilles Grimaud
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +.syntax unified
> +.cpu cortex-m0plus
> +.thumb
> +
> +#define SRAM_END 0x20042000
> +#define TEST_ADDRESS 0x20001000
> +#define TEST_VALUE 0x12345678
> +#define UART0_DR 0x40034000
> +#define MPU_TYPE 0xe000ed90
> +#define MPU_CTRL 0xe000ed94
> +#define MPU_RNR 0xe000ed98
> +#define MPU_RBAR 0xe000ed9c
> +#define MPU_RASR 0xe000eda0
> +#define MPU_TYPE_DREGION_MASK 0x0000ff00
> +#define MPU_TYPE_DREGION_SHIFT 8
> +#define MPU_CTRL_ENABLE_PRIVDEFENA 0x05
> +#define MPU_RASR_SRAM_FULL_ACCESS 0x03000025
> +#define MPU_RASR_XIP_READ_ONLY 0x06000039
> +#define MPU_RASR_TEST_PRIV_RW 0x0100000f
> +#define SYS_EXIT 0x18
> +#define ADP_STOPPED_APPLICATION_EXIT 0x20026
> +#define ADP_STOPPED_RUNTIME_ERROR 0x20023
> +
> +.section .vectors, "a", %progbits
> +vector_table:
> + .word SRAM_END
> + .word reset_handler + 1
> + .word default_handler + 1
> + .word hardfault_handler + 1
> +
> +.section .text, "ax", %progbits
> +.thumb_func
> +.global reset_handler
> +reset_handler:
> + ldr r0, =start_message
> + bl puts
> +
> + ldr r0, =MPU_TYPE
> + ldr r0, [r0]
> + ldr r1, =MPU_TYPE_DREGION_MASK
> + ands r0, r1
> + lsrs r0, r0, MPU_TYPE_DREGION_SHIFT
> + cmp r0, 8
> + bne fail_type
> +
> + ldr r0, =type_message
> + bl puts
> +
> + ldr r4, =TEST_ADDRESS
> + ldr r5, =TEST_VALUE
> + str r5, [r4]
> + ldr r0, [r4]
> + cmp r0, r5
> + bne fail_plain
> +
> + ldr r0, =plain_message
> + bl puts
> +
> + movs r0, 0
> + ldr r1, =0x20000000
> + ldr r2, =MPU_RASR_SRAM_FULL_ACCESS
> + bl configure_region
> +
> + movs r0, 1
> + movs r1, 0
> + ldr r2, =MPU_RASR_XIP_READ_ONLY
> + bl configure_region
> +
> + movs r0, 2
> + ldr r1, =TEST_ADDRESS
> + ldr r2, =MPU_RASR_TEST_PRIV_RW
> + bl configure_region
> +
> + ldr r0, =MPU_CTRL
> + movs r1, MPU_CTRL_ENABLE_PRIVDEFENA
> + str r1, [r0]
> + dsb
> + isb
> +
> + ldr r0, [r4]
> + cmp r0, r5
> + bne fail_privileged
> +
> + ldr r0, =privileged_message
> + bl puts
> +
> + movs r0, 1
> + msr control, r0
> + isb
> +
> + ldr r0, [r4]
> + ldr r0, =fail_no_fault_message
> + b test_failed
> +
> +.thumb_func
> +configure_region:
> + ldr r3, =MPU_RNR
> + str r0, [r3]
> + ldr r3, =MPU_RBAR
> + str r1, [r3]
> + ldr r3, =MPU_RASR
> + str r2, [r3]
> + bx lr
> +
> +fail_type:
> + ldr r0, =fail_type_message
> + b test_failed
> +
> +fail_plain:
> + ldr r0, =fail_plain_message
> + b test_failed
> +
> +fail_privileged:
> + ldr r0, =fail_privileged_message
> +
> +test_failed:
> + bl puts
> + movs r0, SYS_EXIT
> + ldr r1, =ADP_STOPPED_RUNTIME_ERROR
> + bkpt 0xab
> +
> +1:
> + b 1b
> +
> +.thumb_func
> +hardfault_handler:
> + ldr r0, =ok_message
> + bl puts
> + movs r0, SYS_EXIT
> + ldr r1, =ADP_STOPPED_APPLICATION_EXIT
> + bkpt 0xab
> +
> +2:
> + b 2b
> +
> +.thumb_func
> +default_handler:
> + ldr r0, =fail_exception_message
> + b test_failed
> +
> +.thumb_func
> +puts:
> + ldr r2, =UART0_DR
> +
> +3:
> + ldrb r1, [r0]
> + cmp r1, 0
> + beq 4f
> + str r1, [r2]
> + adds r0, 1
> + b 3b
> +
> +4:
> + bx lr
> +
> +.section .rodata, "a", %progbits
> +start_message:
> + .asciz "PICO TCG MPU START\n"
> +type_message:
> + .asciz "PICO TCG MPU TYPE\n"
> +plain_message:
> + .asciz "PICO TCG MPU PLAIN\n"
> +privileged_message:
> + .asciz "PICO TCG MPU PRIV\n"
> +ok_message:
> + .asciz "PICO TCG MPU OK\n"
> +fail_type_message:
> + .asciz "PICO TCG MPU FAIL: TYPE\n"
> +fail_plain_message:
> + .asciz "PICO TCG MPU FAIL: PLAIN\n"
> +fail_privileged_message:
> + .asciz "PICO TCG MPU FAIL: PRIV\n"
> +fail_no_fault_message:
> + .asciz "PICO TCG MPU FAIL: NOFAULT\n"
> +fail_exception_message:
> + .asciz "PICO TCG MPU FAIL: EXCEPTION\n"
> diff --git a/tests/tcg/arm/system/rp2040-mpu.ref b/tests/tcg/arm/system/rp2040-mpu.ref
> new file mode 100644
> index 0000000000..e55590ea16
> --- /dev/null
> +++ b/tests/tcg/arm/system/rp2040-mpu.ref
> @@ -0,0 +1,5 @@
> +PICO TCG MPU START
> +PICO TCG MPU TYPE
> +PICO TCG MPU PLAIN
> +PICO TCG MPU PRIV
> +PICO TCG MPU OK
> diff --git a/tests/tcg/arm/system/rp2040-uart.S b/tests/tcg/arm/system/rp2040-uart.S
> new file mode 100644
> index 0000000000..bdb58cc7f2
> --- /dev/null
> +++ b/tests/tcg/arm/system/rp2040-uart.S
> @@ -0,0 +1,53 @@
> +/*
> + * Raspberry Pi Pico UART boot smoke test.
> + *
> + * Copyright 2026 Gilles Grimaud
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +.syntax unified
> +.cpu cortex-m0plus
> +.thumb
> +
> +#define SRAM_END 0x20042000
> +#define UART0_DR 0x40034000
> +#define SYS_EXIT 0x18
> +#define ADP_STOPPED_APPLICATION_EXIT 0x20026
> +
> +.section .vectors, "a", %progbits
> +vector_table:
> + .word SRAM_END
> + .word reset_handler + 1
> + .word default_handler + 1
> + .word default_handler + 1
> +
> +.section .text, "ax", %progbits
> +.thumb_func
> +.global reset_handler
> +reset_handler:
> + ldr r0, =UART0_DR
> + ldr r1, =message
> +
> +1:
> + ldrb r2, [r1]
> + cmp r2, 0
> + beq 2f
> + str r2, [r0]
> + adds r1, 1
> + b 1b
> +
> +2:
> + movs r0, SYS_EXIT
> + ldr r1, =ADP_STOPPED_APPLICATION_EXIT
> + bkpt 0xab
> +
> +3:
> + b 3b
> +
> +.thumb_func
> +default_handler:
> + b default_handler
> +
> +.section .rodata, "a", %progbits
> +message:
> + .asciz "PICO TCG UART OK\n"
> diff --git a/tests/tcg/arm/system/rp2040-uart.ref b/tests/tcg/arm/system/rp2040-uart.ref
> new file mode 100644
> index 0000000000..1ef16e8299
> --- /dev/null
> +++ b/tests/tcg/arm/system/rp2040-uart.ref
> @@ -0,0 +1 @@
> +PICO TCG UART OK
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (4 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 05/30] tests/tcg/arm: add Raspberry Pi Pico smoke tests Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-01 18:44 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 07/30] hw/misc: add RP2040 TBMAN and voltage regulator Gilles Grimaud
` (24 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the RP2040 identification and system configuration registers, including APB atomic aliases and documented reset and write masks. Wire SYSCFG into the SoC so NMI masks reroute interrupt inputs and MEMPOWERDOWN blocks access to powered-off ROM and SRAM banks.\n\nAdd focused qtests for register values, aliases, power-down behavior, and UART0 IRQ routing to NMI.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
MAINTAINERS | 1 +
hw/arm/Kconfig | 2 +
hw/arm/rp2040.c | 129 +++++++++++-
hw/misc/Kconfig | 6 +
hw/misc/meson.build | 2 +
hw/misc/rp2040_syscfg.c | 244 +++++++++++++++++++++++
hw/misc/rp2040_sysinfo.c | 103 ++++++++++
include/hw/arm/rp2040.h | 12 ++
include/hw/misc/rp2040_syscfg.h | 42 ++++
include/hw/misc/rp2040_sysinfo.h | 25 +++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-sysinfo-syscfg-test.c | 176 ++++++++++++++++
12 files changed, 740 insertions(+), 3 deletions(-)
create mode 100644 hw/misc/rp2040_syscfg.c
create mode 100644 hw/misc/rp2040_sysinfo.c
create mode 100644 include/hw/misc/rp2040_syscfg.h
create mode 100644 include/hw/misc/rp2040_sysinfo.h
create mode 100644 tests/qtest/rp2040-sysinfo-syscfg-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index b000b3287d..fd35521947 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1042,6 +1042,7 @@ S: Maintained
F: hw/*/rp2040*
F: include/hw/*/rp2040*
F: hw/arm/raspi_pico.c
+F: tests/qtest/rp2040*
F: tests/tcg/arm/system/rp2040*
Real View
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index d93303ab80..5d55ca06fd 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -379,6 +379,8 @@ config RP2040
select ARM_V7M
select PL011
select RP2040_NYI
+ select RP2040_SYSCFG
+ select RP2040_SYSINFO
select UNIMP
config STM32F100_SOC
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 2196bad18d..a184d527e1 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -13,6 +13,7 @@
#include "hw/core/loader.h"
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties.h"
+#include "hw/core/irq.h"
#include "hw/misc/unimp.h"
#include "qemu/datadir.h"
#include "target/arm/cpu-qom.h"
@@ -49,8 +50,6 @@ static const struct {
hwaddr base;
hwaddr size;
} rp2040_unimplemented[] = {
- { "rp2040.sysinfo", 0x40000000, 0x4000 },
- { "rp2040.syscfg", 0x40004000, 0x4000 },
{ "rp2040.clocks", 0x40008000, 0x4000 },
{ "rp2040.resets", 0x4000c000, 0x4000 },
{ "rp2040.psm", 0x40010000, 0x4000 },
@@ -84,6 +83,80 @@ static const struct {
{ "rp2040.sio", 0xd0000000, 0x1000 },
};
+static MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr,
+ uint64_t *data, unsigned size,
+ MemTxAttrs attrs)
+{
+ *data = 0;
+ return MEMTX_ERROR;
+}
+
+static MemTxResult rp2040_powered_off_write(void *opaque, hwaddr addr,
+ uint64_t data, unsigned size,
+ MemTxAttrs attrs)
+{
+ return MEMTX_ERROR;
+}
+
+static const MemoryRegionOps rp2040_powered_off_ops = {
+ .read_with_attrs = rp2040_powered_off_read,
+ .write_with_attrs = rp2040_powered_off_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_update_mempowerdown(RP2040State *s)
+{
+ uint32_t mempowerdown;
+ int i;
+
+ if (!s->mempowerdown_ready) {
+ return;
+ }
+
+ mempowerdown = rp2040_syscfg_get_mempowerdown(&s->syscfg);
+ for (i = 0; i < ARRAY_SIZE(s->sram_poweroff); i++) {
+ memory_region_set_enabled(&s->sram_poweroff[i],
+ mempowerdown & BIT(i));
+ }
+ memory_region_set_enabled(&s->rom_poweroff, mempowerdown & BIT(7));
+}
+
+static void rp2040_update_nmi(RP2040State *s)
+{
+ uint32_t nmi_mask = rp2040_syscfg_get_proc0_nmi_mask(&s->syscfg);
+ bool nmi_level = false;
+ int i;
+
+ for (i = 0; i < RP2040_NUM_IRQS; i++) {
+ bool route_to_nmi = nmi_mask & BIT(i);
+
+ qemu_set_irq(s->cpu_irq[i], s->irq_level[i] && !route_to_nmi);
+ nmi_level |= s->irq_level[i] && route_to_nmi;
+ }
+ qemu_set_irq(s->nmi_irq, nmi_level);
+}
+
+static void rp2040_syscfg_update(void *opaque)
+{
+ RP2040State *s = opaque;
+
+ rp2040_update_mempowerdown(s);
+ rp2040_update_nmi(s);
+}
+
+static void rp2040_set_irq(void *opaque, int irq, int level)
+{
+ RP2040State *s = opaque;
+
+ assert(irq >= 0 && irq < RP2040_NUM_IRQS);
+ s->irq_level[irq] = level;
+ rp2040_update_nmi(s);
+}
+
static void rp2040_soc_init(Object *obj)
{
RP2040State *s = RP2040(obj);
@@ -104,6 +177,10 @@ static void rp2040_soc_init(Object *obj)
"chardev");
}
+ object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
+ object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
+
+ s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
s->sysclk = clock_new(obj, "sysclk");
clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ);
}
@@ -117,9 +194,17 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
return false;
}
memory_region_add_subregion(s->board_memory, RP2040_ROM_BASE, &s->rom);
+ memory_region_init_io(&s->rom_poweroff, OBJECT(s),
+ &rp2040_powered_off_ops, s,
+ "rp2040.rom.poweroff", RP2040_ROM_SIZE);
+ memory_region_add_subregion_overlap(s->board_memory, RP2040_ROM_BASE,
+ &s->rom_poweroff, 1);
+ memory_region_set_enabled(&s->rom_poweroff, false);
for (i = 0; i < 4; i++) {
g_autofree char *name = g_strdup_printf("rp2040.sram%d", i);
+ g_autofree char *poweroff_name =
+ g_strdup_printf("rp2040.sram%d.poweroff", i);
if (!memory_region_init_ram(&s->sram[i], OBJECT(s), name,
RP2040_SRAM_BANK_SIZE, errp)) {
@@ -129,6 +214,14 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
RP2040_SRAM_BASE +
i * RP2040_SRAM_BANK_SIZE,
&s->sram[i]);
+ memory_region_init_io(&s->sram_poweroff[i], OBJECT(s),
+ &rp2040_powered_off_ops, s, poweroff_name,
+ RP2040_SRAM_BANK_SIZE);
+ memory_region_add_subregion_overlap(s->board_memory,
+ RP2040_SRAM_BASE +
+ i * RP2040_SRAM_BANK_SIZE,
+ &s->sram_poweroff[i], 1);
+ memory_region_set_enabled(&s->sram_poweroff[i], false);
}
if (!memory_region_init_ram(&s->sram[4], OBJECT(s), "rp2040.sram4",
@@ -137,6 +230,12 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
}
memory_region_add_subregion(s->board_memory, RP2040_SRAM4_BASE,
&s->sram[4]);
+ memory_region_init_io(&s->sram_poweroff[4], OBJECT(s),
+ &rp2040_powered_off_ops, s,
+ "rp2040.sram4.poweroff", RP2040_SRAM_HI_SIZE);
+ memory_region_add_subregion_overlap(s->board_memory, RP2040_SRAM4_BASE,
+ &s->sram_poweroff[4], 1);
+ memory_region_set_enabled(&s->sram_poweroff[4], false);
if (!memory_region_init_ram(&s->sram[5], OBJECT(s), "rp2040.sram5",
RP2040_SRAM_HI_SIZE, errp)) {
@@ -144,6 +243,14 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
}
memory_region_add_subregion(s->board_memory, RP2040_SRAM5_BASE,
&s->sram[5]);
+ memory_region_init_io(&s->sram_poweroff[5], OBJECT(s),
+ &rp2040_powered_off_ops, s,
+ "rp2040.sram5.poweroff", RP2040_SRAM_HI_SIZE);
+ memory_region_add_subregion_overlap(s->board_memory, RP2040_SRAM5_BASE,
+ &s->sram_poweroff[5], 1);
+ memory_region_set_enabled(&s->sram_poweroff[5], false);
+
+ s->mempowerdown_ready = true;
return true;
}
@@ -201,6 +308,22 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
return;
}
+ for (i = 0; i < RP2040_NUM_IRQS; i++) {
+ s->cpu_irq[i] = qdev_get_gpio_in(DEVICE(&s->armv7m), i);
+ }
+ s->nmi_irq = qdev_get_gpio_in_named(DEVICE(&s->armv7m), "NMI", 0);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->syscfg), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->syscfg), 0, RP2040_SYSCFG_BASE);
+ rp2040_syscfg_set_update_callback(&s->syscfg, rp2040_syscfg_update, s);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysinfo), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
+ rp2040_syscfg_update(s);
for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
@@ -209,7 +332,7 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
- qdev_get_gpio_in(DEVICE(&s->armv7m), uart_irq[i]));
+ s->irq[uart_irq[i]]);
}
}
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index de7a0f56fa..f6f344ab51 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -104,6 +104,12 @@ config FSL_IMX8MP_CCM
config RP2040_NYI
bool
+config RP2040_SYSCFG
+ bool
+
+config RP2040_SYSINFO
+ bool
+
config STM32_RCC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 8611d59437..7be9f45c78 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -99,6 +99,8 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_powermgt.c',
))
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
+system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
+system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
diff --git a/hw/misc/rp2040_syscfg.c b/hw/misc/rp2040_syscfg.c
new file mode 100644
index 0000000000..69b4164ccd
--- /dev/null
+++ b/hw/misc/rp2040_syscfg.c
@@ -0,0 +1,244 @@
+/*
+ * RP2040 syscfg emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_syscfg.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+
+#define SYSCFG_PROC0_NMI_MASK 0x00
+#define SYSCFG_PROC1_NMI_MASK 0x04
+#define SYSCFG_PROC_CONFIG 0x08
+#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c
+#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10
+#define SYSCFG_DBGFORCE 0x14
+#define SYSCFG_MEMPOWERDOWN 0x18
+
+#define PROC_CONFIG_RW_MASK 0xff000000
+#define PROC_CONFIG_RESET 0x10000000
+#define PROC_IN_SYNC_BYPASS_MASK 0x3fffffff
+#define PROC_IN_SYNC_BYPASS_HI_MASK 0x0000003f
+#define DBGFORCE_RW_MASK 0x000000ee
+#define DBGFORCE_RESET 0x00000066
+#define MEMPOWERDOWN_MASK 0x000000ff
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_syscfg_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_syscfg_update(RP2040SysCfgState *s)
+{
+ if (s->update) {
+ s->update(s->update_opaque);
+ }
+}
+
+void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s,
+ RP2040SysCfgUpdateFn update,
+ void *opaque)
+{
+ s->update = update;
+ s->update_opaque = opaque;
+}
+
+uint32_t rp2040_syscfg_get_proc0_nmi_mask(RP2040SysCfgState *s)
+{
+ return s->proc0_nmi_mask;
+}
+
+uint32_t rp2040_syscfg_get_mempowerdown(RP2040SysCfgState *s)
+{
+ return s->mempowerdown;
+}
+
+static uint64_t rp2040_syscfg_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040SysCfgState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case SYSCFG_PROC0_NMI_MASK:
+ value = s->proc0_nmi_mask;
+ break;
+ case SYSCFG_PROC1_NMI_MASK:
+ value = s->proc1_nmi_mask;
+ break;
+ case SYSCFG_PROC_CONFIG:
+ value = s->proc_config;
+ break;
+ case SYSCFG_PROC_IN_SYNC_BYPASS:
+ value = s->proc_in_sync_bypass;
+ break;
+ case SYSCFG_PROC_IN_SYNC_BYPASS_HI:
+ value = s->proc_in_sync_bypass_hi;
+ break;
+ case SYSCFG_DBGFORCE:
+ value = s->dbgforce;
+ break;
+ case SYSCFG_MEMPOWERDOWN:
+ value = s->mempowerdown;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("syscfg", size,
+ RP2040_SYSCFG_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_syscfg_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040SysCfgState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+
+ switch (offset) {
+ case SYSCFG_PROC0_NMI_MASK:
+ s->proc0_nmi_mask =
+ rp2040_syscfg_apply_alias(s->proc0_nmi_mask, value, alias);
+ rp2040_syscfg_update(s);
+ break;
+ case SYSCFG_PROC1_NMI_MASK:
+ s->proc1_nmi_mask =
+ rp2040_syscfg_apply_alias(s->proc1_nmi_mask, value, alias);
+ break;
+ case SYSCFG_PROC_CONFIG:
+ s->proc_config =
+ rp2040_syscfg_apply_alias(s->proc_config, value, alias) &
+ PROC_CONFIG_RW_MASK;
+ break;
+ case SYSCFG_PROC_IN_SYNC_BYPASS:
+ s->proc_in_sync_bypass =
+ rp2040_syscfg_apply_alias(s->proc_in_sync_bypass, value, alias) &
+ PROC_IN_SYNC_BYPASS_MASK;
+ break;
+ case SYSCFG_PROC_IN_SYNC_BYPASS_HI:
+ s->proc_in_sync_bypass_hi =
+ rp2040_syscfg_apply_alias(s->proc_in_sync_bypass_hi, value,
+ alias) &
+ PROC_IN_SYNC_BYPASS_HI_MASK;
+ break;
+ case SYSCFG_DBGFORCE:
+ s->dbgforce =
+ rp2040_syscfg_apply_alias(s->dbgforce, value, alias) &
+ DBGFORCE_RW_MASK;
+ break;
+ case SYSCFG_MEMPOWERDOWN:
+ s->mempowerdown =
+ rp2040_syscfg_apply_alias(s->mempowerdown, value, alias) &
+ MEMPOWERDOWN_MASK;
+ rp2040_syscfg_update(s);
+ break;
+ default:
+ rp2040_log_unimplemented_write("syscfg", size,
+ RP2040_SYSCFG_BASE + addr, offset,
+ value64);
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_syscfg_ops = {
+ .read = rp2040_syscfg_read,
+ .write = rp2040_syscfg_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_syscfg_reset(DeviceState *dev)
+{
+ RP2040SysCfgState *s = RP2040_SYSCFG(dev);
+
+ s->proc0_nmi_mask = 0;
+ s->proc1_nmi_mask = 0;
+ s->proc_config = PROC_CONFIG_RESET;
+ s->proc_in_sync_bypass = 0;
+ s->proc_in_sync_bypass_hi = 0;
+ s->dbgforce = DBGFORCE_RESET;
+ s->mempowerdown = 0;
+ rp2040_syscfg_update(s);
+}
+
+static int rp2040_syscfg_post_load(void *opaque, int version_id)
+{
+ RP2040SysCfgState *s = opaque;
+
+ rp2040_syscfg_update(s);
+ return 0;
+}
+
+static void rp2040_syscfg_init(Object *obj)
+{
+ RP2040SysCfgState *s = RP2040_SYSCFG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_syscfg_ops, s,
+ "rp2040.syscfg", RP2040_SYSCFG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_syscfg_vmstate = {
+ .name = TYPE_RP2040_SYSCFG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = rp2040_syscfg_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(proc0_nmi_mask, RP2040SysCfgState),
+ VMSTATE_UINT32(proc1_nmi_mask, RP2040SysCfgState),
+ VMSTATE_UINT32(proc_config, RP2040SysCfgState),
+ VMSTATE_UINT32(proc_in_sync_bypass, RP2040SysCfgState),
+ VMSTATE_UINT32(proc_in_sync_bypass_hi, RP2040SysCfgState),
+ VMSTATE_UINT32(dbgforce, RP2040SysCfgState),
+ VMSTATE_UINT32(mempowerdown, RP2040SysCfgState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_syscfg_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_syscfg_reset);
+ dc->vmsd = &rp2040_syscfg_vmstate;
+}
+
+static const TypeInfo rp2040_syscfg_info = {
+ .name = TYPE_RP2040_SYSCFG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040SysCfgState),
+ .instance_init = rp2040_syscfg_init,
+ .class_init = rp2040_syscfg_class_init,
+};
+
+static void rp2040_syscfg_register_types(void)
+{
+ type_register_static(&rp2040_syscfg_info);
+}
+type_init(rp2040_syscfg_register_types)
diff --git a/hw/misc/rp2040_sysinfo.c b/hw/misc/rp2040_sysinfo.c
new file mode 100644
index 0000000000..15c50df6af
--- /dev/null
+++ b/hw/misc/rp2040_sysinfo.c
@@ -0,0 +1,103 @@
+/*
+ * RP2040 sysinfo emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_sysinfo.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define SYSINFO_CHIP_ID 0x00
+#define SYSINFO_PLATFORM 0x04
+#define SYSINFO_GITREF_RP2040 0x40
+
+#define SYSINFO_PLATFORM_ASIC BIT(1)
+
+static uint64_t rp2040_sysinfo_read(void *opaque, hwaddr addr, unsigned size)
+{
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case SYSINFO_CHIP_ID:
+ case SYSINFO_GITREF_RP2040:
+ value = 0;
+ break;
+ case SYSINFO_PLATFORM:
+ value = SYSINFO_PLATFORM_ASIC;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("sysinfo", size,
+ RP2040_SYSINFO_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_sysinfo_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ hwaddr offset = addr & 0xfff;
+
+ qemu_log_mask(LOG_GUEST_ERROR, "rp2040.sysinfo: write to read-only "
+ "register (size %d, addr 0x%08" HWADDR_PRIx
+ ", offset 0x%04" HWADDR_PRIx
+ ", value 0x%0*" PRIx64 ")\n",
+ size, RP2040_SYSINFO_BASE + addr, offset, size << 1, value);
+}
+
+static const MemoryRegionOps rp2040_sysinfo_ops = {
+ .read = rp2040_sysinfo_read,
+ .write = rp2040_sysinfo_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_sysinfo_init(Object *obj)
+{
+ RP2040SysInfoState *s = RP2040_SYSINFO(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_sysinfo_ops, s,
+ "rp2040.sysinfo", RP2040_SYSINFO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_sysinfo_vmstate = {
+ .name = TYPE_RP2040_SYSINFO,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_sysinfo_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &rp2040_sysinfo_vmstate;
+}
+
+static const TypeInfo rp2040_sysinfo_info = {
+ .name = TYPE_RP2040_SYSINFO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040SysInfoState),
+ .instance_init = rp2040_sysinfo_init,
+ .class_init = rp2040_sysinfo_class_init,
+};
+
+static void rp2040_sysinfo_register_types(void)
+{
+ type_register_static(&rp2040_sysinfo_info);
+}
+type_init(rp2040_sysinfo_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 4e5cd06add..caa1f9127f 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -13,6 +13,8 @@
#include "hw/char/pl011.h"
#include "hw/core/clock.h"
#include "hw/core/sysbus.h"
+#include "hw/misc/rp2040_syscfg.h"
+#include "hw/misc/rp2040_sysinfo.h"
#include "qom/object.h"
#define TYPE_RP2040 "rp2040"
@@ -34,12 +36,22 @@ struct RP2040State {
ARMv7MState armv7m;
PL011State uart[2];
+ RP2040SysCfgState syscfg;
+ RP2040SysInfoState sysinfo;
MemoryRegion *board_memory;
MemoryRegion rom;
+ MemoryRegion rom_poweroff;
MemoryRegion sram[6];
+ MemoryRegion sram_poweroff[6];
char *bootrom_file;
+ qemu_irq *irq;
+ qemu_irq cpu_irq[RP2040_NUM_IRQS];
+ qemu_irq nmi_irq;
+ bool irq_level[RP2040_NUM_IRQS];
+ bool mempowerdown_ready;
+
Clock *sysclk;
};
diff --git a/include/hw/misc/rp2040_syscfg.h b/include/hw/misc/rp2040_syscfg.h
new file mode 100644
index 0000000000..c31799ae7a
--- /dev/null
+++ b/include/hw/misc/rp2040_syscfg.h
@@ -0,0 +1,42 @@
+/*
+ * RP2040 syscfg emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_SYSCFG_H
+#define HW_MISC_RP2040_SYSCFG_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_SYSCFG "rp2040-syscfg"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040SysCfgState, RP2040_SYSCFG)
+
+#define RP2040_SYSCFG_BASE 0x40004000
+#define RP2040_SYSCFG_SIZE 0x4000
+
+typedef void (*RP2040SysCfgUpdateFn)(void *opaque);
+
+struct RP2040SysCfgState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ RP2040SysCfgUpdateFn update;
+ void *update_opaque;
+ uint32_t proc0_nmi_mask;
+ uint32_t proc1_nmi_mask;
+ uint32_t proc_config;
+ uint32_t proc_in_sync_bypass;
+ uint32_t proc_in_sync_bypass_hi;
+ uint32_t dbgforce;
+ uint32_t mempowerdown;
+};
+
+void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s,
+ RP2040SysCfgUpdateFn update,
+ void *opaque);
+uint32_t rp2040_syscfg_get_proc0_nmi_mask(RP2040SysCfgState *s);
+uint32_t rp2040_syscfg_get_mempowerdown(RP2040SysCfgState *s);
+
+#endif
diff --git a/include/hw/misc/rp2040_sysinfo.h b/include/hw/misc/rp2040_sysinfo.h
new file mode 100644
index 0000000000..d521659488
--- /dev/null
+++ b/include/hw/misc/rp2040_sysinfo.h
@@ -0,0 +1,25 @@
+/*
+ * RP2040 sysinfo emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_SYSINFO_H
+#define HW_MISC_RP2040_SYSINFO_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_SYSINFO "rp2040-sysinfo"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040SysInfoState, RP2040_SYSINFO)
+
+#define RP2040_SYSINFO_BASE 0x40000000
+#define RP2040_SYSINFO_SIZE 0x4000
+
+struct RP2040SysInfoState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 5555ce4eae..692d51a78a 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -255,6 +255,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 'tpm-tis-i2c-swtpm-test'] : []) + \
(config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-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-sysinfo-syscfg-test.c b/tests/qtest/rp2040-sysinfo-syscfg-test.c
new file mode 100644
index 0000000000..18da6561bd
--- /dev/null
+++ b/tests/qtest/rp2040-sysinfo-syscfg-test.c
@@ -0,0 +1,176 @@
+/*
+ * QTest testcase for the RP2040 sysinfo and syscfg blocks.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define SYSINFO_BASE 0x40000000
+#define SYSINFO_CHIP_ID 0x00
+#define SYSINFO_PLATFORM 0x04
+#define SYSINFO_GITREF_RP2040 0x40
+#define SYSINFO_PLATFORM_ASIC BIT(1)
+
+#define SYSCFG_BASE 0x40004000
+#define SYSCFG_PROC0_NMI_MASK 0x00
+#define SYSCFG_PROC1_NMI_MASK 0x04
+#define SYSCFG_PROC_CONFIG 0x08
+#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c
+#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10
+#define SYSCFG_DBGFORCE 0x14
+#define SYSCFG_MEMPOWERDOWN 0x18
+
+#define SRAM0_BASE 0x20000000
+#define UART0_BASE 0x40034000
+#define UARTDR 0x00
+#define UARTCR 0x30
+#define UARTIMSC 0x38
+#define UARTICR 0x44
+#define UART_INT_TX BIT(5)
+#define UART0_IRQ 20
+#define NVIC_ISPR 0xe000e200
+#define NVIC_ICPR 0xe000e280
+#define NVIC_ICSR 0xe000ed04
+#define NVIC_ICSR_NMIPENDSET BIT(31)
+
+#define PROC_CONFIG_RESET 0x10000000
+#define DBGFORCE_RESET 0x00000066
+
+#define ATOMIC_SET_ALIAS 0x2000
+#define ATOMIC_CLR_ALIAS 0x3000
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_sysinfo_read_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_CHIP_ID), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_PLATFORM), ==,
+ SYSINFO_PLATFORM_ASIC);
+ g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_GITREF_RP2040),
+ ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_syscfg_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
+ ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK),
+ ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC_CONFIG), ==,
+ PROC_CONFIG_RESET);
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_DBGFORCE), ==,
+ DBGFORCE_RESET);
+
+ qtest_quit(qts);
+}
+
+static void test_syscfg_rw_masks(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0xa5a5a5a5);
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK, 0x5a5a5a5a);
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
+ ==, 0xa5a5a5a5);
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK),
+ ==, 0x5a5a5a5a);
+
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS, 0xffffffff);
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI,
+ 0xffffffff);
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts,
+ SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS),
+ ==, 0x3fffffff);
+ g_assert_cmphex(qtest_readl(qts,
+ SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI),
+ ==, 0x0000003f);
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN), ==,
+ 0x000000ff);
+
+ qtest_quit(qts);
+}
+
+static void test_syscfg_atomic_aliases(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0);
+ qtest_writel(qts, SYSCFG_BASE + ATOMIC_SET_ALIAS + SYSCFG_PROC0_NMI_MASK,
+ 0x0000000f);
+ qtest_writel(qts, SYSCFG_BASE + ATOMIC_CLR_ALIAS + SYSCFG_PROC0_NMI_MASK,
+ 0x00000003);
+ g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
+ ==, 0x0000000c);
+
+ qtest_quit(qts);
+}
+
+static void test_syscfg_mempowerdown_blocks_sram(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SRAM0_BASE, 0x11223344);
+ g_assert_cmphex(qtest_readl(qts, SRAM0_BASE), ==, 0x11223344);
+
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, BIT(0));
+ qtest_writel(qts, SRAM0_BASE, 0xaabbccdd);
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, 0);
+
+ g_assert_cmphex(qtest_readl(qts, SRAM0_BASE), ==, 0x11223344);
+
+ qtest_quit(qts);
+}
+
+static void test_syscfg_nmi_mask_routes_uart0_irq(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, UART0_BASE + UARTCR, BIT(0) | BIT(8));
+ qtest_writel(qts, UART0_BASE + UARTIMSC, UART_INT_TX);
+
+ qtest_writel(qts, UART0_BASE + UARTICR, UART_INT_TX);
+ qtest_writel(qts, NVIC_ICPR, BIT(UART0_IRQ));
+ qtest_writel(qts, UART0_BASE + UARTDR, 'a');
+ g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & BIT(UART0_IRQ), ==,
+ BIT(UART0_IRQ));
+
+ qtest_writel(qts, UART0_BASE + UARTICR, UART_INT_TX);
+ qtest_writel(qts, NVIC_ICPR, BIT(UART0_IRQ));
+ qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, BIT(UART0_IRQ));
+ qtest_writel(qts, UART0_BASE + UARTDR, 'b');
+ g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & BIT(UART0_IRQ), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, NVIC_ICSR) & NVIC_ICSR_NMIPENDSET, ==,
+ NVIC_ICSR_NMIPENDSET);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-sysinfo/read-values", test_sysinfo_read_values);
+ qtest_add_func("/rp2040-syscfg/reset-values", test_syscfg_reset_values);
+ qtest_add_func("/rp2040-syscfg/rw-masks", test_syscfg_rw_masks);
+ qtest_add_func("/rp2040-syscfg/atomic-aliases",
+ test_syscfg_atomic_aliases);
+ qtest_add_func("/rp2040-syscfg/mempowerdown-blocks-sram",
+ test_syscfg_mempowerdown_blocks_sram);
+ qtest_add_func("/rp2040-syscfg/nmi-mask-routes-uart0-irq",
+ test_syscfg_nmi_mask_routes_uart0_irq);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG
2026-08-29 23:42 ` [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG Gilles Grimaud
@ 2026-09-01 18:44 ` Alex Bennée
2026-09-01 23:29 ` gilles grimaud
0 siblings, 1 reply; 49+ messages in thread
From: Alex Bennée @ 2026-09-01 18:44 UTC (permalink / raw)
To: Gilles Grimaud
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell, Fabiano Rosas,
Laurent Vivier
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Model the RP2040 identification and system configuration registers,
> including APB atomic aliases and documented reset and write masks.
> Wire SYSCFG into the SoC so NMI masks reroute interrupt inputs and
> MEMPOWERDOWN blocks access to powered-off ROM and SRAM banks.
What exactly is going to be accessing ROM and SRAM while powered off?
Why do all three things need to be done at the same time?
> \n\nAdd focused qtests for register values, aliases, power-down behavior, and UART0 IRQ routing to NMI.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> MAINTAINERS | 1 +
> hw/arm/Kconfig | 2 +
> hw/arm/rp2040.c | 129 +++++++++++-
> hw/misc/Kconfig | 6 +
> hw/misc/meson.build | 2 +
> hw/misc/rp2040_syscfg.c | 244 +++++++++++++++++++++++
> hw/misc/rp2040_sysinfo.c | 103 ++++++++++
> include/hw/arm/rp2040.h | 12 ++
> include/hw/misc/rp2040_syscfg.h | 42 ++++
> include/hw/misc/rp2040_sysinfo.h | 25 +++
> tests/qtest/meson.build | 1 +
> tests/qtest/rp2040-sysinfo-syscfg-test.c | 176 ++++++++++++++++
> 12 files changed, 740 insertions(+), 3 deletions(-)
> create mode 100644 hw/misc/rp2040_syscfg.c
> create mode 100644 hw/misc/rp2040_sysinfo.c
> create mode 100644 include/hw/misc/rp2040_syscfg.h
> create mode 100644 include/hw/misc/rp2040_sysinfo.h
> create mode 100644 tests/qtest/rp2040-sysinfo-syscfg-test.c
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index b000b3287d..fd35521947 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1042,6 +1042,7 @@ S: Maintained
> F: hw/*/rp2040*
> F: include/hw/*/rp2040*
> F: hw/arm/raspi_pico.c
> +F: tests/qtest/rp2040*
> F: tests/tcg/arm/system/rp2040*
>
> Real View
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index d93303ab80..5d55ca06fd 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -379,6 +379,8 @@ config RP2040
> select ARM_V7M
> select PL011
> select RP2040_NYI
> + select RP2040_SYSCFG
> + select RP2040_SYSINFO
I'm not sure we are going to need Kconfig vars for every sub-system. If
they come with the RP2040 as a piece we should only need one symbol to
select the board, peripheral and CPU models.
> select UNIMP
>
> config STM32F100_SOC
> diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
> index 2196bad18d..a184d527e1 100644
> --- a/hw/arm/rp2040.c
> +++ b/hw/arm/rp2040.c
> @@ -13,6 +13,7 @@
> #include "hw/core/loader.h"
> #include "hw/core/qdev-clock.h"
> #include "hw/core/qdev-properties.h"
> +#include "hw/core/irq.h"
> #include "hw/misc/unimp.h"
> #include "qemu/datadir.h"
> #include "target/arm/cpu-qom.h"
> @@ -49,8 +50,6 @@ static const struct {
> hwaddr base;
> hwaddr size;
> } rp2040_unimplemented[] = {
> - { "rp2040.sysinfo", 0x40000000, 0x4000 },
> - { "rp2040.syscfg", 0x40004000, 0x4000 },
> { "rp2040.clocks", 0x40008000, 0x4000 },
> { "rp2040.resets", 0x4000c000, 0x4000 },
> { "rp2040.psm", 0x40010000, 0x4000 },
> @@ -84,6 +83,80 @@ static const struct {
> { "rp2040.sio", 0xd0000000, 0x1000 },
> };
>
> +static MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr,
> + uint64_t *data, unsigned size,
> + MemTxAttrs attrs)
> +{
> + *data = 0;
> + return MEMTX_ERROR;
> +}
> +
> +static MemTxResult rp2040_powered_off_write(void *opaque, hwaddr addr,
> + uint64_t data, unsigned size,
> + MemTxAttrs attrs)
> +{
> + return MEMTX_ERROR;
> +}
> +
> +static const MemoryRegionOps rp2040_powered_off_ops = {
> + .read_with_attrs = rp2040_powered_off_read,
> + .write_with_attrs = rp2040_powered_off_write,
You choose to have _with_attrs access functions here but not later. Why?
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 1,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void rp2040_update_mempowerdown(RP2040State *s)
> +{
> + uint32_t mempowerdown;
> + int i;
> +
> + if (!s->mempowerdown_ready) {
> + return;
> + }
> +
> + mempowerdown = rp2040_syscfg_get_mempowerdown(&s->syscfg);
> + for (i = 0; i < ARRAY_SIZE(s->sram_poweroff); i++) {
> + memory_region_set_enabled(&s->sram_poweroff[i],
> + mempowerdown & BIT(i));
> + }
> + memory_region_set_enabled(&s->rom_poweroff, mempowerdown & BIT(7));
> +}
> +
> +static void rp2040_update_nmi(RP2040State *s)
> +{
> + uint32_t nmi_mask = rp2040_syscfg_get_proc0_nmi_mask(&s->syscfg);
> + bool nmi_level = false;
> + int i;
> +
> + for (i = 0; i < RP2040_NUM_IRQS; i++) {
> + bool route_to_nmi = nmi_mask & BIT(i);
> +
> + qemu_set_irq(s->cpu_irq[i], s->irq_level[i] && !route_to_nmi);
> + nmi_level |= s->irq_level[i] && route_to_nmi;
> + }
> + qemu_set_irq(s->nmi_irq, nmi_level);
> +}
> +
> +static void rp2040_syscfg_update(void *opaque)
> +{
> + RP2040State *s = opaque;
> +
> + rp2040_update_mempowerdown(s);
> + rp2040_update_nmi(s);
> +}
> +
> +static void rp2040_set_irq(void *opaque, int irq, int level)
> +{
> + RP2040State *s = opaque;
> +
> + assert(irq >= 0 && irq < RP2040_NUM_IRQS);
> + s->irq_level[irq] = level;
> + rp2040_update_nmi(s);
> +}
> +
> static void rp2040_soc_init(Object *obj)
> {
> RP2040State *s = RP2040(obj);
> @@ -104,6 +177,10 @@ static void rp2040_soc_init(Object *obj)
> "chardev");
> }
>
> + object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
> + object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
> +
> + s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
> s->sysclk = clock_new(obj, "sysclk");
> clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ);
> }
> @@ -117,9 +194,17 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
> return false;
> }
> memory_region_add_subregion(s->board_memory, RP2040_ROM_BASE, &s->rom);
> + memory_region_init_io(&s->rom_poweroff, OBJECT(s),
> + &rp2040_powered_off_ops, s,
> + "rp2040.rom.poweroff", RP2040_ROM_SIZE);
> + memory_region_add_subregion_overlap(s->board_memory, RP2040_ROM_BASE,
> + &s->rom_poweroff, 1);
> + memory_region_set_enabled(&s->rom_poweroff, false);
>
> for (i = 0; i < 4; i++) {
> g_autofree char *name = g_strdup_printf("rp2040.sram%d", i);
> + g_autofree char *poweroff_name =
> + g_strdup_printf("rp2040.sram%d.poweroff", i);
>
> if (!memory_region_init_ram(&s->sram[i], OBJECT(s), name,
> RP2040_SRAM_BANK_SIZE, errp)) {
> @@ -129,6 +214,14 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
> RP2040_SRAM_BASE +
> i * RP2040_SRAM_BANK_SIZE,
> &s->sram[i]);
> + memory_region_init_io(&s->sram_poweroff[i], OBJECT(s),
> + &rp2040_powered_off_ops, s, poweroff_name,
> + RP2040_SRAM_BANK_SIZE);
> + memory_region_add_subregion_overlap(s->board_memory,
> + RP2040_SRAM_BASE +
> + i * RP2040_SRAM_BANK_SIZE,
> + &s->sram_poweroff[i], 1);
> + memory_region_set_enabled(&s->sram_poweroff[i], false);
> }
>
> if (!memory_region_init_ram(&s->sram[4], OBJECT(s), "rp2040.sram4",
> @@ -137,6 +230,12 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
> }
> memory_region_add_subregion(s->board_memory, RP2040_SRAM4_BASE,
> &s->sram[4]);
> + memory_region_init_io(&s->sram_poweroff[4], OBJECT(s),
> + &rp2040_powered_off_ops, s,
> + "rp2040.sram4.poweroff", RP2040_SRAM_HI_SIZE);
> + memory_region_add_subregion_overlap(s->board_memory, RP2040_SRAM4_BASE,
> + &s->sram_poweroff[4], 1);
> + memory_region_set_enabled(&s->sram_poweroff[4], false);
>
> if (!memory_region_init_ram(&s->sram[5], OBJECT(s), "rp2040.sram5",
> RP2040_SRAM_HI_SIZE, errp)) {
> @@ -144,6 +243,14 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
> }
> memory_region_add_subregion(s->board_memory, RP2040_SRAM5_BASE,
> &s->sram[5]);
> + memory_region_init_io(&s->sram_poweroff[5], OBJECT(s),
> + &rp2040_powered_off_ops, s,
> + "rp2040.sram5.poweroff", RP2040_SRAM_HI_SIZE);
> + memory_region_add_subregion_overlap(s->board_memory, RP2040_SRAM5_BASE,
> + &s->sram_poweroff[5], 1);
> + memory_region_set_enabled(&s->sram_poweroff[5], false);
> +
> + s->mempowerdown_ready = true;
>
> return true;
> }
> @@ -201,6 +308,22 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
> if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
> return;
> }
> + for (i = 0; i < RP2040_NUM_IRQS; i++) {
> + s->cpu_irq[i] = qdev_get_gpio_in(DEVICE(&s->armv7m), i);
> + }
> + s->nmi_irq = qdev_get_gpio_in_named(DEVICE(&s->armv7m), "NMI", 0);
> +
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->syscfg), errp)) {
> + return;
> + }
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->syscfg), 0, RP2040_SYSCFG_BASE);
> + rp2040_syscfg_set_update_callback(&s->syscfg, rp2040_syscfg_update, s);
> +
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysinfo), errp)) {
> + return;
> + }
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
> + rp2040_syscfg_update(s);
>
> for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
> qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
> @@ -209,7 +332,7 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
> }
> sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
> sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
> - qdev_get_gpio_in(DEVICE(&s->armv7m), uart_irq[i]));
> + s->irq[uart_irq[i]]);
> }
> }
>
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index de7a0f56fa..f6f344ab51 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -104,6 +104,12 @@ config FSL_IMX8MP_CCM
> config RP2040_NYI
> bool
>
> +config RP2040_SYSCFG
> + bool
> +
> +config RP2040_SYSINFO
> + bool
> +
> config STM32_RCC
> bool
>
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 8611d59437..7be9f45c78 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -99,6 +99,8 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
> 'bcm2835_powermgt.c',
> ))
> system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
> +system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
> +system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
> system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
> system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
> system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
> diff --git a/hw/misc/rp2040_syscfg.c b/hw/misc/rp2040_syscfg.c
> new file mode 100644
> index 0000000000..69b4164ccd
> --- /dev/null
> +++ b/hw/misc/rp2040_syscfg.c
> @@ -0,0 +1,244 @@
> +/*
> + * RP2040 syscfg emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/misc/rp2040_nyi.h"
> +#include "hw/misc/rp2040_syscfg.h"
> +#include "migration/vmstate.h"
> +#include "qemu/module.h"
> +
> +#define SYSCFG_PROC0_NMI_MASK 0x00
> +#define SYSCFG_PROC1_NMI_MASK 0x04
> +#define SYSCFG_PROC_CONFIG 0x08
> +#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c
> +#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10
> +#define SYSCFG_DBGFORCE 0x14
> +#define SYSCFG_MEMPOWERDOWN 0x18
> +
> +#define PROC_CONFIG_RW_MASK 0xff000000
> +#define PROC_CONFIG_RESET 0x10000000
> +#define PROC_IN_SYNC_BYPASS_MASK 0x3fffffff
> +#define PROC_IN_SYNC_BYPASS_HI_MASK 0x0000003f
> +#define DBGFORCE_RW_MASK 0x000000ee
> +#define DBGFORCE_RESET 0x00000066
> +#define MEMPOWERDOWN_MASK 0x000000ff
> +
> +#define ATOMIC_ALIAS_MASK 0x3000
> +#define ATOMIC_XOR 0x1000
> +#define ATOMIC_SET 0x2000
> +#define ATOMIC_CLR 0x3000
> +
> +static uint32_t rp2040_syscfg_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_syscfg_update(RP2040SysCfgState *s)
> +{
> + if (s->update) {
> + s->update(s->update_opaque);
> + }
> +}
> +
> +void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s,
> + RP2040SysCfgUpdateFn update,
> + void *opaque)
> +{
> + s->update = update;
> + s->update_opaque = opaque;
> +}
> +
> +uint32_t rp2040_syscfg_get_proc0_nmi_mask(RP2040SysCfgState *s)
> +{
> + return s->proc0_nmi_mask;
> +}
> +
> +uint32_t rp2040_syscfg_get_mempowerdown(RP2040SysCfgState *s)
> +{
> + return s->mempowerdown;
> +}
> +
> +static uint64_t rp2040_syscfg_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + RP2040SysCfgState *s = opaque;
> + hwaddr offset = addr & 0xfff;
> + uint64_t value;
> +
> + switch (offset) {
> + case SYSCFG_PROC0_NMI_MASK:
> + value = s->proc0_nmi_mask;
> + break;
> + case SYSCFG_PROC1_NMI_MASK:
> + value = s->proc1_nmi_mask;
> + break;
> + case SYSCFG_PROC_CONFIG:
> + value = s->proc_config;
> + break;
> + case SYSCFG_PROC_IN_SYNC_BYPASS:
> + value = s->proc_in_sync_bypass;
> + break;
> + case SYSCFG_PROC_IN_SYNC_BYPASS_HI:
> + value = s->proc_in_sync_bypass_hi;
> + break;
> + case SYSCFG_DBGFORCE:
> + value = s->dbgforce;
> + break;
> + case SYSCFG_MEMPOWERDOWN:
> + value = s->mempowerdown;
> + break;
> + default:
> + value = 0;
> + rp2040_log_unimplemented_read("syscfg", size,
> + RP2040_SYSCFG_BASE + addr, offset,
> + value);
> + break;
> + }
> +
> + return value;
> +}
> +
> +static void rp2040_syscfg_write(void *opaque, hwaddr addr,
> + uint64_t value64, unsigned size)
> +{
> + RP2040SysCfgState *s = opaque;
> + hwaddr alias = addr & ATOMIC_ALIAS_MASK;
> + hwaddr offset = addr & 0xfff;
> + uint32_t value = value64;
> +
> + switch (offset) {
> + case SYSCFG_PROC0_NMI_MASK:
> + s->proc0_nmi_mask =
> + rp2040_syscfg_apply_alias(s->proc0_nmi_mask, value, alias);
> + rp2040_syscfg_update(s);
> + break;
> + case SYSCFG_PROC1_NMI_MASK:
> + s->proc1_nmi_mask =
> + rp2040_syscfg_apply_alias(s->proc1_nmi_mask, value, alias);
> + break;
> + case SYSCFG_PROC_CONFIG:
> + s->proc_config =
> + rp2040_syscfg_apply_alias(s->proc_config, value, alias) &
> + PROC_CONFIG_RW_MASK;
> + break;
> + case SYSCFG_PROC_IN_SYNC_BYPASS:
> + s->proc_in_sync_bypass =
> + rp2040_syscfg_apply_alias(s->proc_in_sync_bypass, value, alias) &
> + PROC_IN_SYNC_BYPASS_MASK;
> + break;
> + case SYSCFG_PROC_IN_SYNC_BYPASS_HI:
> + s->proc_in_sync_bypass_hi =
> + rp2040_syscfg_apply_alias(s->proc_in_sync_bypass_hi, value,
> + alias) &
> + PROC_IN_SYNC_BYPASS_HI_MASK;
> + break;
> + case SYSCFG_DBGFORCE:
> + s->dbgforce =
> + rp2040_syscfg_apply_alias(s->dbgforce, value, alias) &
> + DBGFORCE_RW_MASK;
> + break;
> + case SYSCFG_MEMPOWERDOWN:
> + s->mempowerdown =
> + rp2040_syscfg_apply_alias(s->mempowerdown, value, alias) &
> + MEMPOWERDOWN_MASK;
> + rp2040_syscfg_update(s);
> + break;
> + default:
> + rp2040_log_unimplemented_write("syscfg", size,
> + RP2040_SYSCFG_BASE + addr, offset,
> + value64);
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps rp2040_syscfg_ops = {
> + .read = rp2040_syscfg_read,
> + .write = rp2040_syscfg_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void rp2040_syscfg_reset(DeviceState *dev)
> +{
> + RP2040SysCfgState *s = RP2040_SYSCFG(dev);
> +
> + s->proc0_nmi_mask = 0;
> + s->proc1_nmi_mask = 0;
> + s->proc_config = PROC_CONFIG_RESET;
> + s->proc_in_sync_bypass = 0;
> + s->proc_in_sync_bypass_hi = 0;
> + s->dbgforce = DBGFORCE_RESET;
> + s->mempowerdown = 0;
> + rp2040_syscfg_update(s);
> +}
> +
> +static int rp2040_syscfg_post_load(void *opaque, int version_id)
> +{
> + RP2040SysCfgState *s = opaque;
> +
> + rp2040_syscfg_update(s);
> + return 0;
> +}
> +
> +static void rp2040_syscfg_init(Object *obj)
> +{
> + RP2040SysCfgState *s = RP2040_SYSCFG(obj);
> +
> + memory_region_init_io(&s->iomem, obj, &rp2040_syscfg_ops, s,
> + "rp2040.syscfg", RP2040_SYSCFG_SIZE);
> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> +}
> +
> +static const VMStateDescription rp2040_syscfg_vmstate = {
> + .name = TYPE_RP2040_SYSCFG,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .post_load = rp2040_syscfg_post_load,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32(proc0_nmi_mask, RP2040SysCfgState),
> + VMSTATE_UINT32(proc1_nmi_mask, RP2040SysCfgState),
> + VMSTATE_UINT32(proc_config, RP2040SysCfgState),
> + VMSTATE_UINT32(proc_in_sync_bypass, RP2040SysCfgState),
> + VMSTATE_UINT32(proc_in_sync_bypass_hi, RP2040SysCfgState),
> + VMSTATE_UINT32(dbgforce, RP2040SysCfgState),
> + VMSTATE_UINT32(mempowerdown, RP2040SysCfgState),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void rp2040_syscfg_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + device_class_set_legacy_reset(dc, rp2040_syscfg_reset);
> + dc->vmsd = &rp2040_syscfg_vmstate;
> +}
> +
> +static const TypeInfo rp2040_syscfg_info = {
> + .name = TYPE_RP2040_SYSCFG,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(RP2040SysCfgState),
> + .instance_init = rp2040_syscfg_init,
> + .class_init = rp2040_syscfg_class_init,
> +};
> +
> +static void rp2040_syscfg_register_types(void)
> +{
> + type_register_static(&rp2040_syscfg_info);
> +}
> +type_init(rp2040_syscfg_register_types)
> diff --git a/hw/misc/rp2040_sysinfo.c b/hw/misc/rp2040_sysinfo.c
> new file mode 100644
> index 0000000000..15c50df6af
> --- /dev/null
> +++ b/hw/misc/rp2040_sysinfo.c
> @@ -0,0 +1,103 @@
> +/*
> + * RP2040 sysinfo emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/misc/rp2040_nyi.h"
> +#include "hw/misc/rp2040_sysinfo.h"
> +#include "migration/vmstate.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +
> +#define SYSINFO_CHIP_ID 0x00
> +#define SYSINFO_PLATFORM 0x04
> +#define SYSINFO_GITREF_RP2040 0x40
> +
> +#define SYSINFO_PLATFORM_ASIC BIT(1)
> +
> +static uint64_t rp2040_sysinfo_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + hwaddr offset = addr & 0xfff;
> + uint64_t value;
> +
> + switch (offset) {
> + case SYSINFO_CHIP_ID:
> + case SYSINFO_GITREF_RP2040:
> + value = 0;
> + break;
> + case SYSINFO_PLATFORM:
> + value = SYSINFO_PLATFORM_ASIC;
> + break;
> + default:
> + value = 0;
> + rp2040_log_unimplemented_read("sysinfo", size,
> + RP2040_SYSINFO_BASE + addr, offset,
> + value);
> + break;
> + }
> +
> + return value;
> +}
> +
> +static void rp2040_sysinfo_write(void *opaque, hwaddr addr,
> + uint64_t value, unsigned size)
> +{
> + hwaddr offset = addr & 0xfff;
> +
> + qemu_log_mask(LOG_GUEST_ERROR, "rp2040.sysinfo: write to read-only "
> + "register (size %d, addr 0x%08" HWADDR_PRIx
> + ", offset 0x%04" HWADDR_PRIx
> + ", value 0x%0*" PRIx64 ")\n",
> + size, RP2040_SYSINFO_BASE + addr, offset, size << 1, value);
> +}
> +
> +static const MemoryRegionOps rp2040_sysinfo_ops = {
> + .read = rp2040_sysinfo_read,
> + .write = rp2040_sysinfo_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void rp2040_sysinfo_init(Object *obj)
> +{
> + RP2040SysInfoState *s = RP2040_SYSINFO(obj);
> +
> + memory_region_init_io(&s->iomem, obj, &rp2040_sysinfo_ops, s,
> + "rp2040.sysinfo", RP2040_SYSINFO_SIZE);
> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> +}
> +
> +static const VMStateDescription rp2040_sysinfo_vmstate = {
> + .name = TYPE_RP2040_SYSINFO,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void rp2040_sysinfo_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + dc->vmsd = &rp2040_sysinfo_vmstate;
> +}
> +
> +static const TypeInfo rp2040_sysinfo_info = {
> + .name = TYPE_RP2040_SYSINFO,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(RP2040SysInfoState),
> + .instance_init = rp2040_sysinfo_init,
> + .class_init = rp2040_sysinfo_class_init,
> +};
> +
> +static void rp2040_sysinfo_register_types(void)
> +{
> + type_register_static(&rp2040_sysinfo_info);
> +}
> +type_init(rp2040_sysinfo_register_types)
> diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
> index 4e5cd06add..caa1f9127f 100644
> --- a/include/hw/arm/rp2040.h
> +++ b/include/hw/arm/rp2040.h
> @@ -13,6 +13,8 @@
> #include "hw/char/pl011.h"
> #include "hw/core/clock.h"
> #include "hw/core/sysbus.h"
> +#include "hw/misc/rp2040_syscfg.h"
> +#include "hw/misc/rp2040_sysinfo.h"
> #include "qom/object.h"
>
> #define TYPE_RP2040 "rp2040"
> @@ -34,12 +36,22 @@ struct RP2040State {
>
> ARMv7MState armv7m;
> PL011State uart[2];
> + RP2040SysCfgState syscfg;
> + RP2040SysInfoState sysinfo;
>
> MemoryRegion *board_memory;
> MemoryRegion rom;
> + MemoryRegion rom_poweroff;
> MemoryRegion sram[6];
> + MemoryRegion sram_poweroff[6];
> char *bootrom_file;
>
> + qemu_irq *irq;
> + qemu_irq cpu_irq[RP2040_NUM_IRQS];
> + qemu_irq nmi_irq;
> + bool irq_level[RP2040_NUM_IRQS];
> + bool mempowerdown_ready;
> +
> Clock *sysclk;
> };
>
> diff --git a/include/hw/misc/rp2040_syscfg.h b/include/hw/misc/rp2040_syscfg.h
> new file mode 100644
> index 0000000000..c31799ae7a
> --- /dev/null
> +++ b/include/hw/misc/rp2040_syscfg.h
> @@ -0,0 +1,42 @@
> +/*
> + * RP2040 syscfg emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_RP2040_SYSCFG_H
> +#define HW_MISC_RP2040_SYSCFG_H
> +
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RP2040_SYSCFG "rp2040-syscfg"
> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040SysCfgState, RP2040_SYSCFG)
> +
> +#define RP2040_SYSCFG_BASE 0x40004000
> +#define RP2040_SYSCFG_SIZE 0x4000
> +
> +typedef void (*RP2040SysCfgUpdateFn)(void *opaque);
> +
> +struct RP2040SysCfgState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> + RP2040SysCfgUpdateFn update;
> + void *update_opaque;
> + uint32_t proc0_nmi_mask;
> + uint32_t proc1_nmi_mask;
> + uint32_t proc_config;
> + uint32_t proc_in_sync_bypass;
> + uint32_t proc_in_sync_bypass_hi;
> + uint32_t dbgforce;
> + uint32_t mempowerdown;
> +};
> +
> +void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s,
> + RP2040SysCfgUpdateFn update,
> + void *opaque);
> +uint32_t rp2040_syscfg_get_proc0_nmi_mask(RP2040SysCfgState *s);
> +uint32_t rp2040_syscfg_get_mempowerdown(RP2040SysCfgState *s);
> +
> +#endif
> diff --git a/include/hw/misc/rp2040_sysinfo.h b/include/hw/misc/rp2040_sysinfo.h
> new file mode 100644
> index 0000000000..d521659488
> --- /dev/null
> +++ b/include/hw/misc/rp2040_sysinfo.h
> @@ -0,0 +1,25 @@
> +/*
> + * RP2040 sysinfo emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_RP2040_SYSINFO_H
> +#define HW_MISC_RP2040_SYSINFO_H
> +
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RP2040_SYSINFO "rp2040-sysinfo"
> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040SysInfoState, RP2040_SYSINFO)
> +
> +#define RP2040_SYSINFO_BASE 0x40000000
> +#define RP2040_SYSINFO_SIZE 0x4000
> +
> +struct RP2040SysInfoState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> +};
> +
> +#endif
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 5555ce4eae..692d51a78a 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -255,6 +255,7 @@ qtests_arm = \
> (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 'tpm-tis-i2c-swtpm-test'] : []) + \
> (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
> (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
> + (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-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-sysinfo-syscfg-test.c b/tests/qtest/rp2040-sysinfo-syscfg-test.c
> new file mode 100644
> index 0000000000..18da6561bd
> --- /dev/null
> +++ b/tests/qtest/rp2040-sysinfo-syscfg-test.c
> @@ -0,0 +1,176 @@
> +/*
> + * QTest testcase for the RP2040 sysinfo and syscfg blocks.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "qemu/bitops.h"
> +
> +#define SYSINFO_BASE 0x40000000
> +#define SYSINFO_CHIP_ID 0x00
> +#define SYSINFO_PLATFORM 0x04
> +#define SYSINFO_GITREF_RP2040 0x40
> +#define SYSINFO_PLATFORM_ASIC BIT(1)
> +
> +#define SYSCFG_BASE 0x40004000
> +#define SYSCFG_PROC0_NMI_MASK 0x00
> +#define SYSCFG_PROC1_NMI_MASK 0x04
> +#define SYSCFG_PROC_CONFIG 0x08
> +#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c
> +#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10
> +#define SYSCFG_DBGFORCE 0x14
> +#define SYSCFG_MEMPOWERDOWN 0x18
> +
> +#define SRAM0_BASE 0x20000000
> +#define UART0_BASE 0x40034000
> +#define UARTDR 0x00
> +#define UARTCR 0x30
> +#define UARTIMSC 0x38
> +#define UARTICR 0x44
> +#define UART_INT_TX BIT(5)
> +#define UART0_IRQ 20
> +#define NVIC_ISPR 0xe000e200
> +#define NVIC_ICPR 0xe000e280
> +#define NVIC_ICSR 0xe000ed04
> +#define NVIC_ICSR_NMIPENDSET BIT(31)
> +
> +#define PROC_CONFIG_RESET 0x10000000
> +#define DBGFORCE_RESET 0x00000066
> +
> +#define ATOMIC_SET_ALIAS 0x2000
> +#define ATOMIC_CLR_ALIAS 0x3000
> +
> +static QTestState *rp2040_start(void)
> +{
> + return qtest_init("-machine raspi-pico");
> +}
> +
> +static void test_sysinfo_read_values(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_CHIP_ID), ==, 0);
> + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_PLATFORM), ==,
> + SYSINFO_PLATFORM_ASIC);
> + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_GITREF_RP2040),
> + ==, 0);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_syscfg_reset_values(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
> + ==, 0);
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK),
> + ==, 0);
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC_CONFIG), ==,
> + PROC_CONFIG_RESET);
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_DBGFORCE), ==,
> + DBGFORCE_RESET);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_syscfg_rw_masks(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0xa5a5a5a5);
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK, 0x5a5a5a5a);
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
> + ==, 0xa5a5a5a5);
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK),
> + ==, 0x5a5a5a5a);
> +
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS, 0xffffffff);
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI,
> + 0xffffffff);
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, 0xffffffff);
> + g_assert_cmphex(qtest_readl(qts,
> + SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS),
> + ==, 0x3fffffff);
> + g_assert_cmphex(qtest_readl(qts,
> + SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI),
> + ==, 0x0000003f);
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN), ==,
> + 0x000000ff);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_syscfg_atomic_aliases(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0);
> + qtest_writel(qts, SYSCFG_BASE + ATOMIC_SET_ALIAS + SYSCFG_PROC0_NMI_MASK,
> + 0x0000000f);
> + qtest_writel(qts, SYSCFG_BASE + ATOMIC_CLR_ALIAS + SYSCFG_PROC0_NMI_MASK,
> + 0x00000003);
> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
> + ==, 0x0000000c);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_syscfg_mempowerdown_blocks_sram(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + qtest_writel(qts, SRAM0_BASE, 0x11223344);
> + g_assert_cmphex(qtest_readl(qts, SRAM0_BASE), ==, 0x11223344);
> +
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, BIT(0));
> + qtest_writel(qts, SRAM0_BASE, 0xaabbccdd);
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, 0);
> +
> + g_assert_cmphex(qtest_readl(qts, SRAM0_BASE), ==, 0x11223344);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_syscfg_nmi_mask_routes_uart0_irq(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + qtest_writel(qts, UART0_BASE + UARTCR, BIT(0) | BIT(8));
> + qtest_writel(qts, UART0_BASE + UARTIMSC, UART_INT_TX);
> +
> + qtest_writel(qts, UART0_BASE + UARTICR, UART_INT_TX);
> + qtest_writel(qts, NVIC_ICPR, BIT(UART0_IRQ));
> + qtest_writel(qts, UART0_BASE + UARTDR, 'a');
> + g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & BIT(UART0_IRQ), ==,
> + BIT(UART0_IRQ));
> +
> + qtest_writel(qts, UART0_BASE + UARTICR, UART_INT_TX);
> + qtest_writel(qts, NVIC_ICPR, BIT(UART0_IRQ));
> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, BIT(UART0_IRQ));
> + qtest_writel(qts, UART0_BASE + UARTDR, 'b');
> + g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & BIT(UART0_IRQ), ==, 0);
> + g_assert_cmphex(qtest_readl(qts, NVIC_ICSR) & NVIC_ICSR_NMIPENDSET, ==,
> + NVIC_ICSR_NMIPENDSET);
> +
> + qtest_quit(qts);
> +}
> +
> +int main(int argc, char **argv)
> +{
> + g_test_init(&argc, &argv, NULL);
> +
> + qtest_add_func("/rp2040-sysinfo/read-values", test_sysinfo_read_values);
> + qtest_add_func("/rp2040-syscfg/reset-values", test_syscfg_reset_values);
> + qtest_add_func("/rp2040-syscfg/rw-masks", test_syscfg_rw_masks);
> + qtest_add_func("/rp2040-syscfg/atomic-aliases",
> + test_syscfg_atomic_aliases);
> + qtest_add_func("/rp2040-syscfg/mempowerdown-blocks-sram",
> + test_syscfg_mempowerdown_blocks_sram);
> + qtest_add_func("/rp2040-syscfg/nmi-mask-routes-uart0-irq",
> + test_syscfg_nmi_mask_routes_uart0_irq);
> +
> + return g_test_run();
> +}
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG
2026-09-01 18:44 ` Alex Bennée
@ 2026-09-01 23:29 ` gilles grimaud
2026-09-02 10:13 ` Alex Bennée
0 siblings, 1 reply; 49+ messages in thread
From: gilles grimaud @ 2026-09-01 23:29 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell, Fabiano Rosas,
Laurent Vivier
[-- Attachment #1: Type: text/plain, Size: 37452 bytes --]
Hi Alex,
Thanks for the review. I have looked at the impact of these comments, and
for v3 I propose to:
* split the current patch into two focused patches: one for SYSINFO and one
for SYSCFG;
* remove the powered-off memory overlays. They were intended to make CPU
and DMA accesses fail after setting MEMPOWERDOWN, but this goes beyond the
documented register behavior;
* retain the documented MEMPOWERDOWN register state, reset value, and write
mask, without claiming to model physical ROM or SRAM power gating;
* route PROC0_NMI_MASK and PROC1_NMI_MASK independently to the corresponding
core;
* remove the MemoryRegionOps.with_attrs callbacks, since they are only
needed by the powered-off memory overlays;
* remove the per-subsystem RP2040 Kconfig symbols and build the internal
devices under the single CONFIG_RP2040 symbol.
I will add focused qtests to the separate SYSINFO and SYSCFG patches.
Does this sound in the right direction?
Thanks,
--
Gilles Grimaud - 2XS/SISE - CRIStAL/IRCICA - CNRS/Univ. Lille
> Le 1 sept. 2026 à 20:44, Alex Bennée <alex.bennee@linaro.org> a écrit :
>
> Gilles Grimaud <gilles.grimaud@univ-lille.fr <mailto:gilles.grimaud@univ-lille.fr>> writes:
>
>> From: gilles grimaud <gilles.grimaud@univ-lille.fr <mailto:gilles.grimaud@univ-lille.fr>>
>>
>> Model the RP2040 identification and system configuration registers,
>> including APB atomic aliases and documented reset and write masks.
>> Wire SYSCFG into the SoC so NMI masks reroute interrupt inputs and
>> MEMPOWERDOWN blocks access to powered-off ROM and SRAM banks.
>
> What exactly is going to be accessing ROM and SRAM while powered off?
>
> Why do all three things need to be done at the same time?
>
>> \n\nAdd focused qtests for register values, aliases, power-down behavior, and UART0 IRQ routing to NMI.
>>
>> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
>> ---
>> MAINTAINERS | 1 +
>> hw/arm/Kconfig | 2 +
>> hw/arm/rp2040.c | 129 +++++++++++-
>> hw/misc/Kconfig | 6 +
>> hw/misc/meson.build | 2 +
>> hw/misc/rp2040_syscfg.c | 244 +++++++++++++++++++++++
>> hw/misc/rp2040_sysinfo.c | 103 ++++++++++
>> include/hw/arm/rp2040.h | 12 ++
>> include/hw/misc/rp2040_syscfg.h | 42 ++++
>> include/hw/misc/rp2040_sysinfo.h | 25 +++
>> tests/qtest/meson.build | 1 +
>> tests/qtest/rp2040-sysinfo-syscfg-test.c | 176 ++++++++++++++++
>> 12 files changed, 740 insertions(+), 3 deletions(-)
>> create mode 100644 hw/misc/rp2040_syscfg.c
>> create mode 100644 hw/misc/rp2040_sysinfo.c
>> create mode 100644 include/hw/misc/rp2040_syscfg.h
>> create mode 100644 include/hw/misc/rp2040_sysinfo.h
>> create mode 100644 tests/qtest/rp2040-sysinfo-syscfg-test.c
>>
>> diff --git a/MAINTAINERS b/MAINTAINERS
>> index b000b3287d..fd35521947 100644
>> --- a/MAINTAINERS
>> +++ b/MAINTAINERS
>> @@ -1042,6 +1042,7 @@ S: Maintained
>> F: hw/*/rp2040*
>> F: include/hw/*/rp2040*
>> F: hw/arm/raspi_pico.c
>> +F: tests/qtest/rp2040*
>> F: tests/tcg/arm/system/rp2040*
>>
>> Real View
>> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
>> index d93303ab80..5d55ca06fd 100644
>> --- a/hw/arm/Kconfig
>> +++ b/hw/arm/Kconfig
>> @@ -379,6 +379,8 @@ config RP2040
>> select ARM_V7M
>> select PL011
>> select RP2040_NYI
>> + select RP2040_SYSCFG
>> + select RP2040_SYSINFO
>
> I'm not sure we are going to need Kconfig vars for every sub-system. If
> they come with the RP2040 as a piece we should only need one symbol to
> select the board, peripheral and CPU models.
>
>> select UNIMP
>>
>> config STM32F100_SOC
>> diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
>> index 2196bad18d..a184d527e1 100644
>> --- a/hw/arm/rp2040.c
>> +++ b/hw/arm/rp2040.c
>> @@ -13,6 +13,7 @@
>> #include "hw/core/loader.h"
>> #include "hw/core/qdev-clock.h"
>> #include "hw/core/qdev-properties.h"
>> +#include "hw/core/irq.h"
>> #include "hw/misc/unimp.h"
>> #include "qemu/datadir.h"
>> #include "target/arm/cpu-qom.h"
>> @@ -49,8 +50,6 @@ static const struct {
>> hwaddr base;
>> hwaddr size;
>> } rp2040_unimplemented[] = {
>> - { "rp2040.sysinfo", 0x40000000, 0x4000 },
>> - { "rp2040.syscfg", 0x40004000, 0x4000 },
>> { "rp2040.clocks", 0x40008000, 0x4000 },
>> { "rp2040.resets", 0x4000c000, 0x4000 },
>> { "rp2040.psm", 0x40010000, 0x4000 },
>> @@ -84,6 +83,80 @@ static const struct {
>> { "rp2040.sio", 0xd0000000, 0x1000 },
>> };
>>
>> +static MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr,
>> + uint64_t *data, unsigned size,
>> + MemTxAttrs attrs)
>> +{
>> + *data = 0;
>> + return MEMTX_ERROR;
>> +}
>> +
>> +static MemTxResult rp2040_powered_off_write(void *opaque, hwaddr addr,
>> + uint64_t data, unsigned size,
>> + MemTxAttrs attrs)
>> +{
>> + return MEMTX_ERROR;
>> +}
>> +
>> +static const MemoryRegionOps rp2040_powered_off_ops = {
>> + .read_with_attrs = rp2040_powered_off_read,
>> + .write_with_attrs = rp2040_powered_off_write,
>
> You choose to have _with_attrs access functions here but not later. Why?
>
>> + .endianness = DEVICE_LITTLE_ENDIAN,
>> + .valid = {
>> + .min_access_size = 1,
>> + .max_access_size = 4,
>> + },
>> +};
>> +
>> +static void rp2040_update_mempowerdown(RP2040State *s)
>> +{
>> + uint32_t mempowerdown;
>> + int i;
>> +
>> + if (!s->mempowerdown_ready) {
>> + return;
>> + }
>> +
>> + mempowerdown = rp2040_syscfg_get_mempowerdown(&s->syscfg);
>> + for (i = 0; i < ARRAY_SIZE(s->sram_poweroff); i++) {
>> + memory_region_set_enabled(&s->sram_poweroff[i],
>> + mempowerdown & BIT(i));
>> + }
>> + memory_region_set_enabled(&s->rom_poweroff, mempowerdown & BIT(7));
>> +}
>> +
>> +static void rp2040_update_nmi(RP2040State *s)
>> +{
>> + uint32_t nmi_mask = rp2040_syscfg_get_proc0_nmi_mask(&s->syscfg);
>> + bool nmi_level = false;
>> + int i;
>> +
>> + for (i = 0; i < RP2040_NUM_IRQS; i++) {
>> + bool route_to_nmi = nmi_mask & BIT(i);
>> +
>> + qemu_set_irq(s->cpu_irq[i], s->irq_level[i] && !route_to_nmi);
>> + nmi_level |= s->irq_level[i] && route_to_nmi;
>> + }
>> + qemu_set_irq(s->nmi_irq, nmi_level);
>> +}
>> +
>> +static void rp2040_syscfg_update(void *opaque)
>> +{
>> + RP2040State *s = opaque;
>> +
>> + rp2040_update_mempowerdown(s);
>> + rp2040_update_nmi(s);
>> +}
>> +
>> +static void rp2040_set_irq(void *opaque, int irq, int level)
>> +{
>> + RP2040State *s = opaque;
>> +
>> + assert(irq >= 0 && irq < RP2040_NUM_IRQS);
>> + s->irq_level[irq] = level;
>> + rp2040_update_nmi(s);
>> +}
>> +
>> static void rp2040_soc_init(Object *obj)
>> {
>> RP2040State *s = RP2040(obj);
>> @@ -104,6 +177,10 @@ static void rp2040_soc_init(Object *obj)
>> "chardev");
>> }
>>
>> + object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
>> + object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
>> +
>> + s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
>> s->sysclk = clock_new(obj, "sysclk");
>> clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ);
>> }
>> @@ -117,9 +194,17 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
>> return false;
>> }
>> memory_region_add_subregion(s->board_memory, RP2040_ROM_BASE, &s->rom);
>> + memory_region_init_io(&s->rom_poweroff, OBJECT(s),
>> + &rp2040_powered_off_ops, s,
>> + "rp2040.rom.poweroff", RP2040_ROM_SIZE);
>> + memory_region_add_subregion_overlap(s->board_memory, RP2040_ROM_BASE,
>> + &s->rom_poweroff, 1);
>> + memory_region_set_enabled(&s->rom_poweroff, false);
>>
>> for (i = 0; i < 4; i++) {
>> g_autofree char *name = g_strdup_printf("rp2040.sram%d", i);
>> + g_autofree char *poweroff_name =
>> + g_strdup_printf("rp2040.sram%d.poweroff", i);
>>
>> if (!memory_region_init_ram(&s->sram[i], OBJECT(s), name,
>> RP2040_SRAM_BANK_SIZE, errp)) {
>> @@ -129,6 +214,14 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
>> RP2040_SRAM_BASE +
>> i * RP2040_SRAM_BANK_SIZE,
>> &s->sram[i]);
>> + memory_region_init_io(&s->sram_poweroff[i], OBJECT(s),
>> + &rp2040_powered_off_ops, s, poweroff_name,
>> + RP2040_SRAM_BANK_SIZE);
>> + memory_region_add_subregion_overlap(s->board_memory,
>> + RP2040_SRAM_BASE +
>> + i * RP2040_SRAM_BANK_SIZE,
>> + &s->sram_poweroff[i], 1);
>> + memory_region_set_enabled(&s->sram_poweroff[i], false);
>> }
>>
>> if (!memory_region_init_ram(&s->sram[4], OBJECT(s), "rp2040.sram4",
>> @@ -137,6 +230,12 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
>> }
>> memory_region_add_subregion(s->board_memory, RP2040_SRAM4_BASE,
>> &s->sram[4]);
>> + memory_region_init_io(&s->sram_poweroff[4], OBJECT(s),
>> + &rp2040_powered_off_ops, s,
>> + "rp2040.sram4.poweroff", RP2040_SRAM_HI_SIZE);
>> + memory_region_add_subregion_overlap(s->board_memory, RP2040_SRAM4_BASE,
>> + &s->sram_poweroff[4], 1);
>> + memory_region_set_enabled(&s->sram_poweroff[4], false);
>>
>> if (!memory_region_init_ram(&s->sram[5], OBJECT(s), "rp2040.sram5",
>> RP2040_SRAM_HI_SIZE, errp)) {
>> @@ -144,6 +243,14 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
>> }
>> memory_region_add_subregion(s->board_memory, RP2040_SRAM5_BASE,
>> &s->sram[5]);
>> + memory_region_init_io(&s->sram_poweroff[5], OBJECT(s),
>> + &rp2040_powered_off_ops, s,
>> + "rp2040.sram5.poweroff", RP2040_SRAM_HI_SIZE);
>> + memory_region_add_subregion_overlap(s->board_memory, RP2040_SRAM5_BASE,
>> + &s->sram_poweroff[5], 1);
>> + memory_region_set_enabled(&s->sram_poweroff[5], false);
>> +
>> + s->mempowerdown_ready = true;
>>
>> return true;
>> }
>> @@ -201,6 +308,22 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
>> if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
>> return;
>> }
>> + for (i = 0; i < RP2040_NUM_IRQS; i++) {
>> + s->cpu_irq[i] = qdev_get_gpio_in(DEVICE(&s->armv7m), i);
>> + }
>> + s->nmi_irq = qdev_get_gpio_in_named(DEVICE(&s->armv7m), "NMI", 0);
>> +
>> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->syscfg), errp)) {
>> + return;
>> + }
>> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->syscfg), 0, RP2040_SYSCFG_BASE);
>> + rp2040_syscfg_set_update_callback(&s->syscfg, rp2040_syscfg_update, s);
>> +
>> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sysinfo), errp)) {
>> + return;
>> + }
>> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
>> + rp2040_syscfg_update(s);
>>
>> for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
>> qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
>> @@ -209,7 +332,7 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
>> }
>> sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
>> sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
>> - qdev_get_gpio_in(DEVICE(&s->armv7m), uart_irq[i]));
>> + s->irq[uart_irq[i]]);
>> }
>> }
>>
>> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
>> index de7a0f56fa..f6f344ab51 100644
>> --- a/hw/misc/Kconfig
>> +++ b/hw/misc/Kconfig
>> @@ -104,6 +104,12 @@ config FSL_IMX8MP_CCM
>> config RP2040_NYI
>> bool
>>
>> +config RP2040_SYSCFG
>> + bool
>> +
>> +config RP2040_SYSINFO
>> + bool
>> +
>> config STM32_RCC
>> bool
>>
>> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
>> index 8611d59437..7be9f45c78 100644
>> --- a/hw/misc/meson.build
>> +++ b/hw/misc/meson.build
>> @@ -99,6 +99,8 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
>> 'bcm2835_powermgt.c',
>> ))
>> system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
>> +system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
>> +system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
>> system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
>> system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
>> system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
>> diff --git a/hw/misc/rp2040_syscfg.c b/hw/misc/rp2040_syscfg.c
>> new file mode 100644
>> index 0000000000..69b4164ccd
>> --- /dev/null
>> +++ b/hw/misc/rp2040_syscfg.c
>> @@ -0,0 +1,244 @@
>> +/*
>> + * RP2040 syscfg emulation
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "hw/misc/rp2040_nyi.h"
>> +#include "hw/misc/rp2040_syscfg.h"
>> +#include "migration/vmstate.h"
>> +#include "qemu/module.h"
>> +
>> +#define SYSCFG_PROC0_NMI_MASK 0x00
>> +#define SYSCFG_PROC1_NMI_MASK 0x04
>> +#define SYSCFG_PROC_CONFIG 0x08
>> +#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c
>> +#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10
>> +#define SYSCFG_DBGFORCE 0x14
>> +#define SYSCFG_MEMPOWERDOWN 0x18
>> +
>> +#define PROC_CONFIG_RW_MASK 0xff000000
>> +#define PROC_CONFIG_RESET 0x10000000
>> +#define PROC_IN_SYNC_BYPASS_MASK 0x3fffffff
>> +#define PROC_IN_SYNC_BYPASS_HI_MASK 0x0000003f
>> +#define DBGFORCE_RW_MASK 0x000000ee
>> +#define DBGFORCE_RESET 0x00000066
>> +#define MEMPOWERDOWN_MASK 0x000000ff
>> +
>> +#define ATOMIC_ALIAS_MASK 0x3000
>> +#define ATOMIC_XOR 0x1000
>> +#define ATOMIC_SET 0x2000
>> +#define ATOMIC_CLR 0x3000
>> +
>> +static uint32_t rp2040_syscfg_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_syscfg_update(RP2040SysCfgState *s)
>> +{
>> + if (s->update) {
>> + s->update(s->update_opaque);
>> + }
>> +}
>> +
>> +void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s,
>> + RP2040SysCfgUpdateFn update,
>> + void *opaque)
>> +{
>> + s->update = update;
>> + s->update_opaque = opaque;
>> +}
>> +
>> +uint32_t rp2040_syscfg_get_proc0_nmi_mask(RP2040SysCfgState *s)
>> +{
>> + return s->proc0_nmi_mask;
>> +}
>> +
>> +uint32_t rp2040_syscfg_get_mempowerdown(RP2040SysCfgState *s)
>> +{
>> + return s->mempowerdown;
>> +}
>> +
>> +static uint64_t rp2040_syscfg_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> + RP2040SysCfgState *s = opaque;
>> + hwaddr offset = addr & 0xfff;
>> + uint64_t value;
>> +
>> + switch (offset) {
>> + case SYSCFG_PROC0_NMI_MASK:
>> + value = s->proc0_nmi_mask;
>> + break;
>> + case SYSCFG_PROC1_NMI_MASK:
>> + value = s->proc1_nmi_mask;
>> + break;
>> + case SYSCFG_PROC_CONFIG:
>> + value = s->proc_config;
>> + break;
>> + case SYSCFG_PROC_IN_SYNC_BYPASS:
>> + value = s->proc_in_sync_bypass;
>> + break;
>> + case SYSCFG_PROC_IN_SYNC_BYPASS_HI:
>> + value = s->proc_in_sync_bypass_hi;
>> + break;
>> + case SYSCFG_DBGFORCE:
>> + value = s->dbgforce;
>> + break;
>> + case SYSCFG_MEMPOWERDOWN:
>> + value = s->mempowerdown;
>> + break;
>> + default:
>> + value = 0;
>> + rp2040_log_unimplemented_read("syscfg", size,
>> + RP2040_SYSCFG_BASE + addr, offset,
>> + value);
>> + break;
>> + }
>> +
>> + return value;
>> +}
>> +
>> +static void rp2040_syscfg_write(void *opaque, hwaddr addr,
>> + uint64_t value64, unsigned size)
>> +{
>> + RP2040SysCfgState *s = opaque;
>> + hwaddr alias = addr & ATOMIC_ALIAS_MASK;
>> + hwaddr offset = addr & 0xfff;
>> + uint32_t value = value64;
>> +
>> + switch (offset) {
>> + case SYSCFG_PROC0_NMI_MASK:
>> + s->proc0_nmi_mask =
>> + rp2040_syscfg_apply_alias(s->proc0_nmi_mask, value, alias);
>> + rp2040_syscfg_update(s);
>> + break;
>> + case SYSCFG_PROC1_NMI_MASK:
>> + s->proc1_nmi_mask =
>> + rp2040_syscfg_apply_alias(s->proc1_nmi_mask, value, alias);
>> + break;
>> + case SYSCFG_PROC_CONFIG:
>> + s->proc_config =
>> + rp2040_syscfg_apply_alias(s->proc_config, value, alias) &
>> + PROC_CONFIG_RW_MASK;
>> + break;
>> + case SYSCFG_PROC_IN_SYNC_BYPASS:
>> + s->proc_in_sync_bypass =
>> + rp2040_syscfg_apply_alias(s->proc_in_sync_bypass, value, alias) &
>> + PROC_IN_SYNC_BYPASS_MASK;
>> + break;
>> + case SYSCFG_PROC_IN_SYNC_BYPASS_HI:
>> + s->proc_in_sync_bypass_hi =
>> + rp2040_syscfg_apply_alias(s->proc_in_sync_bypass_hi, value,
>> + alias) &
>> + PROC_IN_SYNC_BYPASS_HI_MASK;
>> + break;
>> + case SYSCFG_DBGFORCE:
>> + s->dbgforce =
>> + rp2040_syscfg_apply_alias(s->dbgforce, value, alias) &
>> + DBGFORCE_RW_MASK;
>> + break;
>> + case SYSCFG_MEMPOWERDOWN:
>> + s->mempowerdown =
>> + rp2040_syscfg_apply_alias(s->mempowerdown, value, alias) &
>> + MEMPOWERDOWN_MASK;
>> + rp2040_syscfg_update(s);
>> + break;
>> + default:
>> + rp2040_log_unimplemented_write("syscfg", size,
>> + RP2040_SYSCFG_BASE + addr, offset,
>> + value64);
>> + break;
>> + }
>> +}
>> +
>> +static const MemoryRegionOps rp2040_syscfg_ops = {
>> + .read = rp2040_syscfg_read,
>> + .write = rp2040_syscfg_write,
>> + .endianness = DEVICE_LITTLE_ENDIAN,
>> + .valid = {
>> + .min_access_size = 4,
>> + .max_access_size = 4,
>> + },
>> +};
>> +
>> +static void rp2040_syscfg_reset(DeviceState *dev)
>> +{
>> + RP2040SysCfgState *s = RP2040_SYSCFG(dev);
>> +
>> + s->proc0_nmi_mask = 0;
>> + s->proc1_nmi_mask = 0;
>> + s->proc_config = PROC_CONFIG_RESET;
>> + s->proc_in_sync_bypass = 0;
>> + s->proc_in_sync_bypass_hi = 0;
>> + s->dbgforce = DBGFORCE_RESET;
>> + s->mempowerdown = 0;
>> + rp2040_syscfg_update(s);
>> +}
>> +
>> +static int rp2040_syscfg_post_load(void *opaque, int version_id)
>> +{
>> + RP2040SysCfgState *s = opaque;
>> +
>> + rp2040_syscfg_update(s);
>> + return 0;
>> +}
>> +
>> +static void rp2040_syscfg_init(Object *obj)
>> +{
>> + RP2040SysCfgState *s = RP2040_SYSCFG(obj);
>> +
>> + memory_region_init_io(&s->iomem, obj, &rp2040_syscfg_ops, s,
>> + "rp2040.syscfg", RP2040_SYSCFG_SIZE);
>> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
>> +}
>> +
>> +static const VMStateDescription rp2040_syscfg_vmstate = {
>> + .name = TYPE_RP2040_SYSCFG,
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .post_load = rp2040_syscfg_post_load,
>> + .fields = (const VMStateField[]) {
>> + VMSTATE_UINT32(proc0_nmi_mask, RP2040SysCfgState),
>> + VMSTATE_UINT32(proc1_nmi_mask, RP2040SysCfgState),
>> + VMSTATE_UINT32(proc_config, RP2040SysCfgState),
>> + VMSTATE_UINT32(proc_in_sync_bypass, RP2040SysCfgState),
>> + VMSTATE_UINT32(proc_in_sync_bypass_hi, RP2040SysCfgState),
>> + VMSTATE_UINT32(dbgforce, RP2040SysCfgState),
>> + VMSTATE_UINT32(mempowerdown, RP2040SysCfgState),
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static void rp2040_syscfg_class_init(ObjectClass *klass, const void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> + device_class_set_legacy_reset(dc, rp2040_syscfg_reset);
>> + dc->vmsd = &rp2040_syscfg_vmstate;
>> +}
>> +
>> +static const TypeInfo rp2040_syscfg_info = {
>> + .name = TYPE_RP2040_SYSCFG,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(RP2040SysCfgState),
>> + .instance_init = rp2040_syscfg_init,
>> + .class_init = rp2040_syscfg_class_init,
>> +};
>> +
>> +static void rp2040_syscfg_register_types(void)
>> +{
>> + type_register_static(&rp2040_syscfg_info);
>> +}
>> +type_init(rp2040_syscfg_register_types)
>> diff --git a/hw/misc/rp2040_sysinfo.c b/hw/misc/rp2040_sysinfo.c
>> new file mode 100644
>> index 0000000000..15c50df6af
>> --- /dev/null
>> +++ b/hw/misc/rp2040_sysinfo.c
>> @@ -0,0 +1,103 @@
>> +/*
>> + * RP2040 sysinfo emulation
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "hw/misc/rp2040_nyi.h"
>> +#include "hw/misc/rp2040_sysinfo.h"
>> +#include "migration/vmstate.h"
>> +#include "qemu/log.h"
>> +#include "qemu/module.h"
>> +
>> +#define SYSINFO_CHIP_ID 0x00
>> +#define SYSINFO_PLATFORM 0x04
>> +#define SYSINFO_GITREF_RP2040 0x40
>> +
>> +#define SYSINFO_PLATFORM_ASIC BIT(1)
>> +
>> +static uint64_t rp2040_sysinfo_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> + hwaddr offset = addr & 0xfff;
>> + uint64_t value;
>> +
>> + switch (offset) {
>> + case SYSINFO_CHIP_ID:
>> + case SYSINFO_GITREF_RP2040:
>> + value = 0;
>> + break;
>> + case SYSINFO_PLATFORM:
>> + value = SYSINFO_PLATFORM_ASIC;
>> + break;
>> + default:
>> + value = 0;
>> + rp2040_log_unimplemented_read("sysinfo", size,
>> + RP2040_SYSINFO_BASE + addr, offset,
>> + value);
>> + break;
>> + }
>> +
>> + return value;
>> +}
>> +
>> +static void rp2040_sysinfo_write(void *opaque, hwaddr addr,
>> + uint64_t value, unsigned size)
>> +{
>> + hwaddr offset = addr & 0xfff;
>> +
>> + qemu_log_mask(LOG_GUEST_ERROR, "rp2040.sysinfo: write to read-only "
>> + "register (size %d, addr 0x%08" HWADDR_PRIx
>> + ", offset 0x%04" HWADDR_PRIx
>> + ", value 0x%0*" PRIx64 ")\n",
>> + size, RP2040_SYSINFO_BASE + addr, offset, size << 1, value);
>> +}
>> +
>> +static const MemoryRegionOps rp2040_sysinfo_ops = {
>> + .read = rp2040_sysinfo_read,
>> + .write = rp2040_sysinfo_write,
>> + .endianness = DEVICE_LITTLE_ENDIAN,
>> + .valid = {
>> + .min_access_size = 4,
>> + .max_access_size = 4,
>> + },
>> +};
>> +
>> +static void rp2040_sysinfo_init(Object *obj)
>> +{
>> + RP2040SysInfoState *s = RP2040_SYSINFO(obj);
>> +
>> + memory_region_init_io(&s->iomem, obj, &rp2040_sysinfo_ops, s,
>> + "rp2040.sysinfo", RP2040_SYSINFO_SIZE);
>> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
>> +}
>> +
>> +static const VMStateDescription rp2040_sysinfo_vmstate = {
>> + .name = TYPE_RP2040_SYSINFO,
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .fields = (const VMStateField[]) {
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static void rp2040_sysinfo_class_init(ObjectClass *klass, const void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> + dc->vmsd = &rp2040_sysinfo_vmstate;
>> +}
>> +
>> +static const TypeInfo rp2040_sysinfo_info = {
>> + .name = TYPE_RP2040_SYSINFO,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(RP2040SysInfoState),
>> + .instance_init = rp2040_sysinfo_init,
>> + .class_init = rp2040_sysinfo_class_init,
>> +};
>> +
>> +static void rp2040_sysinfo_register_types(void)
>> +{
>> + type_register_static(&rp2040_sysinfo_info);
>> +}
>> +type_init(rp2040_sysinfo_register_types)
>> diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
>> index 4e5cd06add..caa1f9127f 100644
>> --- a/include/hw/arm/rp2040.h
>> +++ b/include/hw/arm/rp2040.h
>> @@ -13,6 +13,8 @@
>> #include "hw/char/pl011.h"
>> #include "hw/core/clock.h"
>> #include "hw/core/sysbus.h"
>> +#include "hw/misc/rp2040_syscfg.h"
>> +#include "hw/misc/rp2040_sysinfo.h"
>> #include "qom/object.h"
>>
>> #define TYPE_RP2040 "rp2040"
>> @@ -34,12 +36,22 @@ struct RP2040State {
>>
>> ARMv7MState armv7m;
>> PL011State uart[2];
>> + RP2040SysCfgState syscfg;
>> + RP2040SysInfoState sysinfo;
>>
>> MemoryRegion *board_memory;
>> MemoryRegion rom;
>> + MemoryRegion rom_poweroff;
>> MemoryRegion sram[6];
>> + MemoryRegion sram_poweroff[6];
>> char *bootrom_file;
>>
>> + qemu_irq *irq;
>> + qemu_irq cpu_irq[RP2040_NUM_IRQS];
>> + qemu_irq nmi_irq;
>> + bool irq_level[RP2040_NUM_IRQS];
>> + bool mempowerdown_ready;
>> +
>> Clock *sysclk;
>> };
>>
>> diff --git a/include/hw/misc/rp2040_syscfg.h b/include/hw/misc/rp2040_syscfg.h
>> new file mode 100644
>> index 0000000000..c31799ae7a
>> --- /dev/null
>> +++ b/include/hw/misc/rp2040_syscfg.h
>> @@ -0,0 +1,42 @@
>> +/*
>> + * RP2040 syscfg emulation
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_MISC_RP2040_SYSCFG_H
>> +#define HW_MISC_RP2040_SYSCFG_H
>> +
>> +#include "hw/core/sysbus.h"
>> +#include "qom/object.h"
>> +
>> +#define TYPE_RP2040_SYSCFG "rp2040-syscfg"
>> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040SysCfgState, RP2040_SYSCFG)
>> +
>> +#define RP2040_SYSCFG_BASE 0x40004000
>> +#define RP2040_SYSCFG_SIZE 0x4000
>> +
>> +typedef void (*RP2040SysCfgUpdateFn)(void *opaque);
>> +
>> +struct RP2040SysCfgState {
>> + SysBusDevice parent_obj;
>> +
>> + MemoryRegion iomem;
>> + RP2040SysCfgUpdateFn update;
>> + void *update_opaque;
>> + uint32_t proc0_nmi_mask;
>> + uint32_t proc1_nmi_mask;
>> + uint32_t proc_config;
>> + uint32_t proc_in_sync_bypass;
>> + uint32_t proc_in_sync_bypass_hi;
>> + uint32_t dbgforce;
>> + uint32_t mempowerdown;
>> +};
>> +
>> +void rp2040_syscfg_set_update_callback(RP2040SysCfgState *s,
>> + RP2040SysCfgUpdateFn update,
>> + void *opaque);
>> +uint32_t rp2040_syscfg_get_proc0_nmi_mask(RP2040SysCfgState *s);
>> +uint32_t rp2040_syscfg_get_mempowerdown(RP2040SysCfgState *s);
>> +
>> +#endif
>> diff --git a/include/hw/misc/rp2040_sysinfo.h b/include/hw/misc/rp2040_sysinfo.h
>> new file mode 100644
>> index 0000000000..d521659488
>> --- /dev/null
>> +++ b/include/hw/misc/rp2040_sysinfo.h
>> @@ -0,0 +1,25 @@
>> +/*
>> + * RP2040 sysinfo emulation
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_MISC_RP2040_SYSINFO_H
>> +#define HW_MISC_RP2040_SYSINFO_H
>> +
>> +#include "hw/core/sysbus.h"
>> +#include "qom/object.h"
>> +
>> +#define TYPE_RP2040_SYSINFO "rp2040-sysinfo"
>> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040SysInfoState, RP2040_SYSINFO)
>> +
>> +#define RP2040_SYSINFO_BASE 0x40000000
>> +#define RP2040_SYSINFO_SIZE 0x4000
>> +
>> +struct RP2040SysInfoState {
>> + SysBusDevice parent_obj;
>> +
>> + MemoryRegion iomem;
>> +};
>> +
>> +#endif
>> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
>> index 5555ce4eae..692d51a78a 100644
>> --- a/tests/qtest/meson.build
>> +++ b/tests/qtest/meson.build
>> @@ -255,6 +255,7 @@ qtests_arm = \
>> (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 'tpm-tis-i2c-swtpm-test'] : []) + \
>> (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
>> (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
>> + (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-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-sysinfo-syscfg-test.c b/tests/qtest/rp2040-sysinfo-syscfg-test.c
>> new file mode 100644
>> index 0000000000..18da6561bd
>> --- /dev/null
>> +++ b/tests/qtest/rp2040-sysinfo-syscfg-test.c
>> @@ -0,0 +1,176 @@
>> +/*
>> + * QTest testcase for the RP2040 sysinfo and syscfg blocks.
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "libqtest.h"
>> +#include "qemu/bitops.h"
>> +
>> +#define SYSINFO_BASE 0x40000000
>> +#define SYSINFO_CHIP_ID 0x00
>> +#define SYSINFO_PLATFORM 0x04
>> +#define SYSINFO_GITREF_RP2040 0x40
>> +#define SYSINFO_PLATFORM_ASIC BIT(1)
>> +
>> +#define SYSCFG_BASE 0x40004000
>> +#define SYSCFG_PROC0_NMI_MASK 0x00
>> +#define SYSCFG_PROC1_NMI_MASK 0x04
>> +#define SYSCFG_PROC_CONFIG 0x08
>> +#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c
>> +#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10
>> +#define SYSCFG_DBGFORCE 0x14
>> +#define SYSCFG_MEMPOWERDOWN 0x18
>> +
>> +#define SRAM0_BASE 0x20000000
>> +#define UART0_BASE 0x40034000
>> +#define UARTDR 0x00
>> +#define UARTCR 0x30
>> +#define UARTIMSC 0x38
>> +#define UARTICR 0x44
>> +#define UART_INT_TX BIT(5)
>> +#define UART0_IRQ 20
>> +#define NVIC_ISPR 0xe000e200
>> +#define NVIC_ICPR 0xe000e280
>> +#define NVIC_ICSR 0xe000ed04
>> +#define NVIC_ICSR_NMIPENDSET BIT(31)
>> +
>> +#define PROC_CONFIG_RESET 0x10000000
>> +#define DBGFORCE_RESET 0x00000066
>> +
>> +#define ATOMIC_SET_ALIAS 0x2000
>> +#define ATOMIC_CLR_ALIAS 0x3000
>> +
>> +static QTestState *rp2040_start(void)
>> +{
>> + return qtest_init("-machine raspi-pico");
>> +}
>> +
>> +static void test_sysinfo_read_values(void)
>> +{
>> + QTestState *qts = rp2040_start();
>> +
>> + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_CHIP_ID), ==, 0);
>> + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_PLATFORM), ==,
>> + SYSINFO_PLATFORM_ASIC);
>> + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_GITREF_RP2040),
>> + ==, 0);
>> +
>> + qtest_quit(qts);
>> +}
>> +
>> +static void test_syscfg_reset_values(void)
>> +{
>> + QTestState *qts = rp2040_start();
>> +
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
>> + ==, 0);
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK),
>> + ==, 0);
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC_CONFIG), ==,
>> + PROC_CONFIG_RESET);
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_DBGFORCE), ==,
>> + DBGFORCE_RESET);
>> +
>> + qtest_quit(qts);
>> +}
>> +
>> +static void test_syscfg_rw_masks(void)
>> +{
>> + QTestState *qts = rp2040_start();
>> +
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0xa5a5a5a5);
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK, 0x5a5a5a5a);
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
>> + ==, 0xa5a5a5a5);
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK),
>> + ==, 0x5a5a5a5a);
>> +
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS, 0xffffffff);
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI,
>> + 0xffffffff);
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, 0xffffffff);
>> + g_assert_cmphex(qtest_readl(qts,
>> + SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS),
>> + ==, 0x3fffffff);
>> + g_assert_cmphex(qtest_readl(qts,
>> + SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_HI),
>> + ==, 0x0000003f);
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN), ==,
>> + 0x000000ff);
>> +
>> + qtest_quit(qts);
>> +}
>> +
>> +static void test_syscfg_atomic_aliases(void)
>> +{
>> + QTestState *qts = rp2040_start();
>> +
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0);
>> + qtest_writel(qts, SYSCFG_BASE + ATOMIC_SET_ALIAS + SYSCFG_PROC0_NMI_MASK,
>> + 0x0000000f);
>> + qtest_writel(qts, SYSCFG_BASE + ATOMIC_CLR_ALIAS + SYSCFG_PROC0_NMI_MASK,
>> + 0x00000003);
>> + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK),
>> + ==, 0x0000000c);
>> +
>> + qtest_quit(qts);
>> +}
>> +
>> +static void test_syscfg_mempowerdown_blocks_sram(void)
>> +{
>> + QTestState *qts = rp2040_start();
>> +
>> + qtest_writel(qts, SRAM0_BASE, 0x11223344);
>> + g_assert_cmphex(qtest_readl(qts, SRAM0_BASE), ==, 0x11223344);
>> +
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, BIT(0));
>> + qtest_writel(qts, SRAM0_BASE, 0xaabbccdd);
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN, 0);
>> +
>> + g_assert_cmphex(qtest_readl(qts, SRAM0_BASE), ==, 0x11223344);
>> +
>> + qtest_quit(qts);
>> +}
>> +
>> +static void test_syscfg_nmi_mask_routes_uart0_irq(void)
>> +{
>> + QTestState *qts = rp2040_start();
>> +
>> + qtest_writel(qts, UART0_BASE + UARTCR, BIT(0) | BIT(8));
>> + qtest_writel(qts, UART0_BASE + UARTIMSC, UART_INT_TX);
>> +
>> + qtest_writel(qts, UART0_BASE + UARTICR, UART_INT_TX);
>> + qtest_writel(qts, NVIC_ICPR, BIT(UART0_IRQ));
>> + qtest_writel(qts, UART0_BASE + UARTDR, 'a');
>> + g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & BIT(UART0_IRQ), ==,
>> + BIT(UART0_IRQ));
>> +
>> + qtest_writel(qts, UART0_BASE + UARTICR, UART_INT_TX);
>> + qtest_writel(qts, NVIC_ICPR, BIT(UART0_IRQ));
>> + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, BIT(UART0_IRQ));
>> + qtest_writel(qts, UART0_BASE + UARTDR, 'b');
>> + g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & BIT(UART0_IRQ), ==, 0);
>> + g_assert_cmphex(qtest_readl(qts, NVIC_ICSR) & NVIC_ICSR_NMIPENDSET, ==,
>> + NVIC_ICSR_NMIPENDSET);
>> +
>> + qtest_quit(qts);
>> +}
>> +
>> +int main(int argc, char **argv)
>> +{
>> + g_test_init(&argc, &argv, NULL);
>> +
>> + qtest_add_func("/rp2040-sysinfo/read-values", test_sysinfo_read_values);
>> + qtest_add_func("/rp2040-syscfg/reset-values", test_syscfg_reset_values);
>> + qtest_add_func("/rp2040-syscfg/rw-masks", test_syscfg_rw_masks);
>> + qtest_add_func("/rp2040-syscfg/atomic-aliases",
>> + test_syscfg_atomic_aliases);
>> + qtest_add_func("/rp2040-syscfg/mempowerdown-blocks-sram",
>> + test_syscfg_mempowerdown_blocks_sram);
>> + qtest_add_func("/rp2040-syscfg/nmi-mask-routes-uart0-irq",
>> + test_syscfg_nmi_mask_routes_uart0_irq);
>> +
>> + return g_test_run();
>> +}
>
> --
> Alex Bennée
> Virtualisation Tech Lead @ Linaro
[-- Attachment #2: Type: text/html, Size: 74717 bytes --]
^ permalink raw reply [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG
2026-09-01 23:29 ` gilles grimaud
@ 2026-09-02 10:13 ` Alex Bennée
0 siblings, 0 replies; 49+ messages in thread
From: Alex Bennée @ 2026-09-02 10:13 UTC (permalink / raw)
To: gilles grimaud
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell, Fabiano Rosas,
Laurent Vivier
gilles grimaud <gilles.grimaud@univ-lille.fr> writes:
> Hi Alex,
>
> Thanks for the review. I have looked at the impact of these comments, and
> for v3 I propose to:
>
> * split the current patch into two focused patches: one for SYSINFO and one
> for SYSCFG;
Yes
> * remove the powered-off memory overlays. They were intended to make CPU
> and DMA accesses fail after setting MEMPOWERDOWN, but this goes beyond the
> documented register behavior;
> * retain the documented MEMPOWERDOWN register state, reset value, and write
> mask, without claiming to model physical ROM or SRAM power gating;
So is this modelling individual cores power states? I'm still a bit
confused as to what in the model will be accessing them when the cores
are down.
> * route PROC0_NMI_MASK and PROC1_NMI_MASK independently to the corresponding
> core;
> * remove the MemoryRegionOps.with_attrs callbacks, since they are only
> needed by the powered-off memory overlays;
My preference would be the other way. The with_attr callbacks allow for
richer memory modelling. Indeed if you really wanted to model powerdown
state you could keep the same accessor and return MEMTX_ACCESS in the
main implementation without needing an overlay.
> * remove the per-subsystem RP2040 Kconfig symbols and build the internal
> devices under the single CONFIG_RP2040 symbol.
>
> I will add focused qtests to the separate SYSINFO and SYSCFG patches.
>
> Does this sound in the right direction?
>
> Thanks,
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 07/30] hw/misc: add RP2040 TBMAN and voltage regulator
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (5 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-02 10:23 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 08/30] hw/misc: add RP2040 crystal and ring oscillators Gilles Grimaud
` (23 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Replace the TBMAN and VREG/chip-reset placeholders with the RP2040 register models from the development tree. Expose the ASIC platform indication, documented reset values, write masks, regulator status, and APB atomic aliases.\n\nAdd focused qtests for both blocks.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 2 +
hw/arm/rp2040.c | 14 ++-
hw/misc/Kconfig | 6 ++
hw/misc/meson.build | 2 +
hw/misc/rp2040_tbman.c | 92 +++++++++++++++++
hw/misc/rp2040_vreg.c | 178 ++++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 4 +
include/hw/misc/rp2040_tbman.h | 25 +++++
include/hw/misc/rp2040_vreg.h | 28 +++++
tests/qtest/meson.build | 4 +-
tests/qtest/rp2040-tbman-test.c | 32 ++++++
tests/qtest/rp2040-vreg-test.c | 79 ++++++++++++++
12 files changed, 463 insertions(+), 3 deletions(-)
create mode 100644 hw/misc/rp2040_tbman.c
create mode 100644 hw/misc/rp2040_vreg.c
create mode 100644 include/hw/misc/rp2040_tbman.h
create mode 100644 include/hw/misc/rp2040_vreg.h
create mode 100644 tests/qtest/rp2040-tbman-test.c
create mode 100644 tests/qtest/rp2040-vreg-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5d55ca06fd..89c479ab6d 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -381,6 +381,8 @@ config RP2040
select RP2040_NYI
select RP2040_SYSCFG
select RP2040_SYSINFO
+ select RP2040_TBMAN
+ select RP2040_VREG
select UNIMP
config STM32F100_SOC
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index a184d527e1..f2a6613aa3 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -73,8 +73,6 @@ static const struct {
{ "rp2040.watchdog", 0x40058000, 0x4000 },
{ "rp2040.rtc", 0x4005c000, 0x4000 },
{ "rp2040.rosc", 0x40060000, 0x4000 },
- { "rp2040.vreg_and_chip_reset", 0x40064000, 0x4000 },
- { "rp2040.tbman", 0x4006c000, 0x4000 },
{ "rp2040.dma", 0x50000000, 0x1000 },
{ "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
{ "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
@@ -179,6 +177,8 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
+ object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
+ object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
s->sysclk = clock_new(obj, "sysclk");
@@ -325,6 +325,16 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
rp2040_syscfg_update(s);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->tbman), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->tbman), 0, RP2040_TBMAN_BASE);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->vreg), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE);
+
for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index f6f344ab51..597559186f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -110,6 +110,12 @@ config RP2040_SYSCFG
config RP2040_SYSINFO
bool
+config RP2040_TBMAN
+ bool
+
+config RP2040_VREG
+ bool
+
config STM32_RCC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 7be9f45c78..9191b16360 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -101,6 +101,8 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
+system_ss.add(when: 'CONFIG_RP2040_TBMAN', if_true: files('rp2040_tbman.c'))
+system_ss.add(when: 'CONFIG_RP2040_VREG', if_true: files('rp2040_vreg.c'))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
diff --git a/hw/misc/rp2040_tbman.c b/hw/misc/rp2040_tbman.c
new file mode 100644
index 0000000000..a60dd83848
--- /dev/null
+++ b/hw/misc/rp2040_tbman.c
@@ -0,0 +1,92 @@
+/*
+ * RP2040 testbench manager emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_tbman.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+
+#define TBMAN_PLATFORM 0x00
+#define TBMAN_PLATFORM_ASIC BIT(0)
+
+static uint64_t rp2040_tbman_read(void *opaque, hwaddr addr, unsigned size)
+{
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case TBMAN_PLATFORM:
+ value = TBMAN_PLATFORM_ASIC;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("tbman", size,
+ RP2040_TBMAN_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_tbman_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ hwaddr offset = addr & 0xfff;
+
+ rp2040_log_unimplemented_write("tbman", size, RP2040_TBMAN_BASE + addr,
+ offset, value64);
+}
+
+static const MemoryRegionOps rp2040_tbman_ops = {
+ .read = rp2040_tbman_read,
+ .write = rp2040_tbman_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_tbman_init(Object *obj)
+{
+ RP2040TbmanState *s = RP2040_TBMAN(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_tbman_ops, s,
+ "rp2040.tbman", RP2040_TBMAN_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_tbman_vmstate = {
+ .name = TYPE_RP2040_TBMAN,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_tbman_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->vmsd = &rp2040_tbman_vmstate;
+}
+
+static const TypeInfo rp2040_tbman_info = {
+ .name = TYPE_RP2040_TBMAN,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040TbmanState),
+ .instance_init = rp2040_tbman_init,
+ .class_init = rp2040_tbman_class_init,
+};
+
+static void rp2040_tbman_register_types(void)
+{
+ type_register_static(&rp2040_tbman_info);
+}
+type_init(rp2040_tbman_register_types)
diff --git a/hw/misc/rp2040_vreg.c b/hw/misc/rp2040_vreg.c
new file mode 100644
index 0000000000..e8480fc258
--- /dev/null
+++ b/hw/misc/rp2040_vreg.c
@@ -0,0 +1,178 @@
+/*
+ * RP2040 voltage regulator and chip reset emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_vreg.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+
+#define VREG_VREG 0x00
+#define VREG_BOD 0x04
+#define VREG_CHIP_RESET 0x08
+
+#define VREG_ROK BIT(12)
+#define VREG_VSEL_MASK 0x000000f0
+#define VREG_HIZ BIT(1)
+#define VREG_EN BIT(0)
+#define VREG_RW_MASK (VREG_VSEL_MASK | VREG_HIZ | VREG_EN)
+#define VREG_RESET 0x000000b1
+
+#define BOD_VSEL_MASK 0x000000f0
+#define BOD_EN BIT(0)
+#define BOD_RW_MASK (BOD_VSEL_MASK | BOD_EN)
+#define BOD_RESET 0x00000091
+
+#define CHIP_RESET_RW_MASK 0x01000000
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_vreg_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 uint32_t rp2040_vreg_read_vreg(RP2040VregState *s)
+{
+ uint32_t value = s->vreg;
+
+ if ((value & VREG_EN) && !(value & VREG_HIZ)) {
+ value |= VREG_ROK;
+ }
+
+ return value;
+}
+
+static uint64_t rp2040_vreg_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040VregState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case VREG_VREG:
+ value = rp2040_vreg_read_vreg(s);
+ break;
+ case VREG_BOD:
+ value = s->bod;
+ break;
+ case VREG_CHIP_RESET:
+ value = s->chip_reset;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("vreg_and_chip_reset", size,
+ RP2040_VREG_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_vreg_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040VregState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+
+ switch (offset) {
+ case VREG_VREG:
+ s->vreg = rp2040_vreg_apply_alias(s->vreg, value, alias) &
+ VREG_RW_MASK;
+ break;
+ case VREG_BOD:
+ s->bod = rp2040_vreg_apply_alias(s->bod, value, alias) & BOD_RW_MASK;
+ break;
+ case VREG_CHIP_RESET:
+ s->chip_reset =
+ rp2040_vreg_apply_alias(s->chip_reset, value, alias) &
+ CHIP_RESET_RW_MASK;
+ break;
+ default:
+ rp2040_log_unimplemented_write("vreg_and_chip_reset", size,
+ RP2040_VREG_BASE + addr, offset,
+ value64);
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_vreg_ops = {
+ .read = rp2040_vreg_read,
+ .write = rp2040_vreg_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_vreg_reset(DeviceState *dev)
+{
+ RP2040VregState *s = RP2040_VREG(dev);
+
+ s->vreg = VREG_RESET;
+ s->bod = BOD_RESET;
+ s->chip_reset = 0;
+}
+
+static void rp2040_vreg_init(Object *obj)
+{
+ RP2040VregState *s = RP2040_VREG(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_vreg_ops, s,
+ "rp2040.vreg_and_chip_reset", RP2040_VREG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_vreg_vmstate = {
+ .name = TYPE_RP2040_VREG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(vreg, RP2040VregState),
+ VMSTATE_UINT32(bod, RP2040VregState),
+ VMSTATE_UINT32(chip_reset, RP2040VregState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_vreg_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_vreg_reset);
+ dc->vmsd = &rp2040_vreg_vmstate;
+}
+
+static const TypeInfo rp2040_vreg_info = {
+ .name = TYPE_RP2040_VREG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040VregState),
+ .instance_init = rp2040_vreg_init,
+ .class_init = rp2040_vreg_class_init,
+};
+
+static void rp2040_vreg_register_types(void)
+{
+ type_register_static(&rp2040_vreg_info);
+}
+type_init(rp2040_vreg_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index caa1f9127f..86037dd0d7 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -15,6 +15,8 @@
#include "hw/core/sysbus.h"
#include "hw/misc/rp2040_syscfg.h"
#include "hw/misc/rp2040_sysinfo.h"
+#include "hw/misc/rp2040_tbman.h"
+#include "hw/misc/rp2040_vreg.h"
#include "qom/object.h"
#define TYPE_RP2040 "rp2040"
@@ -38,6 +40,8 @@ struct RP2040State {
PL011State uart[2];
RP2040SysCfgState syscfg;
RP2040SysInfoState sysinfo;
+ RP2040TbmanState tbman;
+ RP2040VregState vreg;
MemoryRegion *board_memory;
MemoryRegion rom;
diff --git a/include/hw/misc/rp2040_tbman.h b/include/hw/misc/rp2040_tbman.h
new file mode 100644
index 0000000000..50b55039ce
--- /dev/null
+++ b/include/hw/misc/rp2040_tbman.h
@@ -0,0 +1,25 @@
+/*
+ * RP2040 testbench manager emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_TBMAN_H
+#define HW_MISC_RP2040_TBMAN_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_TBMAN "rp2040-tbman"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040TbmanState, RP2040_TBMAN)
+
+#define RP2040_TBMAN_BASE 0x4006c000
+#define RP2040_TBMAN_SIZE 0x4000
+
+struct RP2040TbmanState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+};
+
+#endif
diff --git a/include/hw/misc/rp2040_vreg.h b/include/hw/misc/rp2040_vreg.h
new file mode 100644
index 0000000000..9f1723b7d5
--- /dev/null
+++ b/include/hw/misc/rp2040_vreg.h
@@ -0,0 +1,28 @@
+/*
+ * RP2040 voltage regulator and chip reset emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_VREG_H
+#define HW_MISC_RP2040_VREG_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_VREG "rp2040-vreg"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040VregState, RP2040_VREG)
+
+#define RP2040_VREG_BASE 0x40064000
+#define RP2040_VREG_SIZE 0x4000
+
+struct RP2040VregState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t vreg;
+ uint32_t bod;
+ uint32_t chip_reset;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 692d51a78a..8916f68bad 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -255,7 +255,9 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 'tpm-tis-i2c-swtpm-test'] : []) + \
(config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
- (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
+ 'rp2040-tbman-test',
+ 'rp2040-vreg-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-tbman-test.c b/tests/qtest/rp2040-tbman-test.c
new file mode 100644
index 0000000000..a05eb3c3e2
--- /dev/null
+++ b/tests/qtest/rp2040-tbman-test.c
@@ -0,0 +1,32 @@
+/*
+ * QTest testcase for the RP2040 testbench manager block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define TBMAN_BASE 0x4006c000
+#define TBMAN_PLATFORM 0x00
+#define TBMAN_PLATFORM_ASIC BIT(0)
+
+static void test_tbman_platform(void)
+{
+ QTestState *qts = qtest_init("-machine raspi-pico");
+
+ g_assert_cmphex(qtest_readl(qts, TBMAN_BASE + TBMAN_PLATFORM), ==,
+ TBMAN_PLATFORM_ASIC);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-tbman/platform", test_tbman_platform);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/rp2040-vreg-test.c b/tests/qtest/rp2040-vreg-test.c
new file mode 100644
index 0000000000..b08bb16765
--- /dev/null
+++ b/tests/qtest/rp2040-vreg-test.c
@@ -0,0 +1,79 @@
+/*
+ * QTest testcase for the RP2040 vreg_and_chip_reset block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define VREG_BASE 0x40064000
+#define VREG_VREG 0x00
+#define VREG_BOD 0x04
+#define VREG_CHIP_RESET 0x08
+
+#define VREG_ROK BIT(12)
+#define VREG_RESET 0x000000b1
+#define BOD_RESET 0x00000091
+#define CHIP_RESET_RESCUE BIT(24)
+
+#define ATOMIC_SET_ALIAS 0x2000
+#define ATOMIC_CLR_ALIAS 0x3000
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_vreg_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_VREG), ==,
+ VREG_RESET | VREG_ROK);
+ g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_BOD), ==, BOD_RESET);
+ g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_CHIP_RESET), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_vreg_rw_masks(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, VREG_BASE + VREG_VREG, 0xffffffff);
+ qtest_writel(qts, VREG_BASE + VREG_BOD, 0xffffffff);
+ qtest_writel(qts, VREG_BASE + VREG_CHIP_RESET, 0xffffffff);
+
+ g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_VREG), ==, 0x000000f3);
+ g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_BOD), ==, 0x000000f1);
+ g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_CHIP_RESET), ==,
+ CHIP_RESET_RESCUE);
+
+ qtest_quit(qts);
+}
+
+static void test_vreg_atomic_aliases(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, VREG_BASE + VREG_BOD, 0);
+ qtest_writel(qts, VREG_BASE + ATOMIC_SET_ALIAS + VREG_BOD, 0x00000011);
+ qtest_writel(qts, VREG_BASE + ATOMIC_CLR_ALIAS + VREG_BOD, 0x00000010);
+
+ g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_BOD), ==, 0x00000001);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-vreg/reset-values", test_vreg_reset_values);
+ qtest_add_func("/rp2040-vreg/rw-masks", test_vreg_rw_masks);
+ qtest_add_func("/rp2040-vreg/atomic-aliases", test_vreg_atomic_aliases);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 07/30] hw/misc: add RP2040 TBMAN and voltage regulator
2026-08-29 23:42 ` [PATCH RFC v2 07/30] hw/misc: add RP2040 TBMAN and voltage regulator Gilles Grimaud
@ 2026-09-02 10:23 ` Alex Bennée
2026-09-04 9:33 ` gilles grimaud
0 siblings, 1 reply; 49+ messages in thread
From: Alex Bennée @ 2026-09-02 10:23 UTC (permalink / raw)
To: Gilles Grimaud
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell, Fabiano Rosas,
Laurent Vivier
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Replace the TBMAN and VREG/chip-reset placeholders with the RP2040 register models from the development tree. Expose the ASIC platform indication, documented reset values, write masks, regulator status, and APB atomic aliases.\n\nAdd focused qtests for both blocks.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> hw/arm/Kconfig | 2 +
> hw/arm/rp2040.c | 14 ++-
> hw/misc/Kconfig | 6 ++
> hw/misc/meson.build | 2 +
> hw/misc/rp2040_tbman.c | 92 +++++++++++++++++
> hw/misc/rp2040_vreg.c | 178 ++++++++++++++++++++++++++++++++
> include/hw/arm/rp2040.h | 4 +
> include/hw/misc/rp2040_tbman.h | 25 +++++
> include/hw/misc/rp2040_vreg.h | 28 +++++
> tests/qtest/meson.build | 4 +-
> tests/qtest/rp2040-tbman-test.c | 32 ++++++
> tests/qtest/rp2040-vreg-test.c | 79 ++++++++++++++
> 12 files changed, 463 insertions(+), 3 deletions(-)
> create mode 100644 hw/misc/rp2040_tbman.c
> create mode 100644 hw/misc/rp2040_vreg.c
> create mode 100644 include/hw/misc/rp2040_tbman.h
> create mode 100644 include/hw/misc/rp2040_vreg.h
> create mode 100644 tests/qtest/rp2040-tbman-test.c
> create mode 100644 tests/qtest/rp2040-vreg-test.c
>
<snip>
> new file mode 100644
> index 0000000000..e8480fc258
> --- /dev/null
> +++ b/hw/misc/rp2040_vreg.c
> @@ -0,0 +1,178 @@
> +/*
> + * RP2040 voltage regulator and chip reset emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/misc/rp2040_nyi.h"
> +#include "hw/misc/rp2040_vreg.h"
> +#include "migration/vmstate.h"
> +#include "qemu/module.h"
> +
> +#define VREG_VREG 0x00
> +#define VREG_BOD 0x04
> +#define VREG_CHIP_RESET 0x08
> +
> +#define VREG_ROK BIT(12)
> +#define VREG_VSEL_MASK 0x000000f0
> +#define VREG_HIZ BIT(1)
> +#define VREG_EN BIT(0)
> +#define VREG_RW_MASK (VREG_VSEL_MASK | VREG_HIZ | VREG_EN)
> +#define VREG_RESET 0x000000b1
> +
> +#define BOD_VSEL_MASK 0x000000f0
> +#define BOD_EN BIT(0)
> +#define BOD_RW_MASK (BOD_VSEL_MASK | BOD_EN)
> +#define BOD_RESET 0x00000091
> +
> +#define CHIP_RESET_RW_MASK 0x01000000
> +
> +#define ATOMIC_ALIAS_MASK 0x3000
> +#define ATOMIC_XOR 0x1000
> +#define ATOMIC_SET 0x2000
> +#define ATOMIC_CLR 0x3000
> +
<snip>
> diff --git a/tests/qtest/rp2040-vreg-test.c b/tests/qtest/rp2040-vreg-test.c
> new file mode 100644
> index 0000000000..b08bb16765
> --- /dev/null
> +++ b/tests/qtest/rp2040-vreg-test.c
> @@ -0,0 +1,79 @@
> +/*
> + * QTest testcase for the RP2040 vreg_and_chip_reset block.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "qemu/bitops.h"
> +
> +#define VREG_BASE 0x40064000
> +#define VREG_VREG 0x00
> +#define VREG_BOD 0x04
> +#define VREG_CHIP_RESET 0x08
> +
> +#define VREG_ROK BIT(12)
> +#define VREG_RESET 0x000000b1
> +#define BOD_RESET 0x00000091
> +#define CHIP_RESET_RESCUE BIT(24)
> +
> +#define ATOMIC_SET_ALIAS 0x2000
> +#define ATOMIC_CLR_ALIAS 0x3000
You don't need this duplication. qtest's can include QEMU headers.
As an aside you might want to look at the hw/core/registerfields.h
helpers which are good for defining registers and their fields and
generate the helpers for you.
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 08/30] hw/misc: add RP2040 crystal and ring oscillators
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (6 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 07/30] hw/misc: add RP2040 TBMAN and voltage regulator Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-02 10:25 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 09/30] hw/misc: add RP2040 PLL and clock controller Gilles Grimaud
` (22 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Philippe Mathieu-Daudé, Fabiano Rosas,
Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the RP2040 XOSC and ROSC register blocks and expose their clock outputs. Use virtual time for the ROSC COUNT register and provide either QEMU guest entropy or a machine-selectable deterministic seed for RANDOMBIT. Add qtests for the ROSC register behavior and deterministic stream.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 2 +
hw/arm/raspi_pico.c | 56 +++++
hw/arm/rp2040.c | 14 +-
hw/misc/Kconfig | 6 +
hw/misc/meson.build | 2 +
hw/misc/rp2040_rosc.c | 430 +++++++++++++++++++++++++++++++++
hw/misc/rp2040_xosc.c | 237 ++++++++++++++++++
include/hw/arm/rp2040.h | 4 +
include/hw/misc/rp2040_rosc.h | 42 ++++
include/hw/misc/rp2040_xosc.h | 33 +++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-rosc-test.c | 146 +++++++++++
12 files changed, 971 insertions(+), 2 deletions(-)
create mode 100644 hw/misc/rp2040_rosc.c
create mode 100644 hw/misc/rp2040_xosc.c
create mode 100644 include/hw/misc/rp2040_rosc.h
create mode 100644 include/hw/misc/rp2040_xosc.h
create mode 100644 tests/qtest/rp2040-rosc-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 89c479ab6d..b55a75fd5a 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -379,10 +379,12 @@ config RP2040
select ARM_V7M
select PL011
select RP2040_NYI
+ select RP2040_ROSC
select RP2040_SYSCFG
select RP2040_SYSINFO
select RP2040_TBMAN
select RP2040_VREG
+ select RP2040_XOSC
select UNIMP
config STM32F100_SOC
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
index 2bb7d4e511..2a65176dc6 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"
@@ -28,8 +29,43 @@ struct RaspiPicoMachineState {
RP2040State soc;
MemoryRegion flash;
+ char *rosc_random_seed;
+ uint64_t rosc_random_seed_value;
+ bool rosc_random_seed_set;
};
+static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ return g_strdup(s->rosc_random_seed ?: "");
+}
+
+static void raspi_pico_set_rosc_random_seed(Object *obj, const char *value,
+ Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+ uint64_t seed;
+
+ if (!value || !*value) {
+ g_free(s->rosc_random_seed);
+ s->rosc_random_seed = NULL;
+ s->rosc_random_seed_value = 0;
+ s->rosc_random_seed_set = false;
+ return;
+ }
+
+ if (qemu_strtou64(value, NULL, 0, &seed) < 0) {
+ error_setg(errp, "invalid ROSC random seed '%s'", value);
+ return;
+ }
+
+ g_free(s->rosc_random_seed);
+ s->rosc_random_seed = g_strdup(value);
+ s->rosc_random_seed_value = seed;
+ s->rosc_random_seed_set = true;
+}
+
static void raspi_pico_init(MachineState *machine)
{
RaspiPicoMachineState *s = RASPI_PICO_MACHINE(machine);
@@ -38,6 +74,10 @@ static void raspi_pico_init(MachineState *machine)
object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
+ qdev_prop_set_uint64(DEVICE(&s->soc.rosc), "random-seed",
+ s->rosc_random_seed_value);
+ qdev_prop_set_bit(DEVICE(&s->soc.rosc), "random-seed-set",
+ s->rosc_random_seed_set);
if (machine->firmware) {
qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
machine->firmware);
@@ -55,6 +95,13 @@ static void raspi_pico_init(MachineState *machine)
RP2040_XIP_BASE, PICO_FLASH_SIZE);
}
+static void raspi_pico_machine_finalize(Object *obj)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ g_free(s->rosc_random_seed);
+}
+
static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -68,6 +115,14 @@ static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
mc->no_parallel = 1;
mc->no_floppy = 1;
mc->no_cdrom = 1;
+
+ object_class_property_add_str(oc, "rosc-random-seed",
+ raspi_pico_get_rosc_random_seed,
+ raspi_pico_set_rosc_random_seed);
+ object_class_property_set_description(oc, "rosc-random-seed",
+ "Use a deterministic seed for the "
+ "ROSC RANDOMBIT stream; if unset, "
+ "QEMU guest entropy is used");
}
static const TypeInfo raspi_pico_machine_info = {
@@ -75,6 +130,7 @@ static const TypeInfo raspi_pico_machine_info = {
.parent = TYPE_MACHINE,
.instance_size = sizeof(RaspiPicoMachineState),
.class_init = raspi_pico_machine_class_init,
+ .instance_finalize = raspi_pico_machine_finalize,
.interfaces = arm_machine_interfaces,
};
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index f2a6613aa3..889decded0 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -57,7 +57,6 @@ static const struct {
{ "rp2040.ioqspi", 0x40018000, 0x4000 },
{ "rp2040.padsbank0", 0x4001c000, 0x4000 },
{ "rp2040.padsqspi", 0x40020000, 0x4000 },
- { "rp2040.xosc", 0x40024000, 0x4000 },
{ "rp2040.pll_sys", 0x40028000, 0x4000 },
{ "rp2040.pll_usb", 0x4002c000, 0x4000 },
{ "rp2040.busctrl", 0x40030000, 0x4000 },
@@ -72,7 +71,6 @@ static const struct {
{ "rp2040.timer", 0x40054000, 0x4000 },
{ "rp2040.watchdog", 0x40058000, 0x4000 },
{ "rp2040.rtc", 0x4005c000, 0x4000 },
- { "rp2040.rosc", 0x40060000, 0x4000 },
{ "rp2040.dma", 0x50000000, 0x1000 },
{ "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
{ "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
@@ -177,8 +175,10 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
+ object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
+ object_initialize_child(obj, "xosc", &s->xosc, TYPE_RP2040_XOSC);
s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
s->sysclk = clock_new(obj, "sysclk");
@@ -325,6 +325,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
rp2040_syscfg_update(s);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->rosc), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->rosc), 0, RP2040_ROSC_BASE);
+
if (!sysbus_realize(SYS_BUS_DEVICE(&s->tbman), errp)) {
return;
}
@@ -335,6 +340,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->xosc), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->xosc), 0, RP2040_XOSC_BASE);
+
for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 597559186f..8f0860139f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -104,6 +104,9 @@ config FSL_IMX8MP_CCM
config RP2040_NYI
bool
+config RP2040_ROSC
+ bool
+
config RP2040_SYSCFG
bool
@@ -116,6 +119,9 @@ config RP2040_TBMAN
config RP2040_VREG
bool
+config RP2040_XOSC
+ bool
+
config STM32_RCC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 9191b16360..7ad307beee 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -99,10 +99,12 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_powermgt.c',
))
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
+system_ss.add(when: 'CONFIG_RP2040_ROSC', if_true: files('rp2040_rosc.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
system_ss.add(when: 'CONFIG_RP2040_TBMAN', if_true: files('rp2040_tbman.c'))
system_ss.add(when: 'CONFIG_RP2040_VREG', if_true: files('rp2040_vreg.c'))
+system_ss.add(when: 'CONFIG_RP2040_XOSC', if_true: files('rp2040_xosc.c'))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
diff --git a/hw/misc/rp2040_rosc.c b/hw/misc/rp2040_rosc.c
new file mode 100644
index 0000000000..dcb340ec57
--- /dev/null
+++ b/hw/misc/rp2040_rosc.c
@@ -0,0 +1,430 @@
+/*
+ * RP2040 ring oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_rosc.h"
+#include "migration/vmstate.h"
+#include "qemu/guest-random.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+
+#define ROSC_NOMINAL_HZ 6500000
+
+#define ROSC_CTRL 0x00
+#define ROSC_FREQA 0x04
+#define ROSC_FREQB 0x08
+#define ROSC_DORMANT 0x0c
+#define ROSC_DIV 0x10
+#define ROSC_PHASE 0x14
+#define ROSC_STATUS 0x18
+#define ROSC_RANDOMBIT 0x1c
+#define ROSC_COUNT 0x20
+
+#define ROSC_ENABLE_DISABLE 0xd1e
+#define ROSC_ENABLE_ENABLE 0xfab
+#define ROSC_FREQ_RANGE_LOW 0xfa4
+#define ROSC_FREQ_RANGE_MEDIUM 0xfa5
+#define ROSC_FREQ_RANGE_HIGH 0xfa7
+#define ROSC_FREQ_RANGE_TOOHIGH 0xfa6
+#define ROSC_DORMANT_VALUE 0x636f6d61
+#define ROSC_WAKE_VALUE 0x77616b65
+#define ROSC_FREQ_PASSWD 0x9696
+#define ROSC_DIV_PASS 0xaa0
+#define ROSC_PHASE_PASSWD 0xaa
+
+#define ROSC_STATUS_STABLE BIT(31)
+#define ROSC_STATUS_BADWRITE BIT(24)
+#define ROSC_STATUS_DIV_RUNNING BIT(16)
+#define ROSC_STATUS_ENABLED BIT(12)
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_rosc_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 bool rp2040_rosc_valid_range(uint32_t range)
+{
+ return range == ROSC_FREQ_RANGE_LOW || range == ROSC_FREQ_RANGE_MEDIUM ||
+ range == ROSC_FREQ_RANGE_HIGH || range == ROSC_FREQ_RANGE_TOOHIGH;
+}
+
+static bool rp2040_rosc_enabled(RP2040RoscState *s)
+{
+ return extract32(s->ctrl, 12, 12) != ROSC_ENABLE_DISABLE;
+}
+
+static bool rp2040_rosc_running(RP2040RoscState *s)
+{
+ return rp2040_rosc_enabled(s) && s->dormant == ROSC_WAKE_VALUE;
+}
+
+static uint32_t rp2040_rosc_divisor(RP2040RoscState *s)
+{
+ uint32_t encoded = s->div & 0xfff;
+ uint32_t div;
+
+ if (encoded < ROSC_DIV_PASS) {
+ return 31;
+ }
+
+ div = encoded - ROSC_DIV_PASS;
+ if (div == 0) {
+ return 32;
+ }
+ if (div > 31) {
+ return 31;
+ }
+ return div;
+}
+
+static uint32_t rp2040_rosc_hz(RP2040RoscState *s)
+{
+ if (!rp2040_rosc_running(s)) {
+ return 0;
+ }
+
+ return ROSC_NOMINAL_HZ / rp2040_rosc_divisor(s);
+}
+
+static void rp2040_rosc_update_clock(RP2040RoscState *s)
+{
+ clock_update_hz(s->clk, rp2040_rosc_hz(s));
+}
+
+static uint32_t rp2040_rosc_count_read(RP2040RoscState *s)
+{
+ uint64_t elapsed_ns;
+ uint64_t elapsed_ticks;
+
+ if (!s->count || !rp2040_rosc_running(s)) {
+ return s->count;
+ }
+
+ elapsed_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->count_start_ns;
+ elapsed_ticks = elapsed_ns * ROSC_NOMINAL_HZ / NANOSECONDS_PER_SECOND;
+ if (elapsed_ticks >= s->count) {
+ s->count = 0;
+ } else {
+ s->count -= elapsed_ticks;
+ s->count_start_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ }
+
+ return s->count;
+}
+
+static uint32_t rp2040_rosc_status(RP2040RoscState *s)
+{
+ uint32_t value = 0;
+
+ if (rp2040_rosc_enabled(s)) {
+ value |= ROSC_STATUS_ENABLED;
+ if (s->dormant == ROSC_WAKE_VALUE) {
+ value |= ROSC_STATUS_STABLE | ROSC_STATUS_DIV_RUNNING;
+ }
+ }
+ if (s->badwrite) {
+ value |= ROSC_STATUS_BADWRITE;
+ }
+
+ return value;
+}
+
+static uint32_t rp2040_rosc_random_bit(RP2040RoscState *s)
+{
+ uint64_t x;
+ uint32_t bit;
+
+ if (!rp2040_rosc_running(s)) {
+ return 0;
+ }
+
+ if (!s->random_seed_set) {
+ if (!s->random_pool_bits) {
+ qemu_guest_getrandom_nofail(&s->random_pool,
+ sizeof(s->random_pool));
+ s->random_pool_bits = 64;
+ }
+
+ bit = s->random_pool & 1;
+ s->random_pool >>= 1;
+ s->random_pool_bits--;
+ return bit;
+ }
+
+ x = s->random_prng_state;
+ x ^= x >> 12;
+ x ^= x << 25;
+ x ^= x >> 27;
+ s->random_prng_state = x;
+ return (x * 0x2545f4914f6cdd1dull) >> 63;
+}
+
+static uint64_t rp2040_rosc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040RoscState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case ROSC_CTRL:
+ value = s->ctrl;
+ break;
+ case ROSC_FREQA:
+ value = s->freqa;
+ break;
+ case ROSC_FREQB:
+ value = s->freqb;
+ break;
+ case ROSC_DORMANT:
+ value = s->dormant;
+ break;
+ case ROSC_DIV:
+ value = s->div;
+ break;
+ case ROSC_PHASE:
+ value = s->phase;
+ break;
+ case ROSC_STATUS:
+ value = rp2040_rosc_status(s);
+ break;
+ case ROSC_RANDOMBIT:
+ value = rp2040_rosc_random_bit(s);
+ break;
+ case ROSC_COUNT:
+ value = rp2040_rosc_count_read(s);
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("rosc", size,
+ RP2040_ROSC_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_rosc_write_ctrl(RP2040RoscState *s, uint32_t value)
+{
+ uint32_t enable = extract32(value, 12, 12);
+ uint32_t range = extract32(value, 0, 12);
+
+ if (enable != ROSC_ENABLE_DISABLE && enable != ROSC_ENABLE_ENABLE) {
+ s->badwrite = true;
+ value = deposit32(value, 12, 12, ROSC_ENABLE_ENABLE);
+ }
+ if (!rp2040_rosc_valid_range(range)) {
+ s->badwrite = true;
+ value = deposit32(value, 0, 12, ROSC_FREQ_RANGE_LOW);
+ }
+
+ s->ctrl = value & 0x00ffffff;
+ rp2040_rosc_update_clock(s);
+}
+
+static void rp2040_rosc_write_freq(RP2040RoscState *s, uint32_t *reg,
+ uint32_t value)
+{
+ if (extract32(value, 16, 16) != ROSC_FREQ_PASSWD) {
+ s->badwrite = true;
+ *reg = 0;
+ return;
+ }
+
+ *reg = value & 0x96967777;
+}
+
+static void rp2040_rosc_write_div(RP2040RoscState *s, uint32_t value)
+{
+ if ((value & 0xfff) < ROSC_DIV_PASS) {
+ s->badwrite = true;
+ value = ROSC_DIV_PASS + 31;
+ }
+
+ s->div = value & 0xfff;
+ rp2040_rosc_update_clock(s);
+}
+
+static void rp2040_rosc_write_phase(RP2040RoscState *s, uint32_t value)
+{
+ if (extract32(value, 4, 8) != ROSC_PHASE_PASSWD) {
+ s->badwrite = true;
+ value = 0;
+ }
+
+ s->phase = value & 0x00000fff;
+}
+
+static void rp2040_rosc_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040RoscState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+
+ switch (offset) {
+ case ROSC_CTRL:
+ rp2040_rosc_write_ctrl(s, rp2040_rosc_apply_alias(s->ctrl, value,
+ alias));
+ break;
+ case ROSC_FREQA:
+ rp2040_rosc_write_freq(s, &s->freqa,
+ rp2040_rosc_apply_alias(s->freqa, value,
+ alias));
+ break;
+ case ROSC_FREQB:
+ rp2040_rosc_write_freq(s, &s->freqb,
+ rp2040_rosc_apply_alias(s->freqb, value,
+ alias));
+ break;
+ case ROSC_DORMANT:
+ s->dormant = value == ROSC_DORMANT_VALUE ? value : ROSC_WAKE_VALUE;
+ if (value != ROSC_DORMANT_VALUE && value != ROSC_WAKE_VALUE) {
+ s->badwrite = true;
+ }
+ rp2040_rosc_update_clock(s);
+ break;
+ case ROSC_DIV:
+ rp2040_rosc_write_div(s, rp2040_rosc_apply_alias(s->div, value,
+ alias));
+ break;
+ case ROSC_PHASE:
+ rp2040_rosc_write_phase(s, rp2040_rosc_apply_alias(s->phase, value,
+ alias));
+ break;
+ case ROSC_STATUS:
+ if (value & ROSC_STATUS_BADWRITE) {
+ s->badwrite = false;
+ }
+ break;
+ case ROSC_COUNT:
+ s->count = value & 0xff;
+ s->count_start_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ break;
+ default:
+ rp2040_log_unimplemented_write("rosc", size,
+ RP2040_ROSC_BASE + addr, offset,
+ value64);
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_rosc_ops = {
+ .read = rp2040_rosc_read,
+ .write = rp2040_rosc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_rosc_reset(DeviceState *dev)
+{
+ RP2040RoscState *s = RP2040_ROSC(dev);
+
+ s->ctrl = (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW;
+ s->freqa = 0;
+ s->freqb = 0;
+ s->dormant = ROSC_WAKE_VALUE;
+ s->div = ROSC_DIV_PASS + 16;
+ s->phase = 0x00000aa8;
+ s->count = 0;
+ s->count_start_ns = 0;
+ s->random_pool = 0;
+ s->random_pool_bits = 0;
+ s->random_prng_state = s->random_seed;
+ if (!s->random_prng_state) {
+ s->random_prng_state = 0x9e3779b97f4a7c15ull;
+ }
+ s->badwrite = false;
+
+ rp2040_rosc_update_clock(s);
+}
+
+static void rp2040_rosc_init(Object *obj)
+{
+ RP2040RoscState *s = RP2040_ROSC(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ s->clk = qdev_init_clock_out(dev, "clk");
+ memory_region_init_io(&s->iomem, obj, &rp2040_rosc_ops, s,
+ "rp2040.rosc", RP2040_ROSC_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_rosc_vmstate = {
+ .name = TYPE_RP2040_ROSC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(ctrl, RP2040RoscState),
+ VMSTATE_UINT32(freqa, RP2040RoscState),
+ VMSTATE_UINT32(freqb, RP2040RoscState),
+ VMSTATE_UINT32(dormant, RP2040RoscState),
+ VMSTATE_UINT32(div, RP2040RoscState),
+ VMSTATE_UINT32(phase, RP2040RoscState),
+ VMSTATE_UINT32(count, RP2040RoscState),
+ VMSTATE_INT64(count_start_ns, RP2040RoscState),
+ VMSTATE_UINT64(random_pool, RP2040RoscState),
+ VMSTATE_UINT64(random_prng_state, RP2040RoscState),
+ VMSTATE_UINT64(random_seed, RP2040RoscState),
+ VMSTATE_UINT8(random_pool_bits, RP2040RoscState),
+ VMSTATE_BOOL(random_seed_set, RP2040RoscState),
+ VMSTATE_BOOL(badwrite, RP2040RoscState),
+ VMSTATE_CLOCK(clk, RP2040RoscState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property rp2040_rosc_properties[] = {
+ DEFINE_PROP_UINT64("random-seed", RP2040RoscState, random_seed, 0),
+ DEFINE_PROP_BOOL("random-seed-set", RP2040RoscState, random_seed_set,
+ false),
+};
+
+static void rp2040_rosc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_rosc_reset);
+ dc->vmsd = &rp2040_rosc_vmstate;
+ device_class_set_props(dc, rp2040_rosc_properties);
+}
+
+static const TypeInfo rp2040_rosc_info = {
+ .name = TYPE_RP2040_ROSC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040RoscState),
+ .instance_init = rp2040_rosc_init,
+ .class_init = rp2040_rosc_class_init,
+};
+
+static void rp2040_rosc_register_types(void)
+{
+ type_register_static(&rp2040_rosc_info);
+}
+type_init(rp2040_rosc_register_types)
diff --git a/hw/misc/rp2040_xosc.c b/hw/misc/rp2040_xosc.c
new file mode 100644
index 0000000000..a8340ca0c9
--- /dev/null
+++ b/hw/misc/rp2040_xosc.c
@@ -0,0 +1,237 @@
+/*
+ * RP2040 crystal oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_xosc.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define XOSC_HZ 12000000
+
+#define XOSC_CTRL 0x00
+#define XOSC_STATUS 0x04
+#define XOSC_DORMANT 0x08
+#define XOSC_STARTUP 0x0c
+#define XOSC_COUNT 0x1c
+
+#define XOSC_ENABLE_DISABLE 0xd1e
+#define XOSC_ENABLE_ENABLE 0xfab
+#define XOSC_FREQ_1_15MHZ 0xaa0
+#define XOSC_DORMANT_VALUE 0x636f6d61
+#define XOSC_WAKE_VALUE 0x77616b65
+
+#define XOSC_STATUS_STABLE BIT(31)
+#define XOSC_STATUS_BADWRITE BIT(24)
+#define XOSC_STATUS_ENABLED BIT(12)
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static bool rp2040_xosc_enabled(RP2040XoscState *s)
+{
+ return extract32(s->ctrl, 12, 12) != XOSC_ENABLE_DISABLE;
+}
+
+static void rp2040_xosc_update_clock(RP2040XoscState *s)
+{
+ clock_update_hz(s->clk, rp2040_xosc_enabled(s) ? XOSC_HZ : 0);
+}
+
+static uint32_t rp2040_xosc_status(RP2040XoscState *s)
+{
+ uint32_t value = 0;
+
+ if (rp2040_xosc_enabled(s)) {
+ value |= XOSC_STATUS_ENABLED;
+ if (s->dormant == XOSC_WAKE_VALUE) {
+ value |= XOSC_STATUS_STABLE;
+ }
+ }
+ if (s->badwrite) {
+ value |= XOSC_STATUS_BADWRITE;
+ }
+
+ return value;
+}
+
+static uint64_t rp2040_xosc_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040XoscState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case XOSC_CTRL:
+ value = s->ctrl;
+ break;
+ case XOSC_STATUS:
+ value = rp2040_xosc_status(s);
+ break;
+ case XOSC_DORMANT:
+ value = s->dormant;
+ break;
+ case XOSC_STARTUP:
+ value = s->startup;
+ break;
+ case XOSC_COUNT:
+ value = 0;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("xosc", size,
+ RP2040_XOSC_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static uint32_t rp2040_xosc_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_xosc_write_ctrl(RP2040XoscState *s, uint32_t value)
+{
+ uint32_t enable = extract32(value, 12, 12);
+ uint32_t freq_range = extract32(value, 0, 12);
+
+ if (freq_range != XOSC_FREQ_1_15MHZ ||
+ (enable != XOSC_ENABLE_DISABLE && enable != XOSC_ENABLE_ENABLE)) {
+ s->badwrite = true;
+ }
+
+ if (enable != XOSC_ENABLE_DISABLE && enable != XOSC_ENABLE_ENABLE) {
+ value = deposit32(value, 12, 12, XOSC_ENABLE_ENABLE);
+ }
+ value = deposit32(value, 0, 12, XOSC_FREQ_1_15MHZ);
+
+ s->ctrl = value;
+ rp2040_xosc_update_clock(s);
+}
+
+static void rp2040_xosc_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040XoscState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+
+ switch (offset) {
+ case XOSC_CTRL:
+ rp2040_xosc_write_ctrl(s, rp2040_xosc_apply_alias(s->ctrl, value,
+ alias));
+ break;
+ case XOSC_STATUS:
+ if (value & XOSC_STATUS_BADWRITE) {
+ s->badwrite = false;
+ }
+ break;
+ case XOSC_DORMANT:
+ s->dormant = value == XOSC_DORMANT_VALUE ? value : XOSC_WAKE_VALUE;
+ rp2040_xosc_update_clock(s);
+ break;
+ case XOSC_STARTUP:
+ s->startup = value & 0x00103fff;
+ break;
+ case XOSC_COUNT:
+ s->count = 0;
+ break;
+ default:
+ rp2040_log_unimplemented_write("xosc", size,
+ RP2040_XOSC_BASE + addr, offset,
+ value64);
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_xosc_ops = {
+ .read = rp2040_xosc_read,
+ .write = rp2040_xosc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_xosc_reset(DeviceState *dev)
+{
+ RP2040XoscState *s = RP2040_XOSC(dev);
+
+ s->ctrl = (XOSC_ENABLE_DISABLE << 12) | XOSC_FREQ_1_15MHZ;
+ s->dormant = XOSC_WAKE_VALUE;
+ s->startup = 0x000000c4;
+ s->count = 0;
+ s->badwrite = false;
+
+ rp2040_xosc_update_clock(s);
+}
+
+static void rp2040_xosc_init(Object *obj)
+{
+ RP2040XoscState *s = RP2040_XOSC(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ s->clk = qdev_init_clock_out(dev, "clk");
+ memory_region_init_io(&s->iomem, obj, &rp2040_xosc_ops, s,
+ "rp2040.xosc", RP2040_XOSC_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_xosc_vmstate = {
+ .name = TYPE_RP2040_XOSC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(ctrl, RP2040XoscState),
+ VMSTATE_UINT32(dormant, RP2040XoscState),
+ VMSTATE_UINT32(startup, RP2040XoscState),
+ VMSTATE_UINT32(count, RP2040XoscState),
+ VMSTATE_BOOL(badwrite, RP2040XoscState),
+ VMSTATE_CLOCK(clk, RP2040XoscState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_xosc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_xosc_reset);
+ dc->vmsd = &rp2040_xosc_vmstate;
+}
+
+static const TypeInfo rp2040_xosc_info = {
+ .name = TYPE_RP2040_XOSC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040XoscState),
+ .instance_init = rp2040_xosc_init,
+ .class_init = rp2040_xosc_class_init,
+};
+
+static void rp2040_xosc_register_types(void)
+{
+ type_register_static(&rp2040_xosc_info);
+}
+type_init(rp2040_xosc_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 86037dd0d7..47a8cb2236 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -13,10 +13,12 @@
#include "hw/char/pl011.h"
#include "hw/core/clock.h"
#include "hw/core/sysbus.h"
+#include "hw/misc/rp2040_rosc.h"
#include "hw/misc/rp2040_syscfg.h"
#include "hw/misc/rp2040_sysinfo.h"
#include "hw/misc/rp2040_tbman.h"
#include "hw/misc/rp2040_vreg.h"
+#include "hw/misc/rp2040_xosc.h"
#include "qom/object.h"
#define TYPE_RP2040 "rp2040"
@@ -40,8 +42,10 @@ struct RP2040State {
PL011State uart[2];
RP2040SysCfgState syscfg;
RP2040SysInfoState sysinfo;
+ RP2040RoscState rosc;
RP2040TbmanState tbman;
RP2040VregState vreg;
+ RP2040XoscState xosc;
MemoryRegion *board_memory;
MemoryRegion rom;
diff --git a/include/hw/misc/rp2040_rosc.h b/include/hw/misc/rp2040_rosc.h
new file mode 100644
index 0000000000..6377704e22
--- /dev/null
+++ b/include/hw/misc/rp2040_rosc.h
@@ -0,0 +1,42 @@
+/*
+ * RP2040 ring oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_ROSC_H
+#define HW_MISC_RP2040_ROSC_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_ROSC "rp2040-rosc"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040RoscState, RP2040_ROSC)
+
+#define RP2040_ROSC_BASE 0x40060000
+#define RP2040_ROSC_SIZE 0x4000
+
+struct RP2040RoscState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ Clock *clk;
+
+ uint32_t ctrl;
+ uint32_t freqa;
+ uint32_t freqb;
+ uint32_t dormant;
+ uint32_t div;
+ uint32_t phase;
+ uint32_t count;
+ int64_t count_start_ns;
+ uint64_t random_pool;
+ uint64_t random_prng_state;
+ uint64_t random_seed;
+ uint8_t random_pool_bits;
+ bool random_seed_set;
+ bool badwrite;
+};
+
+#endif
diff --git a/include/hw/misc/rp2040_xosc.h b/include/hw/misc/rp2040_xosc.h
new file mode 100644
index 0000000000..7b9ef62d22
--- /dev/null
+++ b/include/hw/misc/rp2040_xosc.h
@@ -0,0 +1,33 @@
+/*
+ * RP2040 crystal oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_XOSC_H
+#define HW_MISC_RP2040_XOSC_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_XOSC "rp2040-xosc"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040XoscState, RP2040_XOSC)
+
+#define RP2040_XOSC_BASE 0x40024000
+#define RP2040_XOSC_SIZE 0x4000
+
+struct RP2040XoscState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ Clock *clk;
+
+ uint32_t ctrl;
+ uint32_t dormant;
+ uint32_t startup;
+ uint32_t count;
+ bool badwrite;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 8916f68bad..441f6f294a 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -256,6 +256,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
+ 'rp2040-rosc-test',
'rp2040-tbman-test',
'rp2040-vreg-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
diff --git a/tests/qtest/rp2040-rosc-test.c b/tests/qtest/rp2040-rosc-test.c
new file mode 100644
index 0000000000..2f1fb96022
--- /dev/null
+++ b/tests/qtest/rp2040-rosc-test.c
@@ -0,0 +1,146 @@
+/*
+ * QTest testcase for the RP2040 ring oscillator block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define ROSC_BASE 0x40060000
+#define ROSC_CTRL 0x00
+#define ROSC_FREQA 0x04
+#define ROSC_DORMANT 0x0c
+#define ROSC_DIV 0x10
+#define ROSC_PHASE 0x14
+#define ROSC_STATUS 0x18
+#define ROSC_RANDOMBIT 0x1c
+#define ROSC_COUNT 0x20
+
+#define ROSC_ENABLE_ENABLE 0xfab
+#define ROSC_ENABLE_DISABLE 0xd1e
+#define ROSC_FREQ_RANGE_LOW 0xfa4
+#define ROSC_WAKE_VALUE 0x77616b65
+#define ROSC_STATUS_STABLE BIT(31)
+#define ROSC_STATUS_BADWRITE BIT(24)
+#define ROSC_STATUS_DIV_RUNNING BIT(16)
+#define ROSC_STATUS_ENABLED BIT(12)
+#define ROSC_STATUS_RUNNING \
+ (ROSC_STATUS_STABLE | ROSC_STATUS_DIV_RUNNING | ROSC_STATUS_ENABLED)
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static uint32_t read_random_bits(QTestState *qts, unsigned count)
+{
+ uint32_t value = 0;
+ unsigned i;
+
+ for (i = 0; i < count; i++) {
+ value |= (qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & BIT(0)) << i;
+ }
+
+ return value;
+}
+
+static void test_rosc_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_CTRL), ==,
+ (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DORMANT), ==,
+ ROSC_WAKE_VALUE);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), ==, 0x00000ab0);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS), ==,
+ ROSC_STATUS_RUNNING);
+
+ qtest_quit(qts);
+}
+
+static void test_rosc_badwrite_and_clear(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, ROSC_BASE + ROSC_FREQA, 0x00007777);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_FREQA), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
+ ROSC_STATUS_BADWRITE, ==, ROSC_STATUS_BADWRITE);
+
+ qtest_writel(qts, ROSC_BASE + ROSC_STATUS, ROSC_STATUS_BADWRITE);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
+ ROSC_STATUS_BADWRITE, ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rosc_count_reaches_zero(void)
+{
+ QTestState *qts = rp2040_start();
+ int i;
+
+ qtest_writel(qts, ROSC_BASE + ROSC_COUNT, 1);
+ for (i = 0; i < 16 && qtest_readl(qts, ROSC_BASE + ROSC_COUNT); i++) {
+ qtest_clock_step(qts, 1000);
+ }
+
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_COUNT), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rosc_misc_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & ~BIT(0),
+ ==, 0);
+ qtest_writel(qts, ROSC_BASE + ROSC_DIV, 0xaa1);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), ==, 0xaa1);
+ qtest_writel(qts, ROSC_BASE + ROSC_PHASE, 0xaa8);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_PHASE), ==, 0xaa8);
+
+ qtest_writel(qts, ROSC_BASE + ROSC_CTRL,
+ (ROSC_ENABLE_DISABLE << 12) | ROSC_FREQ_RANGE_LOW);
+ g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
+ ROSC_STATUS_ENABLED, ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_rosc_seeded_randombit(void)
+{
+ QTestState *qts_a;
+ QTestState *qts_b;
+ uint32_t bits_a;
+ uint32_t bits_b;
+
+ qts_a = qtest_init("-machine raspi-pico,rosc-random-seed=0x1234");
+ bits_a = read_random_bits(qts_a, 32);
+ qtest_quit(qts_a);
+
+ qts_b = qtest_init("-machine raspi-pico,rosc-random-seed=0x1234");
+ bits_b = read_random_bits(qts_b, 32);
+ qtest_quit(qts_b);
+
+ g_assert_cmphex(bits_a, ==, bits_b);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-rosc/reset-values", test_rosc_reset_values);
+ qtest_add_func("/rp2040-rosc/badwrite-and-clear",
+ test_rosc_badwrite_and_clear);
+ qtest_add_func("/rp2040-rosc/count-reaches-zero",
+ test_rosc_count_reaches_zero);
+ qtest_add_func("/rp2040-rosc/misc-values", test_rosc_misc_values);
+ qtest_add_func("/rp2040-rosc/seeded-randombit",
+ test_rosc_seeded_randombit);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 08/30] hw/misc: add RP2040 crystal and ring oscillators
2026-08-29 23:42 ` [PATCH RFC v2 08/30] hw/misc: add RP2040 crystal and ring oscillators Gilles Grimaud
@ 2026-09-02 10:25 ` Alex Bennée
2026-09-04 9:36 ` gilles grimaud
0 siblings, 1 reply; 49+ messages in thread
From: Alex Bennée @ 2026-09-02 10:25 UTC (permalink / raw)
To: Gilles Grimaud
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell,
Philippe Mathieu-Daudé, Fabiano Rosas, Laurent Vivier
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Model the RP2040 XOSC and ROSC register blocks and expose their clock outputs. Use virtual time for the ROSC COUNT register and provide either QEMU guest entropy or a machine-selectable deterministic seed for RANDOMBIT. Add qtests for the ROSC register behavior and deterministic stream.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> hw/arm/Kconfig | 2 +
> hw/arm/raspi_pico.c | 56 +++++
> hw/arm/rp2040.c | 14 +-
> hw/misc/Kconfig | 6 +
> hw/misc/meson.build | 2 +
> hw/misc/rp2040_rosc.c | 430 +++++++++++++++++++++++++++++++++
> hw/misc/rp2040_xosc.c | 237 ++++++++++++++++++
> include/hw/arm/rp2040.h | 4 +
> include/hw/misc/rp2040_rosc.h | 42 ++++
> include/hw/misc/rp2040_xosc.h | 33 +++
> tests/qtest/meson.build | 1 +
> tests/qtest/rp2040-rosc-test.c | 146 +++++++++++
> 12 files changed, 971 insertions(+), 2 deletions(-)
> create mode 100644 hw/misc/rp2040_rosc.c
> create mode 100644 hw/misc/rp2040_xosc.c
> create mode 100644 include/hw/misc/rp2040_rosc.h
> create mode 100644 include/hw/misc/rp2040_xosc.h
> create mode 100644 tests/qtest/rp2040-rosc-test.c
>
> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
> index 89c479ab6d..b55a75fd5a 100644
> --- a/hw/arm/Kconfig
> +++ b/hw/arm/Kconfig
> @@ -379,10 +379,12 @@ config RP2040
> select ARM_V7M
> select PL011
> select RP2040_NYI
> + select RP2040_ROSC
> select RP2040_SYSCFG
> select RP2040_SYSINFO
> select RP2040_TBMAN
> select RP2040_VREG
> + select RP2040_XOSC
> select UNIMP
>
> config STM32F100_SOC
> diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
> index 2bb7d4e511..2a65176dc6 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"
> @@ -28,8 +29,43 @@ struct RaspiPicoMachineState {
>
> RP2040State soc;
> MemoryRegion flash;
> + char *rosc_random_seed;
> + uint64_t rosc_random_seed_value;
> + bool rosc_random_seed_set;
> };
>
> +static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp)
> +{
> + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
> +
> + return g_strdup(s->rosc_random_seed ?: "");
> +}
> +
> +static void raspi_pico_set_rosc_random_seed(Object *obj, const char *value,
> + Error **errp)
> +{
> + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
> + uint64_t seed;
> +
> + if (!value || !*value) {
> + g_free(s->rosc_random_seed);
> + s->rosc_random_seed = NULL;
> + s->rosc_random_seed_value = 0;
> + s->rosc_random_seed_set = false;
> + return;
> + }
> +
> + if (qemu_strtou64(value, NULL, 0, &seed) < 0) {
> + error_setg(errp, "invalid ROSC random seed '%s'", value);
> + return;
> + }
> +
> + g_free(s->rosc_random_seed);
> + s->rosc_random_seed = g_strdup(value);
Why do you need to keep the string if it is simply a representation of a
uint64_t?
> + s->rosc_random_seed_value = seed;
> + s->rosc_random_seed_set = true;
This never gets looked at.
> +}
> +
> static void raspi_pico_init(MachineState *machine)
> {
> RaspiPicoMachineState *s = RASPI_PICO_MACHINE(machine);
> @@ -38,6 +74,10 @@ static void raspi_pico_init(MachineState *machine)
> object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
> qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
> qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
> + qdev_prop_set_uint64(DEVICE(&s->soc.rosc), "random-seed",
> + s->rosc_random_seed_value);
> + qdev_prop_set_bit(DEVICE(&s->soc.rosc), "random-seed-set",
> + s->rosc_random_seed_set);
> if (machine->firmware) {
> qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
> machine->firmware);
> @@ -55,6 +95,13 @@ static void raspi_pico_init(MachineState *machine)
> RP2040_XIP_BASE, PICO_FLASH_SIZE);
> }
>
> +static void raspi_pico_machine_finalize(Object *obj)
> +{
> + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
> +
> + g_free(s->rosc_random_seed);
> +}
> +
> static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
> {
> MachineClass *mc = MACHINE_CLASS(oc);
> @@ -68,6 +115,14 @@ static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
> mc->no_parallel = 1;
> mc->no_floppy = 1;
> mc->no_cdrom = 1;
> +
> + object_class_property_add_str(oc, "rosc-random-seed",
> + raspi_pico_get_rosc_random_seed,
> + raspi_pico_set_rosc_random_seed);
> + object_class_property_set_description(oc, "rosc-random-seed",
> + "Use a deterministic seed for the "
> + "ROSC RANDOMBIT stream; if unset, "
> + "QEMU guest entropy is used");
> }
>
> static const TypeInfo raspi_pico_machine_info = {
> @@ -75,6 +130,7 @@ static const TypeInfo raspi_pico_machine_info = {
> .parent = TYPE_MACHINE,
> .instance_size = sizeof(RaspiPicoMachineState),
> .class_init = raspi_pico_machine_class_init,
> + .instance_finalize = raspi_pico_machine_finalize,
> .interfaces = arm_machine_interfaces,
> };
>
> diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
> index f2a6613aa3..889decded0 100644
> --- a/hw/arm/rp2040.c
> +++ b/hw/arm/rp2040.c
> @@ -57,7 +57,6 @@ static const struct {
> { "rp2040.ioqspi", 0x40018000, 0x4000 },
> { "rp2040.padsbank0", 0x4001c000, 0x4000 },
> { "rp2040.padsqspi", 0x40020000, 0x4000 },
> - { "rp2040.xosc", 0x40024000, 0x4000 },
> { "rp2040.pll_sys", 0x40028000, 0x4000 },
> { "rp2040.pll_usb", 0x4002c000, 0x4000 },
> { "rp2040.busctrl", 0x40030000, 0x4000 },
> @@ -72,7 +71,6 @@ static const struct {
> { "rp2040.timer", 0x40054000, 0x4000 },
> { "rp2040.watchdog", 0x40058000, 0x4000 },
> { "rp2040.rtc", 0x4005c000, 0x4000 },
> - { "rp2040.rosc", 0x40060000, 0x4000 },
> { "rp2040.dma", 0x50000000, 0x1000 },
> { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
> { "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
> @@ -177,8 +175,10 @@ static void rp2040_soc_init(Object *obj)
>
> object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
> object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
> + object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
> object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
> object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
> + object_initialize_child(obj, "xosc", &s->xosc, TYPE_RP2040_XOSC);
>
> s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
> s->sysclk = clock_new(obj, "sysclk");
> @@ -325,6 +325,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
> sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
> rp2040_syscfg_update(s);
>
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->rosc), errp)) {
> + return;
> + }
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->rosc), 0, RP2040_ROSC_BASE);
> +
> if (!sysbus_realize(SYS_BUS_DEVICE(&s->tbman), errp)) {
> return;
> }
> @@ -335,6 +340,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
> }
> sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE);
>
> + if (!sysbus_realize(SYS_BUS_DEVICE(&s->xosc), errp)) {
> + return;
> + }
> + sysbus_mmio_map(SYS_BUS_DEVICE(&s->xosc), 0, RP2040_XOSC_BASE);
> +
> for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
> qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
> if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
> diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
> index 597559186f..8f0860139f 100644
> --- a/hw/misc/Kconfig
> +++ b/hw/misc/Kconfig
> @@ -104,6 +104,9 @@ config FSL_IMX8MP_CCM
> config RP2040_NYI
> bool
>
> +config RP2040_ROSC
> + bool
> +
> config RP2040_SYSCFG
> bool
>
> @@ -116,6 +119,9 @@ config RP2040_TBMAN
> config RP2040_VREG
> bool
>
> +config RP2040_XOSC
> + bool
> +
> config STM32_RCC
> bool
>
> diff --git a/hw/misc/meson.build b/hw/misc/meson.build
> index 9191b16360..7ad307beee 100644
> --- a/hw/misc/meson.build
> +++ b/hw/misc/meson.build
> @@ -99,10 +99,12 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
> 'bcm2835_powermgt.c',
> ))
> system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
> +system_ss.add(when: 'CONFIG_RP2040_ROSC', if_true: files('rp2040_rosc.c'))
> system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
> system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
> system_ss.add(when: 'CONFIG_RP2040_TBMAN', if_true: files('rp2040_tbman.c'))
> system_ss.add(when: 'CONFIG_RP2040_VREG', if_true: files('rp2040_vreg.c'))
> +system_ss.add(when: 'CONFIG_RP2040_XOSC', if_true: files('rp2040_xosc.c'))
> system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
> system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
> system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: files('xlnx-zynq-ddrc.c'))
> diff --git a/hw/misc/rp2040_rosc.c b/hw/misc/rp2040_rosc.c
> new file mode 100644
> index 0000000000..dcb340ec57
> --- /dev/null
> +++ b/hw/misc/rp2040_rosc.c
> @@ -0,0 +1,430 @@
> +/*
> + * RP2040 ring oscillator emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/core/qdev-clock.h"
> +#include "hw/core/qdev-properties.h"
> +#include "hw/misc/rp2040_nyi.h"
> +#include "hw/misc/rp2040_rosc.h"
> +#include "migration/vmstate.h"
> +#include "qemu/guest-random.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +#include "qemu/timer.h"
> +
> +#define ROSC_NOMINAL_HZ 6500000
> +
> +#define ROSC_CTRL 0x00
> +#define ROSC_FREQA 0x04
> +#define ROSC_FREQB 0x08
> +#define ROSC_DORMANT 0x0c
> +#define ROSC_DIV 0x10
> +#define ROSC_PHASE 0x14
> +#define ROSC_STATUS 0x18
> +#define ROSC_RANDOMBIT 0x1c
> +#define ROSC_COUNT 0x20
> +
> +#define ROSC_ENABLE_DISABLE 0xd1e
> +#define ROSC_ENABLE_ENABLE 0xfab
> +#define ROSC_FREQ_RANGE_LOW 0xfa4
> +#define ROSC_FREQ_RANGE_MEDIUM 0xfa5
> +#define ROSC_FREQ_RANGE_HIGH 0xfa7
> +#define ROSC_FREQ_RANGE_TOOHIGH 0xfa6
> +#define ROSC_DORMANT_VALUE 0x636f6d61
> +#define ROSC_WAKE_VALUE 0x77616b65
> +#define ROSC_FREQ_PASSWD 0x9696
> +#define ROSC_DIV_PASS 0xaa0
> +#define ROSC_PHASE_PASSWD 0xaa
> +
> +#define ROSC_STATUS_STABLE BIT(31)
> +#define ROSC_STATUS_BADWRITE BIT(24)
> +#define ROSC_STATUS_DIV_RUNNING BIT(16)
> +#define ROSC_STATUS_ENABLED BIT(12)
> +
> +#define ATOMIC_ALIAS_MASK 0x3000
> +#define ATOMIC_XOR 0x1000
> +#define ATOMIC_SET 0x2000
> +#define ATOMIC_CLR 0x3000
> +
> +static uint32_t rp2040_rosc_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 bool rp2040_rosc_valid_range(uint32_t range)
> +{
> + return range == ROSC_FREQ_RANGE_LOW || range == ROSC_FREQ_RANGE_MEDIUM ||
> + range == ROSC_FREQ_RANGE_HIGH || range == ROSC_FREQ_RANGE_TOOHIGH;
> +}
> +
> +static bool rp2040_rosc_enabled(RP2040RoscState *s)
> +{
> + return extract32(s->ctrl, 12, 12) != ROSC_ENABLE_DISABLE;
> +}
> +
> +static bool rp2040_rosc_running(RP2040RoscState *s)
> +{
> + return rp2040_rosc_enabled(s) && s->dormant == ROSC_WAKE_VALUE;
> +}
> +
> +static uint32_t rp2040_rosc_divisor(RP2040RoscState *s)
> +{
> + uint32_t encoded = s->div & 0xfff;
> + uint32_t div;
> +
> + if (encoded < ROSC_DIV_PASS) {
> + return 31;
> + }
> +
> + div = encoded - ROSC_DIV_PASS;
> + if (div == 0) {
> + return 32;
> + }
> + if (div > 31) {
> + return 31;
> + }
> + return div;
> +}
> +
> +static uint32_t rp2040_rosc_hz(RP2040RoscState *s)
> +{
> + if (!rp2040_rosc_running(s)) {
> + return 0;
> + }
> +
> + return ROSC_NOMINAL_HZ / rp2040_rosc_divisor(s);
> +}
> +
> +static void rp2040_rosc_update_clock(RP2040RoscState *s)
> +{
> + clock_update_hz(s->clk, rp2040_rosc_hz(s));
> +}
> +
> +static uint32_t rp2040_rosc_count_read(RP2040RoscState *s)
> +{
> + uint64_t elapsed_ns;
> + uint64_t elapsed_ticks;
> +
> + if (!s->count || !rp2040_rosc_running(s)) {
> + return s->count;
> + }
> +
> + elapsed_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->count_start_ns;
> + elapsed_ticks = elapsed_ns * ROSC_NOMINAL_HZ / NANOSECONDS_PER_SECOND;
> + if (elapsed_ticks >= s->count) {
> + s->count = 0;
> + } else {
> + s->count -= elapsed_ticks;
> + s->count_start_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> + }
> +
> + return s->count;
> +}
> +
> +static uint32_t rp2040_rosc_status(RP2040RoscState *s)
> +{
> + uint32_t value = 0;
> +
> + if (rp2040_rosc_enabled(s)) {
> + value |= ROSC_STATUS_ENABLED;
> + if (s->dormant == ROSC_WAKE_VALUE) {
> + value |= ROSC_STATUS_STABLE | ROSC_STATUS_DIV_RUNNING;
> + }
> + }
> + if (s->badwrite) {
> + value |= ROSC_STATUS_BADWRITE;
> + }
> +
> + return value;
> +}
> +
> +static uint32_t rp2040_rosc_random_bit(RP2040RoscState *s)
> +{
> + uint64_t x;
> + uint32_t bit;
> +
> + if (!rp2040_rosc_running(s)) {
> + return 0;
> + }
> +
> + if (!s->random_seed_set) {
> + if (!s->random_pool_bits) {
> + qemu_guest_getrandom_nofail(&s->random_pool,
> + sizeof(s->random_pool));
> + s->random_pool_bits = 64;
> + }
> +
> + bit = s->random_pool & 1;
> + s->random_pool >>= 1;
> + s->random_pool_bits--;
> + return bit;
> + }
> +
> + x = s->random_prng_state;
> + x ^= x >> 12;
> + x ^= x << 25;
> + x ^= x >> 27;
> + s->random_prng_state = x;
> + return (x * 0x2545f4914f6cdd1dull) >> 63;
> +}
> +
> +static uint64_t rp2040_rosc_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + RP2040RoscState *s = opaque;
> + hwaddr offset = addr & 0xfff;
> + uint64_t value;
> +
> + switch (offset) {
> + case ROSC_CTRL:
> + value = s->ctrl;
> + break;
> + case ROSC_FREQA:
> + value = s->freqa;
> + break;
> + case ROSC_FREQB:
> + value = s->freqb;
> + break;
> + case ROSC_DORMANT:
> + value = s->dormant;
> + break;
> + case ROSC_DIV:
> + value = s->div;
> + break;
> + case ROSC_PHASE:
> + value = s->phase;
> + break;
> + case ROSC_STATUS:
> + value = rp2040_rosc_status(s);
> + break;
> + case ROSC_RANDOMBIT:
> + value = rp2040_rosc_random_bit(s);
> + break;
> + case ROSC_COUNT:
> + value = rp2040_rosc_count_read(s);
> + break;
> + default:
> + value = 0;
> + rp2040_log_unimplemented_read("rosc", size,
> + RP2040_ROSC_BASE + addr, offset,
> + value);
> + break;
> + }
> +
> + return value;
> +}
> +
> +static void rp2040_rosc_write_ctrl(RP2040RoscState *s, uint32_t value)
> +{
> + uint32_t enable = extract32(value, 12, 12);
> + uint32_t range = extract32(value, 0, 12);
> +
> + if (enable != ROSC_ENABLE_DISABLE && enable != ROSC_ENABLE_ENABLE) {
> + s->badwrite = true;
> + value = deposit32(value, 12, 12, ROSC_ENABLE_ENABLE);
> + }
> + if (!rp2040_rosc_valid_range(range)) {
> + s->badwrite = true;
> + value = deposit32(value, 0, 12, ROSC_FREQ_RANGE_LOW);
> + }
> +
> + s->ctrl = value & 0x00ffffff;
> + rp2040_rosc_update_clock(s);
> +}
> +
> +static void rp2040_rosc_write_freq(RP2040RoscState *s, uint32_t *reg,
> + uint32_t value)
> +{
> + if (extract32(value, 16, 16) != ROSC_FREQ_PASSWD) {
> + s->badwrite = true;
> + *reg = 0;
> + return;
> + }
> +
> + *reg = value & 0x96967777;
> +}
> +
> +static void rp2040_rosc_write_div(RP2040RoscState *s, uint32_t value)
> +{
> + if ((value & 0xfff) < ROSC_DIV_PASS) {
> + s->badwrite = true;
> + value = ROSC_DIV_PASS + 31;
> + }
> +
> + s->div = value & 0xfff;
> + rp2040_rosc_update_clock(s);
> +}
> +
> +static void rp2040_rosc_write_phase(RP2040RoscState *s, uint32_t value)
> +{
> + if (extract32(value, 4, 8) != ROSC_PHASE_PASSWD) {
> + s->badwrite = true;
> + value = 0;
> + }
> +
> + s->phase = value & 0x00000fff;
> +}
> +
> +static void rp2040_rosc_write(void *opaque, hwaddr addr,
> + uint64_t value64, unsigned size)
> +{
> + RP2040RoscState *s = opaque;
> + hwaddr alias = addr & ATOMIC_ALIAS_MASK;
> + hwaddr offset = addr & 0xfff;
> + uint32_t value = value64;
> +
> + switch (offset) {
> + case ROSC_CTRL:
> + rp2040_rosc_write_ctrl(s, rp2040_rosc_apply_alias(s->ctrl, value,
> + alias));
> + break;
> + case ROSC_FREQA:
> + rp2040_rosc_write_freq(s, &s->freqa,
> + rp2040_rosc_apply_alias(s->freqa, value,
> + alias));
> + break;
> + case ROSC_FREQB:
> + rp2040_rosc_write_freq(s, &s->freqb,
> + rp2040_rosc_apply_alias(s->freqb, value,
> + alias));
> + break;
> + case ROSC_DORMANT:
> + s->dormant = value == ROSC_DORMANT_VALUE ? value : ROSC_WAKE_VALUE;
> + if (value != ROSC_DORMANT_VALUE && value != ROSC_WAKE_VALUE) {
> + s->badwrite = true;
> + }
> + rp2040_rosc_update_clock(s);
> + break;
> + case ROSC_DIV:
> + rp2040_rosc_write_div(s, rp2040_rosc_apply_alias(s->div, value,
> + alias));
> + break;
> + case ROSC_PHASE:
> + rp2040_rosc_write_phase(s, rp2040_rosc_apply_alias(s->phase, value,
> + alias));
> + break;
> + case ROSC_STATUS:
> + if (value & ROSC_STATUS_BADWRITE) {
> + s->badwrite = false;
> + }
> + break;
> + case ROSC_COUNT:
> + s->count = value & 0xff;
> + s->count_start_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
> + break;
> + default:
> + rp2040_log_unimplemented_write("rosc", size,
> + RP2040_ROSC_BASE + addr, offset,
> + value64);
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps rp2040_rosc_ops = {
> + .read = rp2040_rosc_read,
> + .write = rp2040_rosc_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void rp2040_rosc_reset(DeviceState *dev)
> +{
> + RP2040RoscState *s = RP2040_ROSC(dev);
> +
> + s->ctrl = (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW;
> + s->freqa = 0;
> + s->freqb = 0;
> + s->dormant = ROSC_WAKE_VALUE;
> + s->div = ROSC_DIV_PASS + 16;
> + s->phase = 0x00000aa8;
> + s->count = 0;
> + s->count_start_ns = 0;
> + s->random_pool = 0;
> + s->random_pool_bits = 0;
> + s->random_prng_state = s->random_seed;
> + if (!s->random_prng_state) {
> + s->random_prng_state = 0x9e3779b97f4a7c15ull;
> + }
> + s->badwrite = false;
> +
> + rp2040_rosc_update_clock(s);
> +}
> +
> +static void rp2040_rosc_init(Object *obj)
> +{
> + RP2040RoscState *s = RP2040_ROSC(obj);
> + DeviceState *dev = DEVICE(obj);
> +
> + s->clk = qdev_init_clock_out(dev, "clk");
> + memory_region_init_io(&s->iomem, obj, &rp2040_rosc_ops, s,
> + "rp2040.rosc", RP2040_ROSC_SIZE);
> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> +}
> +
> +static const VMStateDescription rp2040_rosc_vmstate = {
> + .name = TYPE_RP2040_ROSC,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32(ctrl, RP2040RoscState),
> + VMSTATE_UINT32(freqa, RP2040RoscState),
> + VMSTATE_UINT32(freqb, RP2040RoscState),
> + VMSTATE_UINT32(dormant, RP2040RoscState),
> + VMSTATE_UINT32(div, RP2040RoscState),
> + VMSTATE_UINT32(phase, RP2040RoscState),
> + VMSTATE_UINT32(count, RP2040RoscState),
> + VMSTATE_INT64(count_start_ns, RP2040RoscState),
> + VMSTATE_UINT64(random_pool, RP2040RoscState),
> + VMSTATE_UINT64(random_prng_state, RP2040RoscState),
> + VMSTATE_UINT64(random_seed, RP2040RoscState),
> + VMSTATE_UINT8(random_pool_bits, RP2040RoscState),
> + VMSTATE_BOOL(random_seed_set, RP2040RoscState),
> + VMSTATE_BOOL(badwrite, RP2040RoscState),
> + VMSTATE_CLOCK(clk, RP2040RoscState),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const Property rp2040_rosc_properties[] = {
> + DEFINE_PROP_UINT64("random-seed", RP2040RoscState, random_seed, 0),
> + DEFINE_PROP_BOOL("random-seed-set", RP2040RoscState, random_seed_set,
> + false),
> +};
> +
> +static void rp2040_rosc_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + device_class_set_legacy_reset(dc, rp2040_rosc_reset);
> + dc->vmsd = &rp2040_rosc_vmstate;
> + device_class_set_props(dc, rp2040_rosc_properties);
> +}
> +
> +static const TypeInfo rp2040_rosc_info = {
> + .name = TYPE_RP2040_ROSC,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(RP2040RoscState),
> + .instance_init = rp2040_rosc_init,
> + .class_init = rp2040_rosc_class_init,
> +};
> +
> +static void rp2040_rosc_register_types(void)
> +{
> + type_register_static(&rp2040_rosc_info);
> +}
> +type_init(rp2040_rosc_register_types)
> diff --git a/hw/misc/rp2040_xosc.c b/hw/misc/rp2040_xosc.c
> new file mode 100644
> index 0000000000..a8340ca0c9
> --- /dev/null
> +++ b/hw/misc/rp2040_xosc.c
> @@ -0,0 +1,237 @@
> +/*
> + * RP2040 crystal oscillator emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "hw/core/qdev-clock.h"
> +#include "hw/misc/rp2040_nyi.h"
> +#include "hw/misc/rp2040_xosc.h"
> +#include "migration/vmstate.h"
> +#include "qemu/log.h"
> +#include "qemu/module.h"
> +
> +#define XOSC_HZ 12000000
> +
> +#define XOSC_CTRL 0x00
> +#define XOSC_STATUS 0x04
> +#define XOSC_DORMANT 0x08
> +#define XOSC_STARTUP 0x0c
> +#define XOSC_COUNT 0x1c
> +
> +#define XOSC_ENABLE_DISABLE 0xd1e
> +#define XOSC_ENABLE_ENABLE 0xfab
> +#define XOSC_FREQ_1_15MHZ 0xaa0
> +#define XOSC_DORMANT_VALUE 0x636f6d61
> +#define XOSC_WAKE_VALUE 0x77616b65
> +
> +#define XOSC_STATUS_STABLE BIT(31)
> +#define XOSC_STATUS_BADWRITE BIT(24)
> +#define XOSC_STATUS_ENABLED BIT(12)
> +
> +#define ATOMIC_ALIAS_MASK 0x3000
> +#define ATOMIC_XOR 0x1000
> +#define ATOMIC_SET 0x2000
> +#define ATOMIC_CLR 0x3000
> +
> +static bool rp2040_xosc_enabled(RP2040XoscState *s)
> +{
> + return extract32(s->ctrl, 12, 12) != XOSC_ENABLE_DISABLE;
> +}
> +
> +static void rp2040_xosc_update_clock(RP2040XoscState *s)
> +{
> + clock_update_hz(s->clk, rp2040_xosc_enabled(s) ? XOSC_HZ : 0);
> +}
> +
> +static uint32_t rp2040_xosc_status(RP2040XoscState *s)
> +{
> + uint32_t value = 0;
> +
> + if (rp2040_xosc_enabled(s)) {
> + value |= XOSC_STATUS_ENABLED;
> + if (s->dormant == XOSC_WAKE_VALUE) {
> + value |= XOSC_STATUS_STABLE;
> + }
> + }
> + if (s->badwrite) {
> + value |= XOSC_STATUS_BADWRITE;
> + }
> +
> + return value;
> +}
> +
> +static uint64_t rp2040_xosc_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + RP2040XoscState *s = opaque;
> + hwaddr offset = addr & 0xfff;
> + uint64_t value;
> +
> + switch (offset) {
> + case XOSC_CTRL:
> + value = s->ctrl;
> + break;
> + case XOSC_STATUS:
> + value = rp2040_xosc_status(s);
> + break;
> + case XOSC_DORMANT:
> + value = s->dormant;
> + break;
> + case XOSC_STARTUP:
> + value = s->startup;
> + break;
> + case XOSC_COUNT:
> + value = 0;
> + break;
> + default:
> + value = 0;
> + rp2040_log_unimplemented_read("xosc", size,
> + RP2040_XOSC_BASE + addr, offset,
> + value);
> + break;
> + }
> +
> + return value;
> +}
> +
> +static uint32_t rp2040_xosc_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_xosc_write_ctrl(RP2040XoscState *s, uint32_t value)
> +{
> + uint32_t enable = extract32(value, 12, 12);
> + uint32_t freq_range = extract32(value, 0, 12);
> +
> + if (freq_range != XOSC_FREQ_1_15MHZ ||
> + (enable != XOSC_ENABLE_DISABLE && enable != XOSC_ENABLE_ENABLE)) {
> + s->badwrite = true;
> + }
> +
> + if (enable != XOSC_ENABLE_DISABLE && enable != XOSC_ENABLE_ENABLE) {
> + value = deposit32(value, 12, 12, XOSC_ENABLE_ENABLE);
> + }
> + value = deposit32(value, 0, 12, XOSC_FREQ_1_15MHZ);
> +
> + s->ctrl = value;
> + rp2040_xosc_update_clock(s);
> +}
> +
> +static void rp2040_xosc_write(void *opaque, hwaddr addr,
> + uint64_t value64, unsigned size)
> +{
> + RP2040XoscState *s = opaque;
> + hwaddr alias = addr & ATOMIC_ALIAS_MASK;
> + hwaddr offset = addr & 0xfff;
> + uint32_t value = value64;
> +
> + switch (offset) {
> + case XOSC_CTRL:
> + rp2040_xosc_write_ctrl(s, rp2040_xosc_apply_alias(s->ctrl, value,
> + alias));
> + break;
> + case XOSC_STATUS:
> + if (value & XOSC_STATUS_BADWRITE) {
> + s->badwrite = false;
> + }
> + break;
> + case XOSC_DORMANT:
> + s->dormant = value == XOSC_DORMANT_VALUE ? value : XOSC_WAKE_VALUE;
> + rp2040_xosc_update_clock(s);
> + break;
> + case XOSC_STARTUP:
> + s->startup = value & 0x00103fff;
> + break;
> + case XOSC_COUNT:
> + s->count = 0;
> + break;
> + default:
> + rp2040_log_unimplemented_write("xosc", size,
> + RP2040_XOSC_BASE + addr, offset,
> + value64);
> + break;
> + }
> +}
> +
> +static const MemoryRegionOps rp2040_xosc_ops = {
> + .read = rp2040_xosc_read,
> + .write = rp2040_xosc_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void rp2040_xosc_reset(DeviceState *dev)
> +{
> + RP2040XoscState *s = RP2040_XOSC(dev);
> +
> + s->ctrl = (XOSC_ENABLE_DISABLE << 12) | XOSC_FREQ_1_15MHZ;
> + s->dormant = XOSC_WAKE_VALUE;
> + s->startup = 0x000000c4;
> + s->count = 0;
> + s->badwrite = false;
> +
> + rp2040_xosc_update_clock(s);
> +}
> +
> +static void rp2040_xosc_init(Object *obj)
> +{
> + RP2040XoscState *s = RP2040_XOSC(obj);
> + DeviceState *dev = DEVICE(obj);
> +
> + s->clk = qdev_init_clock_out(dev, "clk");
> + memory_region_init_io(&s->iomem, obj, &rp2040_xosc_ops, s,
> + "rp2040.xosc", RP2040_XOSC_SIZE);
> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> +}
> +
> +static const VMStateDescription rp2040_xosc_vmstate = {
> + .name = TYPE_RP2040_XOSC,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32(ctrl, RP2040XoscState),
> + VMSTATE_UINT32(dormant, RP2040XoscState),
> + VMSTATE_UINT32(startup, RP2040XoscState),
> + VMSTATE_UINT32(count, RP2040XoscState),
> + VMSTATE_BOOL(badwrite, RP2040XoscState),
> + VMSTATE_CLOCK(clk, RP2040XoscState),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static void rp2040_xosc_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + device_class_set_legacy_reset(dc, rp2040_xosc_reset);
> + dc->vmsd = &rp2040_xosc_vmstate;
> +}
> +
> +static const TypeInfo rp2040_xosc_info = {
> + .name = TYPE_RP2040_XOSC,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(RP2040XoscState),
> + .instance_init = rp2040_xosc_init,
> + .class_init = rp2040_xosc_class_init,
> +};
> +
> +static void rp2040_xosc_register_types(void)
> +{
> + type_register_static(&rp2040_xosc_info);
> +}
> +type_init(rp2040_xosc_register_types)
> diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
> index 86037dd0d7..47a8cb2236 100644
> --- a/include/hw/arm/rp2040.h
> +++ b/include/hw/arm/rp2040.h
> @@ -13,10 +13,12 @@
> #include "hw/char/pl011.h"
> #include "hw/core/clock.h"
> #include "hw/core/sysbus.h"
> +#include "hw/misc/rp2040_rosc.h"
> #include "hw/misc/rp2040_syscfg.h"
> #include "hw/misc/rp2040_sysinfo.h"
> #include "hw/misc/rp2040_tbman.h"
> #include "hw/misc/rp2040_vreg.h"
> +#include "hw/misc/rp2040_xosc.h"
> #include "qom/object.h"
>
> #define TYPE_RP2040 "rp2040"
> @@ -40,8 +42,10 @@ struct RP2040State {
> PL011State uart[2];
> RP2040SysCfgState syscfg;
> RP2040SysInfoState sysinfo;
> + RP2040RoscState rosc;
> RP2040TbmanState tbman;
> RP2040VregState vreg;
> + RP2040XoscState xosc;
>
> MemoryRegion *board_memory;
> MemoryRegion rom;
> diff --git a/include/hw/misc/rp2040_rosc.h b/include/hw/misc/rp2040_rosc.h
> new file mode 100644
> index 0000000000..6377704e22
> --- /dev/null
> +++ b/include/hw/misc/rp2040_rosc.h
> @@ -0,0 +1,42 @@
> +/*
> + * RP2040 ring oscillator emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_RP2040_ROSC_H
> +#define HW_MISC_RP2040_ROSC_H
> +
> +#include "hw/core/clock.h"
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RP2040_ROSC "rp2040-rosc"
> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040RoscState, RP2040_ROSC)
> +
> +#define RP2040_ROSC_BASE 0x40060000
> +#define RP2040_ROSC_SIZE 0x4000
> +
> +struct RP2040RoscState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> + Clock *clk;
> +
> + uint32_t ctrl;
> + uint32_t freqa;
> + uint32_t freqb;
> + uint32_t dormant;
> + uint32_t div;
> + uint32_t phase;
> + uint32_t count;
> + int64_t count_start_ns;
> + uint64_t random_pool;
> + uint64_t random_prng_state;
> + uint64_t random_seed;
> + uint8_t random_pool_bits;
> + bool random_seed_set;
> + bool badwrite;
> +};
> +
> +#endif
> diff --git a/include/hw/misc/rp2040_xosc.h b/include/hw/misc/rp2040_xosc.h
> new file mode 100644
> index 0000000000..7b9ef62d22
> --- /dev/null
> +++ b/include/hw/misc/rp2040_xosc.h
> @@ -0,0 +1,33 @@
> +/*
> + * RP2040 crystal oscillator emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_RP2040_XOSC_H
> +#define HW_MISC_RP2040_XOSC_H
> +
> +#include "hw/core/clock.h"
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RP2040_XOSC "rp2040-xosc"
> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040XoscState, RP2040_XOSC)
> +
> +#define RP2040_XOSC_BASE 0x40024000
> +#define RP2040_XOSC_SIZE 0x4000
> +
> +struct RP2040XoscState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> + Clock *clk;
> +
> + uint32_t ctrl;
> + uint32_t dormant;
> + uint32_t startup;
> + uint32_t count;
> + bool badwrite;
> +};
> +
> +#endif
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 8916f68bad..441f6f294a 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -256,6 +256,7 @@ qtests_arm = \
> (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
> (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
> (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
> + 'rp2040-rosc-test',
> 'rp2040-tbman-test',
> 'rp2040-vreg-test'] : []) + \
> (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
> diff --git a/tests/qtest/rp2040-rosc-test.c b/tests/qtest/rp2040-rosc-test.c
> new file mode 100644
> index 0000000000..2f1fb96022
> --- /dev/null
> +++ b/tests/qtest/rp2040-rosc-test.c
> @@ -0,0 +1,146 @@
> +/*
> + * QTest testcase for the RP2040 ring oscillator block.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "qemu/bitops.h"
> +
> +#define ROSC_BASE 0x40060000
> +#define ROSC_CTRL 0x00
> +#define ROSC_FREQA 0x04
> +#define ROSC_DORMANT 0x0c
> +#define ROSC_DIV 0x10
> +#define ROSC_PHASE 0x14
> +#define ROSC_STATUS 0x18
> +#define ROSC_RANDOMBIT 0x1c
> +#define ROSC_COUNT 0x20
> +
> +#define ROSC_ENABLE_ENABLE 0xfab
> +#define ROSC_ENABLE_DISABLE 0xd1e
> +#define ROSC_FREQ_RANGE_LOW 0xfa4
> +#define ROSC_WAKE_VALUE 0x77616b65
> +#define ROSC_STATUS_STABLE BIT(31)
> +#define ROSC_STATUS_BADWRITE BIT(24)
> +#define ROSC_STATUS_DIV_RUNNING BIT(16)
> +#define ROSC_STATUS_ENABLED BIT(12)
> +#define ROSC_STATUS_RUNNING \
> + (ROSC_STATUS_STABLE | ROSC_STATUS_DIV_RUNNING | ROSC_STATUS_ENABLED)
> +
> +static QTestState *rp2040_start(void)
> +{
> + return qtest_init("-machine raspi-pico");
> +}
> +
> +static uint32_t read_random_bits(QTestState *qts, unsigned count)
> +{
> + uint32_t value = 0;
> + unsigned i;
> +
> + for (i = 0; i < count; i++) {
> + value |= (qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & BIT(0)) << i;
> + }
> +
> + return value;
> +}
> +
> +static void test_rosc_reset_values(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_CTRL), ==,
> + (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DORMANT), ==,
> + ROSC_WAKE_VALUE);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), ==, 0x00000ab0);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS), ==,
> + ROSC_STATUS_RUNNING);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_rosc_badwrite_and_clear(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + qtest_writel(qts, ROSC_BASE + ROSC_FREQA, 0x00007777);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_FREQA), ==, 0);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
> + ROSC_STATUS_BADWRITE, ==, ROSC_STATUS_BADWRITE);
> +
> + qtest_writel(qts, ROSC_BASE + ROSC_STATUS, ROSC_STATUS_BADWRITE);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
> + ROSC_STATUS_BADWRITE, ==, 0);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_rosc_count_reaches_zero(void)
> +{
> + QTestState *qts = rp2040_start();
> + int i;
> +
> + qtest_writel(qts, ROSC_BASE + ROSC_COUNT, 1);
> + for (i = 0; i < 16 && qtest_readl(qts, ROSC_BASE + ROSC_COUNT); i++) {
> + qtest_clock_step(qts, 1000);
> + }
> +
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_COUNT), ==, 0);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_rosc_misc_values(void)
> +{
> + QTestState *qts = rp2040_start();
> +
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & ~BIT(0),
> + ==, 0);
> + qtest_writel(qts, ROSC_BASE + ROSC_DIV, 0xaa1);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), ==, 0xaa1);
> + qtest_writel(qts, ROSC_BASE + ROSC_PHASE, 0xaa8);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_PHASE), ==, 0xaa8);
> +
> + qtest_writel(qts, ROSC_BASE + ROSC_CTRL,
> + (ROSC_ENABLE_DISABLE << 12) | ROSC_FREQ_RANGE_LOW);
> + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
> + ROSC_STATUS_ENABLED, ==, 0);
> +
> + qtest_quit(qts);
> +}
> +
> +static void test_rosc_seeded_randombit(void)
> +{
> + QTestState *qts_a;
> + QTestState *qts_b;
> + uint32_t bits_a;
> + uint32_t bits_b;
> +
> + qts_a = qtest_init("-machine raspi-pico,rosc-random-seed=0x1234");
> + bits_a = read_random_bits(qts_a, 32);
> + qtest_quit(qts_a);
> +
> + qts_b = qtest_init("-machine raspi-pico,rosc-random-seed=0x1234");
> + bits_b = read_random_bits(qts_b, 32);
> + qtest_quit(qts_b);
> +
> + g_assert_cmphex(bits_a, ==, bits_b);
> +}
> +
> +int main(int argc, char **argv)
> +{
> + g_test_init(&argc, &argv, NULL);
> +
> + qtest_add_func("/rp2040-rosc/reset-values", test_rosc_reset_values);
> + qtest_add_func("/rp2040-rosc/badwrite-and-clear",
> + test_rosc_badwrite_and_clear);
> + qtest_add_func("/rp2040-rosc/count-reaches-zero",
> + test_rosc_count_reaches_zero);
> + qtest_add_func("/rp2040-rosc/misc-values", test_rosc_misc_values);
> + qtest_add_func("/rp2040-rosc/seeded-randombit",
> + test_rosc_seeded_randombit);
> +
> + return g_test_run();
> +}
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 08/30] hw/misc: add RP2040 crystal and ring oscillators
2026-09-02 10:25 ` Alex Bennée
@ 2026-09-04 9:36 ` gilles grimaud
0 siblings, 0 replies; 49+ messages in thread
From: gilles grimaud @ 2026-09-04 9:36 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell,
Philippe Mathieu-Daudé, Fabiano Rosas, Laurent Vivier
[-- Attachment #1: Type: text/plain, Size: 4123 bytes --]
> Le 2 sept. 2026 à 12:25, Alex Bennée <alex.bennee@linaro.org> a écrit :
>
> Gilles Grimaud <gilles.grimaud@univ-lille.fr <mailto:gilles.grimaud@univ-lille.fr>> writes:
>
>> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>>
>> Model the RP2040 XOSC and ROSC register blocks and expose their clock outputs. Use virtual time for the ROSC COUNT register and provide either QEMU guest entropy or a machine-selectable deterministic seed for RANDOMBIT. Add qtests for the ROSC register behavior and deterministic stream.
>>
>> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
>> ---
>> hw/arm/Kconfig | 2 +
>> hw/arm/raspi_pico.c | 56 +++++
>> hw/arm/rp2040.c | 14 +-
>> hw/misc/Kconfig | 6 +
>> hw/misc/meson.build | 2 +
>> hw/misc/rp2040_rosc.c | 430 +++++++++++++++++++++++++++++++++
>> hw/misc/rp2040_xosc.c | 237 ++++++++++++++++++
>> include/hw/arm/rp2040.h | 4 +
>> include/hw/misc/rp2040_rosc.h | 42 ++++
>> include/hw/misc/rp2040_xosc.h | 33 +++
>> tests/qtest/meson.build | 1 +
>> tests/qtest/rp2040-rosc-test.c | 146 +++++++++++
>> 12 files changed, 971 insertions(+), 2 deletions(-)
>> create mode 100644 hw/misc/rp2040_rosc.c
>> create mode 100644 hw/misc/rp2040_xosc.c
>> create mode 100644 include/hw/misc/rp2040_rosc.h
>> create mode 100644 include/hw/misc/rp2040_xosc.h
>> create mode 100644 tests/qtest/rp2040-rosc-test.c
>>
>> diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
>> index 89c479ab6d..b55a75fd5a 100644
>> --- a/hw/arm/Kconfig
>> +++ b/hw/arm/Kconfig
>> @@ -379,10 +379,12 @@ config RP2040
>> select ARM_V7M
>> select PL011
>> select RP2040_NYI
>> + select RP2040_ROSC
>> select RP2040_SYSCFG
>> select RP2040_SYSINFO
>> select RP2040_TBMAN
>> select RP2040_VREG
>> + select RP2040_XOSC
>> select UNIMP
>>
>> config STM32F100_SOC
>> diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
>> index 2bb7d4e511..2a65176dc6 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"
>> @@ -28,8 +29,43 @@ struct RaspiPicoMachineState {
>>
>> RP2040State soc;
>> MemoryRegion flash;
>> + char *rosc_random_seed;
>> + uint64_t rosc_random_seed_value;
>> + bool rosc_random_seed_set;
>> };
>>
>> +static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp)
>> +{
>> + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
>> +
>> + return g_strdup(s->rosc_random_seed ?: "");
>> +}
>> +
>> +static void raspi_pico_set_rosc_random_seed(Object *obj, const char *value,
>> + Error **errp)
>> +{
>> + RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
>> + uint64_t seed;
>> +
>> + if (!value || !*value) {
>> + g_free(s->rosc_random_seed);
>> + s->rosc_random_seed = NULL;
>> + s->rosc_random_seed_value = 0;
>> + s->rosc_random_seed_set = false;
>> + return;
>> + }
>> +
>> + if (qemu_strtou64(value, NULL, 0, &seed) < 0) {
>> + error_setg(errp, "invalid ROSC random seed '%s'", value);
>> + return;
>> + }
>> +
>> + g_free(s->rosc_random_seed);
>> + s->rosc_random_seed = g_strdup(value);
>
> Why do you need to keep the string if it is simply a representation of a
> uint64_t?
Agreed. I will replace it with a typed uint64 property in v3.
>
>> + s->rosc_random_seed_value = seed;
>> + s->rosc_random_seed_set = true;
>
> This never gets looked at.
These are passed to the ROSC from raspi_pico_init(). I will keep the
presence flag only to distinguish an unset property from an explicit
zero seed, and make that path clearer in v3.
--
Gilles Grimaud - 2XS/SISE - CRIStAL/IRCICA - CNRS/Univ. Lille
[-- Attachment #2: Type: text/html, Size: 12069 bytes --]
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 09/30] hw/misc: add RP2040 PLL and clock controller
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (7 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 08/30] hw/misc: add RP2040 crystal and ring oscillators Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-09-02 10:30 ` Alex Bennée
2026-08-29 23:42 ` [PATCH RFC v2 10/30] hw/misc: add RP2040 reset and power state controllers Gilles Grimaud
` (21 subsequent siblings)
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the RP2040 system and USB PLL register blocks and their clock outputs. Add the clock mux, divider, selected-source and frequency-counter registers, then route clk-sys to the Cortex-M0+ and clk-peri to both UARTs. Include focused qtests for PLL configuration, clock switching and frequency measurement.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 2 +
hw/arm/rp2040.c | 45 ++-
hw/misc/Kconfig | 6 +
hw/misc/meson.build | 2 +
hw/misc/rp2040_clocks.c | 480 +++++++++++++++++++++++++++++++
hw/misc/rp2040_pll.c | 234 +++++++++++++++
include/hw/arm/rp2040.h | 5 +
include/hw/misc/rp2040_clocks.h | 36 +++
include/hw/misc/rp2040_pll.h | 37 +++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-clocks-test.c | 107 +++++++
11 files changed, 948 insertions(+), 7 deletions(-)
create mode 100644 hw/misc/rp2040_clocks.c
create mode 100644 hw/misc/rp2040_pll.c
create mode 100644 include/hw/misc/rp2040_clocks.h
create mode 100644 include/hw/misc/rp2040_pll.h
create mode 100644 tests/qtest/rp2040-clocks-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index b55a75fd5a..3d1b424fe5 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -378,7 +378,9 @@ config RP2040
bool
select ARM_V7M
select PL011
+ select RP2040_CLOCKS
select RP2040_NYI
+ select RP2040_PLL
select RP2040_ROSC
select RP2040_SYSCFG
select RP2040_SYSINFO
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 889decded0..b80155ba0b 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -18,8 +18,6 @@
#include "qemu/datadir.h"
#include "target/arm/cpu-qom.h"
-#define RP2040_SYSCLK_FRQ 125000000
-
#define RP2040_UART0_BASE 0x40034000
#define RP2040_UART0_IRQ 20
#define RP2040_UART1_BASE 0x40038000
@@ -50,15 +48,12 @@ static const struct {
hwaddr base;
hwaddr size;
} rp2040_unimplemented[] = {
- { "rp2040.clocks", 0x40008000, 0x4000 },
{ "rp2040.resets", 0x4000c000, 0x4000 },
{ "rp2040.psm", 0x40010000, 0x4000 },
{ "rp2040.iobank0", 0x40014000, 0x4000 },
{ "rp2040.ioqspi", 0x40018000, 0x4000 },
{ "rp2040.padsbank0", 0x4001c000, 0x4000 },
{ "rp2040.padsqspi", 0x40020000, 0x4000 },
- { "rp2040.pll_sys", 0x40028000, 0x4000 },
- { "rp2040.pll_usb", 0x4002c000, 0x4000 },
{ "rp2040.busctrl", 0x40030000, 0x4000 },
{ "rp2040.uart0_aliases", 0x40035000, 0x3000 },
{ "rp2040.uart1_aliases", 0x40039000, 0x3000 },
@@ -173,6 +168,20 @@ static void rp2040_soc_init(Object *obj)
"chardev");
}
+ object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS);
+
+ object_initialize_child(obj, "pll-sys", &s->pll_sys, TYPE_RP2040_PLL);
+ qdev_prop_set_string(DEVICE(&s->pll_sys), "trace-name",
+ "rp2040.pll_sys");
+ qdev_prop_set_uint32(DEVICE(&s->pll_sys), "base", RP2040_PLL_SYS_BASE);
+ qdev_prop_set_uint32(DEVICE(&s->pll_sys), "fallback-hz", 125000000);
+
+ object_initialize_child(obj, "pll-usb", &s->pll_usb, TYPE_RP2040_PLL);
+ qdev_prop_set_string(DEVICE(&s->pll_usb), "trace-name",
+ "rp2040.pll_usb");
+ qdev_prop_set_uint32(DEVICE(&s->pll_usb), "base", RP2040_PLL_USB_BASE);
+ qdev_prop_set_uint32(DEVICE(&s->pll_usb), "fallback-hz", 48000000);
+
object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
@@ -182,7 +191,6 @@ static void rp2040_soc_init(Object *obj)
s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
s->sysclk = clock_new(obj, "sysclk");
- clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ);
}
static bool rp2040_init_memory(RP2040State *s, Error **errp)
@@ -298,6 +306,27 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
rp2040_unimplemented[i].size);
}
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pll_sys), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->pll_sys), 0, RP2040_PLL_SYS_BASE);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pll_usb), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->pll_usb), 0, RP2040_PLL_USB_BASE);
+
+ qdev_connect_clock_in(DEVICE(&s->clocks), "pll-sys",
+ qdev_get_clock_out(DEVICE(&s->pll_sys), "clk"));
+ qdev_connect_clock_in(DEVICE(&s->clocks), "pll-usb",
+ qdev_get_clock_out(DEVICE(&s->pll_usb), "clk"));
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->clocks), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->clocks), 0, RP2040_CLOCKS_BASE);
+ clock_set_source(s->sysclk, qdev_get_clock_out(DEVICE(&s->clocks),
+ "clk-sys"));
+
qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk);
object_property_set_link(OBJECT(&s->armv7m), "memory",
OBJECT(s->board_memory), &err);
@@ -346,7 +375,9 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->xosc), 0, RP2040_XOSC_BASE);
for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
- qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
+ qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk",
+ qdev_get_clock_out(DEVICE(&s->clocks),
+ "clk-peri"));
if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
return;
}
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 8f0860139f..8ed229cf27 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -101,9 +101,15 @@ config FSL_IMX8MP_ANALOG
config FSL_IMX8MP_CCM
bool
+config RP2040_CLOCKS
+ bool
+
config RP2040_NYI
bool
+config RP2040_PLL
+ bool
+
config RP2040_ROSC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 7ad307beee..af48fa6dba 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -98,7 +98,9 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_cprman.c',
'bcm2835_powermgt.c',
))
+system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c'))
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
+system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c'))
system_ss.add(when: 'CONFIG_RP2040_ROSC', if_true: files('rp2040_rosc.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
diff --git a/hw/misc/rp2040_clocks.c b/hw/misc/rp2040_clocks.c
new file mode 100644
index 0000000000..8a59009c40
--- /dev/null
+++ b/hw/misc/rp2040_clocks.c
@@ -0,0 +1,480 @@
+/*
+ * RP2040 clocks emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/misc/rp2040_clocks.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define ROSC_HZ 6000000
+#define XOSC_HZ 12000000
+
+#define CLK_GPOUT0_CTRL 0x00
+#define CLK_REF_CTRL 0x30
+#define CLK_REF_DIV 0x34
+#define CLK_REF_SELECTED 0x38
+#define CLK_SYS_CTRL 0x3c
+#define CLK_SYS_DIV 0x40
+#define CLK_SYS_SELECTED 0x44
+#define CLK_PERI_CTRL 0x48
+#define CLK_PERI_DIV 0x4c
+#define CLK_PERI_SELECTED 0x50
+#define CLK_USB_CTRL 0x54
+#define CLK_USB_DIV 0x58
+#define CLK_USB_SELECTED 0x5c
+#define CLK_ADC_CTRL 0x60
+#define CLK_ADC_DIV 0x64
+#define CLK_ADC_SELECTED 0x68
+#define CLK_RTC_CTRL 0x6c
+#define CLK_RTC_DIV 0x70
+#define CLK_RTC_SELECTED 0x74
+#define FC0_REF_KHZ 0x80
+#define FC0_MIN_KHZ 0x84
+#define FC0_MAX_KHZ 0x88
+#define FC0_DELAY 0x8c
+#define FC0_INTERVAL 0x90
+#define FC0_SRC 0x94
+#define FC0_STATUS 0x98
+#define FC0_RESULT 0x9c
+#define WAKE_EN0 0xa0
+#define WAKE_EN1 0xa4
+#define SLEEP_EN0 0xa8
+#define SLEEP_EN1 0xac
+#define ENABLED0 0xb0
+#define ENABLED1 0xb4
+#define INTR 0xb8
+
+#define CTRL_ENABLE BIT(11)
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static void rp2040_clocks_update(RP2040ClocksState *s);
+
+static uint32_t rp2040_clocks_div(uint32_t reg)
+{
+ uint32_t div = reg >> 8;
+
+ return div == 0 ? 1u << 16 : div;
+}
+
+static unsigned rp2040_clocks_hz(Clock *clk)
+{
+ return MIN(clock_get_hz(clk), UINT_MAX);
+}
+
+static unsigned rp2040_clocks_sys_aux_freq(RP2040ClocksState *s,
+ uint32_t auxsrc)
+{
+ switch (auxsrc & 0x7) {
+ case 0x0:
+ return rp2040_clocks_hz(s->pll_sys);
+ case 0x1:
+ return rp2040_clocks_hz(s->pll_usb);
+ case 0x2:
+ return ROSC_HZ;
+ case 0x3:
+ return XOSC_HZ;
+ default:
+ return 0;
+ }
+}
+
+static unsigned rp2040_clocks_ref_aux_freq(RP2040ClocksState *s,
+ uint32_t auxsrc)
+{
+ switch (auxsrc & 0x3) {
+ case 0x0:
+ return rp2040_clocks_hz(s->pll_usb);
+ case 0x1:
+ return ROSC_HZ;
+ case 0x2:
+ return XOSC_HZ;
+ default:
+ return 0;
+ }
+}
+
+static unsigned rp2040_clocks_peri_aux_freq(RP2040ClocksState *s,
+ uint32_t auxsrc)
+{
+ switch (auxsrc & 0x7) {
+ case 0x0:
+ return rp2040_clocks_hz(s->clk_sys);
+ case 0x1:
+ return rp2040_clocks_hz(s->pll_sys);
+ case 0x2:
+ return rp2040_clocks_hz(s->pll_usb);
+ case 0x3:
+ return ROSC_HZ;
+ case 0x4:
+ return XOSC_HZ;
+ default:
+ return 0;
+ }
+}
+
+static unsigned rp2040_clocks_usb_adc_rtc_aux_freq(RP2040ClocksState *s,
+ uint32_t auxsrc)
+{
+ switch (auxsrc & 0x7) {
+ case 0x0:
+ return rp2040_clocks_hz(s->pll_usb);
+ case 0x1:
+ return rp2040_clocks_hz(s->pll_sys);
+ case 0x2:
+ return ROSC_HZ;
+ case 0x3:
+ return XOSC_HZ;
+ default:
+ return 0;
+ }
+}
+
+static unsigned rp2040_clocks_ctrl_auxsrc(uint32_t ctrl)
+{
+ return extract32(ctrl, 5, 4);
+}
+
+static unsigned rp2040_clocks_ref_freq(RP2040ClocksState *s)
+{
+ uint32_t ctrl = s->regs[CLK_REF_CTRL / 4];
+ uint32_t src = extract32(ctrl, 0, 2);
+ unsigned freq;
+
+ switch (src) {
+ case 0:
+ freq = ROSC_HZ;
+ break;
+ case 1:
+ freq = rp2040_clocks_ref_aux_freq(s,
+ rp2040_clocks_ctrl_auxsrc(ctrl));
+ break;
+ case 2:
+ freq = XOSC_HZ;
+ break;
+ default:
+ freq = 0;
+ break;
+ }
+
+ return freq / rp2040_clocks_div(s->regs[CLK_REF_DIV / 4]);
+}
+
+static unsigned rp2040_clocks_sys_freq(RP2040ClocksState *s)
+{
+ uint32_t ctrl = s->regs[CLK_SYS_CTRL / 4];
+ uint32_t src = extract32(ctrl, 0, 1);
+ unsigned freq;
+
+ if (src) {
+ freq = rp2040_clocks_sys_aux_freq(s,
+ rp2040_clocks_ctrl_auxsrc(ctrl));
+ } else {
+ freq = rp2040_clocks_ref_freq(s);
+ }
+
+ return freq / rp2040_clocks_div(s->regs[CLK_SYS_DIV / 4]);
+}
+
+static unsigned rp2040_clocks_peri_freq(RP2040ClocksState *s)
+{
+ uint32_t ctrl = s->regs[CLK_PERI_CTRL / 4];
+
+ if (!(ctrl & CTRL_ENABLE)) {
+ return 0;
+ }
+
+ return rp2040_clocks_peri_aux_freq(s, rp2040_clocks_ctrl_auxsrc(ctrl)) /
+ rp2040_clocks_div(s->regs[CLK_PERI_DIV / 4]);
+}
+
+static unsigned rp2040_clocks_usb_adc_rtc_freq(RP2040ClocksState *s,
+ uint32_t ctrl_off,
+ uint32_t div_off)
+{
+ uint32_t ctrl = s->regs[ctrl_off / 4];
+
+ if (!(ctrl & CTRL_ENABLE)) {
+ return 0;
+ }
+
+ return rp2040_clocks_usb_adc_rtc_aux_freq(s,
+ rp2040_clocks_ctrl_auxsrc(ctrl)) /
+ rp2040_clocks_div(s->regs[div_off / 4]);
+}
+
+static void rp2040_clocks_update(RP2040ClocksState *s)
+{
+ unsigned ref_hz = rp2040_clocks_ref_freq(s);
+ unsigned sys_hz = rp2040_clocks_sys_freq(s);
+
+ clock_update_hz(s->clk_ref, ref_hz);
+ clock_update_hz(s->clk_sys, sys_hz);
+ clock_update_hz(s->clk_peri, rp2040_clocks_peri_freq(s));
+ clock_update_hz(s->clk_usb,
+ rp2040_clocks_usb_adc_rtc_freq(s, CLK_USB_CTRL,
+ CLK_USB_DIV));
+ clock_update_hz(s->clk_adc,
+ rp2040_clocks_usb_adc_rtc_freq(s, CLK_ADC_CTRL,
+ CLK_ADC_DIV));
+ clock_update_hz(s->clk_rtc,
+ rp2040_clocks_usb_adc_rtc_freq(s, CLK_RTC_CTRL,
+ CLK_RTC_DIV));
+}
+
+static uint32_t rp2040_clocks_selected(RP2040ClocksState *s, hwaddr offset)
+{
+ switch (offset) {
+ case CLK_REF_SELECTED:
+ return 1u << extract32(s->regs[CLK_REF_CTRL / 4], 0, 2);
+ case CLK_SYS_SELECTED:
+ return 1u << extract32(s->regs[CLK_SYS_CTRL / 4], 0, 1);
+ case CLK_PERI_SELECTED:
+ case CLK_USB_SELECTED:
+ case CLK_ADC_SELECTED:
+ case CLK_RTC_SELECTED:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static uint32_t rp2040_clocks_fc0_result(RP2040ClocksState *s)
+{
+ unsigned khz;
+
+ switch (s->regs[FC0_SRC / 4] & 0xff) {
+ case 0x01:
+ khz = rp2040_clocks_hz(s->pll_sys) / 1000;
+ break;
+ case 0x02:
+ khz = rp2040_clocks_hz(s->pll_usb) / 1000;
+ break;
+ case 0x03:
+ case 0x04:
+ khz = ROSC_HZ / 1000;
+ break;
+ case 0x05:
+ khz = XOSC_HZ / 1000;
+ break;
+ case 0x08:
+ khz = rp2040_clocks_ref_freq(s) / 1000;
+ break;
+ case 0x09:
+ khz = rp2040_clocks_sys_freq(s) / 1000;
+ break;
+ case 0x0a:
+ khz = clock_get_hz(s->clk_peri) / 1000;
+ break;
+ case 0x0b:
+ khz = clock_get_hz(s->clk_usb) / 1000;
+ break;
+ case 0x0c:
+ khz = clock_get_hz(s->clk_adc) / 1000;
+ break;
+ case 0x0d:
+ khz = clock_get_hz(s->clk_rtc) / 1000;
+ break;
+ default:
+ khz = 0;
+ break;
+ }
+
+ return khz << 5;
+}
+
+static bool rp2040_clocks_is_selected(hwaddr offset)
+{
+ return offset == CLK_REF_SELECTED ||
+ offset == CLK_SYS_SELECTED ||
+ offset == CLK_PERI_SELECTED ||
+ offset == CLK_USB_SELECTED ||
+ offset == CLK_ADC_SELECTED ||
+ offset == CLK_RTC_SELECTED;
+}
+
+static uint64_t rp2040_clocks_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040ClocksState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ if (offset >= sizeof(s->regs)) {
+ value = 0;
+ rp2040_log_unimplemented_read("clocks", size,
+ RP2040_CLOCKS_BASE + addr, offset,
+ value);
+ } else if (rp2040_clocks_is_selected(offset)) {
+ value = rp2040_clocks_selected(s, offset);
+ } else {
+ switch (offset) {
+ case FC0_STATUS:
+ value = BIT(4);
+ break;
+ case FC0_RESULT:
+ value = rp2040_clocks_fc0_result(s);
+ break;
+ case ENABLED0:
+ case ENABLED1:
+ value = s->regs[(offset - 0x10) / 4];
+ break;
+ default:
+ value = s->regs[offset / 4];
+ break;
+ }
+ }
+
+ return value;
+}
+
+static void rp2040_clocks_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ RP2040ClocksState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t old;
+ uint32_t newval = value;
+
+ if (offset < sizeof(s->regs) && !rp2040_clocks_is_selected(offset)) {
+ old = s->regs[offset / 4];
+ switch (alias) {
+ case ATOMIC_XOR:
+ newval = old ^ value;
+ break;
+ case ATOMIC_SET:
+ newval = old | value;
+ break;
+ case ATOMIC_CLR:
+ newval = old & ~value;
+ break;
+ default:
+ break;
+ }
+
+ switch (offset) {
+ case ENABLED0:
+ case ENABLED1:
+ case FC0_STATUS:
+ case FC0_RESULT:
+ case INTR:
+ break;
+ default:
+ s->regs[offset / 4] = newval;
+ rp2040_clocks_update(s);
+ break;
+ }
+ } else if (offset >= sizeof(s->regs)) {
+ rp2040_log_unimplemented_write("clocks", size,
+ RP2040_CLOCKS_BASE + addr, offset,
+ value);
+ }
+}
+
+static void rp2040_clocks_input_update(void *opaque, ClockEvent event)
+{
+ rp2040_clocks_update(opaque);
+}
+
+static const MemoryRegionOps rp2040_clocks_ops = {
+ .read = rp2040_clocks_read,
+ .write = rp2040_clocks_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_clocks_reset(DeviceState *dev)
+{
+ RP2040ClocksState *s = RP2040_CLOCKS(dev);
+
+ memset(s->regs, 0, sizeof(s->regs));
+
+ s->regs[CLK_REF_DIV / 4] = 0x00000100;
+ s->regs[CLK_SYS_DIV / 4] = 0x00000100;
+ s->regs[CLK_PERI_CTRL / 4] = CTRL_ENABLE;
+ s->regs[CLK_PERI_DIV / 4] = 0x00000100;
+ s->regs[CLK_USB_CTRL / 4] = CTRL_ENABLE | (0x3 << 5);
+ s->regs[CLK_USB_DIV / 4] = 0x00000100;
+ s->regs[CLK_ADC_CTRL / 4] = CTRL_ENABLE | (0x3 << 5);
+ s->regs[CLK_ADC_DIV / 4] = 0x00000100;
+ s->regs[CLK_RTC_CTRL / 4] = CTRL_ENABLE | (0xa << 5);
+ s->regs[CLK_RTC_DIV / 4] = 0x00000100;
+ s->regs[FC0_REF_KHZ / 4] = XOSC_HZ / 1000;
+ s->regs[WAKE_EN0 / 4] = 0xffffffff;
+ s->regs[WAKE_EN1 / 4] = 0xffffffff;
+ s->regs[SLEEP_EN0 / 4] = 0xffffffff;
+ s->regs[SLEEP_EN1 / 4] = 0xffffffff;
+
+ rp2040_clocks_update(s);
+}
+
+static void rp2040_clocks_init(Object *obj)
+{
+ RP2040ClocksState *s = RP2040_CLOCKS(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ s->clk_ref = qdev_init_clock_out(dev, "clk-ref");
+ s->clk_sys = qdev_init_clock_out(dev, "clk-sys");
+ s->clk_peri = qdev_init_clock_out(dev, "clk-peri");
+ s->clk_usb = qdev_init_clock_out(dev, "clk-usb");
+ s->clk_adc = qdev_init_clock_out(dev, "clk-adc");
+ s->clk_rtc = qdev_init_clock_out(dev, "clk-rtc");
+ s->pll_sys = qdev_init_clock_in(dev, "pll-sys",
+ rp2040_clocks_input_update, s, 0);
+ s->pll_usb = qdev_init_clock_in(dev, "pll-usb",
+ rp2040_clocks_input_update, s, 0);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_clocks_ops, s,
+ "rp2040.clocks", RP2040_CLOCKS_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_clocks_vmstate = {
+ .name = TYPE_RP2040_CLOCKS,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, RP2040ClocksState, 0x100 / 4),
+ VMSTATE_CLOCK(clk_ref, RP2040ClocksState),
+ VMSTATE_CLOCK(clk_sys, RP2040ClocksState),
+ VMSTATE_CLOCK(clk_peri, RP2040ClocksState),
+ VMSTATE_CLOCK(clk_usb, RP2040ClocksState),
+ VMSTATE_CLOCK(clk_adc, RP2040ClocksState),
+ VMSTATE_CLOCK(clk_rtc, RP2040ClocksState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_clocks_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_clocks_reset);
+ dc->vmsd = &rp2040_clocks_vmstate;
+}
+
+static const TypeInfo rp2040_clocks_info = {
+ .name = TYPE_RP2040_CLOCKS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040ClocksState),
+ .instance_init = rp2040_clocks_init,
+ .class_init = rp2040_clocks_class_init,
+};
+
+static void rp2040_clocks_register_types(void)
+{
+ type_register_static(&rp2040_clocks_info);
+}
+type_init(rp2040_clocks_register_types)
diff --git a/hw/misc/rp2040_pll.c b/hw/misc/rp2040_pll.c
new file mode 100644
index 0000000000..6fe2be6608
--- /dev/null
+++ b/hw/misc/rp2040_pll.c
@@ -0,0 +1,234 @@
+/*
+ * RP2040 PLL emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_pll.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define XOSC_HZ 12000000
+
+#define PLL_CS 0x00
+#define PLL_PWR 0x04
+#define PLL_FBDIV_INT 0x08
+#define PLL_PRIM 0x0c
+
+#define PLL_CS_LOCK BIT(31)
+#define PLL_CS_BYPASS BIT(8)
+#define PLL_CS_REFDIV_MASK 0x3f
+
+#define PLL_PWR_VCOPD BIT(5)
+#define PLL_PWR_POSTDIVPD BIT(3)
+#define PLL_PWR_DSMPD BIT(2)
+#define PLL_PWR_PD BIT(0)
+#define PLL_PWR_MASK (PLL_PWR_VCOPD | PLL_PWR_POSTDIVPD | \
+ PLL_PWR_DSMPD | PLL_PWR_PD)
+
+#define PLL_FBDIV_MASK 0x00000fff
+#define PLL_PRIM_MASK 0x00077000
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_pll_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 bool rp2040_pll_locked(RP2040PllState *s)
+{
+ return !(s->pwr & (PLL_PWR_PD | PLL_PWR_VCOPD));
+}
+
+static unsigned rp2040_pll_output_hz(RP2040PllState *s)
+{
+ uint32_t refdiv = s->cs & PLL_CS_REFDIV_MASK;
+ uint32_t fbdiv = s->fbdiv_int & PLL_FBDIV_MASK;
+ uint32_t postdiv1 = extract32(s->prim, 16, 3);
+ uint32_t postdiv2 = extract32(s->prim, 12, 3);
+ uint64_t hz;
+
+ if (!rp2040_pll_locked(s) || (s->pwr & PLL_PWR_POSTDIVPD)) {
+ return 0;
+ }
+
+ refdiv = refdiv ? refdiv : 1;
+ if (s->cs & PLL_CS_BYPASS) {
+ return XOSC_HZ / refdiv;
+ }
+
+ if (!fbdiv || !postdiv1 || !postdiv2) {
+ return s->fallback_hz;
+ }
+
+ hz = XOSC_HZ;
+ hz = hz * fbdiv / refdiv / postdiv1 / postdiv2;
+ return hz;
+}
+
+static void rp2040_pll_update_clock(RP2040PllState *s)
+{
+ clock_update_hz(s->clk, rp2040_pll_output_hz(s));
+}
+
+static uint64_t rp2040_pll_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040PllState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ const char *name = s->trace_name ? s->trace_name : "rp2040.pll";
+ uint64_t value;
+
+ switch (offset) {
+ case PLL_CS:
+ value = s->cs & ~PLL_CS_LOCK;
+ if (rp2040_pll_locked(s)) {
+ value |= PLL_CS_LOCK;
+ }
+ break;
+ case PLL_PWR:
+ value = s->pwr;
+ break;
+ case PLL_FBDIV_INT:
+ value = s->fbdiv_int;
+ break;
+ case PLL_PRIM:
+ value = s->prim;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read(name, size, s->base + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_pll_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040PllState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ const char *name = s->trace_name ? s->trace_name : "rp2040.pll";
+ uint32_t value = value64;
+
+ switch (offset) {
+ case PLL_CS:
+ s->cs = rp2040_pll_apply_alias(s->cs, value, alias) &
+ (PLL_CS_BYPASS | PLL_CS_REFDIV_MASK);
+ break;
+ case PLL_PWR:
+ s->pwr = rp2040_pll_apply_alias(s->pwr, value, alias) & PLL_PWR_MASK;
+ break;
+ case PLL_FBDIV_INT:
+ s->fbdiv_int = rp2040_pll_apply_alias(s->fbdiv_int, value, alias) &
+ PLL_FBDIV_MASK;
+ break;
+ case PLL_PRIM:
+ s->prim = rp2040_pll_apply_alias(s->prim, value, alias) &
+ PLL_PRIM_MASK;
+ break;
+ default:
+ rp2040_log_unimplemented_write(name, size, s->base + addr, offset,
+ value64);
+ break;
+ }
+
+ rp2040_pll_update_clock(s);
+}
+
+static const MemoryRegionOps rp2040_pll_ops = {
+ .read = rp2040_pll_read,
+ .write = rp2040_pll_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_pll_reset(DeviceState *dev)
+{
+ RP2040PllState *s = RP2040_PLL(dev);
+
+ s->cs = 1;
+ s->pwr = PLL_PWR_MASK;
+ s->fbdiv_int = 0;
+ s->prim = (7 << 16) | (7 << 12);
+
+ rp2040_pll_update_clock(s);
+}
+
+static void rp2040_pll_init(Object *obj)
+{
+ RP2040PllState *s = RP2040_PLL(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ s->clk = qdev_init_clock_out(dev, "clk");
+ memory_region_init_io(&s->iomem, obj, &rp2040_pll_ops, s,
+ "rp2040.pll", RP2040_PLL_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_pll_vmstate = {
+ .name = TYPE_RP2040_PLL,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(cs, RP2040PllState),
+ VMSTATE_UINT32(pwr, RP2040PllState),
+ VMSTATE_UINT32(fbdiv_int, RP2040PllState),
+ VMSTATE_UINT32(prim, RP2040PllState),
+ VMSTATE_CLOCK(clk, RP2040PllState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property rp2040_pll_properties[] = {
+ DEFINE_PROP_STRING("trace-name", RP2040PllState, trace_name),
+ DEFINE_PROP_UINT32("base", RP2040PllState, base, 0),
+ DEFINE_PROP_UINT32("fallback-hz", RP2040PllState, fallback_hz, 0),
+};
+
+static void rp2040_pll_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_pll_reset);
+ device_class_set_props(dc, rp2040_pll_properties);
+ dc->vmsd = &rp2040_pll_vmstate;
+}
+
+static const TypeInfo rp2040_pll_info = {
+ .name = TYPE_RP2040_PLL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040PllState),
+ .instance_init = rp2040_pll_init,
+ .class_init = rp2040_pll_class_init,
+};
+
+static void rp2040_pll_register_types(void)
+{
+ type_register_static(&rp2040_pll_info);
+}
+type_init(rp2040_pll_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 47a8cb2236..abdd2f006c 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -13,6 +13,8 @@
#include "hw/char/pl011.h"
#include "hw/core/clock.h"
#include "hw/core/sysbus.h"
+#include "hw/misc/rp2040_clocks.h"
+#include "hw/misc/rp2040_pll.h"
#include "hw/misc/rp2040_rosc.h"
#include "hw/misc/rp2040_syscfg.h"
#include "hw/misc/rp2040_sysinfo.h"
@@ -40,6 +42,9 @@ struct RP2040State {
ARMv7MState armv7m;
PL011State uart[2];
+ RP2040ClocksState clocks;
+ RP2040PllState pll_sys;
+ RP2040PllState pll_usb;
RP2040SysCfgState syscfg;
RP2040SysInfoState sysinfo;
RP2040RoscState rosc;
diff --git a/include/hw/misc/rp2040_clocks.h b/include/hw/misc/rp2040_clocks.h
new file mode 100644
index 0000000000..ebbb14e3fa
--- /dev/null
+++ b/include/hw/misc/rp2040_clocks.h
@@ -0,0 +1,36 @@
+/*
+ * RP2040 clocks emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_CLOCKS_H
+#define HW_MISC_RP2040_CLOCKS_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_CLOCKS "rp2040-clocks"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040ClocksState, RP2040_CLOCKS)
+
+#define RP2040_CLOCKS_BASE 0x40008000
+#define RP2040_CLOCKS_SIZE 0x4000
+
+struct RP2040ClocksState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ Clock *clk_ref;
+ Clock *clk_sys;
+ Clock *clk_peri;
+ Clock *clk_usb;
+ Clock *clk_adc;
+ Clock *clk_rtc;
+ Clock *pll_sys;
+ Clock *pll_usb;
+
+ uint32_t regs[0x100 / 4];
+};
+
+#endif
diff --git a/include/hw/misc/rp2040_pll.h b/include/hw/misc/rp2040_pll.h
new file mode 100644
index 0000000000..ad188aa46e
--- /dev/null
+++ b/include/hw/misc/rp2040_pll.h
@@ -0,0 +1,37 @@
+/*
+ * RP2040 PLL emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_PLL_H
+#define HW_MISC_RP2040_PLL_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_PLL "rp2040-pll"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040PllState, RP2040_PLL)
+
+#define RP2040_PLL_SYS_BASE 0x40028000
+#define RP2040_PLL_USB_BASE 0x4002c000
+#define RP2040_PLL_SIZE 0x4000
+
+struct RP2040PllState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ Clock *clk;
+
+ char *trace_name;
+ uint32_t base;
+ uint32_t fallback_hz;
+
+ uint32_t cs;
+ uint32_t pwr;
+ uint32_t fbdiv_int;
+ uint32_t prim;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 441f6f294a..416d90ae94 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -256,6 +256,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
+ 'rp2040-clocks-test',
'rp2040-rosc-test',
'rp2040-tbman-test',
'rp2040-vreg-test'] : []) + \
diff --git a/tests/qtest/rp2040-clocks-test.c b/tests/qtest/rp2040-clocks-test.c
new file mode 100644
index 0000000000..39e49eb57f
--- /dev/null
+++ b/tests/qtest/rp2040-clocks-test.c
@@ -0,0 +1,107 @@
+/*
+ * QTest testcase for the RP2040 clocks block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define CLOCKS_BASE 0x40008000
+#define CLK_SYS_CTRL 0x3c
+#define CLK_SYS_DIV 0x40
+#define CLK_PERI_CTRL 0x48
+#define CLK_PERI_DIV 0x4c
+#define CLK_USB_CTRL 0x54
+#define CLK_USB_DIV 0x58
+#define CLK_ADC_CTRL 0x60
+#define CLK_ADC_DIV 0x64
+#define CLK_RTC_CTRL 0x6c
+#define CLK_RTC_DIV 0x70
+#define FC0_SRC 0x94
+#define FC0_STATUS 0x98
+#define FC0_RESULT 0x9c
+
+#define PLL_SYS_BASE 0x40028000
+#define PLL_USB_BASE 0x4002c000
+#define PLL_CS 0x00
+#define PLL_PWR 0x04
+#define PLL_FBDIV_INT 0x08
+#define PLL_PRIM 0x0c
+
+#define PLL_PWR_BITS (BIT(5) | BIT(3) | BIT(2) | BIT(0))
+#define CLK_CTRL_ENABLE BIT(11)
+#define FC0_STATUS_DONE BIT(4)
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void pll_configure(QTestState *qts, uint32_t base,
+ uint32_t fbdiv, uint32_t postdiv1,
+ uint32_t postdiv2)
+{
+ qtest_writel(qts, base + PLL_CS, 1);
+ qtest_writel(qts, base + PLL_FBDIV_INT, fbdiv);
+ qtest_writel(qts, base + PLL_PWR, 0);
+ qtest_writel(qts, base + PLL_PRIM, (postdiv1 << 16) | (postdiv2 << 12));
+}
+
+static uint32_t frequency_count_khz(QTestState *qts, uint32_t src)
+{
+ qtest_writel(qts, CLOCKS_BASE + FC0_SRC, src);
+ g_assert_cmphex(qtest_readl(qts, CLOCKS_BASE + FC0_STATUS) &
+ FC0_STATUS_DONE, ==, FC0_STATUS_DONE);
+ return qtest_readl(qts, CLOCKS_BASE + FC0_RESULT) >> 5;
+}
+
+static void test_clock_mux_and_frequency_counter(void)
+{
+ QTestState *qts = rp2040_start();
+
+ pll_configure(qts, PLL_SYS_BASE, 125, 6, 2);
+ pll_configure(qts, PLL_USB_BASE, 100, 5, 5);
+
+ qtest_writel(qts, CLOCKS_BASE + CLK_SYS_DIV, 0x100);
+ qtest_writel(qts, CLOCKS_BASE + CLK_PERI_DIV, 0x100);
+ qtest_writel(qts, CLOCKS_BASE + CLK_USB_DIV, 0x100);
+ qtest_writel(qts, CLOCKS_BASE + CLK_ADC_DIV, 0x100);
+ qtest_writel(qts, CLOCKS_BASE + CLK_RTC_DIV, 0x400);
+
+ qtest_writel(qts, CLOCKS_BASE + CLK_SYS_CTRL, BIT(0));
+ qtest_writel(qts, CLOCKS_BASE + CLK_PERI_CTRL, CLK_CTRL_ENABLE);
+ qtest_writel(qts, CLOCKS_BASE + CLK_USB_CTRL, CLK_CTRL_ENABLE);
+ qtest_writel(qts, CLOCKS_BASE + CLK_ADC_CTRL, CLK_CTRL_ENABLE);
+ qtest_writel(qts, CLOCKS_BASE + CLK_RTC_CTRL,
+ CLK_CTRL_ENABLE | (0x3 << 5));
+
+ g_assert_cmpuint(frequency_count_khz(qts, 0x01), ==, 125000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x02), ==, 48000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x03), ==, 6000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x09), ==, 125000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x0a), ==, 125000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x0b), ==, 48000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x0c), ==, 48000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x0d), ==, 3000);
+
+ qtest_writel(qts, CLOCKS_BASE + CLK_SYS_CTRL, BIT(0) | (0x1 << 5));
+ qtest_writel(qts, PLL_SYS_BASE + PLL_PWR, PLL_PWR_BITS);
+
+ g_assert_cmpuint(frequency_count_khz(qts, 0x01), ==, 0);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x09), ==, 48000);
+ g_assert_cmpuint(frequency_count_khz(qts, 0x0a), ==, 48000);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-clocks/mux-and-frequency-counter",
+ test_clock_mux_and_frequency_counter);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 09/30] hw/misc: add RP2040 PLL and clock controller
2026-08-29 23:42 ` [PATCH RFC v2 09/30] hw/misc: add RP2040 PLL and clock controller Gilles Grimaud
@ 2026-09-02 10:30 ` Alex Bennée
2026-09-04 14:46 ` gilles grimaud
0 siblings, 1 reply; 49+ messages in thread
From: Alex Bennée @ 2026-09-02 10:30 UTC (permalink / raw)
To: Gilles Grimaud
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell, Fabiano Rosas,
Laurent Vivier
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Model the RP2040 system and USB PLL register blocks and their clock outputs. Add the clock mux, divider, selected-source and frequency-counter registers, then route clk-sys to the Cortex-M0+ and clk-peri to both UARTs. Include focused qtests for PLL configuration, clock switching and frequency measurement.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> hw/arm/Kconfig | 2 +
<snip>
> +
> +#define ATOMIC_ALIAS_MASK 0x3000
> +#define ATOMIC_XOR 0x1000
> +#define ATOMIC_SET 0x2000
> +#define ATOMIC_CLR 0x3000
These seem to be common defines - why not have a common 2040 header that
they all use.
<snip>
> diff --git a/hw/misc/rp2040_pll.c b/hw/misc/rp2040_pll.c
> new file mode 100644
> index 0000000000..6fe2be6608
> --- /dev/null
> +++ b/hw/misc/rp2040_pll.c
> @@ -0,0 +1,234 @@
<snip>
> +
> +#define ATOMIC_ALIAS_MASK 0x3000
> +#define ATOMIC_XOR 0x1000
> +#define ATOMIC_SET 0x2000
> +#define ATOMIC_CLR 0x3000
> +
> +static uint32_t rp2040_pll_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;
> + }
This is repeated throughout the code, sometimes inline sometimes not.
This screams for a common inline helper in the headers.
> +}
> +
> +static bool rp2040_pll_locked(RP2040PllState *s)
> +{
> + return !(s->pwr & (PLL_PWR_PD | PLL_PWR_VCOPD));
> +}
> +
> +static unsigned rp2040_pll_output_hz(RP2040PllState *s)
> +{
> + uint32_t refdiv = s->cs & PLL_CS_REFDIV_MASK;
> + uint32_t fbdiv = s->fbdiv_int & PLL_FBDIV_MASK;
> + uint32_t postdiv1 = extract32(s->prim, 16, 3);
> + uint32_t postdiv2 = extract32(s->prim, 12, 3);
> + uint64_t hz;
> +
> + if (!rp2040_pll_locked(s) || (s->pwr & PLL_PWR_POSTDIVPD)) {
> + return 0;
> + }
> +
> + refdiv = refdiv ? refdiv : 1;
> + if (s->cs & PLL_CS_BYPASS) {
> + return XOSC_HZ / refdiv;
> + }
> +
> + if (!fbdiv || !postdiv1 || !postdiv2) {
> + return s->fallback_hz;
> + }
> +
> + hz = XOSC_HZ;
> + hz = hz * fbdiv / refdiv / postdiv1 / postdiv2;
> + return hz;
> +}
> +
> +static void rp2040_pll_update_clock(RP2040PllState *s)
> +{
> + clock_update_hz(s->clk, rp2040_pll_output_hz(s));
> +}
> +
> +static uint64_t rp2040_pll_read(void *opaque, hwaddr addr, unsigned size)
> +{
> + RP2040PllState *s = opaque;
> + hwaddr offset = addr & 0xfff;
> + const char *name = s->trace_name ? s->trace_name : "rp2040.pll";
> + uint64_t value;
> +
> + switch (offset) {
> + case PLL_CS:
> + value = s->cs & ~PLL_CS_LOCK;
> + if (rp2040_pll_locked(s)) {
> + value |= PLL_CS_LOCK;
> + }
> + break;
> + case PLL_PWR:
> + value = s->pwr;
> + break;
> + case PLL_FBDIV_INT:
> + value = s->fbdiv_int;
> + break;
> + case PLL_PRIM:
> + value = s->prim;
> + break;
> + default:
> + value = 0;
> + rp2040_log_unimplemented_read(name, size, s->base + addr, offset,
> + value);
> + break;
> + }
> +
> + return value;
> +}
> +
> +static void rp2040_pll_write(void *opaque, hwaddr addr,
> + uint64_t value64, unsigned size)
> +{
> + RP2040PllState *s = opaque;
> + hwaddr alias = addr & ATOMIC_ALIAS_MASK;
> + hwaddr offset = addr & 0xfff;
> + const char *name = s->trace_name ? s->trace_name : "rp2040.pll";
> + uint32_t value = value64;
> +
> + switch (offset) {
> + case PLL_CS:
> + s->cs = rp2040_pll_apply_alias(s->cs, value, alias) &
> + (PLL_CS_BYPASS | PLL_CS_REFDIV_MASK);
> + break;
> + case PLL_PWR:
> + s->pwr = rp2040_pll_apply_alias(s->pwr, value, alias) & PLL_PWR_MASK;
> + break;
> + case PLL_FBDIV_INT:
> + s->fbdiv_int = rp2040_pll_apply_alias(s->fbdiv_int, value, alias) &
> + PLL_FBDIV_MASK;
> + break;
> + case PLL_PRIM:
> + s->prim = rp2040_pll_apply_alias(s->prim, value, alias) &
> + PLL_PRIM_MASK;
> + break;
> + default:
> + rp2040_log_unimplemented_write(name, size, s->base + addr, offset,
> + value64);
> + break;
> + }
> +
> + rp2040_pll_update_clock(s);
> +}
> +
> +static const MemoryRegionOps rp2040_pll_ops = {
> + .read = rp2040_pll_read,
> + .write = rp2040_pll_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 4,
> + .max_access_size = 4,
> + },
> +};
> +
> +static void rp2040_pll_reset(DeviceState *dev)
> +{
> + RP2040PllState *s = RP2040_PLL(dev);
> +
> + s->cs = 1;
> + s->pwr = PLL_PWR_MASK;
> + s->fbdiv_int = 0;
> + s->prim = (7 << 16) | (7 << 12);
#defines for magic numbers please.
> +
> + rp2040_pll_update_clock(s);
> +}
> +
> +static void rp2040_pll_init(Object *obj)
> +{
> + RP2040PllState *s = RP2040_PLL(obj);
> + DeviceState *dev = DEVICE(obj);
> +
> + s->clk = qdev_init_clock_out(dev, "clk");
> + memory_region_init_io(&s->iomem, obj, &rp2040_pll_ops, s,
> + "rp2040.pll", RP2040_PLL_SIZE);
> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> +}
> +
> +static const VMStateDescription rp2040_pll_vmstate = {
> + .name = TYPE_RP2040_PLL,
> + .version_id = 1,
> + .minimum_version_id = 1,
> + .fields = (const VMStateField[]) {
> + VMSTATE_UINT32(cs, RP2040PllState),
> + VMSTATE_UINT32(pwr, RP2040PllState),
> + VMSTATE_UINT32(fbdiv_int, RP2040PllState),
> + VMSTATE_UINT32(prim, RP2040PllState),
> + VMSTATE_CLOCK(clk, RP2040PllState),
> + VMSTATE_END_OF_LIST()
> + }
> +};
> +
> +static const Property rp2040_pll_properties[] = {
> + DEFINE_PROP_STRING("trace-name", RP2040PllState, trace_name),
> + DEFINE_PROP_UINT32("base", RP2040PllState, base, 0),
> + DEFINE_PROP_UINT32("fallback-hz", RP2040PllState, fallback_hz, 0),
> +};
> +
> +static void rp2040_pll_class_init(ObjectClass *klass, const void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> +
> + device_class_set_legacy_reset(dc, rp2040_pll_reset);
> + device_class_set_props(dc, rp2040_pll_properties);
> + dc->vmsd = &rp2040_pll_vmstate;
> +}
> +
> +static const TypeInfo rp2040_pll_info = {
> + .name = TYPE_RP2040_PLL,
> + .parent = TYPE_SYS_BUS_DEVICE,
> + .instance_size = sizeof(RP2040PllState),
> + .instance_init = rp2040_pll_init,
> + .class_init = rp2040_pll_class_init,
> +};
> +
> +static void rp2040_pll_register_types(void)
> +{
> + type_register_static(&rp2040_pll_info);
> +}
> +type_init(rp2040_pll_register_types)
> diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
> index 47a8cb2236..abdd2f006c 100644
> --- a/include/hw/arm/rp2040.h
> +++ b/include/hw/arm/rp2040.h
> @@ -13,6 +13,8 @@
> #include "hw/char/pl011.h"
> #include "hw/core/clock.h"
> #include "hw/core/sysbus.h"
> +#include "hw/misc/rp2040_clocks.h"
> +#include "hw/misc/rp2040_pll.h"
> #include "hw/misc/rp2040_rosc.h"
> #include "hw/misc/rp2040_syscfg.h"
> #include "hw/misc/rp2040_sysinfo.h"
> @@ -40,6 +42,9 @@ struct RP2040State {
>
> ARMv7MState armv7m;
> PL011State uart[2];
> + RP2040ClocksState clocks;
> + RP2040PllState pll_sys;
> + RP2040PllState pll_usb;
> RP2040SysCfgState syscfg;
> RP2040SysInfoState sysinfo;
> RP2040RoscState rosc;
> diff --git a/include/hw/misc/rp2040_clocks.h b/include/hw/misc/rp2040_clocks.h
> new file mode 100644
> index 0000000000..ebbb14e3fa
> --- /dev/null
> +++ b/include/hw/misc/rp2040_clocks.h
> @@ -0,0 +1,36 @@
> +/*
> + * RP2040 clocks emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_RP2040_CLOCKS_H
> +#define HW_MISC_RP2040_CLOCKS_H
> +
> +#include "hw/core/clock.h"
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RP2040_CLOCKS "rp2040-clocks"
> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040ClocksState, RP2040_CLOCKS)
> +
> +#define RP2040_CLOCKS_BASE 0x40008000
> +#define RP2040_CLOCKS_SIZE 0x4000
> +
> +struct RP2040ClocksState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> + Clock *clk_ref;
> + Clock *clk_sys;
> + Clock *clk_peri;
> + Clock *clk_usb;
> + Clock *clk_adc;
> + Clock *clk_rtc;
> + Clock *pll_sys;
> + Clock *pll_usb;
> +
> + uint32_t regs[0x100 / 4];
> +};
> +
> +#endif
> diff --git a/include/hw/misc/rp2040_pll.h b/include/hw/misc/rp2040_pll.h
> new file mode 100644
> index 0000000000..ad188aa46e
> --- /dev/null
> +++ b/include/hw/misc/rp2040_pll.h
> @@ -0,0 +1,37 @@
> +/*
> + * RP2040 PLL emulation
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#ifndef HW_MISC_RP2040_PLL_H
> +#define HW_MISC_RP2040_PLL_H
> +
> +#include "hw/core/clock.h"
> +#include "hw/core/sysbus.h"
> +#include "qom/object.h"
> +
> +#define TYPE_RP2040_PLL "rp2040-pll"
> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040PllState, RP2040_PLL)
> +
> +#define RP2040_PLL_SYS_BASE 0x40028000
> +#define RP2040_PLL_USB_BASE 0x4002c000
> +#define RP2040_PLL_SIZE 0x4000
> +
> +struct RP2040PllState {
> + SysBusDevice parent_obj;
> +
> + MemoryRegion iomem;
> + Clock *clk;
> +
> + char *trace_name;
> + uint32_t base;
> + uint32_t fallback_hz;
> +
> + uint32_t cs;
> + uint32_t pwr;
> + uint32_t fbdiv_int;
> + uint32_t prim;
> +};
> +
> +#endif
> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
> index 441f6f294a..416d90ae94 100644
> --- a/tests/qtest/meson.build
> +++ b/tests/qtest/meson.build
> @@ -256,6 +256,7 @@ qtests_arm = \
> (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
> (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
> (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
> + 'rp2040-clocks-test',
> 'rp2040-rosc-test',
> 'rp2040-tbman-test',
> 'rp2040-vreg-test'] : []) + \
> diff --git a/tests/qtest/rp2040-clocks-test.c b/tests/qtest/rp2040-clocks-test.c
> new file mode 100644
> index 0000000000..39e49eb57f
> --- /dev/null
> +++ b/tests/qtest/rp2040-clocks-test.c
> @@ -0,0 +1,107 @@
> +/*
> + * QTest testcase for the RP2040 clocks block.
> + *
> + * SPDX-License-Identifier: GPL-2.0-or-later
> + */
> +
> +#include "qemu/osdep.h"
> +#include "libqtest.h"
> +#include "qemu/bitops.h"
> +
> +#define CLOCKS_BASE 0x40008000
> +#define CLK_SYS_CTRL 0x3c
> +#define CLK_SYS_DIV 0x40
> +#define CLK_PERI_CTRL 0x48
> +#define CLK_PERI_DIV 0x4c
> +#define CLK_USB_CTRL 0x54
> +#define CLK_USB_DIV 0x58
> +#define CLK_ADC_CTRL 0x60
> +#define CLK_ADC_DIV 0x64
> +#define CLK_RTC_CTRL 0x6c
> +#define CLK_RTC_DIV 0x70
> +#define FC0_SRC 0x94
> +#define FC0_STATUS 0x98
> +#define FC0_RESULT 0x9c
> +
> +#define PLL_SYS_BASE 0x40028000
> +#define PLL_USB_BASE 0x4002c000
> +#define PLL_CS 0x00
> +#define PLL_PWR 0x04
> +#define PLL_FBDIV_INT 0x08
> +#define PLL_PRIM 0x0c
> +
> +#define PLL_PWR_BITS (BIT(5) | BIT(3) | BIT(2) | BIT(0))
> +#define CLK_CTRL_ENABLE BIT(11)
> +#define FC0_STATUS_DONE BIT(4)
Again you can re-use include definitions.
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 09/30] hw/misc: add RP2040 PLL and clock controller
2026-09-02 10:30 ` Alex Bennée
@ 2026-09-04 14:46 ` gilles grimaud
0 siblings, 0 replies; 49+ messages in thread
From: gilles grimaud @ 2026-09-04 14:46 UTC (permalink / raw)
To: Alex Bennée
Cc: qemu-devel, qemu-arm, Paolo Bonzini, Peter Maydell, Fabiano Rosas,
Laurent Vivier
> Le 2 sept. 2026 à 12:30, Alex Bennée <alex.bennee@linaro.org> a écrit :
>
> Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
>
>> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>>
>> Model the RP2040 system and USB PLL register blocks and their clock outputs. Add the clock mux, divider, selected-source and frequency-counter registers, then route clk-sys to the Cortex-M0+ and clk-peri to both UARTs. Include focused qtests for PLL configuration, clock switching and frequency measurement.
>>
>> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
>> ---
>> hw/arm/Kconfig | 2 +
> <snip>
>> +
>> +#define ATOMIC_ALIAS_MASK 0x3000
>> +#define ATOMIC_XOR 0x1000
>> +#define ATOMIC_SET 0x2000
>> +#define ATOMIC_CLR 0x3000
>
> These seem to be common defines - why not have a common 2040 header that
> they all use.
Agreed. I will add a common RP2040 header for the APB atomic aliases and
use its inline helper throughout the peripheral models in v3.
>
> <snip>
>> diff --git a/hw/misc/rp2040_pll.c b/hw/misc/rp2040_pll.c
>> new file mode 100644
>> index 0000000000..6fe2be6608
>> --- /dev/null
>> +++ b/hw/misc/rp2040_pll.c
>> @@ -0,0 +1,234 @@
> <snip>
>> +
>> +#define ATOMIC_ALIAS_MASK 0x3000
>> +#define ATOMIC_XOR 0x1000
>> +#define ATOMIC_SET 0x2000
>> +#define ATOMIC_CLR 0x3000
>> +
>> +static uint32_t rp2040_pll_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;
>> + }
>
> This is repeated throughout the code, sometimes inline sometimes not.
> This screams for a common inline helper in the headers.
Yes, I will use a common inline helper throughout the RP2040 peripheral
models in v3.
>
>> +}
>> +
>> +static bool rp2040_pll_locked(RP2040PllState *s)
>> +{
>> + return !(s->pwr & (PLL_PWR_PD | PLL_PWR_VCOPD));
>> +}
>> +
>> +static unsigned rp2040_pll_output_hz(RP2040PllState *s)
>> +{
>> + uint32_t refdiv = s->cs & PLL_CS_REFDIV_MASK;
>> + uint32_t fbdiv = s->fbdiv_int & PLL_FBDIV_MASK;
>> + uint32_t postdiv1 = extract32(s->prim, 16, 3);
>> + uint32_t postdiv2 = extract32(s->prim, 12, 3);
>> + uint64_t hz;
>> +
>> + if (!rp2040_pll_locked(s) || (s->pwr & PLL_PWR_POSTDIVPD)) {
>> + return 0;
>> + }
>> +
>> + refdiv = refdiv ? refdiv : 1;
>> + if (s->cs & PLL_CS_BYPASS) {
>> + return XOSC_HZ / refdiv;
>> + }
>> +
>> + if (!fbdiv || !postdiv1 || !postdiv2) {
>> + return s->fallback_hz;
>> + }
>> +
>> + hz = XOSC_HZ;
>> + hz = hz * fbdiv / refdiv / postdiv1 / postdiv2;
>> + return hz;
>> +}
>> +
>> +static void rp2040_pll_update_clock(RP2040PllState *s)
>> +{
>> + clock_update_hz(s->clk, rp2040_pll_output_hz(s));
>> +}
>> +
>> +static uint64_t rp2040_pll_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> + RP2040PllState *s = opaque;
>> + hwaddr offset = addr & 0xfff;
>> + const char *name = s->trace_name ? s->trace_name : "rp2040.pll";
>> + uint64_t value;
>> +
>> + switch (offset) {
>> + case PLL_CS:
>> + value = s->cs & ~PLL_CS_LOCK;
>> + if (rp2040_pll_locked(s)) {
>> + value |= PLL_CS_LOCK;
>> + }
>> + break;
>> + case PLL_PWR:
>> + value = s->pwr;
>> + break;
>> + case PLL_FBDIV_INT:
>> + value = s->fbdiv_int;
>> + break;
>> + case PLL_PRIM:
>> + value = s->prim;
>> + break;
>> + default:
>> + value = 0;
>> + rp2040_log_unimplemented_read(name, size, s->base + addr, offset,
>> + value);
>> + break;
>> + }
>> +
>> + return value;
>> +}
>> +
>> +static void rp2040_pll_write(void *opaque, hwaddr addr,
>> + uint64_t value64, unsigned size)
>> +{
>> + RP2040PllState *s = opaque;
>> + hwaddr alias = addr & ATOMIC_ALIAS_MASK;
>> + hwaddr offset = addr & 0xfff;
>> + const char *name = s->trace_name ? s->trace_name : "rp2040.pll";
>> + uint32_t value = value64;
>> +
>> + switch (offset) {
>> + case PLL_CS:
>> + s->cs = rp2040_pll_apply_alias(s->cs, value, alias) &
>> + (PLL_CS_BYPASS | PLL_CS_REFDIV_MASK);
>> + break;
>> + case PLL_PWR:
>> + s->pwr = rp2040_pll_apply_alias(s->pwr, value, alias) & PLL_PWR_MASK;
>> + break;
>> + case PLL_FBDIV_INT:
>> + s->fbdiv_int = rp2040_pll_apply_alias(s->fbdiv_int, value, alias) &
>> + PLL_FBDIV_MASK;
>> + break;
>> + case PLL_PRIM:
>> + s->prim = rp2040_pll_apply_alias(s->prim, value, alias) &
>> + PLL_PRIM_MASK;
>> + break;
>> + default:
>> + rp2040_log_unimplemented_write(name, size, s->base + addr, offset,
>> + value64);
>> + break;
>> + }
>> +
>> + rp2040_pll_update_clock(s);
>> +}
>> +
>> +static const MemoryRegionOps rp2040_pll_ops = {
>> + .read = rp2040_pll_read,
>> + .write = rp2040_pll_write,
>> + .endianness = DEVICE_LITTLE_ENDIAN,
>> + .valid = {
>> + .min_access_size = 4,
>> + .max_access_size = 4,
>> + },
>> +};
>> +
>> +static void rp2040_pll_reset(DeviceState *dev)
>> +{
>> + RP2040PllState *s = RP2040_PLL(dev);
>> +
>> + s->cs = 1;
>> + s->pwr = PLL_PWR_MASK;
>> + s->fbdiv_int = 0;
>> + s->prim = (7 << 16) | (7 << 12);
>
> #defines for magic numbers please.
Agreed. I will replace these with named PLL post-divider fields and a
named reset value.
>
>> +
>> + rp2040_pll_update_clock(s);
>> +}
>> +
>> +static void rp2040_pll_init(Object *obj)
>> +{
>> + RP2040PllState *s = RP2040_PLL(obj);
>> + DeviceState *dev = DEVICE(obj);
>> +
>> + s->clk = qdev_init_clock_out(dev, "clk");
>> + memory_region_init_io(&s->iomem, obj, &rp2040_pll_ops, s,
>> + "rp2040.pll", RP2040_PLL_SIZE);
>> + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
>> +}
>> +
>> +static const VMStateDescription rp2040_pll_vmstate = {
>> + .name = TYPE_RP2040_PLL,
>> + .version_id = 1,
>> + .minimum_version_id = 1,
>> + .fields = (const VMStateField[]) {
>> + VMSTATE_UINT32(cs, RP2040PllState),
>> + VMSTATE_UINT32(pwr, RP2040PllState),
>> + VMSTATE_UINT32(fbdiv_int, RP2040PllState),
>> + VMSTATE_UINT32(prim, RP2040PllState),
>> + VMSTATE_CLOCK(clk, RP2040PllState),
>> + VMSTATE_END_OF_LIST()
>> + }
>> +};
>> +
>> +static const Property rp2040_pll_properties[] = {
>> + DEFINE_PROP_STRING("trace-name", RP2040PllState, trace_name),
>> + DEFINE_PROP_UINT32("base", RP2040PllState, base, 0),
>> + DEFINE_PROP_UINT32("fallback-hz", RP2040PllState, fallback_hz, 0),
>> +};
>> +
>> +static void rp2040_pll_class_init(ObjectClass *klass, const void *data)
>> +{
>> + DeviceClass *dc = DEVICE_CLASS(klass);
>> +
>> + device_class_set_legacy_reset(dc, rp2040_pll_reset);
>> + device_class_set_props(dc, rp2040_pll_properties);
>> + dc->vmsd = &rp2040_pll_vmstate;
>> +}
>> +
>> +static const TypeInfo rp2040_pll_info = {
>> + .name = TYPE_RP2040_PLL,
>> + .parent = TYPE_SYS_BUS_DEVICE,
>> + .instance_size = sizeof(RP2040PllState),
>> + .instance_init = rp2040_pll_init,
>> + .class_init = rp2040_pll_class_init,
>> +};
>> +
>> +static void rp2040_pll_register_types(void)
>> +{
>> + type_register_static(&rp2040_pll_info);
>> +}
>> +type_init(rp2040_pll_register_types)
>> diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
>> index 47a8cb2236..abdd2f006c 100644
>> --- a/include/hw/arm/rp2040.h
>> +++ b/include/hw/arm/rp2040.h
>> @@ -13,6 +13,8 @@
>> #include "hw/char/pl011.h"
>> #include "hw/core/clock.h"
>> #include "hw/core/sysbus.h"
>> +#include "hw/misc/rp2040_clocks.h"
>> +#include "hw/misc/rp2040_pll.h"
>> #include "hw/misc/rp2040_rosc.h"
>> #include "hw/misc/rp2040_syscfg.h"
>> #include "hw/misc/rp2040_sysinfo.h"
>> @@ -40,6 +42,9 @@ struct RP2040State {
>>
>> ARMv7MState armv7m;
>> PL011State uart[2];
>> + RP2040ClocksState clocks;
>> + RP2040PllState pll_sys;
>> + RP2040PllState pll_usb;
>> RP2040SysCfgState syscfg;
>> RP2040SysInfoState sysinfo;
>> RP2040RoscState rosc;
>> diff --git a/include/hw/misc/rp2040_clocks.h b/include/hw/misc/rp2040_clocks.h
>> new file mode 100644
>> index 0000000000..ebbb14e3fa
>> --- /dev/null
>> +++ b/include/hw/misc/rp2040_clocks.h
>> @@ -0,0 +1,36 @@
>> +/*
>> + * RP2040 clocks emulation
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_MISC_RP2040_CLOCKS_H
>> +#define HW_MISC_RP2040_CLOCKS_H
>> +
>> +#include "hw/core/clock.h"
>> +#include "hw/core/sysbus.h"
>> +#include "qom/object.h"
>> +
>> +#define TYPE_RP2040_CLOCKS "rp2040-clocks"
>> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040ClocksState, RP2040_CLOCKS)
>> +
>> +#define RP2040_CLOCKS_BASE 0x40008000
>> +#define RP2040_CLOCKS_SIZE 0x4000
>> +
>> +struct RP2040ClocksState {
>> + SysBusDevice parent_obj;
>> +
>> + MemoryRegion iomem;
>> + Clock *clk_ref;
>> + Clock *clk_sys;
>> + Clock *clk_peri;
>> + Clock *clk_usb;
>> + Clock *clk_adc;
>> + Clock *clk_rtc;
>> + Clock *pll_sys;
>> + Clock *pll_usb;
>> +
>> + uint32_t regs[0x100 / 4];
>> +};
>> +
>> +#endif
>> diff --git a/include/hw/misc/rp2040_pll.h b/include/hw/misc/rp2040_pll.h
>> new file mode 100644
>> index 0000000000..ad188aa46e
>> --- /dev/null
>> +++ b/include/hw/misc/rp2040_pll.h
>> @@ -0,0 +1,37 @@
>> +/*
>> + * RP2040 PLL emulation
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#ifndef HW_MISC_RP2040_PLL_H
>> +#define HW_MISC_RP2040_PLL_H
>> +
>> +#include "hw/core/clock.h"
>> +#include "hw/core/sysbus.h"
>> +#include "qom/object.h"
>> +
>> +#define TYPE_RP2040_PLL "rp2040-pll"
>> +OBJECT_DECLARE_SIMPLE_TYPE(RP2040PllState, RP2040_PLL)
>> +
>> +#define RP2040_PLL_SYS_BASE 0x40028000
>> +#define RP2040_PLL_USB_BASE 0x4002c000
>> +#define RP2040_PLL_SIZE 0x4000
>> +
>> +struct RP2040PllState {
>> + SysBusDevice parent_obj;
>> +
>> + MemoryRegion iomem;
>> + Clock *clk;
>> +
>> + char *trace_name;
>> + uint32_t base;
>> + uint32_t fallback_hz;
>> +
>> + uint32_t cs;
>> + uint32_t pwr;
>> + uint32_t fbdiv_int;
>> + uint32_t prim;
>> +};
>> +
>> +#endif
>> diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
>> index 441f6f294a..416d90ae94 100644
>> --- a/tests/qtest/meson.build
>> +++ b/tests/qtest/meson.build
>> @@ -256,6 +256,7 @@ qtests_arm = \
>> (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
>> (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
>> (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
>> + 'rp2040-clocks-test',
>> 'rp2040-rosc-test',
>> 'rp2040-tbman-test',
>> 'rp2040-vreg-test'] : []) + \
>> diff --git a/tests/qtest/rp2040-clocks-test.c b/tests/qtest/rp2040-clocks-test.c
>> new file mode 100644
>> index 0000000000..39e49eb57f
>> --- /dev/null
>> +++ b/tests/qtest/rp2040-clocks-test.c
>> @@ -0,0 +1,107 @@
>> +/*
>> + * QTest testcase for the RP2040 clocks block.
>> + *
>> + * SPDX-License-Identifier: GPL-2.0-or-later
>> + */
>> +
>> +#include "qemu/osdep.h"
>> +#include "libqtest.h"
>> +#include "qemu/bitops.h"
>> +
>> +#define CLOCKS_BASE 0x40008000
>> +#define CLK_SYS_CTRL 0x3c
>> +#define CLK_SYS_DIV 0x40
>> +#define CLK_PERI_CTRL 0x48
>> +#define CLK_PERI_DIV 0x4c
>> +#define CLK_USB_CTRL 0x54
>> +#define CLK_USB_DIV 0x58
>> +#define CLK_ADC_CTRL 0x60
>> +#define CLK_ADC_DIV 0x64
>> +#define CLK_RTC_CTRL 0x6c
>> +#define CLK_RTC_DIV 0x70
>> +#define FC0_SRC 0x94
>> +#define FC0_STATUS 0x98
>> +#define FC0_RESULT 0x9c
>> +
>> +#define PLL_SYS_BASE 0x40028000
>> +#define PLL_USB_BASE 0x4002c000
>> +#define PLL_CS 0x00
>> +#define PLL_PWR 0x04
>> +#define PLL_FBDIV_INT 0x08
>> +#define PLL_PRIM 0x0c
>> +
>> +#define PLL_PWR_BITS (BIT(5) | BIT(3) | BIT(2) | BIT(0))
>> +#define CLK_CTRL_ENABLE BIT(11)
>> +#define FC0_STATUS_DONE BIT(4)
>
> Again you can re-use include definitions.
Yes.
>
> --
> Alex Bennée
> Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread
* [PATCH RFC v2 10/30] hw/misc: add RP2040 reset and power state controllers
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (8 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 09/30] hw/misc: add RP2040 PLL and clock controller Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 11/30] hw/misc: add RP2040 watchdog Gilles Grimaud
` (20 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the RP2040 RESETS and PSM register blocks, including atomic aliases, watchdog selection and completion state. Expose the PSM update callback used by the later multicore integration and add focused qtests for reset-controller behavior.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 2 +
hw/arm/rp2040.c | 14 ++-
hw/misc/Kconfig | 6 +
hw/misc/meson.build | 2 +
hw/misc/rp2040_psm.c | 198 +++++++++++++++++++++++++++++++
hw/misc/rp2040_resets.c | 152 ++++++++++++++++++++++++
include/hw/arm/rp2040.h | 4 +
include/hw/misc/rp2040_psm.h | 40 +++++++
include/hw/misc/rp2040_resets.h | 28 +++++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-resets-test.c | 84 +++++++++++++
11 files changed, 529 insertions(+), 2 deletions(-)
create mode 100644 hw/misc/rp2040_psm.c
create mode 100644 hw/misc/rp2040_resets.c
create mode 100644 include/hw/misc/rp2040_psm.h
create mode 100644 include/hw/misc/rp2040_resets.h
create mode 100644 tests/qtest/rp2040-resets-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 3d1b424fe5..2ea9ec31df 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -381,6 +381,8 @@ config RP2040
select RP2040_CLOCKS
select RP2040_NYI
select RP2040_PLL
+ select RP2040_PSM
+ select RP2040_RESETS
select RP2040_ROSC
select RP2040_SYSCFG
select RP2040_SYSINFO
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index b80155ba0b..28b4079ff9 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -48,8 +48,6 @@ static const struct {
hwaddr base;
hwaddr size;
} rp2040_unimplemented[] = {
- { "rp2040.resets", 0x4000c000, 0x4000 },
- { "rp2040.psm", 0x40010000, 0x4000 },
{ "rp2040.iobank0", 0x40014000, 0x4000 },
{ "rp2040.ioqspi", 0x40018000, 0x4000 },
{ "rp2040.padsbank0", 0x4001c000, 0x4000 },
@@ -182,6 +180,8 @@ static void rp2040_soc_init(Object *obj)
qdev_prop_set_uint32(DEVICE(&s->pll_usb), "base", RP2040_PLL_USB_BASE);
qdev_prop_set_uint32(DEVICE(&s->pll_usb), "fallback-hz", 48000000);
+ object_initialize_child(obj, "psm", &s->psm, TYPE_RP2040_PSM);
+ object_initialize_child(obj, "resets", &s->resets, TYPE_RP2040_RESETS);
object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
@@ -327,6 +327,16 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
clock_set_source(s->sysclk, qdev_get_clock_out(DEVICE(&s->clocks),
"clk-sys"));
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->psm), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->psm), 0, RP2040_PSM_BASE);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->resets), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->resets), 0, RP2040_RESETS_BASE);
+
qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk);
object_property_set_link(OBJECT(&s->armv7m), "memory",
OBJECT(s->board_memory), &err);
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 8ed229cf27..9c15b56f74 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -110,6 +110,12 @@ config RP2040_NYI
config RP2040_PLL
bool
+config RP2040_PSM
+ bool
+
+config RP2040_RESETS
+ bool
+
config RP2040_ROSC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index af48fa6dba..2589bec8a6 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -101,6 +101,8 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c'))
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c'))
+system_ss.add(when: 'CONFIG_RP2040_PSM', if_true: files('rp2040_psm.c'))
+system_ss.add(when: 'CONFIG_RP2040_RESETS', if_true: files('rp2040_resets.c'))
system_ss.add(when: 'CONFIG_RP2040_ROSC', if_true: files('rp2040_rosc.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
diff --git a/hw/misc/rp2040_psm.c b/hw/misc/rp2040_psm.c
new file mode 100644
index 0000000000..90b0264d81
--- /dev/null
+++ b/hw/misc/rp2040_psm.c
@@ -0,0 +1,198 @@
+/*
+ * RP2040 power-on state machine emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_psm.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define PSM_FRCE_ON 0x00
+#define PSM_FRCE_OFF 0x04
+#define PSM_WDSEL 0x08
+#define PSM_DONE 0x0c
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_psm_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_psm_update(RP2040PsmState *s)
+{
+ if (s->update) {
+ s->update(s->update_opaque);
+ }
+}
+
+void rp2040_psm_set_update_callback(RP2040PsmState *s,
+ RP2040PsmUpdateFn update,
+ void *opaque)
+{
+ s->update = update;
+ s->update_opaque = opaque;
+}
+
+uint32_t rp2040_psm_get_frce_off(RP2040PsmState *s)
+{
+ return s->frce_off;
+}
+
+static uint32_t rp2040_psm_done(RP2040PsmState *s)
+{
+ return RP2040_PSM_VALID_MASK & ~s->frce_off;
+}
+
+static uint64_t rp2040_psm_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040PsmState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case PSM_FRCE_ON:
+ value = s->frce_on;
+ break;
+ case PSM_FRCE_OFF:
+ value = s->frce_off;
+ break;
+ case PSM_WDSEL:
+ value = s->wdsel;
+ break;
+ case PSM_DONE:
+ value = rp2040_psm_done(s);
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("psm", size,
+ RP2040_PSM_BASE + addr, offset, value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_psm_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040PsmState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+ uint32_t old_frce_off = s->frce_off;
+
+ switch (offset) {
+ case PSM_FRCE_ON:
+ s->frce_on = rp2040_psm_apply_alias(s->frce_on, value, alias) &
+ RP2040_PSM_VALID_MASK;
+ break;
+ case PSM_FRCE_OFF:
+ s->frce_off = rp2040_psm_apply_alias(s->frce_off, value, alias) &
+ RP2040_PSM_VALID_MASK;
+ if (s->frce_off != old_frce_off) {
+ rp2040_psm_update(s);
+ }
+ break;
+ case PSM_WDSEL:
+ s->wdsel = rp2040_psm_apply_alias(s->wdsel, value, alias) &
+ RP2040_PSM_VALID_MASK;
+ break;
+ case PSM_DONE:
+ break;
+ default:
+ rp2040_log_unimplemented_write("psm", size,
+ RP2040_PSM_BASE + addr, offset,
+ value64);
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_psm_ops = {
+ .read = rp2040_psm_read,
+ .write = rp2040_psm_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_psm_reset(DeviceState *dev)
+{
+ RP2040PsmState *s = RP2040_PSM(dev);
+
+ s->frce_on = 0;
+ s->frce_off = 0;
+ s->wdsel = 0;
+ rp2040_psm_update(s);
+}
+
+static int rp2040_psm_post_load(void *opaque, int version_id)
+{
+ RP2040PsmState *s = opaque;
+
+ rp2040_psm_update(s);
+ return 0;
+}
+
+static void rp2040_psm_init(Object *obj)
+{
+ RP2040PsmState *s = RP2040_PSM(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_psm_ops, s,
+ "rp2040.psm", RP2040_PSM_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_psm_vmstate = {
+ .name = TYPE_RP2040_PSM,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = rp2040_psm_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(frce_on, RP2040PsmState),
+ VMSTATE_UINT32(frce_off, RP2040PsmState),
+ VMSTATE_UINT32(wdsel, RP2040PsmState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_psm_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_psm_reset);
+ dc->vmsd = &rp2040_psm_vmstate;
+}
+
+static const TypeInfo rp2040_psm_info = {
+ .name = TYPE_RP2040_PSM,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040PsmState),
+ .instance_init = rp2040_psm_init,
+ .class_init = rp2040_psm_class_init,
+};
+
+static void rp2040_psm_register_types(void)
+{
+ type_register_static(&rp2040_psm_info);
+}
+type_init(rp2040_psm_register_types)
diff --git a/hw/misc/rp2040_resets.c b/hw/misc/rp2040_resets.c
new file mode 100644
index 0000000000..227ecf64fd
--- /dev/null
+++ b/hw/misc/rp2040_resets.c
@@ -0,0 +1,152 @@
+/*
+ * RP2040 reset controller emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_resets.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define RESETS_RESET 0x00
+#define RESETS_WDSEL 0x04
+#define RESETS_RESET_DONE 0x08
+
+#define RESETS_VALID_MASK 0x01ffffff
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_resets_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 uint64_t rp2040_resets_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040ResetsState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case RESETS_RESET:
+ value = s->reset;
+ break;
+ case RESETS_WDSEL:
+ value = s->wdsel;
+ break;
+ case RESETS_RESET_DONE:
+ value = (~s->reset) & RESETS_VALID_MASK;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("resets", size,
+ RP2040_RESETS_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_resets_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040ResetsState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+
+ switch (offset) {
+ case RESETS_RESET:
+ s->reset = rp2040_resets_apply_alias(s->reset, value, alias) &
+ RESETS_VALID_MASK;
+ break;
+ case RESETS_WDSEL:
+ s->wdsel = rp2040_resets_apply_alias(s->wdsel, value, alias) &
+ RESETS_VALID_MASK;
+ break;
+ case RESETS_RESET_DONE:
+ break;
+ default:
+ rp2040_log_unimplemented_write("resets", size,
+ RP2040_RESETS_BASE + addr, offset,
+ value64);
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_resets_ops = {
+ .read = rp2040_resets_read,
+ .write = rp2040_resets_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_resets_reset(DeviceState *dev)
+{
+ RP2040ResetsState *s = RP2040_RESETS(dev);
+
+ s->reset = RESETS_VALID_MASK;
+ s->wdsel = 0;
+}
+
+static void rp2040_resets_init(Object *obj)
+{
+ RP2040ResetsState *s = RP2040_RESETS(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_resets_ops, s,
+ "rp2040.resets", RP2040_RESETS_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_resets_vmstate = {
+ .name = TYPE_RP2040_RESETS,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(reset, RP2040ResetsState),
+ VMSTATE_UINT32(wdsel, RP2040ResetsState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_resets_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_resets_reset);
+ dc->vmsd = &rp2040_resets_vmstate;
+}
+
+static const TypeInfo rp2040_resets_info = {
+ .name = TYPE_RP2040_RESETS,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040ResetsState),
+ .instance_init = rp2040_resets_init,
+ .class_init = rp2040_resets_class_init,
+};
+
+static void rp2040_resets_register_types(void)
+{
+ type_register_static(&rp2040_resets_info);
+}
+type_init(rp2040_resets_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index abdd2f006c..431648af63 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -15,6 +15,8 @@
#include "hw/core/sysbus.h"
#include "hw/misc/rp2040_clocks.h"
#include "hw/misc/rp2040_pll.h"
+#include "hw/misc/rp2040_psm.h"
+#include "hw/misc/rp2040_resets.h"
#include "hw/misc/rp2040_rosc.h"
#include "hw/misc/rp2040_syscfg.h"
#include "hw/misc/rp2040_sysinfo.h"
@@ -45,6 +47,8 @@ struct RP2040State {
RP2040ClocksState clocks;
RP2040PllState pll_sys;
RP2040PllState pll_usb;
+ RP2040PsmState psm;
+ RP2040ResetsState resets;
RP2040SysCfgState syscfg;
RP2040SysInfoState sysinfo;
RP2040RoscState rosc;
diff --git a/include/hw/misc/rp2040_psm.h b/include/hw/misc/rp2040_psm.h
new file mode 100644
index 0000000000..8b68fb2ed8
--- /dev/null
+++ b/include/hw/misc/rp2040_psm.h
@@ -0,0 +1,40 @@
+/*
+ * RP2040 power-on state machine emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_PSM_H
+#define HW_MISC_RP2040_PSM_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_PSM "rp2040-psm"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040PsmState, RP2040_PSM)
+
+#define RP2040_PSM_BASE 0x40010000
+#define RP2040_PSM_SIZE 0x4000
+#define RP2040_PSM_VALID_MASK 0x0001ffff
+#define RP2040_PSM_PROC1 (1u << 16)
+
+typedef void (*RP2040PsmUpdateFn)(void *opaque);
+
+struct RP2040PsmState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t frce_on;
+ uint32_t frce_off;
+ uint32_t wdsel;
+
+ RP2040PsmUpdateFn update;
+ void *update_opaque;
+};
+
+void rp2040_psm_set_update_callback(RP2040PsmState *s,
+ RP2040PsmUpdateFn update,
+ void *opaque);
+uint32_t rp2040_psm_get_frce_off(RP2040PsmState *s);
+
+#endif
diff --git a/include/hw/misc/rp2040_resets.h b/include/hw/misc/rp2040_resets.h
new file mode 100644
index 0000000000..ba87d8d7c1
--- /dev/null
+++ b/include/hw/misc/rp2040_resets.h
@@ -0,0 +1,28 @@
+/*
+ * RP2040 reset controller emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_RESETS_H
+#define HW_MISC_RP2040_RESETS_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_RESETS "rp2040-resets"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040ResetsState, RP2040_RESETS)
+
+#define RP2040_RESETS_BASE 0x4000c000
+#define RP2040_RESETS_SIZE 0x4000
+
+struct RP2040ResetsState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t reset;
+ uint32_t wdsel;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 416d90ae94..03bd78b551 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -257,6 +257,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
'rp2040-clocks-test',
+ 'rp2040-resets-test',
'rp2040-rosc-test',
'rp2040-tbman-test',
'rp2040-vreg-test'] : []) + \
diff --git a/tests/qtest/rp2040-resets-test.c b/tests/qtest/rp2040-resets-test.c
new file mode 100644
index 0000000000..f874a25571
--- /dev/null
+++ b/tests/qtest/rp2040-resets-test.c
@@ -0,0 +1,84 @@
+/*
+ * QTest testcase for the RP2040 reset controller block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define RESETS_BASE 0x4000c000
+#define RESETS_RESET 0x00
+#define RESETS_WDSEL 0x04
+#define RESETS_RESET_DONE 0x08
+
+#define RESETS_VALID_MASK 0x01ffffff
+#define RESET_ADC (1u << 0)
+#define RESET_PWM (1u << 14)
+
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_resets_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET), ==,
+ RESETS_VALID_MASK);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_WDSEL), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_resets_set_clear_done(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t mask = RESET_PWM | RESET_ADC;
+
+ qtest_writel(qts, RESETS_BASE + RESETS_RESET + ATOMIC_CLR, mask);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET) & mask,
+ ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE) & mask,
+ ==, mask);
+
+ qtest_writel(qts, RESETS_BASE + RESETS_RESET + ATOMIC_SET, RESET_PWM);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET) & RESET_PWM,
+ ==, RESET_PWM);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE) &
+ RESET_PWM, ==, 0);
+
+ qtest_writel(qts, RESETS_BASE + RESETS_RESET + ATOMIC_CLR, RESET_PWM);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE) & mask,
+ ==, mask);
+
+ qtest_quit(qts);
+}
+
+static void test_resets_wdsel(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, RESETS_BASE + RESETS_WDSEL, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_WDSEL), ==,
+ RESETS_VALID_MASK);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-resets/reset-values", test_resets_reset_values);
+ qtest_add_func("/rp2040-resets/set-clear-done",
+ test_resets_set_clear_done);
+ qtest_add_func("/rp2040-resets/wdsel", test_resets_wdsel);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 11/30] hw/misc: add RP2040 watchdog
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (9 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 10/30] hw/misc: add RP2040 reset and power state controllers Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 12/30] hw/misc: add RP2040 pad controls Gilles Grimaud
` (19 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the RP2040 watchdog control, load, reason, scratch and tick registers. Drive the counter from clk_ref with the RP2040-E1 twice-per-tick behavior and use QEMU's standard watchdog action for timer expiry and forced resets. Add qtests for reset state, sub-word scratch accesses, timer expiry and forced triggering.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/rp2040.c | 11 +-
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/rp2040_watchdog.c | 361 +++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 2 +
include/hw/misc/rp2040_watchdog.h | 35 +++
tests/qtest/meson.build | 3 +-
tests/qtest/rp2040-watchdog-test.c | 148 ++++++++++++
9 files changed, 563 insertions(+), 2 deletions(-)
create mode 100644 hw/misc/rp2040_watchdog.c
create mode 100644 include/hw/misc/rp2040_watchdog.h
create mode 100644 tests/qtest/rp2040-watchdog-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 2ea9ec31df..384ea0520f 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -388,6 +388,7 @@ config RP2040
select RP2040_SYSINFO
select RP2040_TBMAN
select RP2040_VREG
+ select RP2040_WATCHDOG
select RP2040_XOSC
select UNIMP
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 28b4079ff9..7b3bb0e7b1 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -62,7 +62,6 @@ static const struct {
{ "rp2040.adc", 0x4004c000, 0x4000 },
{ "rp2040.pwm", 0x40050000, 0x4000 },
{ "rp2040.timer", 0x40054000, 0x4000 },
- { "rp2040.watchdog", 0x40058000, 0x4000 },
{ "rp2040.rtc", 0x4005c000, 0x4000 },
{ "rp2040.dma", 0x50000000, 0x1000 },
{ "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
@@ -187,6 +186,8 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
+ object_initialize_child(obj, "watchdog", &s->watchdog,
+ TYPE_RP2040_WATCHDOG);
object_initialize_child(obj, "xosc", &s->xosc, TYPE_RP2040_XOSC);
s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
@@ -379,6 +380,14 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE);
+ qdev_connect_clock_in(DEVICE(&s->watchdog), "clk-ref",
+ qdev_get_clock_out(DEVICE(&s->clocks), "clk-ref"));
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->watchdog), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->watchdog), 0,
+ RP2040_WATCHDOG_BASE);
+
if (!sysbus_realize(SYS_BUS_DEVICE(&s->xosc), errp)) {
return;
}
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 9c15b56f74..c6a815f9bc 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -131,6 +131,9 @@ config RP2040_TBMAN
config RP2040_VREG
bool
+config RP2040_WATCHDOG
+ bool
+
config RP2040_XOSC
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 2589bec8a6..cdbfa7de31 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -108,6 +108,7 @@ system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
system_ss.add(when: 'CONFIG_RP2040_TBMAN', if_true: files('rp2040_tbman.c'))
system_ss.add(when: 'CONFIG_RP2040_VREG', if_true: files('rp2040_vreg.c'))
+system_ss.add(when: 'CONFIG_RP2040_WATCHDOG', if_true: files('rp2040_watchdog.c'))
system_ss.add(when: 'CONFIG_RP2040_XOSC', if_true: files('rp2040_xosc.c'))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
diff --git a/hw/misc/rp2040_watchdog.c b/hw/misc/rp2040_watchdog.c
new file mode 100644
index 0000000000..eb767304b2
--- /dev/null
+++ b/hw/misc/rp2040_watchdog.c
@@ -0,0 +1,361 @@
+/*
+ * RP2040 watchdog emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_watchdog.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "system/watchdog.h"
+
+#define WATCHDOG_CTRL 0x00
+#define WATCHDOG_LOAD 0x04
+#define WATCHDOG_REASON 0x08
+#define WATCHDOG_SCRATCH0 0x0c
+#define WATCHDOG_SCRATCH7 0x28
+#define WATCHDOG_TICK 0x2c
+
+#define WATCHDOG_CTRL_TRIGGER BIT(31)
+#define WATCHDOG_CTRL_ENABLE BIT(30)
+#define WATCHDOG_CTRL_PAUSE_DBG1 BIT(26)
+#define WATCHDOG_CTRL_PAUSE_DBG0 BIT(25)
+#define WATCHDOG_CTRL_PAUSE_JTAG BIT(24)
+#define WATCHDOG_CTRL_PAUSE_MASK \
+ (WATCHDOG_CTRL_PAUSE_DBG1 | WATCHDOG_CTRL_PAUSE_DBG0 | \
+ WATCHDOG_CTRL_PAUSE_JTAG)
+#define WATCHDOG_CTRL_TIME_MASK 0x00ffffff
+#define WATCHDOG_CTRL_RW_MASK \
+ (WATCHDOG_CTRL_ENABLE | WATCHDOG_CTRL_PAUSE_MASK)
+
+#define WATCHDOG_LOAD_MASK 0x00ffffff
+#define WATCHDOG_REASON_FORCE BIT(1)
+#define WATCHDOG_REASON_TIMER BIT(0)
+
+#define WATCHDOG_TICK_ENABLE BIT(9)
+#define WATCHDOG_TICK_RUNNING BIT(10)
+#define WATCHDOG_TICK_CYCLES_MASK 0x000001ff
+#define WATCHDOG_TICK_RW_MASK \
+ (WATCHDOG_TICK_ENABLE | WATCHDOG_TICK_CYCLES_MASK)
+#define WATCHDOG_TICK_COUNT_SHIFT 11
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_watchdog_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 uint32_t rp2040_watchdog_expand_write(uint32_t value, unsigned size)
+{
+ switch (size) {
+ case 1:
+ return value * 0x01010101u;
+ case 2:
+ return (value & 0xffffu) * 0x00010001u;
+ default:
+ return value;
+ }
+}
+
+static bool rp2040_watchdog_scratch_offset(hwaddr offset, unsigned *index)
+{
+ if (offset < WATCHDOG_SCRATCH0 || offset > WATCHDOG_SCRATCH7) {
+ return false;
+ }
+
+ *index = (offset - WATCHDOG_SCRATCH0) / sizeof(uint32_t);
+ return true;
+}
+
+static bool rp2040_watchdog_tick_running(RP2040WatchdogState *s)
+{
+ return (s->tick & WATCHDOG_TICK_ENABLE) &&
+ (s->tick & WATCHDOG_TICK_CYCLES_MASK) &&
+ clock_get_hz(s->clk_ref);
+}
+
+static uint32_t rp2040_watchdog_tick_freq_hz(RP2040WatchdogState *s)
+{
+ uint64_t clk_ref_hz = clock_get_hz(s->clk_ref);
+ uint32_t cycles = s->tick & WATCHDOG_TICK_CYCLES_MASK;
+
+ if (!cycles || !clk_ref_hz || !(s->tick & WATCHDOG_TICK_ENABLE)) {
+ return 0;
+ }
+
+ return MIN(UINT32_MAX, (uint32_t)(clk_ref_hz / cycles));
+}
+
+static uint32_t rp2040_watchdog_tick_read(RP2040WatchdogState *s)
+{
+ uint32_t value = s->tick & WATCHDOG_TICK_RW_MASK;
+
+ if (rp2040_watchdog_tick_running(s)) {
+ /*
+ * The precise divider phase is not observable by current firmware
+ * tests. Report a stable running state and a valid non-zero count.
+ */
+ value |= WATCHDOG_TICK_RUNNING;
+ value |= (s->tick & WATCHDOG_TICK_CYCLES_MASK) <<
+ WATCHDOG_TICK_COUNT_SHIFT;
+ }
+
+ return value;
+}
+
+static void rp2040_watchdog_update(RP2040WatchdogState *s)
+{
+ uint32_t tick_hz = rp2040_watchdog_tick_freq_hz(s);
+ uint64_t watchdog_hz = (uint64_t)tick_hz * 2;
+
+ ptimer_transaction_begin(s->timer);
+ if (!tick_hz) {
+ ptimer_stop(s->timer);
+ } else {
+ /*
+ * RP2040-E1: the watchdog counter decrements twice per watchdog
+ * tick, so a LOAD of N expires after N / 2 generated ticks.
+ */
+ ptimer_set_freq(s->timer, MIN(watchdog_hz, (uint64_t)UINT32_MAX));
+ if (s->ctrl & WATCHDOG_CTRL_ENABLE) {
+ ptimer_run(s->timer, 1);
+ } else {
+ ptimer_stop(s->timer);
+ }
+ }
+ ptimer_transaction_commit(s->timer);
+}
+
+static uint32_t rp2040_watchdog_ctrl_read(RP2040WatchdogState *s)
+{
+ uint32_t time = MIN(ptimer_get_count(s->timer), WATCHDOG_CTRL_TIME_MASK);
+
+ return (s->ctrl & WATCHDOG_CTRL_RW_MASK) | time;
+}
+
+static void rp2040_watchdog_reset_request(RP2040WatchdogState *s,
+ uint32_t reason,
+ bool in_ptimer_transaction)
+{
+ s->reason = reason;
+ if (!in_ptimer_transaction) {
+ ptimer_transaction_begin(s->timer);
+ }
+ ptimer_stop(s->timer);
+ if (!in_ptimer_transaction) {
+ ptimer_transaction_commit(s->timer);
+ }
+ watchdog_perform_action();
+}
+
+static uint64_t rp2040_watchdog_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040WatchdogState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ hwaddr word_offset = offset & ~3ULL;
+ unsigned scratch;
+ uint64_t value;
+
+ switch (word_offset) {
+ case WATCHDOG_CTRL:
+ value = rp2040_watchdog_ctrl_read(s);
+ break;
+ case WATCHDOG_LOAD:
+ value = 0;
+ break;
+ case WATCHDOG_REASON:
+ value = s->reason;
+ break;
+ case WATCHDOG_TICK:
+ value = rp2040_watchdog_tick_read(s);
+ break;
+ default:
+ if (rp2040_watchdog_scratch_offset(word_offset, &scratch)) {
+ value = s->scratch[scratch];
+ } else {
+ value = 0;
+ rp2040_log_unimplemented_read("watchdog", size,
+ RP2040_WATCHDOG_BASE + addr,
+ offset, value);
+ }
+ break;
+ }
+
+ return extract64(value, (offset & 3) * 8, size * 8);
+}
+
+static void rp2040_watchdog_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040WatchdogState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ hwaddr word_offset = offset & ~3ULL;
+ unsigned scratch;
+ uint32_t value = rp2040_watchdog_expand_write(value64, size);
+
+ switch (word_offset) {
+ case WATCHDOG_CTRL: {
+ uint32_t old = rp2040_watchdog_ctrl_read(s);
+ uint32_t newval = rp2040_watchdog_apply_alias(old, value, alias);
+
+ s->ctrl = newval & WATCHDOG_CTRL_RW_MASK;
+ rp2040_watchdog_update(s);
+ if (newval & WATCHDOG_CTRL_TRIGGER) {
+ rp2040_watchdog_reset_request(s, WATCHDOG_REASON_FORCE, false);
+ }
+ break;
+ }
+ case WATCHDOG_LOAD:
+ s->load = rp2040_watchdog_apply_alias(s->load, value, alias) &
+ WATCHDOG_LOAD_MASK;
+ ptimer_transaction_begin(s->timer);
+ ptimer_set_limit(s->timer, s->load, 1);
+ ptimer_transaction_commit(s->timer);
+ rp2040_watchdog_update(s);
+ break;
+ case WATCHDOG_REASON:
+ break;
+ case WATCHDOG_TICK:
+ s->tick = rp2040_watchdog_apply_alias(s->tick, value, alias) &
+ WATCHDOG_TICK_RW_MASK;
+ rp2040_watchdog_update(s);
+ break;
+ default:
+ if (rp2040_watchdog_scratch_offset(word_offset, &scratch)) {
+ s->scratch[scratch] =
+ rp2040_watchdog_apply_alias(s->scratch[scratch], value,
+ alias);
+ } else {
+ rp2040_log_unimplemented_write("watchdog", size,
+ RP2040_WATCHDOG_BASE + addr,
+ offset, value64);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_watchdog_ops = {
+ .read = rp2040_watchdog_read,
+ .write = rp2040_watchdog_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void rp2040_watchdog_reset(DeviceState *dev)
+{
+ RP2040WatchdogState *s = RP2040_WATCHDOG(dev);
+
+ s->ctrl = WATCHDOG_CTRL_PAUSE_MASK;
+ s->load = 0;
+ s->tick = WATCHDOG_TICK_ENABLE;
+
+ ptimer_transaction_begin(s->timer);
+ ptimer_stop(s->timer);
+ ptimer_set_limit(s->timer, 0, 1);
+ ptimer_transaction_commit(s->timer);
+}
+
+static void rp2040_watchdog_clk_ref_update(void *opaque, ClockEvent event)
+{
+ rp2040_watchdog_update(opaque);
+}
+
+static void rp2040_watchdog_tick(void *opaque)
+{
+ RP2040WatchdogState *s = RP2040_WATCHDOG(opaque);
+
+ rp2040_watchdog_reset_request(s, WATCHDOG_REASON_TIMER, true);
+}
+
+static void rp2040_watchdog_init(Object *obj)
+{
+ RP2040WatchdogState *s = RP2040_WATCHDOG(obj);
+ DeviceState *dev = DEVICE(obj);
+
+ s->clk_ref = qdev_init_clock_in(dev, "clk-ref",
+ rp2040_watchdog_clk_ref_update, s,
+ ClockUpdate);
+ memory_region_init_io(&s->iomem, obj, &rp2040_watchdog_ops, s,
+ "rp2040.watchdog", RP2040_WATCHDOG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void rp2040_watchdog_realize(DeviceState *dev, Error **errp)
+{
+ RP2040WatchdogState *s = RP2040_WATCHDOG(dev);
+
+ if (!clock_has_source(s->clk_ref)) {
+ error_setg(errp, "RP2040 watchdog: clk-ref clock must be connected");
+ return;
+ }
+
+ s->timer = ptimer_init(rp2040_watchdog_tick, s,
+ PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
+ PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT |
+ PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
+ PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
+}
+
+static const VMStateDescription rp2040_watchdog_vmstate = {
+ .name = TYPE_RP2040_WATCHDOG,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_CLOCK(clk_ref, RP2040WatchdogState),
+ VMSTATE_PTIMER(timer, RP2040WatchdogState),
+ VMSTATE_UINT32(ctrl, RP2040WatchdogState),
+ VMSTATE_UINT32(load, RP2040WatchdogState),
+ VMSTATE_UINT32(reason, RP2040WatchdogState),
+ VMSTATE_UINT32_ARRAY(scratch, RP2040WatchdogState, 8),
+ VMSTATE_UINT32(tick, RP2040WatchdogState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_watchdog_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = rp2040_watchdog_realize;
+ device_class_set_legacy_reset(dc, rp2040_watchdog_reset);
+ dc->vmsd = &rp2040_watchdog_vmstate;
+}
+
+static const TypeInfo rp2040_watchdog_info = {
+ .name = TYPE_RP2040_WATCHDOG,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040WatchdogState),
+ .instance_init = rp2040_watchdog_init,
+ .class_init = rp2040_watchdog_class_init,
+};
+
+static void rp2040_watchdog_register_types(void)
+{
+ type_register_static(&rp2040_watchdog_info);
+}
+type_init(rp2040_watchdog_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 431648af63..6c6eb3d927 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -22,6 +22,7 @@
#include "hw/misc/rp2040_sysinfo.h"
#include "hw/misc/rp2040_tbman.h"
#include "hw/misc/rp2040_vreg.h"
+#include "hw/misc/rp2040_watchdog.h"
#include "hw/misc/rp2040_xosc.h"
#include "qom/object.h"
@@ -54,6 +55,7 @@ struct RP2040State {
RP2040RoscState rosc;
RP2040TbmanState tbman;
RP2040VregState vreg;
+ RP2040WatchdogState watchdog;
RP2040XoscState xosc;
MemoryRegion *board_memory;
diff --git a/include/hw/misc/rp2040_watchdog.h b/include/hw/misc/rp2040_watchdog.h
new file mode 100644
index 0000000000..2da12c9441
--- /dev/null
+++ b/include/hw/misc/rp2040_watchdog.h
@@ -0,0 +1,35 @@
+/*
+ * RP2040 watchdog emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_WATCHDOG_H
+#define HW_MISC_RP2040_WATCHDOG_H
+
+#include "hw/core/clock.h"
+#include "hw/core/ptimer.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_WATCHDOG "rp2040-watchdog"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040WatchdogState, RP2040_WATCHDOG)
+
+#define RP2040_WATCHDOG_BASE 0x40058000
+#define RP2040_WATCHDOG_SIZE 0x4000
+
+struct RP2040WatchdogState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ Clock *clk_ref;
+ ptimer_state *timer;
+
+ uint32_t ctrl;
+ uint32_t load;
+ uint32_t reason;
+ uint32_t scratch[8];
+ uint32_t tick;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 03bd78b551..e5ddd4ce3b 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -260,7 +260,8 @@ qtests_arm = \
'rp2040-resets-test',
'rp2040-rosc-test',
'rp2040-tbman-test',
- 'rp2040-vreg-test'] : []) + \
+ 'rp2040-vreg-test',
+ 'rp2040-watchdog-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-watchdog-test.c b/tests/qtest/rp2040-watchdog-test.c
new file mode 100644
index 0000000000..14b6f12f1a
--- /dev/null
+++ b/tests/qtest/rp2040-watchdog-test.c
@@ -0,0 +1,148 @@
+/*
+ * QTest testcase for the RP2040 watchdog.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+#include "qemu/timer.h"
+#include "qobject/qdict.h"
+
+#define WATCHDOG_BASE 0x40058000
+#define WATCHDOG_CTRL 0x00
+#define WATCHDOG_LOAD 0x04
+#define WATCHDOG_REASON 0x08
+#define WATCHDOG_SCRATCH0 0x0c
+#define WATCHDOG_TICK 0x2c
+
+#define CTRL_TRIGGER BIT(31)
+#define CTRL_ENABLE BIT(30)
+#define CTRL_PAUSE_DBG1 BIT(26)
+#define CTRL_PAUSE_DBG0 BIT(25)
+#define CTRL_PAUSE_JTAG BIT(24)
+#define CTRL_TIME_MASK 0x00ffffff
+
+#define REASON_FORCE BIT(1)
+#define REASON_TIMER BIT(0)
+
+#define TICK_RUNNING BIT(10)
+#define TICK_ENABLE BIT(9)
+
+#define NS_PER_US 1000
+
+static QTestState *rp2040_watchdog_start(void)
+{
+ return qtest_init("-machine raspi-pico -watchdog-action none");
+}
+
+static void check_watchdog_event(QTestState *qts)
+{
+ QDict *event = qtest_qmp_eventwait_ref(qts, "WATCHDOG");
+ QDict *data = qdict_get_qdict(event, "data");
+
+ g_assert_cmpstr(qdict_get_str(data, "action"), ==, "none");
+ qobject_unref(event);
+}
+
+static void test_reset_values(void)
+{
+ QTestState *qts = rp2040_watchdog_start();
+
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_CTRL), ==,
+ CTRL_PAUSE_DBG1 | CTRL_PAUSE_DBG0 | CTRL_PAUSE_JTAG);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_LOAD), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_REASON), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_TICK), ==,
+ TICK_ENABLE);
+
+ qtest_quit(qts);
+}
+
+static void test_scratch_registers(void)
+{
+ QTestState *qts = rp2040_watchdog_start();
+
+ qtest_writel(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0, 0x12345678);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), ==,
+ 0x12345678);
+
+ g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), ==,
+ 0x78);
+ g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 1),
+ ==, 0x56);
+ g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 2),
+ ==, 0x34);
+ g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 3),
+ ==, 0x12);
+
+ qtest_writeb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0, 0xa5);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), ==,
+ 0xa5a5a5a5);
+
+ qtest_writeb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 1, 0x3c);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), ==,
+ 0x3c3c3c3c);
+
+ qtest_writew(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0, 0xf00d);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), ==,
+ 0xf00df00d);
+
+ qtest_quit(qts);
+}
+
+static void test_timer_expiry(void)
+{
+ QTestState *qts = rp2040_watchdog_start();
+ uint32_t ctrl;
+
+ /*
+ * The default clk_ref is 6 MHz. CYCLES=6 generates a 1 MHz watchdog
+ * tick; RP2040-E1 makes the counter decrement twice per tick.
+ */
+ qtest_writel(qts, WATCHDOG_BASE + WATCHDOG_TICK, TICK_ENABLE | 6);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_TICK) &
+ (TICK_RUNNING | TICK_ENABLE), ==,
+ TICK_RUNNING | TICK_ENABLE);
+
+ qtest_writel(qts, WATCHDOG_BASE + WATCHDOG_LOAD, 20);
+ qtest_writel(qts, WATCHDOG_BASE + WATCHDOG_CTRL, CTRL_ENABLE);
+
+ qtest_clock_step(qts, 5 * NS_PER_US);
+ ctrl = qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_CTRL);
+ g_assert_cmpuint(ctrl & CTRL_TIME_MASK, <, 20);
+ g_assert_cmpuint(ctrl & CTRL_TIME_MASK, >, 0);
+
+ qtest_clock_step(qts, 10 * NS_PER_US);
+ check_watchdog_event(qts);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_REASON), ==,
+ REASON_TIMER);
+
+ qtest_quit(qts);
+}
+
+static void test_force_trigger(void)
+{
+ QTestState *qts = rp2040_watchdog_start();
+
+ qtest_writel(qts, WATCHDOG_BASE + WATCHDOG_CTRL, CTRL_TRIGGER);
+ check_watchdog_event(qts);
+ g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_REASON), ==,
+ REASON_FORCE);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-watchdog/reset-values", test_reset_values);
+ qtest_add_func("/rp2040-watchdog/scratch-registers",
+ test_scratch_registers);
+ qtest_add_func("/rp2040-watchdog/timer-expiry", test_timer_expiry);
+ qtest_add_func("/rp2040-watchdog/force-trigger", test_force_trigger);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 12/30] hw/misc: add RP2040 pad controls
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (10 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 11/30] hw/misc: add RP2040 watchdog Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 13/30] hw/misc: add RP2040 IO_BANK0 Gilles Grimaud
` (18 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the RP2040 PADS_BANK0 and PADS_QSPI register banks, including documented reset values, writable masks and atomic aliases. Wire both blocks into the SoC and add focused qtests for their reset and update behavior.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/rp2040.c | 18 +-
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/rp2040_pads.c | 323 +++++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 3 +
include/hw/misc/rp2040_pads.h | 42 +++++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-pads-test.c | 105 +++++++++++
9 files changed, 495 insertions(+), 2 deletions(-)
create mode 100644 hw/misc/rp2040_pads.c
create mode 100644 include/hw/misc/rp2040_pads.h
create mode 100644 tests/qtest/rp2040-pads-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 384ea0520f..0c455fe5ce 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -380,6 +380,7 @@ config RP2040
select PL011
select RP2040_CLOCKS
select RP2040_NYI
+ select RP2040_PADS
select RP2040_PLL
select RP2040_PSM
select RP2040_RESETS
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 7b3bb0e7b1..894f5ec3c2 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -50,8 +50,6 @@ static const struct {
} rp2040_unimplemented[] = {
{ "rp2040.iobank0", 0x40014000, 0x4000 },
{ "rp2040.ioqspi", 0x40018000, 0x4000 },
- { "rp2040.padsbank0", 0x4001c000, 0x4000 },
- { "rp2040.padsqspi", 0x40020000, 0x4000 },
{ "rp2040.busctrl", 0x40030000, 0x4000 },
{ "rp2040.uart0_aliases", 0x40035000, 0x3000 },
{ "rp2040.uart1_aliases", 0x40039000, 0x3000 },
@@ -166,6 +164,10 @@ static void rp2040_soc_init(Object *obj)
}
object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS);
+ object_initialize_child(obj, "pads-bank0", &s->pads_bank0,
+ TYPE_RP2040_PADS_BANK0);
+ object_initialize_child(obj, "pads-qspi", &s->pads_qspi,
+ TYPE_RP2040_PADS_QSPI);
object_initialize_child(obj, "pll-sys", &s->pll_sys, TYPE_RP2040_PLL);
qdev_prop_set_string(DEVICE(&s->pll_sys), "trace-name",
@@ -380,6 +382,18 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pads_bank0), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->pads_bank0), 0,
+ RP2040_PADS_BANK0_BASE);
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->pads_qspi), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->pads_qspi), 0,
+ RP2040_PADS_QSPI_BASE);
+
qdev_connect_clock_in(DEVICE(&s->watchdog), "clk-ref",
qdev_get_clock_out(DEVICE(&s->clocks), "clk-ref"));
if (!sysbus_realize(SYS_BUS_DEVICE(&s->watchdog), errp)) {
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index c6a815f9bc..fc1d5bd58b 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -107,6 +107,9 @@ config RP2040_CLOCKS
config RP2040_NYI
bool
+config RP2040_PADS
+ bool
+
config RP2040_PLL
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index cdbfa7de31..a60226c073 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -100,6 +100,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
))
system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c'))
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
+system_ss.add(when: 'CONFIG_RP2040_PADS', if_true: files('rp2040_pads.c'))
system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c'))
system_ss.add(when: 'CONFIG_RP2040_PSM', if_true: files('rp2040_psm.c'))
system_ss.add(when: 'CONFIG_RP2040_RESETS', if_true: files('rp2040_resets.c'))
diff --git a/hw/misc/rp2040_pads.c b/hw/misc/rp2040_pads.c
new file mode 100644
index 0000000000..e18dc0fa90
--- /dev/null
+++ b/hw/misc/rp2040_pads.c
@@ -0,0 +1,323 @@
+/*
+ * RP2040 pad control emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_pads.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define PADS_BANK0_VOLTAGE_SELECT 0x00
+#define PADS_BANK0_GPIO0 0x04
+#define PADS_BANK0_GPIO29 0x78
+#define PADS_BANK0_SWCLK 0x7c
+#define PADS_BANK0_SWD 0x80
+
+#define PADS_QSPI_VOLTAGE_SELECT 0x00
+#define PADS_QSPI_SCLK 0x04
+#define PADS_QSPI_SS 0x18
+
+#define PADS_VOLTAGE_SELECT_MASK BIT(0)
+#define PADS_PAD_MASK 0xff
+#define PADS_BANK0_GPIO_RESET 0x56
+#define PADS_BANK0_SWD_RESET 0x5a
+#define PADS_QSPI_SCLK_RESET 0x56
+#define PADS_QSPI_SD_RESET 0x52
+#define PADS_QSPI_SS_RESET 0x5a
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_pads_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 bool rp2040_pads_bank0_gpio_offset(hwaddr offset, unsigned *index)
+{
+ if (offset < PADS_BANK0_GPIO0 || offset > PADS_BANK0_GPIO29 ||
+ (offset & 0x3)) {
+ return false;
+ }
+
+ *index = (offset - PADS_BANK0_GPIO0) / sizeof(uint32_t);
+ return *index < ARRAY_SIZE(((RP2040PadsBank0State *)0)->gpio);
+}
+
+static bool rp2040_pads_qspi_pad_offset(hwaddr offset, unsigned *index)
+{
+ if (offset < PADS_QSPI_SCLK || offset > PADS_QSPI_SS ||
+ (offset & 0x3)) {
+ return false;
+ }
+
+ *index = (offset - PADS_QSPI_SCLK) / sizeof(uint32_t);
+ return *index < ARRAY_SIZE(((RP2040PadsQspiState *)0)->pad);
+}
+
+static uint64_t rp2040_pads_bank0_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ RP2040PadsBank0State *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint64_t value;
+
+ switch (offset) {
+ case PADS_BANK0_VOLTAGE_SELECT:
+ value = s->voltage_select;
+ break;
+ case PADS_BANK0_SWCLK:
+ value = s->swclk;
+ break;
+ case PADS_BANK0_SWD:
+ value = s->swd;
+ break;
+ default:
+ if (rp2040_pads_bank0_gpio_offset(offset, &index)) {
+ value = s->gpio[index];
+ } else {
+ value = 0;
+ rp2040_log_unimplemented_read("padsbank0", size,
+ RP2040_PADS_BANK0_BASE + addr,
+ offset, value);
+ }
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_pads_bank0_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040PadsBank0State *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint32_t value = value64;
+
+ switch (offset) {
+ case PADS_BANK0_VOLTAGE_SELECT:
+ s->voltage_select =
+ rp2040_pads_apply_alias(s->voltage_select, value, alias) &
+ PADS_VOLTAGE_SELECT_MASK;
+ break;
+ case PADS_BANK0_SWCLK:
+ s->swclk = rp2040_pads_apply_alias(s->swclk, value, alias) &
+ PADS_PAD_MASK;
+ break;
+ case PADS_BANK0_SWD:
+ s->swd = rp2040_pads_apply_alias(s->swd, value, alias) &
+ PADS_PAD_MASK;
+ break;
+ default:
+ if (rp2040_pads_bank0_gpio_offset(offset, &index)) {
+ s->gpio[index] = rp2040_pads_apply_alias(s->gpio[index], value,
+ alias) & PADS_PAD_MASK;
+ } else {
+ rp2040_log_unimplemented_write("padsbank0", size,
+ RP2040_PADS_BANK0_BASE + addr,
+ offset, value64);
+ }
+ break;
+ }
+}
+
+static uint64_t rp2040_pads_qspi_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ RP2040PadsQspiState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint64_t value;
+
+ switch (offset) {
+ case PADS_QSPI_VOLTAGE_SELECT:
+ value = s->voltage_select;
+ break;
+ default:
+ if (rp2040_pads_qspi_pad_offset(offset, &index)) {
+ value = s->pad[index];
+ } else {
+ value = 0;
+ rp2040_log_unimplemented_read("padsqspi", size,
+ RP2040_PADS_QSPI_BASE + addr,
+ offset, value);
+ }
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_pads_qspi_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040PadsQspiState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint32_t value = value64;
+
+ switch (offset) {
+ case PADS_QSPI_VOLTAGE_SELECT:
+ s->voltage_select =
+ rp2040_pads_apply_alias(s->voltage_select, value, alias) &
+ PADS_VOLTAGE_SELECT_MASK;
+ break;
+ default:
+ if (rp2040_pads_qspi_pad_offset(offset, &index)) {
+ s->pad[index] = rp2040_pads_apply_alias(s->pad[index], value,
+ alias) & PADS_PAD_MASK;
+ } else {
+ rp2040_log_unimplemented_write("padsqspi", size,
+ RP2040_PADS_QSPI_BASE + addr,
+ offset, value64);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_pads_bank0_ops = {
+ .read = rp2040_pads_bank0_read,
+ .write = rp2040_pads_bank0_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const MemoryRegionOps rp2040_pads_qspi_ops = {
+ .read = rp2040_pads_qspi_read,
+ .write = rp2040_pads_qspi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_pads_bank0_reset(DeviceState *dev)
+{
+ RP2040PadsBank0State *s = RP2040_PADS_BANK0(dev);
+ unsigned i;
+
+ s->voltage_select = 0;
+ for (i = 0; i < ARRAY_SIZE(s->gpio); i++) {
+ s->gpio[i] = PADS_BANK0_GPIO_RESET;
+ }
+ s->swclk = PADS_BANK0_SWD_RESET;
+ s->swd = PADS_BANK0_SWD_RESET;
+}
+
+static void rp2040_pads_qspi_reset(DeviceState *dev)
+{
+ RP2040PadsQspiState *s = RP2040_PADS_QSPI(dev);
+
+ s->voltage_select = 0;
+ s->pad[0] = PADS_QSPI_SCLK_RESET;
+ s->pad[1] = PADS_QSPI_SD_RESET;
+ s->pad[2] = PADS_QSPI_SD_RESET;
+ s->pad[3] = PADS_QSPI_SD_RESET;
+ s->pad[4] = PADS_QSPI_SD_RESET;
+ s->pad[5] = PADS_QSPI_SS_RESET;
+}
+
+static void rp2040_pads_bank0_init(Object *obj)
+{
+ RP2040PadsBank0State *s = RP2040_PADS_BANK0(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_pads_bank0_ops, s,
+ "rp2040.padsbank0", RP2040_PADS_BANK0_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void rp2040_pads_qspi_init(Object *obj)
+{
+ RP2040PadsQspiState *s = RP2040_PADS_QSPI(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_pads_qspi_ops, s,
+ "rp2040.padsqspi", RP2040_PADS_QSPI_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_pads_bank0_vmstate = {
+ .name = TYPE_RP2040_PADS_BANK0,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(voltage_select, RP2040PadsBank0State),
+ VMSTATE_UINT32_ARRAY(gpio, RP2040PadsBank0State, 30),
+ VMSTATE_UINT32(swclk, RP2040PadsBank0State),
+ VMSTATE_UINT32(swd, RP2040PadsBank0State),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription rp2040_pads_qspi_vmstate = {
+ .name = TYPE_RP2040_PADS_QSPI,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(voltage_select, RP2040PadsQspiState),
+ VMSTATE_UINT32_ARRAY(pad, RP2040PadsQspiState, 6),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_pads_bank0_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_pads_bank0_reset);
+ dc->vmsd = &rp2040_pads_bank0_vmstate;
+}
+
+static void rp2040_pads_qspi_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_pads_qspi_reset);
+ dc->vmsd = &rp2040_pads_qspi_vmstate;
+}
+
+static const TypeInfo rp2040_pads_bank0_info = {
+ .name = TYPE_RP2040_PADS_BANK0,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040PadsBank0State),
+ .instance_init = rp2040_pads_bank0_init,
+ .class_init = rp2040_pads_bank0_class_init,
+};
+
+static const TypeInfo rp2040_pads_qspi_info = {
+ .name = TYPE_RP2040_PADS_QSPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040PadsQspiState),
+ .instance_init = rp2040_pads_qspi_init,
+ .class_init = rp2040_pads_qspi_class_init,
+};
+
+static void rp2040_pads_register_types(void)
+{
+ type_register_static(&rp2040_pads_bank0_info);
+ type_register_static(&rp2040_pads_qspi_info);
+}
+type_init(rp2040_pads_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 6c6eb3d927..033fc17ffc 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -14,6 +14,7 @@
#include "hw/core/clock.h"
#include "hw/core/sysbus.h"
#include "hw/misc/rp2040_clocks.h"
+#include "hw/misc/rp2040_pads.h"
#include "hw/misc/rp2040_pll.h"
#include "hw/misc/rp2040_psm.h"
#include "hw/misc/rp2040_resets.h"
@@ -46,6 +47,8 @@ struct RP2040State {
ARMv7MState armv7m;
PL011State uart[2];
RP2040ClocksState clocks;
+ RP2040PadsBank0State pads_bank0;
+ RP2040PadsQspiState pads_qspi;
RP2040PllState pll_sys;
RP2040PllState pll_usb;
RP2040PsmState psm;
diff --git a/include/hw/misc/rp2040_pads.h b/include/hw/misc/rp2040_pads.h
new file mode 100644
index 0000000000..3fc6cd54bb
--- /dev/null
+++ b/include/hw/misc/rp2040_pads.h
@@ -0,0 +1,42 @@
+/*
+ * RP2040 pad control emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_PADS_H
+#define HW_MISC_RP2040_PADS_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_PADS_QSPI "rp2040-pads-qspi"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040PadsQspiState, RP2040_PADS_QSPI)
+
+#define TYPE_RP2040_PADS_BANK0 "rp2040-pads-bank0"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040PadsBank0State, RP2040_PADS_BANK0)
+
+#define RP2040_PADS_BANK0_BASE 0x4001c000
+#define RP2040_PADS_BANK0_SIZE 0x4000
+#define RP2040_PADS_QSPI_BASE 0x40020000
+#define RP2040_PADS_QSPI_SIZE 0x4000
+
+struct RP2040PadsBank0State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t voltage_select;
+ uint32_t gpio[30];
+ uint32_t swclk;
+ uint32_t swd;
+};
+
+struct RP2040PadsQspiState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t voltage_select;
+ uint32_t pad[6];
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index e5ddd4ce3b..07a9e97610 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -257,6 +257,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
'rp2040-clocks-test',
+ 'rp2040-pads-test',
'rp2040-resets-test',
'rp2040-rosc-test',
'rp2040-tbman-test',
diff --git a/tests/qtest/rp2040-pads-test.c b/tests/qtest/rp2040-pads-test.c
new file mode 100644
index 0000000000..cf55ed5c3b
--- /dev/null
+++ b/tests/qtest/rp2040-pads-test.c
@@ -0,0 +1,105 @@
+/*
+ * QTest testcase for the RP2040 pad controls.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define PADS_BANK0_BASE 0x4001c000
+#define PADS_BANK0_VOLTAGE 0x00
+#define PADS_BANK0_GPIO0 0x04
+#define PADS_BANK0_GPIO29 0x78
+#define PADS_BANK0_SWCLK 0x7c
+#define PADS_BANK0_SWD 0x80
+
+#define PADS_QSPI_BASE 0x40020000
+#define PADS_QSPI_VOLTAGE 0x00
+#define PADS_QSPI_SCLK 0x04
+#define PADS_QSPI_SD0 0x08
+#define PADS_QSPI_SD3 0x14
+#define PADS_QSPI_SS 0x18
+
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static QTestState *rp2040_pads_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_bank0(void)
+{
+ QTestState *qts = rp2040_pads_start();
+
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE +
+ PADS_BANK0_VOLTAGE), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0),
+ ==, 0x56);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO29),
+ ==, 0x56);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_SWCLK),
+ ==, 0x5a);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_SWD),
+ ==, 0x5a);
+
+ qtest_writel(qts, PADS_BANK0_BASE + PADS_BANK0_VOLTAGE, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE +
+ PADS_BANK0_VOLTAGE), ==, 1);
+
+ qtest_writel(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0, 0x12);
+ qtest_writel(qts, PADS_BANK0_BASE + ATOMIC_SET + PADS_BANK0_GPIO0,
+ 0x81);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0),
+ ==, 0x93);
+ qtest_writel(qts, PADS_BANK0_BASE + ATOMIC_CLR + PADS_BANK0_GPIO0,
+ 0x11);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0),
+ ==, 0x82);
+ qtest_writel(qts, PADS_BANK0_BASE + ATOMIC_XOR + PADS_BANK0_GPIO0,
+ 0x03);
+ g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0),
+ ==, 0x81);
+
+ qtest_quit(qts);
+}
+
+static void test_qspi(void)
+{
+ QTestState *qts = rp2040_pads_start();
+
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_VOLTAGE),
+ ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SCLK),
+ ==, 0x56);
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SD0),
+ ==, 0x52);
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SD3),
+ ==, 0x52);
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS),
+ ==, 0x5a);
+
+ qtest_writel(qts, PADS_QSPI_BASE + PADS_QSPI_SS, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS),
+ ==, 0xff);
+ qtest_writel(qts, PADS_QSPI_BASE + ATOMIC_CLR + PADS_QSPI_SS, 0x0f);
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS),
+ ==, 0xf0);
+ qtest_writel(qts, PADS_QSPI_BASE + ATOMIC_XOR + PADS_QSPI_SS, 0x33);
+ g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS),
+ ==, 0xc3);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-pads/bank0", test_bank0);
+ qtest_add_func("/rp2040-pads/qspi", test_qspi);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 13/30] hw/misc: add RP2040 IO_BANK0
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (11 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 12/30] hw/misc: add RP2040 pad controls Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-08-29 23:42 ` [PATCH RFC v2 14/30] hw/misc: add RP2040 IO_QSPI Gilles Grimaud
` (17 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model GPIO control registers and the PROC0, PROC1 and dormant-wake interrupt banks, including atomic aliases and last-bank masks. Connect the PROC0 interrupt output to the Cortex-M0+ NVIC and add focused qtests for register behavior and interrupt propagation.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/rp2040.c | 11 +-
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/rp2040_iobank0.c | 353 ++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 2 +
include/hw/misc/rp2040_iobank0.h | 45 ++++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-iobank0-test.c | 122 +++++++++++
9 files changed, 538 insertions(+), 1 deletion(-)
create mode 100644 hw/misc/rp2040_iobank0.c
create mode 100644 include/hw/misc/rp2040_iobank0.h
create mode 100644 tests/qtest/rp2040-iobank0-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 0c455fe5ce..7e3d23e81c 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -379,6 +379,7 @@ config RP2040
select ARM_V7M
select PL011
select RP2040_CLOCKS
+ select RP2040_IOBANK0
select RP2040_NYI
select RP2040_PADS
select RP2040_PLL
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 894f5ec3c2..4cceec863e 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -22,6 +22,7 @@
#define RP2040_UART0_IRQ 20
#define RP2040_UART1_BASE 0x40038000
#define RP2040_UART1_IRQ 21
+#define RP2040_IO_IRQ_BANK0 13
/*
* Temporary boot ROM used until the synthetic ROM is introduced. It loads
@@ -48,7 +49,6 @@ static const struct {
hwaddr base;
hwaddr size;
} rp2040_unimplemented[] = {
- { "rp2040.iobank0", 0x40014000, 0x4000 },
{ "rp2040.ioqspi", 0x40018000, 0x4000 },
{ "rp2040.busctrl", 0x40030000, 0x4000 },
{ "rp2040.uart0_aliases", 0x40035000, 0x3000 },
@@ -164,6 +164,8 @@ static void rp2040_soc_init(Object *obj)
}
object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS);
+ object_initialize_child(obj, "iobank0", &s->iobank0,
+ TYPE_RP2040_IOBANK0);
object_initialize_child(obj, "pads-bank0", &s->pads_bank0,
TYPE_RP2040_PADS_BANK0);
object_initialize_child(obj, "pads-qspi", &s->pads_qspi,
@@ -367,6 +369,13 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
rp2040_syscfg_update(s);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->iobank0), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->iobank0), 0, RP2040_IOBANK0_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 0,
+ s->irq[RP2040_IO_IRQ_BANK0]);
+
if (!sysbus_realize(SYS_BUS_DEVICE(&s->rosc), errp)) {
return;
}
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index fc1d5bd58b..daf513766c 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -104,6 +104,9 @@ config FSL_IMX8MP_CCM
config RP2040_CLOCKS
bool
+config RP2040_IOBANK0
+ bool
+
config RP2040_NYI
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index a60226c073..e84cbd735f 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -99,6 +99,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_powermgt.c',
))
system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c'))
+system_ss.add(when: 'CONFIG_RP2040_IOBANK0', if_true: files('rp2040_iobank0.c'))
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
system_ss.add(when: 'CONFIG_RP2040_PADS', if_true: files('rp2040_pads.c'))
system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c'))
diff --git a/hw/misc/rp2040_iobank0.c b/hw/misc/rp2040_iobank0.c
new file mode 100644
index 0000000000..60462298da
--- /dev/null
+++ b/hw/misc/rp2040_iobank0.c
@@ -0,0 +1,353 @@
+/*
+ * RP2040 GPIO IO bank emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_iobank0.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define IOBANK0_GPIO_STRIDE 0x8
+#define IOBANK0_GPIO_STATUS 0x0
+#define IOBANK0_GPIO_CTRL 0x4
+
+#define IOBANK0_INTR 0x0f0
+#define IOBANK0_PROC0_INTE 0x100
+#define IOBANK0_PROC0_INTF 0x110
+#define IOBANK0_PROC0_INTS 0x120
+#define IOBANK0_PROC1_INTE 0x130
+#define IOBANK0_PROC1_INTF 0x140
+#define IOBANK0_PROC1_INTS 0x150
+#define IOBANK0_DORMANT_INTE 0x160
+#define IOBANK0_DORMANT_INTF 0x170
+#define IOBANK0_DORMANT_INTS 0x180
+
+#define IOBANK0_CTRL_RESET 0x1f
+#define IOBANK0_CTRL_RW_MASK 0x33333f
+#define IOBANK0_CTRL_FUNCSEL_MASK 0x1f
+#define IOBANK0_FUNCSEL_UART 2
+#define IOBANK0_INTR_EDGE_MASK 0xcccccccc
+#define IOBANK0_INTR_LAST_EDGE_MASK 0x00cccccc
+#define IOBANK0_IRQ_MASK 0xffffffff
+#define IOBANK0_IRQ_LAST_MASK 0x00ffffff
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_iobank0_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 bool rp2040_iobank0_gpio_offset(hwaddr offset, unsigned *gpio,
+ unsigned *reg)
+{
+ if (offset >= RP2040_IOBANK0_NUM_GPIOS * IOBANK0_GPIO_STRIDE) {
+ return false;
+ }
+
+ *gpio = offset / IOBANK0_GPIO_STRIDE;
+ *reg = offset & (IOBANK0_GPIO_STRIDE - 1);
+ return *reg == IOBANK0_GPIO_STATUS || *reg == IOBANK0_GPIO_CTRL;
+}
+
+static bool rp2040_iobank0_bank_offset(hwaddr offset, hwaddr base,
+ unsigned *bank)
+{
+ if (offset < base || offset >= base + RP2040_IOBANK0_IRQ_BANKS * 4 ||
+ (offset & 0x3)) {
+ return false;
+ }
+
+ *bank = (offset - base) / 4;
+ return true;
+}
+
+static uint32_t rp2040_iobank0_irq_mask(unsigned bank)
+{
+ return bank == 3 ? IOBANK0_IRQ_LAST_MASK : IOBANK0_IRQ_MASK;
+}
+
+static uint32_t rp2040_iobank0_intr_edge_mask(unsigned bank)
+{
+ return bank == 3 ? IOBANK0_INTR_LAST_EDGE_MASK : IOBANK0_INTR_EDGE_MASK;
+}
+
+static uint32_t rp2040_iobank0_ints(uint32_t intr, uint32_t inte,
+ uint32_t intf)
+{
+ return (intr & inte) | intf;
+}
+
+static bool rp2040_iobank0_gpio_is_uart(RP2040IoBank0State *s, unsigned gpio)
+{
+ return (s->ctrl[gpio] & IOBANK0_CTRL_FUNCSEL_MASK) == IOBANK0_FUNCSEL_UART;
+}
+
+bool rp2040_iobank0_uart0_tx_enabled(RP2040IoBank0State *s)
+{
+ return rp2040_iobank0_gpio_is_uart(s, 0);
+}
+
+bool rp2040_iobank0_uart0_rx_enabled(RP2040IoBank0State *s)
+{
+ return rp2040_iobank0_gpio_is_uart(s, 1);
+}
+
+bool rp2040_iobank0_uart1_tx_enabled(RP2040IoBank0State *s)
+{
+ return rp2040_iobank0_gpio_is_uart(s, 4);
+}
+
+bool rp2040_iobank0_uart1_rx_enabled(RP2040IoBank0State *s)
+{
+ return rp2040_iobank0_gpio_is_uart(s, 5);
+}
+
+static void rp2040_iobank0_update_uart_pins(RP2040IoBank0State *s)
+{
+ qemu_set_irq(s->uart0_pin[0], rp2040_iobank0_uart0_tx_enabled(s));
+ qemu_set_irq(s->uart0_pin[1], rp2040_iobank0_uart0_rx_enabled(s));
+ qemu_set_irq(s->uart1_pin[0], rp2040_iobank0_uart1_tx_enabled(s));
+ qemu_set_irq(s->uart1_pin[1], rp2040_iobank0_uart1_rx_enabled(s));
+}
+
+static void rp2040_iobank0_update_irq(RP2040IoBank0State *s)
+{
+ bool proc0_level = false;
+ bool proc1_level = false;
+ int i;
+
+ for (i = 0; i < RP2040_IOBANK0_IRQ_BANKS; i++) {
+ proc0_level |= rp2040_iobank0_ints(s->intr[i], s->proc0_inte[i],
+ s->proc0_intf[i]) != 0;
+ proc1_level |= rp2040_iobank0_ints(s->intr[i], s->proc1_inte[i],
+ s->proc1_intf[i]) != 0;
+ }
+
+ qemu_set_irq(s->proc0_irq, proc0_level);
+ qemu_set_irq(s->proc1_irq, proc1_level);
+}
+
+static uint64_t rp2040_iobank0_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040IoBank0State *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ unsigned gpio;
+ unsigned reg;
+ unsigned bank;
+ uint64_t value;
+
+ if (rp2040_iobank0_gpio_offset(offset, &gpio, ®)) {
+ value = reg == IOBANK0_GPIO_STATUS ? 0 : s->ctrl[gpio];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_INTR, &bank)) {
+ value = s->intr[bank];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTE, &bank)) {
+ value = s->proc0_inte[bank];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTF, &bank)) {
+ value = s->proc0_intf[bank];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTS, &bank)) {
+ value = rp2040_iobank0_ints(s->intr[bank], s->proc0_inte[bank],
+ s->proc0_intf[bank]);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTE, &bank)) {
+ value = s->proc1_inte[bank];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTF, &bank)) {
+ value = s->proc1_intf[bank];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTS, &bank)) {
+ value = rp2040_iobank0_ints(s->intr[bank], s->proc1_inte[bank],
+ s->proc1_intf[bank]);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTE,
+ &bank)) {
+ value = s->dormant_wake_inte[bank];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTF,
+ &bank)) {
+ value = s->dormant_wake_intf[bank];
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTS,
+ &bank)) {
+ value = rp2040_iobank0_ints(s->intr[bank],
+ s->dormant_wake_inte[bank],
+ s->dormant_wake_intf[bank]);
+ } else {
+ value = 0;
+ rp2040_log_unimplemented_read("iobank0", size,
+ RP2040_IOBANK0_BASE + addr, offset,
+ value);
+ }
+
+ return value;
+}
+
+static void rp2040_iobank0_write_irq_reg(uint32_t *reg, uint64_t value64,
+ hwaddr alias, unsigned bank)
+{
+ *reg = rp2040_iobank0_apply_alias(*reg, value64, alias) &
+ rp2040_iobank0_irq_mask(bank);
+}
+
+static void rp2040_iobank0_write(void *opaque, hwaddr addr, uint64_t value64,
+ unsigned size)
+{
+ RP2040IoBank0State *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ unsigned gpio;
+ unsigned reg;
+ unsigned bank;
+ uint32_t value = value64;
+
+ if (rp2040_iobank0_gpio_offset(offset, &gpio, ®)) {
+ if (reg == IOBANK0_GPIO_CTRL) {
+ s->ctrl[gpio] =
+ rp2040_iobank0_apply_alias(s->ctrl[gpio], value, alias) &
+ IOBANK0_CTRL_RW_MASK;
+ if (gpio <= 1 || gpio == 4 || gpio == 5) {
+ rp2040_iobank0_update_uart_pins(s);
+ }
+ }
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_INTR, &bank)) {
+ s->intr[bank] &= ~(value & rp2040_iobank0_intr_edge_mask(bank));
+ rp2040_iobank0_update_irq(s);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTE, &bank)) {
+ rp2040_iobank0_write_irq_reg(&s->proc0_inte[bank], value, alias,
+ bank);
+ rp2040_iobank0_update_irq(s);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTF, &bank)) {
+ rp2040_iobank0_write_irq_reg(&s->proc0_intf[bank], value, alias,
+ bank);
+ rp2040_iobank0_update_irq(s);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTE, &bank)) {
+ rp2040_iobank0_write_irq_reg(&s->proc1_inte[bank], value, alias,
+ bank);
+ rp2040_iobank0_update_irq(s);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTF, &bank)) {
+ rp2040_iobank0_write_irq_reg(&s->proc1_intf[bank], value, alias,
+ bank);
+ rp2040_iobank0_update_irq(s);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTE,
+ &bank)) {
+ rp2040_iobank0_write_irq_reg(&s->dormant_wake_inte[bank], value,
+ alias, bank);
+ } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTF,
+ &bank)) {
+ rp2040_iobank0_write_irq_reg(&s->dormant_wake_intf[bank], value,
+ alias, bank);
+ } else if (!rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTS,
+ &bank) &&
+ !rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTS,
+ &bank) &&
+ !rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTS,
+ &bank)) {
+ rp2040_log_unimplemented_write("iobank0", size,
+ RP2040_IOBANK0_BASE + addr, offset,
+ value64);
+ }
+}
+
+static const MemoryRegionOps rp2040_iobank0_ops = {
+ .read = rp2040_iobank0_read,
+ .write = rp2040_iobank0_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_iobank0_reset(DeviceState *dev)
+{
+ RP2040IoBank0State *s = RP2040_IOBANK0(dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(s->ctrl); i++) {
+ s->ctrl[i] = IOBANK0_CTRL_RESET;
+ }
+
+ memset(s->intr, 0, sizeof(s->intr));
+ memset(s->proc0_inte, 0, sizeof(s->proc0_inte));
+ memset(s->proc0_intf, 0, sizeof(s->proc0_intf));
+ memset(s->proc1_inte, 0, sizeof(s->proc1_inte));
+ memset(s->proc1_intf, 0, sizeof(s->proc1_intf));
+ memset(s->dormant_wake_inte, 0, sizeof(s->dormant_wake_inte));
+ memset(s->dormant_wake_intf, 0, sizeof(s->dormant_wake_intf));
+ rp2040_iobank0_update_irq(s);
+ rp2040_iobank0_update_uart_pins(s);
+}
+
+static void rp2040_iobank0_init(Object *obj)
+{
+ RP2040IoBank0State *s = RP2040_IOBANK0(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_iobank0_ops, s,
+ "rp2040.iobank0", RP2040_IOBANK0_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->proc0_irq);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->proc1_irq);
+ qdev_init_gpio_out_named(DEVICE(obj), s->uart0_pin, "uart0-pin",
+ ARRAY_SIZE(s->uart0_pin));
+ qdev_init_gpio_out_named(DEVICE(obj), s->uart1_pin, "uart1-pin",
+ ARRAY_SIZE(s->uart1_pin));
+}
+
+static const VMStateDescription rp2040_iobank0_vmstate = {
+ .name = TYPE_RP2040_IOBANK0,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(ctrl, RP2040IoBank0State,
+ RP2040_IOBANK0_NUM_GPIOS),
+ VMSTATE_UINT32_ARRAY(intr, RP2040IoBank0State,
+ RP2040_IOBANK0_IRQ_BANKS),
+ VMSTATE_UINT32_ARRAY(proc0_inte, RP2040IoBank0State,
+ RP2040_IOBANK0_IRQ_BANKS),
+ VMSTATE_UINT32_ARRAY(proc0_intf, RP2040IoBank0State,
+ RP2040_IOBANK0_IRQ_BANKS),
+ VMSTATE_UINT32_ARRAY(proc1_inte, RP2040IoBank0State,
+ RP2040_IOBANK0_IRQ_BANKS),
+ VMSTATE_UINT32_ARRAY(proc1_intf, RP2040IoBank0State,
+ RP2040_IOBANK0_IRQ_BANKS),
+ VMSTATE_UINT32_ARRAY(dormant_wake_inte, RP2040IoBank0State,
+ RP2040_IOBANK0_IRQ_BANKS),
+ VMSTATE_UINT32_ARRAY(dormant_wake_intf, RP2040IoBank0State,
+ RP2040_IOBANK0_IRQ_BANKS),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_iobank0_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_iobank0_reset);
+ dc->vmsd = &rp2040_iobank0_vmstate;
+}
+
+static const TypeInfo rp2040_iobank0_info = {
+ .name = TYPE_RP2040_IOBANK0,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040IoBank0State),
+ .instance_init = rp2040_iobank0_init,
+ .class_init = rp2040_iobank0_class_init,
+};
+
+static void rp2040_iobank0_register_types(void)
+{
+ type_register_static(&rp2040_iobank0_info);
+}
+type_init(rp2040_iobank0_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 033fc17ffc..1ed6fa1449 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -14,6 +14,7 @@
#include "hw/core/clock.h"
#include "hw/core/sysbus.h"
#include "hw/misc/rp2040_clocks.h"
+#include "hw/misc/rp2040_iobank0.h"
#include "hw/misc/rp2040_pads.h"
#include "hw/misc/rp2040_pll.h"
#include "hw/misc/rp2040_psm.h"
@@ -47,6 +48,7 @@ struct RP2040State {
ARMv7MState armv7m;
PL011State uart[2];
RP2040ClocksState clocks;
+ RP2040IoBank0State iobank0;
RP2040PadsBank0State pads_bank0;
RP2040PadsQspiState pads_qspi;
RP2040PllState pll_sys;
diff --git a/include/hw/misc/rp2040_iobank0.h b/include/hw/misc/rp2040_iobank0.h
new file mode 100644
index 0000000000..93f5b4b2f4
--- /dev/null
+++ b/include/hw/misc/rp2040_iobank0.h
@@ -0,0 +1,45 @@
+/*
+ * RP2040 GPIO IO bank emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_IOBANK0_H
+#define HW_MISC_RP2040_IOBANK0_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_IOBANK0 "rp2040-iobank0"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040IoBank0State, RP2040_IOBANK0)
+
+#define RP2040_IOBANK0_BASE 0x40014000
+#define RP2040_IOBANK0_SIZE 0x4000
+#define RP2040_IOBANK0_NUM_GPIOS 30
+#define RP2040_IOBANK0_IRQ_BANKS 4
+
+struct RP2040IoBank0State {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq proc0_irq;
+ qemu_irq proc1_irq;
+ qemu_irq uart0_pin[2];
+ qemu_irq uart1_pin[2];
+
+ uint32_t ctrl[RP2040_IOBANK0_NUM_GPIOS];
+ uint32_t intr[RP2040_IOBANK0_IRQ_BANKS];
+ uint32_t proc0_inte[RP2040_IOBANK0_IRQ_BANKS];
+ uint32_t proc0_intf[RP2040_IOBANK0_IRQ_BANKS];
+ uint32_t proc1_inte[RP2040_IOBANK0_IRQ_BANKS];
+ uint32_t proc1_intf[RP2040_IOBANK0_IRQ_BANKS];
+ uint32_t dormant_wake_inte[RP2040_IOBANK0_IRQ_BANKS];
+ uint32_t dormant_wake_intf[RP2040_IOBANK0_IRQ_BANKS];
+};
+
+bool rp2040_iobank0_uart0_tx_enabled(RP2040IoBank0State *s);
+bool rp2040_iobank0_uart0_rx_enabled(RP2040IoBank0State *s);
+bool rp2040_iobank0_uart1_tx_enabled(RP2040IoBank0State *s);
+bool rp2040_iobank0_uart1_rx_enabled(RP2040IoBank0State *s);
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 07a9e97610..372d412b9b 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -257,6 +257,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
'rp2040-clocks-test',
+ 'rp2040-iobank0-test',
'rp2040-pads-test',
'rp2040-resets-test',
'rp2040-rosc-test',
diff --git a/tests/qtest/rp2040-iobank0-test.c b/tests/qtest/rp2040-iobank0-test.c
new file mode 100644
index 0000000000..21bb27cdc4
--- /dev/null
+++ b/tests/qtest/rp2040-iobank0-test.c
@@ -0,0 +1,122 @@
+/*
+ * QTest testcase for the RP2040 IO_BANK0.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define IOBANK0_BASE 0x40014000
+#define GPIO0_STATUS 0x000
+#define GPIO0_CTRL 0x004
+#define GPIO29_CTRL 0x0ec
+#define INTR0 0x0f0
+#define PROC0_INTE0 0x100
+#define PROC0_INTF0 0x110
+#define PROC0_INTS0 0x120
+#define PROC1_INTE3 0x13c
+#define PROC1_INTF3 0x14c
+#define PROC1_INTS3 0x15c
+#define DORMANT_INTE3 0x16c
+#define DORMANT_INTF3 0x17c
+#define DORMANT_INTS3 0x18c
+
+#define CTRL_RESET 0x1f
+#define CTRL_RW_MASK 0x33333f
+#define IRQ_LAST_MASK 0x00ffffff
+#define IO_IRQ_BANK0 13
+
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+#define NVIC_ISPR 0xe000e200
+#define NVIC_ICPR 0xe000e280
+
+static QTestState *rp2040_iobank0_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_gpio_control(void)
+{
+ QTestState *qts = rp2040_iobank0_start();
+
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_STATUS), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), ==,
+ CTRL_RESET);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO29_CTRL), ==,
+ CTRL_RESET);
+
+ qtest_writel(qts, IOBANK0_BASE + GPIO0_CTRL, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), ==,
+ CTRL_RW_MASK);
+ qtest_writel(qts, IOBANK0_BASE + ATOMIC_CLR + GPIO0_CTRL, 0x30);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), ==,
+ CTRL_RW_MASK & ~0x30);
+ qtest_writel(qts, IOBANK0_BASE + ATOMIC_XOR + GPIO0_CTRL, 0x03);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), ==,
+ (CTRL_RW_MASK & ~0x30) ^ 0x03);
+
+ qtest_quit(qts);
+}
+
+static void test_proc0_irq(void)
+{
+ QTestState *qts = rp2040_iobank0_start();
+ uint32_t irq = BIT(IO_IRQ_BANK0);
+
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + INTR0), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTS0), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & irq, ==, 0);
+
+ qtest_writel(qts, IOBANK0_BASE + PROC0_INTF0, BIT(7));
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTS0), ==,
+ BIT(7));
+ g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & irq, ==, irq);
+
+ qtest_writel(qts, IOBANK0_BASE + ATOMIC_CLR + PROC0_INTF0, BIT(7));
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTS0), ==, 0);
+ qtest_writel(qts, NVIC_ICPR, irq);
+ g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & irq, ==, 0);
+
+ qtest_writel(qts, IOBANK0_BASE + ATOMIC_SET + PROC0_INTE0, BIT(3));
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTE0), ==,
+ BIT(3));
+
+ qtest_quit(qts);
+}
+
+static void test_last_bank_masks(void)
+{
+ QTestState *qts = rp2040_iobank0_start();
+
+ qtest_writel(qts, IOBANK0_BASE + PROC1_INTE3, 0xffffffff);
+ qtest_writel(qts, IOBANK0_BASE + PROC1_INTF3, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC1_INTE3), ==,
+ IRQ_LAST_MASK);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC1_INTS3), ==,
+ IRQ_LAST_MASK);
+
+ qtest_writel(qts, IOBANK0_BASE + DORMANT_INTE3, 0xffffffff);
+ qtest_writel(qts, IOBANK0_BASE + DORMANT_INTF3, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + DORMANT_INTE3), ==,
+ IRQ_LAST_MASK);
+ g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + DORMANT_INTS3), ==,
+ IRQ_LAST_MASK);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-iobank0/gpio-control", test_gpio_control);
+ qtest_add_func("/rp2040-iobank0/proc0-irq", test_proc0_irq);
+ qtest_add_func("/rp2040-iobank0/last-bank-masks", test_last_bank_masks);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 14/30] hw/misc: add RP2040 IO_QSPI
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (12 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 13/30] hw/misc: add RP2040 IO_BANK0 Gilles Grimaud
@ 2026-08-29 23:42 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 15/30] hw/char: integrate RP2040 UART pinmux Gilles Grimaud
` (16 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-29 23:42 UTC (permalink / raw)
To: qemu-devel
Cc: alex.bennee, qemu-arm, gilles grimaud, Paolo Bonzini,
Peter Maydell, Fabiano Rosas, Laurent Vivier
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the six QSPI GPIO control pairs and the PROC0, PROC1 and dormant-wake interrupt registers, including atomic aliases and documented masks. Wire the register block into the RP2040 SoC and add focused qtests for reset, atomic control updates and forced interrupt status. The QSPI chip-select connection is deferred to the XIP controller patch.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/rp2040.c | 8 +-
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/rp2040_ioqspi.c | 263 +++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 2 +
include/hw/misc/rp2040_ioqspi.h | 33 ++++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-ioqspi-test.c | 81 ++++++++++
9 files changed, 392 insertions(+), 1 deletion(-)
create mode 100644 hw/misc/rp2040_ioqspi.c
create mode 100644 include/hw/misc/rp2040_ioqspi.h
create mode 100644 tests/qtest/rp2040-ioqspi-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 7e3d23e81c..a1b94532b4 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -380,6 +380,7 @@ config RP2040
select PL011
select RP2040_CLOCKS
select RP2040_IOBANK0
+ select RP2040_IOQSPI
select RP2040_NYI
select RP2040_PADS
select RP2040_PLL
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 4cceec863e..28c17232a5 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -49,7 +49,6 @@ static const struct {
hwaddr base;
hwaddr size;
} rp2040_unimplemented[] = {
- { "rp2040.ioqspi", 0x40018000, 0x4000 },
{ "rp2040.busctrl", 0x40030000, 0x4000 },
{ "rp2040.uart0_aliases", 0x40035000, 0x3000 },
{ "rp2040.uart1_aliases", 0x40039000, 0x3000 },
@@ -166,6 +165,8 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS);
object_initialize_child(obj, "iobank0", &s->iobank0,
TYPE_RP2040_IOBANK0);
+ object_initialize_child(obj, "ioqspi", &s->ioqspi,
+ TYPE_RP2040_IOQSPI);
object_initialize_child(obj, "pads-bank0", &s->pads_bank0,
TYPE_RP2040_PADS_BANK0);
object_initialize_child(obj, "pads-qspi", &s->pads_qspi,
@@ -376,6 +377,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 0,
s->irq[RP2040_IO_IRQ_BANK0]);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->ioqspi), 0, RP2040_IOQSPI_BASE);
+
if (!sysbus_realize(SYS_BUS_DEVICE(&s->rosc), errp)) {
return;
}
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index daf513766c..553a92d4b6 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -107,6 +107,9 @@ config RP2040_CLOCKS
config RP2040_IOBANK0
bool
+config RP2040_IOQSPI
+ bool
+
config RP2040_NYI
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index e84cbd735f..5e4910e83d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -100,6 +100,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
))
system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c'))
system_ss.add(when: 'CONFIG_RP2040_IOBANK0', if_true: files('rp2040_iobank0.c'))
+system_ss.add(when: 'CONFIG_RP2040_IOQSPI', if_true: files('rp2040_ioqspi.c'))
system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
system_ss.add(when: 'CONFIG_RP2040_PADS', if_true: files('rp2040_pads.c'))
system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c'))
diff --git a/hw/misc/rp2040_ioqspi.c b/hw/misc/rp2040_ioqspi.c
new file mode 100644
index 0000000000..eafd5e9eef
--- /dev/null
+++ b/hw/misc/rp2040_ioqspi.c
@@ -0,0 +1,263 @@
+/*
+ * RP2040 QSPI IO bank emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_ioqspi.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define IOQSPI_INTR 0x30
+#define IOQSPI_PROC0_INTE 0x34
+#define IOQSPI_PROC0_INTF 0x38
+#define IOQSPI_PROC0_INTS 0x3c
+#define IOQSPI_PROC1_INTE 0x40
+#define IOQSPI_PROC1_INTF 0x44
+#define IOQSPI_PROC1_INTS 0x48
+#define IOQSPI_DORMANT_INTE 0x4c
+#define IOQSPI_DORMANT_INTF 0x50
+#define IOQSPI_DORMANT_INTS 0x54
+
+#define IOQSPI_CTRL_RESET 0x1f
+#define IOQSPI_CTRL_RW_MASK 0x33333f
+#define IOQSPI_INTR_EDGE_MASK 0x00cccccc
+#define IOQSPI_IRQ_MASK 0x00ffffff
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_ioqspi_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 bool rp2040_ioqspi_ctrl_offset(hwaddr offset, unsigned *index)
+{
+ if (offset > 0x2c || (offset & 0x7) != 0x4) {
+ return false;
+ }
+
+ *index = offset / 8;
+ return *index < ARRAY_SIZE(((RP2040IoQspiState *)0)->ctrl);
+}
+
+static bool rp2040_ioqspi_status_offset(hwaddr offset)
+{
+ return offset <= 0x28 && (offset & 0x7) == 0;
+}
+
+static uint32_t rp2040_ioqspi_ints(uint32_t intr, uint32_t inte,
+ uint32_t intf)
+{
+ return (intr & inte) | intf;
+}
+
+static uint64_t rp2040_ioqspi_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040IoQspiState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint64_t value;
+
+ if (rp2040_ioqspi_status_offset(offset)) {
+ value = 0;
+ } else if (rp2040_ioqspi_ctrl_offset(offset, &index)) {
+ value = s->ctrl[index];
+ } else {
+ switch (offset) {
+ case IOQSPI_INTR:
+ value = s->intr;
+ break;
+ case IOQSPI_PROC0_INTE:
+ value = s->proc0_inte;
+ break;
+ case IOQSPI_PROC0_INTF:
+ value = s->proc0_intf;
+ break;
+ case IOQSPI_PROC0_INTS:
+ value = rp2040_ioqspi_ints(s->intr, s->proc0_inte,
+ s->proc0_intf);
+ break;
+ case IOQSPI_PROC1_INTE:
+ value = s->proc1_inte;
+ break;
+ case IOQSPI_PROC1_INTF:
+ value = s->proc1_intf;
+ break;
+ case IOQSPI_PROC1_INTS:
+ value = rp2040_ioqspi_ints(s->intr, s->proc1_inte,
+ s->proc1_intf);
+ break;
+ case IOQSPI_DORMANT_INTE:
+ value = s->dormant_wake_inte;
+ break;
+ case IOQSPI_DORMANT_INTF:
+ value = s->dormant_wake_intf;
+ break;
+ case IOQSPI_DORMANT_INTS:
+ value = rp2040_ioqspi_ints(s->intr, s->dormant_wake_inte,
+ s->dormant_wake_intf);
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("ioqspi", size,
+ RP2040_IOQSPI_BASE + addr, offset,
+ value);
+ break;
+ }
+ }
+
+ return value;
+}
+
+static void rp2040_ioqspi_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040IoQspiState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint32_t value = value64;
+
+ if (rp2040_ioqspi_ctrl_offset(offset, &index)) {
+ s->ctrl[index] =
+ rp2040_ioqspi_apply_alias(s->ctrl[index], value, alias) &
+ IOQSPI_CTRL_RW_MASK;
+ } else {
+ switch (offset) {
+ case IOQSPI_INTR:
+ s->intr &= ~(value & IOQSPI_INTR_EDGE_MASK);
+ break;
+ case IOQSPI_PROC0_INTE:
+ s->proc0_inte =
+ rp2040_ioqspi_apply_alias(s->proc0_inte, value, alias) &
+ IOQSPI_IRQ_MASK;
+ break;
+ case IOQSPI_PROC0_INTF:
+ s->proc0_intf =
+ rp2040_ioqspi_apply_alias(s->proc0_intf, value, alias) &
+ IOQSPI_IRQ_MASK;
+ break;
+ case IOQSPI_PROC1_INTE:
+ s->proc1_inte =
+ rp2040_ioqspi_apply_alias(s->proc1_inte, value, alias) &
+ IOQSPI_IRQ_MASK;
+ break;
+ case IOQSPI_PROC1_INTF:
+ s->proc1_intf =
+ rp2040_ioqspi_apply_alias(s->proc1_intf, value, alias) &
+ IOQSPI_IRQ_MASK;
+ break;
+ case IOQSPI_DORMANT_INTE:
+ s->dormant_wake_inte =
+ rp2040_ioqspi_apply_alias(s->dormant_wake_inte, value,
+ alias) &
+ IOQSPI_IRQ_MASK;
+ break;
+ case IOQSPI_DORMANT_INTF:
+ s->dormant_wake_intf =
+ rp2040_ioqspi_apply_alias(s->dormant_wake_intf, value,
+ alias) &
+ IOQSPI_IRQ_MASK;
+ break;
+ default:
+ if (!rp2040_ioqspi_status_offset(offset)) {
+ rp2040_log_unimplemented_write("ioqspi", size,
+ RP2040_IOQSPI_BASE + addr,
+ offset, value64);
+ }
+ break;
+ }
+ }
+}
+
+static const MemoryRegionOps rp2040_ioqspi_ops = {
+ .read = rp2040_ioqspi_read,
+ .write = rp2040_ioqspi_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_ioqspi_reset(DeviceState *dev)
+{
+ RP2040IoQspiState *s = RP2040_IOQSPI(dev);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(s->ctrl); i++) {
+ s->ctrl[i] = IOQSPI_CTRL_RESET;
+ }
+ s->intr = 0;
+ s->proc0_inte = 0;
+ s->proc0_intf = 0;
+ s->proc1_inte = 0;
+ s->proc1_intf = 0;
+ s->dormant_wake_inte = 0;
+ s->dormant_wake_intf = 0;
+}
+
+static void rp2040_ioqspi_init(Object *obj)
+{
+ RP2040IoQspiState *s = RP2040_IOQSPI(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_ioqspi_ops, s,
+ "rp2040.ioqspi", RP2040_IOQSPI_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_ioqspi_vmstate = {
+ .name = TYPE_RP2040_IOQSPI,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(ctrl, RP2040IoQspiState, 6),
+ VMSTATE_UINT32(intr, RP2040IoQspiState),
+ VMSTATE_UINT32(proc0_inte, RP2040IoQspiState),
+ VMSTATE_UINT32(proc0_intf, RP2040IoQspiState),
+ VMSTATE_UINT32(proc1_inte, RP2040IoQspiState),
+ VMSTATE_UINT32(proc1_intf, RP2040IoQspiState),
+ VMSTATE_UINT32(dormant_wake_inte, RP2040IoQspiState),
+ VMSTATE_UINT32(dormant_wake_intf, RP2040IoQspiState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+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);
+ dc->vmsd = &rp2040_ioqspi_vmstate;
+}
+
+static const TypeInfo rp2040_ioqspi_info = {
+ .name = TYPE_RP2040_IOQSPI,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040IoQspiState),
+ .instance_init = rp2040_ioqspi_init,
+ .class_init = rp2040_ioqspi_class_init,
+};
+
+static void rp2040_ioqspi_register_types(void)
+{
+ type_register_static(&rp2040_ioqspi_info);
+}
+type_init(rp2040_ioqspi_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 1ed6fa1449..068aab584f 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -15,6 +15,7 @@
#include "hw/core/sysbus.h"
#include "hw/misc/rp2040_clocks.h"
#include "hw/misc/rp2040_iobank0.h"
+#include "hw/misc/rp2040_ioqspi.h"
#include "hw/misc/rp2040_pads.h"
#include "hw/misc/rp2040_pll.h"
#include "hw/misc/rp2040_psm.h"
@@ -49,6 +50,7 @@ struct RP2040State {
PL011State uart[2];
RP2040ClocksState clocks;
RP2040IoBank0State iobank0;
+ RP2040IoQspiState ioqspi;
RP2040PadsBank0State pads_bank0;
RP2040PadsQspiState pads_qspi;
RP2040PllState pll_sys;
diff --git a/include/hw/misc/rp2040_ioqspi.h b/include/hw/misc/rp2040_ioqspi.h
new file mode 100644
index 0000000000..e671d2a1b5
--- /dev/null
+++ b/include/hw/misc/rp2040_ioqspi.h
@@ -0,0 +1,33 @@
+/*
+ * RP2040 QSPI IO bank emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_IOQSPI_H
+#define HW_MISC_RP2040_IOQSPI_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_IOQSPI "rp2040-ioqspi"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040IoQspiState, RP2040_IOQSPI)
+
+#define RP2040_IOQSPI_BASE 0x40018000
+#define RP2040_IOQSPI_SIZE 0x4000
+
+struct RP2040IoQspiState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ uint32_t ctrl[6];
+ uint32_t intr;
+ uint32_t proc0_inte;
+ uint32_t proc0_intf;
+ uint32_t proc1_inte;
+ uint32_t proc1_intf;
+ uint32_t dormant_wake_inte;
+ uint32_t dormant_wake_intf;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 372d412b9b..a232813014 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -258,6 +258,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
'rp2040-clocks-test',
'rp2040-iobank0-test',
+ 'rp2040-ioqspi-test',
'rp2040-pads-test',
'rp2040-resets-test',
'rp2040-rosc-test',
diff --git a/tests/qtest/rp2040-ioqspi-test.c b/tests/qtest/rp2040-ioqspi-test.c
new file mode 100644
index 0000000000..4f94b5f3a2
--- /dev/null
+++ b/tests/qtest/rp2040-ioqspi-test.c
@@ -0,0 +1,81 @@
+/*
+ * QTest testcase for the RP2040 QSPI IO bank block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define IOQSPI_BASE 0x40018000
+#define IOQSPI_SCLK_STATUS 0x00
+#define IOQSPI_SCLK_CTRL 0x04
+#define IOQSPI_SD1_CTRL 0x1c
+#define IOQSPI_PROC0_INTE 0x34
+#define IOQSPI_PROC0_INTF 0x38
+#define IOQSPI_PROC0_INTS 0x3c
+
+#define IOQSPI_CTRL_RESET 0x1f
+#define IOQSPI_SD1_CTRL_SET 0x201c
+#define IOQSPI_SD1_CTRL_CLR 0x301c
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_ioqspi_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SCLK_STATUS), ==,
+ 0);
+ g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SCLK_CTRL), ==,
+ IOQSPI_CTRL_RESET);
+ g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL), ==,
+ IOQSPI_CTRL_RESET);
+
+ qtest_quit(qts);
+}
+
+static void test_ioqspi_atomic_ctrl_aliases(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL, 0);
+ qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL_SET, BIT(17));
+ qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL_CLR, BIT(17));
+ qtest_writel(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL_SET, BIT(9));
+
+ g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL), ==,
+ BIT(9));
+
+ qtest_quit(qts);
+}
+
+static void test_ioqspi_interrupt_force_status(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, IOQSPI_BASE + IOQSPI_PROC0_INTE, BIT(1));
+ qtest_writel(qts, IOQSPI_BASE + IOQSPI_PROC0_INTF, BIT(2));
+
+ g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_PROC0_INTS), ==,
+ BIT(2));
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-ioqspi/reset-values", test_ioqspi_reset_values);
+ qtest_add_func("/rp2040-ioqspi/atomic-ctrl-aliases",
+ test_ioqspi_atomic_ctrl_aliases);
+ qtest_add_func("/rp2040-ioqspi/interrupt-force-status",
+ test_ioqspi_interrupt_force_status);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 15/30] hw/char: integrate RP2040 UART pinmux
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (13 preceding siblings ...)
2026-08-29 23:42 ` [PATCH RFC v2 14/30] hw/misc: add RP2040 IO_QSPI Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 16/30] hw/misc: add RP2040 SIO and multicore support Gilles Grimaud
` (15 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/raspi_pico.c | 33 +++++++
hw/arm/rp2040.c | 57 +++++++++++-
hw/char/pl011.c | 86 ++++++++++++++++-
include/hw/arm/rp2040.h | 5 +
include/hw/char/pl011.h | 6 ++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-uart-test.c | 142 +++++++++++++++++++++++++++++
tests/tcg/arm/system/rp2040-mpu.S | 5 +
tests/tcg/arm/system/rp2040-uart.S | 5 +
9 files changed, 336 insertions(+), 4 deletions(-)
create mode 100644 tests/qtest/rp2040-uart-test.c
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
index 2a65176dc6..e75d61ca07 100644
--- a/hw/arm/raspi_pico.c
+++ b/hw/arm/raspi_pico.c
@@ -32,6 +32,7 @@ struct RaspiPicoMachineState {
char *rosc_random_seed;
uint64_t rosc_random_seed_value;
bool rosc_random_seed_set;
+ bool strict_uart_pins;
};
static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp)
@@ -74,6 +75,8 @@ static void raspi_pico_init(MachineState *machine)
object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
+ qdev_prop_set_bit(DEVICE(&s->soc), "strict-uart-pins",
+ s->strict_uart_pins);
qdev_prop_set_uint64(DEVICE(&s->soc.rosc), "random-seed",
s->rosc_random_seed_value);
qdev_prop_set_bit(DEVICE(&s->soc.rosc), "random-seed-set",
@@ -102,6 +105,28 @@ static void raspi_pico_machine_finalize(Object *obj)
g_free(s->rosc_random_seed);
}
+static bool raspi_pico_get_strict_uart_pins(Object *obj, Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ return s->strict_uart_pins;
+}
+
+static void raspi_pico_set_strict_uart_pins(Object *obj, bool value,
+ Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ s->strict_uart_pins = value;
+}
+
+static void raspi_pico_machine_initfn(Object *obj)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ s->strict_uart_pins = true;
+}
+
static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
{
MachineClass *mc = MACHINE_CLASS(oc);
@@ -123,12 +148,20 @@ static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
"Use a deterministic seed for the "
"ROSC RANDOMBIT stream; if unset, "
"QEMU guest entropy is used");
+ object_class_property_add_bool(oc, "strict-uart-pins",
+ raspi_pico_get_strict_uart_pins,
+ raspi_pico_set_strict_uart_pins);
+ object_class_property_set_description(oc, "strict-uart-pins",
+ "Require the RP2040 IO_BANK0 "
+ "UART pinmux before UART0 or UART1 "
+ "reaches its host serial backend");
}
static const TypeInfo raspi_pico_machine_info = {
.name = TYPE_RASPI_PICO_MACHINE,
.parent = TYPE_MACHINE,
.instance_size = sizeof(RaspiPicoMachineState),
+ .instance_init = raspi_pico_machine_initfn,
.class_init = raspi_pico_machine_class_init,
.instance_finalize = raspi_pico_machine_finalize,
.interfaces = arm_machine_interfaces,
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 28c17232a5..c8fe2c0934 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -50,8 +50,6 @@ static const struct {
hwaddr size;
} rp2040_unimplemented[] = {
{ "rp2040.busctrl", 0x40030000, 0x4000 },
- { "rp2040.uart0_aliases", 0x40035000, 0x3000 },
- { "rp2040.uart1_aliases", 0x40039000, 0x3000 },
{ "rp2040.spi0", 0x4003c000, 0x4000 },
{ "rp2040.spi1", 0x40040000, 0x4000 },
{ "rp2040.i2c0", 0x40044000, 0x4000 },
@@ -142,11 +140,54 @@ static void rp2040_set_irq(void *opaque, int irq, int level)
rp2040_update_nmi(s);
}
+static void rp2040_update_uart_pins(RP2040State *s)
+{
+ pl011_set_tx_connected(&s->uart[0],
+ !s->strict_uart_pins ||
+ s->uart0_tx_pin_enabled);
+ pl011_set_rx_connected(&s->uart[0],
+ !s->strict_uart_pins ||
+ s->uart0_rx_pin_enabled);
+ pl011_set_tx_connected(&s->uart[1],
+ !s->strict_uart_pins ||
+ s->uart1_tx_pin_enabled);
+ pl011_set_rx_connected(&s->uart[1],
+ !s->strict_uart_pins ||
+ s->uart1_rx_pin_enabled);
+}
+
+static void rp2040_set_uart_pin(void *opaque, int pin, int level)
+{
+ RP2040State *s = opaque;
+
+ switch (pin) {
+ case 0:
+ s->uart0_tx_pin_enabled = level;
+ break;
+ case 1:
+ s->uart0_rx_pin_enabled = level;
+ break;
+ case 2:
+ s->uart1_tx_pin_enabled = level;
+ break;
+ case 3:
+ s->uart1_rx_pin_enabled = level;
+ break;
+ default:
+ g_assert_not_reached();
+ }
+
+ rp2040_update_uart_pins(s);
+}
+
static void rp2040_soc_init(Object *obj)
{
RP2040State *s = RP2040(obj);
int i;
+ qdev_init_gpio_in_named(DEVICE(obj), rp2040_set_uart_pin,
+ "uart-pin", 4);
+
object_initialize_child(obj, "proc0", &s->armv7m, TYPE_ARMV7M);
qdev_prop_set_string(DEVICE(&s->armv7m), "cpu-type",
ARM_CPU_TYPE_NAME("cortex-m0"));
@@ -376,6 +417,14 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->iobank0), 0, RP2040_IOBANK0_BASE);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 0,
s->irq[RP2040_IO_IRQ_BANK0]);
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 0,
+ qdev_get_gpio_in_named(dev, "uart-pin", 0));
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 1,
+ qdev_get_gpio_in_named(dev, "uart-pin", 1));
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 0,
+ qdev_get_gpio_in_named(dev, "uart-pin", 2));
+ qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 1,
+ qdev_get_gpio_in_named(dev, "uart-pin", 3));
if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) {
return;
@@ -430,15 +479,19 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 0, uart_base[i]);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart[i]), 1,
+ uart_base[i] + 0x1000);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
s->irq[uart_irq[i]]);
}
+ rp2040_update_uart_pins(s);
}
static const Property rp2040_soc_properties[] = {
DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGION,
MemoryRegion *),
DEFINE_PROP_STRING("bootrom-file", RP2040State, bootrom_file),
+ DEFINE_PROP_BOOL("strict-uart-pins", RP2040State, strict_uart_pins, true),
};
static void rp2040_soc_class_init(ObjectClass *klass, const void *data)
diff --git a/hw/char/pl011.c b/hw/char/pl011.c
index 77fa29ae28..dfd0886ac6 100644
--- a/hw/char/pl011.c
+++ b/hw/char/pl011.c
@@ -101,6 +101,12 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr)
/* Fractional Baud Rate Divider, UARTFBRD */
#define FBRD_MASK 0x3f
+/* RP2040 APB atomic register aliases. */
+#define PL011_ATOMIC_ALIAS_MASK 0x3000
+#define PL011_ATOMIC_XOR 0x1000
+#define PL011_ATOMIC_SET 0x2000
+#define PL011_ATOMIC_CLR 0x3000
+
static const unsigned char pl011_id_arm[8] =
{ 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
static const unsigned char pl011_id_luminary[8] =
@@ -171,6 +177,21 @@ static void pl011_update_dreq(PL011State *s)
(s->dmacr & DMACR_RXDMAE) && s->read_count > 0);
}
+static uint32_t pl011_apply_atomic_alias(uint32_t old, uint32_t value,
+ hwaddr alias)
+{
+ switch (alias) {
+ case PL011_ATOMIC_XOR:
+ return old ^ value;
+ case PL011_ATOMIC_SET:
+ return old | value;
+ case PL011_ATOMIC_CLR:
+ return old & ~value;
+ default:
+ return value;
+ }
+}
+
static inline void pl011_reset_rx_fifo(PL011State *s)
{
s->read_count = 0;
@@ -269,7 +290,13 @@ static void pl011_write_txdata(PL011State *s, uint8_t data)
* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks
*/
- qemu_chr_fe_write_all(&s->chr, &data, 1);
+ if (s->tx_connected) {
+ qemu_chr_fe_write_all(&s->chr, &data, 1);
+ } else if (!s->logged_disconnected_tx) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PL011 data written while TX pin is disconnected\n");
+ s->logged_disconnected_tx = true;
+ }
pl011_loopback_tx(s, data);
s->int_level |= INT_TX;
pl011_update(s);
@@ -535,6 +562,10 @@ static int pl011_can_receive(void *opaque)
* UART continuously enabled regardless of the enable bits.
*/
+ if (!s->rx_connected) {
+ return 0;
+ }
+
trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_available);
return fifo_available;
}
@@ -542,6 +573,9 @@ static int pl011_can_receive(void *opaque)
static void pl011_receive(void *opaque, const uint8_t *buf, int size)
{
trace_pl011_receive(size);
+ if (!PL011(opaque)->rx_connected) {
+ return;
+ }
/*
* In loopback mode, the RX input signal is internally disconnected
* from the entire receiving logics; thus, all inputs are ignored,
@@ -558,11 +592,45 @@ static void pl011_receive(void *opaque, const uint8_t *buf, int size)
static void pl011_event(void *opaque, QEMUChrEvent event)
{
- if (event == CHR_EVENT_BREAK && !pl011_loopback_enabled(opaque)) {
+ PL011State *s = opaque;
+
+ if (event == CHR_EVENT_BREAK && s->rx_connected &&
+ !pl011_loopback_enabled(opaque)) {
pl011_fifo_rx_put(opaque, DR_BE);
}
}
+void pl011_set_tx_connected(PL011State *s, bool connected)
+{
+ s->tx_connected = connected;
+ if (connected) {
+ s->logged_disconnected_tx = false;
+ }
+}
+
+void pl011_set_rx_connected(PL011State *s, bool connected)
+{
+ s->rx_connected = connected;
+ qemu_chr_fe_accept_input(&s->chr);
+}
+
+static uint64_t pl011_atomic_alias_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ return pl011_read(opaque, offset & 0xfff, size);
+}
+
+static void pl011_atomic_alias_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ hwaddr alias = (offset + PL011_ATOMIC_XOR) & PL011_ATOMIC_ALIAS_MASK;
+ hwaddr reg = offset & 0xfff;
+ uint32_t old = pl011_read(opaque, reg, size);
+ uint32_t new = pl011_apply_atomic_alias(old, value, alias);
+
+ pl011_write(opaque, reg, new, size);
+}
+
static void pl011_clock_update(void *opaque, ClockEvent event)
{
PL011State *s = PL011(opaque);
@@ -578,6 +646,14 @@ static const MemoryRegionOps pl011_ops = {
.impl.max_access_size = 4,
};
+static const MemoryRegionOps pl011_atomic_alias_ops = {
+ .read = pl011_atomic_alias_read,
+ .write = pl011_atomic_alias_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl.min_access_size = 4,
+ .impl.max_access_size = 4,
+};
+
static bool pl011_clock_needed(void *opaque)
{
PL011State *s = PL011(opaque);
@@ -666,8 +742,14 @@ static void pl011_init(Object *obj)
PL011State *s = PL011(obj);
int i;
+ s->tx_connected = true;
+ s->rx_connected = true;
memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x1000);
sysbus_init_mmio(sbd, &s->iomem);
+ memory_region_init_io(&s->atomic_alias_iomem, OBJECT(s),
+ &pl011_atomic_alias_ops, s,
+ "pl011-atomic-alias", 0x3000);
+ sysbus_init_mmio(sbd, &s->atomic_alias_iomem);
for (i = 0; i < ARRAY_SIZE(s->irq); i++) {
sysbus_init_irq(sbd, &s->irq[i]);
}
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 068aab584f..aa4b6f4736 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -77,6 +77,11 @@ struct RP2040State {
qemu_irq nmi_irq;
bool irq_level[RP2040_NUM_IRQS];
bool mempowerdown_ready;
+ bool strict_uart_pins;
+ bool uart0_tx_pin_enabled;
+ bool uart0_rx_pin_enabled;
+ bool uart1_tx_pin_enabled;
+ bool uart1_rx_pin_enabled;
Clock *sysclk;
};
diff --git a/include/hw/char/pl011.h b/include/hw/char/pl011.h
index c7b5a3a2b3..20c0b5694b 100644
--- a/include/hw/char/pl011.h
+++ b/include/hw/char/pl011.h
@@ -32,6 +32,7 @@ struct PL011State {
SysBusDevice parent_obj;
MemoryRegion iomem;
+ MemoryRegion atomic_alias_iomem;
uint32_t flags;
uint32_t lcr;
uint32_t rsr;
@@ -53,7 +54,10 @@ struct PL011State {
qemu_irq dreq_rx;
Clock *clk;
bool migrate_clk;
+ bool tx_connected;
+ bool rx_connected;
bool logged_disabled_uart;
+ bool logged_disconnected_tx;
const unsigned char *id;
/*
* Since some users embed this struct directly, we must
@@ -63,5 +67,7 @@ struct PL011State {
};
DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr);
+void pl011_set_tx_connected(PL011State *s, bool connected);
+void pl011_set_rx_connected(PL011State *s, bool connected);
#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index a232813014..b093f5da8a 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -263,6 +263,7 @@ qtests_arm = \
'rp2040-resets-test',
'rp2040-rosc-test',
'rp2040-tbman-test',
+ 'rp2040-uart-test',
'rp2040-vreg-test',
'rp2040-watchdog-test'] : []) + \
(config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) + \
diff --git a/tests/qtest/rp2040-uart-test.c b/tests/qtest/rp2040-uart-test.c
new file mode 100644
index 0000000000..2464c090ad
--- /dev/null
+++ b/tests/qtest/rp2040-uart-test.c
@@ -0,0 +1,142 @@
+/*
+ * QTest testcase for the RP2040 UART pinmux integration.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define IOBANK0_BASE 0x40014000
+#define GPIO_CTRL(n) (0x004 + (n) * 8)
+#define GPIO_FUNC_UART 2
+
+#define UART0_BASE 0x40034000
+#define UART1_BASE 0x40038000
+#define UART_DR 0x000
+#define UART_FR 0x018
+#define UART_CR 0x030
+#define UART_FR_RXFE 0x10
+
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static char *new_output_path(void)
+{
+ int fd;
+ char *path = NULL;
+
+ fd = g_file_open_tmp("rp2040-uart-XXXXXX", &path, NULL);
+ g_assert_cmpint(fd, >=, 0);
+ close(fd);
+ return path;
+}
+
+static void assert_file_contents(const char *path, const char *expected)
+{
+ g_autofree char *contents = NULL;
+ gsize length;
+
+ g_assert_true(g_file_get_contents(path, &contents, &length, NULL));
+ g_assert_cmpuint(length, ==, strlen(expected));
+ g_assert_cmpmem(contents, length, expected, strlen(expected));
+}
+
+static void test_uart0_pinmux(void)
+{
+ g_autofree char *path = new_output_path();
+ QTestState *qts = qtest_initf("-machine raspi-pico "
+ "-chardev file,id=uart0,path=%s "
+ "-serial chardev:uart0", path);
+
+ qtest_writel(qts, UART0_BASE + UART_DR, 'X');
+ qtest_writel(qts, IOBANK0_BASE + GPIO_CTRL(0), GPIO_FUNC_UART);
+ qtest_writel(qts, UART0_BASE + UART_DR, '0');
+ qtest_quit(qts);
+
+ assert_file_contents(path, "0");
+ unlink(path);
+}
+
+static void test_uart1_pinmux(void)
+{
+ g_autofree char *path = new_output_path();
+ QTestState *qts = qtest_initf("-machine raspi-pico -serial null "
+ "-chardev file,id=uart1,path=%s "
+ "-serial chardev:uart1", path);
+
+ qtest_writel(qts, UART1_BASE + UART_DR, 'X');
+ qtest_writel(qts, IOBANK0_BASE + GPIO_CTRL(4), GPIO_FUNC_UART);
+ qtest_writel(qts, UART1_BASE + UART_DR, '1');
+ qtest_quit(qts);
+
+ assert_file_contents(path, "1");
+ unlink(path);
+}
+
+static void test_non_strict_uart_pins(void)
+{
+ g_autofree char *path = new_output_path();
+ QTestState *qts = qtest_initf("-machine raspi-pico,strict-uart-pins=off "
+ "-chardev file,id=uart0,path=%s "
+ "-serial chardev:uart0", path);
+
+ qtest_writel(qts, UART0_BASE + UART_DR, 'L');
+ qtest_quit(qts);
+
+ assert_file_contents(path, "L");
+ unlink(path);
+}
+
+static void test_uart0_rx_pinmux(void)
+{
+ int sock_fd;
+ int retries;
+ QTestState *qts = qtest_init_with_serial("-machine raspi-pico",
+ &sock_fd);
+
+ qtest_writel(qts, IOBANK0_BASE + GPIO_CTRL(1), GPIO_FUNC_UART);
+ g_assert_cmpint(send(sock_fd, "R", 1, 0), ==, 1);
+ for (retries = 0; retries < 1000; retries++) {
+ if (!(qtest_readl(qts, UART0_BASE + UART_FR) & UART_FR_RXFE)) {
+ break;
+ }
+ g_usleep(1000);
+ }
+ g_assert_cmpint(retries, <, 1000);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_DR), ==, 'R');
+
+ close(sock_fd);
+ qtest_quit(qts);
+}
+
+static void test_atomic_aliases(void)
+{
+ QTestState *qts = qtest_init("-machine raspi-pico");
+ uint32_t reset = qtest_readl(qts, UART0_BASE + UART_CR);
+
+ qtest_writel(qts, UART0_BASE + ATOMIC_CLR + UART_CR, 0x100);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), ==,
+ reset & ~0x100);
+ qtest_writel(qts, UART0_BASE + ATOMIC_SET + UART_CR, 0x100);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), ==, reset);
+ qtest_writel(qts, UART0_BASE + ATOMIC_XOR + UART_CR, 0x200);
+ g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), ==,
+ reset ^ 0x200);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-uart/uart0-pinmux", test_uart0_pinmux);
+ qtest_add_func("/rp2040-uart/uart1-pinmux", test_uart1_pinmux);
+ qtest_add_func("/rp2040-uart/non-strict", test_non_strict_uart_pins);
+ qtest_add_func("/rp2040-uart/uart0-rx-pinmux", test_uart0_rx_pinmux);
+ qtest_add_func("/rp2040-uart/atomic-aliases", test_atomic_aliases);
+
+ return g_test_run();
+}
diff --git a/tests/tcg/arm/system/rp2040-mpu.S b/tests/tcg/arm/system/rp2040-mpu.S
index a3aef55f5f..8984e94ca2 100644
--- a/tests/tcg/arm/system/rp2040-mpu.S
+++ b/tests/tcg/arm/system/rp2040-mpu.S
@@ -13,6 +13,8 @@
#define TEST_ADDRESS 0x20001000
#define TEST_VALUE 0x12345678
#define UART0_DR 0x40034000
+#define GPIO0_CTRL 0x40014004
+#define GPIO_FUNC_UART 2
#define MPU_TYPE 0xe000ed90
#define MPU_CTRL 0xe000ed94
#define MPU_RNR 0xe000ed98
@@ -39,6 +41,9 @@ vector_table:
.thumb_func
.global reset_handler
reset_handler:
+ ldr r0, =GPIO0_CTRL
+ movs r1, GPIO_FUNC_UART
+ str r1, [r0]
ldr r0, =start_message
bl puts
diff --git a/tests/tcg/arm/system/rp2040-uart.S b/tests/tcg/arm/system/rp2040-uart.S
index bdb58cc7f2..6058699200 100644
--- a/tests/tcg/arm/system/rp2040-uart.S
+++ b/tests/tcg/arm/system/rp2040-uart.S
@@ -11,6 +11,8 @@
#define SRAM_END 0x20042000
#define UART0_DR 0x40034000
+#define GPIO0_CTRL 0x40014004
+#define GPIO_FUNC_UART 2
#define SYS_EXIT 0x18
#define ADP_STOPPED_APPLICATION_EXIT 0x20026
@@ -25,6 +27,9 @@ vector_table:
.thumb_func
.global reset_handler
reset_handler:
+ ldr r0, =GPIO0_CTRL
+ movs r1, GPIO_FUNC_UART
+ str r1, [r0]
ldr r0, =UART0_DR
ldr r1, =message
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 16/30] hw/misc: add RP2040 SIO and multicore support
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (14 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 15/30] hw/char: integrate RP2040 UART pinmux Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 17/30] tests/tcg/arm: test RP2040 SIO and multicore Gilles Grimaud
` (14 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/raspi_pico.c | 9 +-
hw/arm/rp2040.c | 187 +++++--
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/rp2040_sio.c | 905 ++++++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 12 +-
include/hw/misc/rp2040_sio.h | 57 +++
tests/qtest/meson.build | 2 +
tests/qtest/rp2040-psm-test.c | 81 +++
tests/qtest/rp2040-sio-test.c | 270 ++++++++++
11 files changed, 1484 insertions(+), 44 deletions(-)
create mode 100644 hw/misc/rp2040_sio.c
create mode 100644 include/hw/misc/rp2040_sio.h
create mode 100644 tests/qtest/rp2040-psm-test.c
create mode 100644 tests/qtest/rp2040-sio-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index a1b94532b4..a5cbbaef28 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -387,6 +387,7 @@ config RP2040
select RP2040_PSM
select RP2040_RESETS
select RP2040_ROSC
+ select RP2040_SIO
select RP2040_SYSCFG
select RP2040_SYSINFO
select RP2040_TBMAN
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
index e75d61ca07..dd07c58801 100644
--- a/hw/arm/raspi_pico.c
+++ b/hw/arm/raspi_pico.c
@@ -81,6 +81,7 @@ static void raspi_pico_init(MachineState *machine)
s->rosc_random_seed_value);
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 (machine->firmware) {
qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
machine->firmware);
@@ -94,7 +95,7 @@ static void raspi_pico_init(MachineState *machine)
sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal);
- armv7m_load_kernel(s->soc.armv7m.cpu, machine->kernel_filename,
+ armv7m_load_kernel(s->soc.armv7m[0].cpu, machine->kernel_filename,
RP2040_XIP_BASE, PICO_FLASH_SIZE);
}
@@ -133,9 +134,9 @@ static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
mc->desc = "Raspberry Pi Pico (Cortex-M0+)";
mc->init = raspi_pico_init;
- mc->default_cpus = 1;
- mc->min_cpus = 1;
- mc->max_cpus = 1;
+ mc->default_cpus = RP2040_NUM_CORES;
+ mc->min_cpus = RP2040_NUM_CORES;
+ mc->max_cpus = RP2040_NUM_CORES;
mc->default_ram_size = 0;
mc->no_parallel = 1;
mc->no_floppy = 1;
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index c8fe2c0934..6067d53246 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 "target/arm/cpu.h"
#include "target/arm/cpu-qom.h"
#define RP2040_UART0_BASE 0x40034000
@@ -23,25 +24,42 @@
#define RP2040_UART1_BASE 0x40038000
#define RP2040_UART1_IRQ 21
#define RP2040_IO_IRQ_BANK0 13
+#define RP2040_SIO_IRQ_PROC0 15
+#define RP2040_SIO_IRQ_PROC1 16
+#define RP2040_PROC1 1
/*
- * Temporary boot ROM used until the synthetic ROM is introduced. It loads
- * the reset handler from a vector table at the base of the XIP window.
+ * 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 }.
*/
static const uint8_t rp2040_bootrom[] = {
- 0x00, 0x20, 0x04, 0x20, /* initial SP: 0x20042000 */
- 0x09, 0x00, 0x00, 0x00, /* reset handler: 0x00000009 */
- 0x03, 0x48, /* ldr r0, vtor */
- 0x04, 0x49, /* ldr r1, xip_base */
- 0x01, 0x60, /* str r1, [r0] */
- 0x04, 0x48, /* ldr r0, xip_reset_vector */
- 0x01, 0x68, /* ldr r1, [r0] */
- 0x08, 0x47, /* bx r1 */
- 0xfe, 0xe7, /* b . */
- 0xc0, 0x46, /* nop */
- 0x08, 0xed, 0x00, 0xe0, /* VTOR: 0xe000ed08 */
- 0x00, 0x00, 0x00, 0x10, /* XIP base: 0x10000000 */
- 0x04, 0x00, 0x00, 0x10, /* XIP reset vector: 0x10000004 */
+ 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,
};
static const struct {
@@ -63,7 +81,6 @@ static const struct {
{ "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
{ "rp2040.pio0", 0x50200000, 0x10000 },
{ "rp2040.pio1", 0x50300000, 0x10000 },
- { "rp2040.sio", 0xd0000000, 0x1000 },
};
static MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr,
@@ -117,10 +134,12 @@ static void rp2040_update_nmi(RP2040State *s)
for (i = 0; i < RP2040_NUM_IRQS; i++) {
bool route_to_nmi = nmi_mask & BIT(i);
- qemu_set_irq(s->cpu_irq[i], s->irq_level[i] && !route_to_nmi);
- nmi_level |= s->irq_level[i] && route_to_nmi;
+ bool irq_level = s->irq_level[0][i];
+
+ qemu_set_irq(s->cpu_irq[0][i], irq_level && !route_to_nmi);
+ nmi_level |= irq_level && route_to_nmi;
}
- qemu_set_irq(s->nmi_irq, nmi_level);
+ qemu_set_irq(s->nmi_irq[0], nmi_level);
}
static void rp2040_syscfg_update(void *opaque)
@@ -136,10 +155,75 @@ static void rp2040_set_irq(void *opaque, int irq, int level)
RP2040State *s = opaque;
assert(irq >= 0 && irq < RP2040_NUM_IRQS);
- s->irq_level[irq] = level;
+ s->irq_level[0][irq] = level;
rp2040_update_nmi(s);
}
+static void rp2040_start_core1_async_work(CPUState *cs, run_on_cpu_data data)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+ CPUARMState *env = &cpu->env;
+
+ cpu_reset(cs);
+ cpu->power_state = PSCI_ON;
+ env->halt_reason = NOT_HALTED;
+ arm_rebuild_hflags(env);
+ cs->halted = 0;
+ cpu_resume(cs);
+}
+
+static bool rp2040_core1_powered_off(RP2040State *s)
+{
+ ARMCPU *cpu = s->armv7m[RP2040_PROC1].cpu;
+
+ return !cpu || cpu->power_state == PSCI_OFF;
+}
+
+static void rp2040_start_core1(RP2040State *s)
+{
+ if (!s->armv7m[RP2040_PROC1].cpu ||
+ !rp2040_core1_powered_off(s)) {
+ return;
+ }
+
+ async_run_on_cpu(CPU(s->armv7m[RP2040_PROC1].cpu),
+ rp2040_start_core1_async_work, RUN_ON_CPU_NULL);
+}
+
+static void rp2040_stop_core1_async_work(CPUState *cs, run_on_cpu_data data)
+{
+ ARMCPU *cpu = ARM_CPU(cs);
+
+ cpu->power_state = PSCI_OFF;
+ cpu->env.halt_reason = HALT_PSCI;
+ cs->halted = 1;
+ cs->exception_index = EXCP_HLT;
+}
+
+static void rp2040_stop_core1(RP2040State *s)
+{
+ if (!s->armv7m[RP2040_PROC1].cpu ||
+ rp2040_core1_powered_off(s)) {
+ return;
+ }
+
+ async_run_on_cpu(CPU(s->armv7m[RP2040_PROC1].cpu),
+ rp2040_stop_core1_async_work, RUN_ON_CPU_NULL);
+}
+
+static void rp2040_psm_update(void *opaque)
+{
+ RP2040State *s = opaque;
+ bool proc1_forced_off = rp2040_psm_get_frce_off(&s->psm) &
+ RP2040_PSM_PROC1;
+
+ if (proc1_forced_off) {
+ rp2040_stop_core1(s);
+ } else {
+ rp2040_start_core1(s);
+ }
+}
+
static void rp2040_update_uart_pins(RP2040State *s)
{
pl011_set_tx_connected(&s->uart[0],
@@ -188,11 +272,18 @@ static void rp2040_soc_init(Object *obj)
qdev_init_gpio_in_named(DEVICE(obj), rp2040_set_uart_pin,
"uart-pin", 4);
- object_initialize_child(obj, "proc0", &s->armv7m, TYPE_ARMV7M);
- qdev_prop_set_string(DEVICE(&s->armv7m), "cpu-type",
- ARM_CPU_TYPE_NAME("cortex-m0"));
- qdev_prop_set_uint32(DEVICE(&s->armv7m), "num-irq", RP2040_NUM_IRQS);
- qdev_prop_set_uint32(DEVICE(&s->armv7m), "mpu-ns-regions", 8);
+ for (i = 0; i < RP2040_NUM_CORES; i++) {
+ g_autofree char *name = g_strdup_printf("proc%d", i);
+
+ object_initialize_child(obj, name, &s->armv7m[i], TYPE_ARMV7M);
+ qdev_prop_set_string(DEVICE(&s->armv7m[i]), "cpu-type",
+ ARM_CPU_TYPE_NAME("cortex-m0"));
+ qdev_prop_set_uint32(DEVICE(&s->armv7m[i]), "num-irq",
+ RP2040_NUM_IRQS);
+ qdev_prop_set_uint32(DEVICE(&s->armv7m[i]), "mpu-ns-regions", 8);
+ }
+ qdev_prop_set_bit(DEVICE(&s->armv7m[RP2040_PROC1]),
+ "start-powered-off", true);
for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
g_autofree char *name = g_strdup_printf("uart%d", i);
@@ -230,6 +321,7 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
+ object_initialize_child(obj, "sio", &s->sio, TYPE_RP2040_SIO);
object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
object_initialize_child(obj, "watchdog", &s->watchdog,
@@ -378,26 +470,45 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->psm), 0, RP2040_PSM_BASE);
+ rp2040_psm_set_update_callback(&s->psm, rp2040_psm_update, s);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->resets), errp)) {
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->resets), 0, RP2040_RESETS_BASE);
- qdev_connect_clock_in(DEVICE(&s->armv7m), "cpuclk", s->sysclk);
- object_property_set_link(OBJECT(&s->armv7m), "memory",
- OBJECT(s->board_memory), &err);
- if (err) {
- error_propagate(errp, err);
- return;
- }
- if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m), errp)) {
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->sio), errp)) {
return;
}
- for (i = 0; i < RP2040_NUM_IRQS; i++) {
- s->cpu_irq[i] = qdev_get_gpio_in(DEVICE(&s->armv7m), i);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->sio), 0, RP2040_SIO_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->sio), 0,
+ s->irq[RP2040_SIO_IRQ_PROC0]);
+
+ for (i = 0; i < RP2040_NUM_CORES; i++) {
+ int irq;
+ g_autofree char *name = g_strdup_printf("rp2040.proc%d-memory", i);
+
+ qdev_connect_clock_in(DEVICE(&s->armv7m[i]), "cpuclk", s->sysclk);
+ memory_region_init_alias(&s->cpu_memory[i], OBJECT(dev), name,
+ s->board_memory, 0,
+ memory_region_size(s->board_memory));
+ object_property_set_link(OBJECT(&s->armv7m[i]), "memory",
+ OBJECT(&s->cpu_memory[i]), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->armv7m[i]), errp)) {
+ return;
+ }
+ for (irq = 0; irq < RP2040_NUM_IRQS; irq++) {
+ s->cpu_irq[i][irq] = qdev_get_gpio_in(DEVICE(&s->armv7m[i]),
+ irq);
+ }
+ s->nmi_irq[i] = qdev_get_gpio_in_named(DEVICE(&s->armv7m[i]),
+ "NMI", 0);
}
- s->nmi_irq = qdev_get_gpio_in_named(DEVICE(&s->armv7m), "NMI", 0);
+ rp2040_update_nmi(s);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->syscfg), errp)) {
return;
@@ -417,6 +528,8 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
sysbus_mmio_map(SYS_BUS_DEVICE(&s->iobank0), 0, RP2040_IOBANK0_BASE);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 0,
s->irq[RP2040_IO_IRQ_BANK0]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 1,
+ s->cpu_irq[RP2040_PROC1][RP2040_IO_IRQ_BANK0]);
qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 0,
qdev_get_gpio_in_named(dev, "uart-pin", 0));
qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 1,
@@ -425,6 +538,8 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
qdev_get_gpio_in_named(dev, "uart-pin", 2));
qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 1,
qdev_get_gpio_in_named(dev, "uart-pin", 3));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->sio), 1,
+ s->cpu_irq[RP2040_PROC1][RP2040_SIO_IRQ_PROC1]);
if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) {
return;
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 553a92d4b6..0168f85810 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -128,6 +128,9 @@ config RP2040_RESETS
config RP2040_ROSC
bool
+config RP2040_SIO
+ bool
+
config RP2040_SYSCFG
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 5e4910e83d..5cdbde9b37 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -107,6 +107,7 @@ system_ss.add(when: 'CONFIG_RP2040_PLL', if_true: files('rp2040_pll.c'))
system_ss.add(when: 'CONFIG_RP2040_PSM', if_true: files('rp2040_psm.c'))
system_ss.add(when: 'CONFIG_RP2040_RESETS', if_true: files('rp2040_resets.c'))
system_ss.add(when: 'CONFIG_RP2040_ROSC', if_true: files('rp2040_rosc.c'))
+system_ss.add(when: 'CONFIG_RP2040_SIO', if_true: files('rp2040_sio.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
system_ss.add(when: 'CONFIG_RP2040_TBMAN', if_true: files('rp2040_tbman.c'))
diff --git a/hw/misc/rp2040_sio.c b/hw/misc/rp2040_sio.c
new file mode 100644
index 0000000000..1a04c426a7
--- /dev/null
+++ b/hw/misc/rp2040_sio.c
@@ -0,0 +1,905 @@
+/*
+ * RP2040 single-cycle IO block emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_sio.h"
+#include "hw/core/cpu.h"
+#include "hw/core/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define SIO_CPUID 0x000
+#define SIO_GPIO_IN 0x004
+#define SIO_GPIO_HI_IN 0x008
+#define SIO_GPIO_OUT 0x010
+#define SIO_GPIO_OUT_SET 0x014
+#define SIO_GPIO_OUT_CLR 0x018
+#define SIO_GPIO_OUT_XOR 0x01c
+#define SIO_GPIO_OE 0x020
+#define SIO_GPIO_OE_SET 0x024
+#define SIO_GPIO_OE_CLR 0x028
+#define SIO_GPIO_OE_XOR 0x02c
+#define SIO_GPIO_HI_OUT 0x030
+#define SIO_GPIO_HI_OUT_SET 0x034
+#define SIO_GPIO_HI_OUT_CLR 0x038
+#define SIO_GPIO_HI_OUT_XOR 0x03c
+#define SIO_GPIO_HI_OE 0x040
+#define SIO_GPIO_HI_OE_SET 0x044
+#define SIO_GPIO_HI_OE_CLR 0x048
+#define SIO_GPIO_HI_OE_XOR 0x04c
+#define SIO_FIFO_ST 0x050
+#define SIO_FIFO_WR 0x054
+#define SIO_FIFO_RD 0x058
+#define SIO_SPINLOCK_ST 0x05c
+#define SIO_DIV_UDIVIDEND 0x060
+#define SIO_DIV_UDIVISOR 0x064
+#define SIO_DIV_SDIVIDEND 0x068
+#define SIO_DIV_SDIVISOR 0x06c
+#define SIO_DIV_QUOTIENT 0x070
+#define SIO_DIV_REMAINDER 0x074
+#define SIO_DIV_CSR 0x078
+#define SIO_INTERP0_BASE 0x080
+#define SIO_INTERP1_BASE 0x0c0
+#define SIO_INTERP_BLOCK_SIZE 0x040
+#define SIO_INTERP_ACCUM0 0x00
+#define SIO_INTERP_ACCUM1 0x04
+#define SIO_INTERP_BASE0 0x08
+#define SIO_INTERP_BASE1 0x0c
+#define SIO_INTERP_BASE2 0x10
+#define SIO_INTERP_POP_LANE0 0x14
+#define SIO_INTERP_POP_LANE1 0x18
+#define SIO_INTERP_POP_FULL 0x1c
+#define SIO_INTERP_PEEK_LANE0 0x20
+#define SIO_INTERP_PEEK_LANE1 0x24
+#define SIO_INTERP_PEEK_FULL 0x28
+#define SIO_INTERP_CTRL_LANE0 0x2c
+#define SIO_INTERP_CTRL_LANE1 0x30
+#define SIO_INTERP_ACCUM0_ADD 0x34
+#define SIO_INTERP_ACCUM1_ADD 0x38
+#define SIO_INTERP_BASE_1AND0 0x3c
+#define SIO_SPINLOCK_BASE 0x100
+#define SIO_SPINLOCK_LAST 0x17c
+
+#define SIO_GPIO_MASK 0x3fffffff
+#define SIO_GPIO_HI_MASK 0x3f
+#define SIO_FIFO_ST_VLD BIT(0)
+#define SIO_FIFO_ST_RDY BIT(1)
+#define SIO_FIFO_ST_WC_MASK (BIT(3) | BIT(2))
+#define SIO_DIV_CSR_READY BIT(0)
+#define SIO_DIV_CSR_DIRTY BIT(1)
+#define SIO_INTERP_CTRL_FORCE_MSB_SHIFT 19
+#define SIO_INTERP_CTRL_ADD_RAW BIT(18)
+#define SIO_INTERP_CTRL_CROSS_RESULT BIT(17)
+#define SIO_INTERP_CTRL_CROSS_INPUT BIT(16)
+#define SIO_INTERP_CTRL_SIGNED BIT(15)
+#define SIO_INTERP_CTRL_MASK_MSB_SHIFT 10
+#define SIO_INTERP_CTRL_MASK_LSB_SHIFT 5
+#define SIO_INTERP_CTRL_SHIFT_MASK 0x1f
+#define SIO_INTERP_CTRL_LSB_MASK (0x1f << 5)
+#define SIO_INTERP_CTRL_MSB_MASK (0x1f << 10)
+#define SIO_INTERP0_CTRL_BLEND BIT(21)
+#define SIO_INTERP1_CTRL_CLAMP BIT(22)
+#define SIO_INTERP_CTRL_OVERF BIT(25)
+#define SIO_INTERP_CTRL_OVERF1 BIT(24)
+#define SIO_INTERP_CTRL_OVERF0 BIT(23)
+#define SIO_INTERP_CTRL_OVERF_MASK (SIO_INTERP_CTRL_OVERF | \
+ SIO_INTERP_CTRL_OVERF1 | \
+ SIO_INTERP_CTRL_OVERF0)
+
+typedef struct RP2040SioInterpResult {
+ uint32_t raw[RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t lane[RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t full;
+} RP2040SioInterpResult;
+
+static unsigned rp2040_sio_current_core(void)
+{
+ /*
+ * current_cpu is QEMU's thread-local pointer to the currently executing
+ * guest vCPU, not a host CPU identifier.
+ */
+ if (current_cpu && current_cpu->cpu_index == 1) {
+ return 1;
+ }
+
+ return 0;
+}
+
+static void rp2040_sio_update_fifo_irq(RP2040SioState *s)
+{
+ int i;
+
+ for (i = 0; i < RP2040_SIO_NUM_CORES; i++) {
+ qemu_set_irq(s->fifo_irq[i], s->fifo_level[i] != 0);
+ }
+}
+
+static uint32_t rp2040_sio_fifo_status(RP2040SioState *s, unsigned core)
+{
+ unsigned peer = core ^ 1;
+ uint32_t value = s->fifo_sticky[core] & SIO_FIFO_ST_WC_MASK;
+
+ if (s->fifo_level[core] != 0) {
+ value |= SIO_FIFO_ST_VLD;
+ }
+ if (s->fifo_level[peer] < RP2040_SIO_FIFO_DEPTH) {
+ value |= SIO_FIFO_ST_RDY;
+ }
+
+ return value;
+}
+
+static void rp2040_sio_fifo_push(RP2040SioState *s, unsigned core,
+ uint32_t value)
+{
+ unsigned peer = core ^ 1;
+
+ if (s->fifo_level[peer] == RP2040_SIO_FIFO_DEPTH) {
+ s->fifo_sticky[core] |= BIT(2);
+ return;
+ }
+
+ s->fifo[peer][s->fifo_wptr[peer]] = value;
+ s->fifo_wptr[peer] = (s->fifo_wptr[peer] + 1) % RP2040_SIO_FIFO_DEPTH;
+ s->fifo_level[peer]++;
+ rp2040_sio_update_fifo_irq(s);
+}
+
+static uint32_t rp2040_sio_fifo_pop(RP2040SioState *s, unsigned core)
+{
+ uint32_t value;
+
+ if (s->fifo_level[core] == 0) {
+ s->fifo_sticky[core] |= BIT(3);
+ return 0;
+ }
+
+ value = s->fifo[core][s->fifo_rptr[core]];
+ s->fifo_rptr[core] = (s->fifo_rptr[core] + 1) % RP2040_SIO_FIFO_DEPTH;
+ s->fifo_level[core]--;
+ rp2040_sio_update_fifo_irq(s);
+
+ return value;
+}
+
+static bool rp2040_sio_spinlock_offset(hwaddr offset, unsigned *index)
+{
+ if (offset < SIO_SPINLOCK_BASE || offset > SIO_SPINLOCK_LAST ||
+ (offset & 0x3)) {
+ return false;
+ }
+
+ *index = (offset - SIO_SPINLOCK_BASE) / sizeof(uint32_t);
+ return *index < 32;
+}
+
+static int32_t rp2040_sio_sign32(uint32_t value)
+{
+ int32_t signed_value = (int32_t)value;
+
+ if (signed_value > 0) {
+ return 1;
+ }
+ if (signed_value < 0) {
+ return -1;
+ }
+ return 0;
+}
+
+static void rp2040_sio_divide(RP2040SioState *s, unsigned core,
+ bool is_signed)
+{
+ uint32_t dividend = s->div_dividend[core];
+ uint32_t divisor = s->div_divisor[core];
+ uint32_t quotient;
+ uint32_t remainder;
+
+ if (is_signed) {
+ int32_t signed_dividend = (int32_t)dividend;
+ int32_t signed_divisor = (int32_t)divisor;
+
+ if (signed_divisor == 0) {
+ quotient = -rp2040_sio_sign32(dividend);
+ remainder = dividend;
+ } else if (signed_dividend == INT32_MIN && signed_divisor == -1) {
+ quotient = dividend;
+ remainder = 0;
+ } else {
+ quotient = signed_dividend / signed_divisor;
+ remainder = signed_dividend % signed_divisor;
+ }
+ } else if (divisor == 0) {
+ quotient = UINT32_MAX;
+ remainder = dividend;
+ } else {
+ quotient = dividend / divisor;
+ remainder = dividend % divisor;
+ }
+
+ s->div_quotient[core] = quotient;
+ s->div_remainder[core] = remainder;
+ s->div_dirty[core] = true;
+}
+
+static uint32_t rp2040_sio_div_csr(RP2040SioState *s, unsigned core)
+{
+ return SIO_DIV_CSR_READY |
+ (s->div_dirty[core] ? SIO_DIV_CSR_DIRTY : 0);
+}
+
+static unsigned rp2040_sio_interp_index(unsigned interp, unsigned lane)
+{
+ return interp * RP2040_SIO_INTERP_NUM_LANES + lane;
+}
+
+static unsigned rp2040_sio_interp_base_index(unsigned interp, unsigned base)
+{
+ return interp * RP2040_SIO_INTERP_NUM_BASES + base;
+}
+
+static uint32_t rp2040_sio_interp_ctrl_mask(unsigned interp, unsigned lane)
+{
+ uint32_t mask = SIO_INTERP_CTRL_SHIFT_MASK |
+ SIO_INTERP_CTRL_LSB_MASK |
+ SIO_INTERP_CTRL_MSB_MASK |
+ SIO_INTERP_CTRL_SIGNED |
+ SIO_INTERP_CTRL_CROSS_INPUT |
+ SIO_INTERP_CTRL_CROSS_RESULT |
+ SIO_INTERP_CTRL_ADD_RAW |
+ (0x3 << SIO_INTERP_CTRL_FORCE_MSB_SHIFT);
+
+ if (interp == 0 && lane == 0) {
+ mask |= SIO_INTERP0_CTRL_BLEND;
+ } else if (interp == 1 && lane == 0) {
+ mask |= SIO_INTERP1_CTRL_CLAMP;
+ }
+
+ return mask;
+}
+
+static bool rp2040_sio_interp_offset(hwaddr addr, unsigned *interp,
+ hwaddr *reg)
+{
+ if (addr >= SIO_INTERP0_BASE &&
+ addr < SIO_INTERP0_BASE + SIO_INTERP_BLOCK_SIZE) {
+ *interp = 0;
+ *reg = addr - SIO_INTERP0_BASE;
+ return true;
+ }
+
+ if (addr >= SIO_INTERP1_BASE &&
+ addr < SIO_INTERP1_BASE + SIO_INTERP_BLOCK_SIZE) {
+ *interp = 1;
+ *reg = addr - SIO_INTERP1_BASE;
+ return true;
+ }
+
+ return false;
+}
+
+static uint32_t rp2040_sio_interp_mask(unsigned lsb, unsigned msb)
+{
+ if (msb < lsb) {
+ return 0;
+ }
+
+ if (msb == 31) {
+ return UINT32_MAX << lsb;
+ }
+
+ return (BIT(msb + 1) - 1) & ~(BIT(lsb) - 1);
+}
+
+static uint32_t rp2040_sio_interp_sign_extend(uint32_t value, unsigned msb)
+{
+ if (msb == 31 || !(value & BIT(msb))) {
+ return value;
+ }
+
+ return value | (UINT32_MAX << (msb + 1));
+}
+
+static uint32_t rp2040_sio_interp_raw(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ uint32_t input;
+ uint32_t shifted;
+ uint32_t value;
+ unsigned shift = ctrl & SIO_INTERP_CTRL_SHIFT_MASK;
+ unsigned lsb = (ctrl & SIO_INTERP_CTRL_LSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_LSB_SHIFT;
+ unsigned msb = (ctrl & SIO_INTERP_CTRL_MSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_MSB_SHIFT;
+
+ input = s->interp_accum[core][rp2040_sio_interp_index(
+ interp,
+ (ctrl & SIO_INTERP_CTRL_CROSS_INPUT) ?
+ (lane ^ 1) : lane)];
+ if (ctrl & SIO_INTERP_CTRL_ADD_RAW) {
+ return input;
+ }
+
+ shifted = input >> shift;
+ value = shifted & rp2040_sio_interp_mask(lsb, msb);
+ if (ctrl & SIO_INTERP_CTRL_SIGNED) {
+ value = rp2040_sio_interp_sign_extend(value, msb);
+ }
+
+ return value;
+}
+
+static uint32_t rp2040_sio_interp_force_msb(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane,
+ uint32_t value)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ uint32_t force = (ctrl >> SIO_INTERP_CTRL_FORCE_MSB_SHIFT) & 0x3;
+
+ return value | (force << 28);
+}
+
+static uint32_t rp2040_sio_interp_blend_result(RP2040SioState *s,
+ unsigned core, uint32_t alpha)
+{
+ uint32_t ctrl1 = s->interp_ctrl[core][rp2040_sio_interp_index(0, 1)];
+ uint32_t base0 = s->interp_base[core][rp2040_sio_interp_base_index(0, 0)];
+ uint32_t base1 = s->interp_base[core][rp2040_sio_interp_base_index(0, 1)];
+ int64_t start;
+ int64_t end;
+
+ if (ctrl1 & SIO_INTERP_CTRL_SIGNED) {
+ start = (int16_t)base0;
+ end = (int16_t)base1;
+ } else {
+ start = (uint16_t)base0;
+ end = (uint16_t)base1;
+ }
+
+ return start + (((end - start) * (alpha & 0xff)) >> 8);
+}
+
+static void rp2040_sio_interp_compute(RP2040SioState *s, unsigned core,
+ unsigned interp,
+ RP2040SioInterpResult *result)
+{
+ bool blend = interp == 0 &&
+ (s->interp_ctrl[core][rp2040_sio_interp_index(0, 0)] &
+ SIO_INTERP0_CTRL_BLEND);
+ bool clamp = interp == 1 &&
+ (s->interp_ctrl[core][rp2040_sio_interp_index(1, 0)] &
+ SIO_INTERP1_CTRL_CLAMP);
+ uint32_t base0 = s->interp_base[core]
+ [rp2040_sio_interp_base_index(interp, 0)];
+ uint32_t base1 = s->interp_base[core]
+ [rp2040_sio_interp_base_index(interp, 1)];
+ uint32_t base2 = s->interp_base[core]
+ [rp2040_sio_interp_base_index(interp, 2)];
+
+ result->raw[0] = rp2040_sio_interp_raw(s, core, interp, 0);
+ result->raw[1] = rp2040_sio_interp_raw(s, core, interp, 1);
+ result->lane[0] = base0 + result->raw[0];
+ result->lane[1] = base1 + result->raw[1];
+ result->full = base2 + result->raw[0] + result->raw[1];
+
+ if (blend) {
+ uint32_t alpha = result->raw[1] & 0xff;
+
+ result->lane[0] = alpha;
+ result->lane[1] = rp2040_sio_interp_blend_result(s, core, alpha);
+ result->full = base2 + result->raw[0];
+ } else if (clamp) {
+ uint32_t ctrl0 = s->interp_ctrl[core][rp2040_sio_interp_index(1, 0)];
+
+ if (ctrl0 & SIO_INTERP_CTRL_SIGNED) {
+ int32_t value = result->raw[0];
+ int32_t lower = base0;
+ int32_t upper = base1;
+
+ result->lane[0] = value < lower ? lower :
+ value > upper ? upper : value;
+ } else {
+ result->lane[0] = result->raw[0] < base0 ? base0 :
+ result->raw[0] > base1 ? base1 :
+ result->raw[0];
+ }
+ }
+}
+
+static uint32_t rp2040_sio_interp_pop(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ RP2040SioInterpResult result;
+ uint32_t ctrl0 = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, 0)];
+ uint32_t ctrl1 = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, 1)];
+ uint32_t next0;
+ uint32_t next1;
+
+ rp2040_sio_interp_compute(s, core, interp, &result);
+ next0 = (ctrl0 & SIO_INTERP_CTRL_CROSS_RESULT) ?
+ result.lane[1] : result.lane[0];
+ next1 = (ctrl1 & SIO_INTERP_CTRL_CROSS_RESULT) ?
+ result.lane[0] : result.lane[1];
+
+ s->interp_accum[core][rp2040_sio_interp_index(interp, 0)] = next0;
+ s->interp_accum[core][rp2040_sio_interp_index(interp, 1)] = next1;
+
+ return lane < 2 ? rp2040_sio_interp_force_msb(s, core, interp, lane,
+ result.lane[lane]) :
+ result.full;
+}
+
+static uint32_t rp2040_sio_interp_overf(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ unsigned shift = ctrl & SIO_INTERP_CTRL_SHIFT_MASK;
+ unsigned lsb = (ctrl & SIO_INTERP_CTRL_LSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_LSB_SHIFT;
+ unsigned msb = (ctrl & SIO_INTERP_CTRL_MSB_MASK) >>
+ SIO_INTERP_CTRL_MASK_MSB_SHIFT;
+ uint32_t input = s->interp_accum[core][rp2040_sio_interp_index(
+ interp,
+ (ctrl &
+ SIO_INTERP_CTRL_CROSS_INPUT) ?
+ (lane ^ 1) : lane)];
+ uint32_t shifted = input >> shift;
+
+ return (shifted & ~rp2040_sio_interp_mask(lsb, msb)) != 0;
+}
+
+static uint32_t rp2040_sio_interp_ctrl_read(RP2040SioState *s, unsigned core,
+ unsigned interp, unsigned lane)
+{
+ uint32_t ctrl = s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp, lane)];
+ bool overf0 = rp2040_sio_interp_overf(s, core, interp, 0);
+ bool overf1 = rp2040_sio_interp_overf(s, core, interp, 1);
+
+ ctrl &= ~SIO_INTERP_CTRL_OVERF_MASK;
+ if (overf0) {
+ ctrl |= SIO_INTERP_CTRL_OVERF0;
+ }
+ if (overf1) {
+ ctrl |= SIO_INTERP_CTRL_OVERF1;
+ }
+ if (overf0 || overf1) {
+ ctrl |= SIO_INTERP_CTRL_OVERF;
+ }
+
+ return ctrl;
+}
+
+static uint32_t rp2040_sio_interp_read(RP2040SioState *s, unsigned core,
+ unsigned interp, hwaddr reg)
+{
+ RP2040SioInterpResult result;
+
+ switch (reg) {
+ case SIO_INTERP_ACCUM0:
+ case SIO_INTERP_ACCUM1:
+ return s->interp_accum[core][rp2040_sio_interp_index(
+ interp, reg == SIO_INTERP_ACCUM1)];
+ case SIO_INTERP_BASE0:
+ case SIO_INTERP_BASE1:
+ case SIO_INTERP_BASE2:
+ return s->interp_base[core][rp2040_sio_interp_base_index(
+ interp, (reg - SIO_INTERP_BASE0) /
+ sizeof(uint32_t))];
+ case SIO_INTERP_POP_LANE0:
+ return rp2040_sio_interp_pop(s, core, interp, 0);
+ case SIO_INTERP_POP_LANE1:
+ return rp2040_sio_interp_pop(s, core, interp, 1);
+ case SIO_INTERP_POP_FULL:
+ return rp2040_sio_interp_pop(s, core, interp, 2);
+ case SIO_INTERP_PEEK_LANE0:
+ case SIO_INTERP_PEEK_LANE1:
+ case SIO_INTERP_PEEK_FULL:
+ rp2040_sio_interp_compute(s, core, interp, &result);
+ if (reg == SIO_INTERP_PEEK_LANE0) {
+ return rp2040_sio_interp_force_msb(s, core, interp, 0,
+ result.lane[0]);
+ }
+ if (reg == SIO_INTERP_PEEK_LANE1) {
+ return rp2040_sio_interp_force_msb(s, core, interp, 1,
+ result.lane[1]);
+ }
+ return result.full;
+ case SIO_INTERP_CTRL_LANE0:
+ case SIO_INTERP_CTRL_LANE1:
+ return rp2040_sio_interp_ctrl_read(s, core, interp,
+ reg == SIO_INTERP_CTRL_LANE1);
+ case SIO_INTERP_ACCUM0_ADD:
+ case SIO_INTERP_ACCUM1_ADD:
+ return rp2040_sio_interp_raw(s, core, interp,
+ reg == SIO_INTERP_ACCUM1_ADD);
+ case SIO_INTERP_BASE_1AND0:
+ return 0;
+ default:
+ return 0;
+ }
+}
+
+static void rp2040_sio_interp_write(RP2040SioState *s, unsigned core,
+ unsigned interp, hwaddr reg,
+ uint32_t value)
+{
+ unsigned lane;
+ unsigned base;
+ bool base01_signed;
+
+ switch (reg) {
+ case SIO_INTERP_ACCUM0:
+ case SIO_INTERP_ACCUM1:
+ lane = reg == SIO_INTERP_ACCUM1;
+ s->interp_accum[core][rp2040_sio_interp_index(interp, lane)] = value;
+ break;
+ case SIO_INTERP_BASE0:
+ case SIO_INTERP_BASE1:
+ case SIO_INTERP_BASE2:
+ base = (reg - SIO_INTERP_BASE0) / sizeof(uint32_t);
+ s->interp_base[core][rp2040_sio_interp_base_index(interp, base)] =
+ value;
+ break;
+ case SIO_INTERP_CTRL_LANE0:
+ case SIO_INTERP_CTRL_LANE1:
+ lane = reg == SIO_INTERP_CTRL_LANE1;
+ s->interp_ctrl[core][rp2040_sio_interp_index(interp, lane)] =
+ value & rp2040_sio_interp_ctrl_mask(interp, lane);
+ break;
+ case SIO_INTERP_ACCUM0_ADD:
+ case SIO_INTERP_ACCUM1_ADD:
+ lane = reg == SIO_INTERP_ACCUM1_ADD;
+ s->interp_accum[core][rp2040_sio_interp_index(interp, lane)] +=
+ value & 0x00ffffff;
+ break;
+ case SIO_INTERP_BASE_1AND0:
+ base01_signed = interp == 0 &&
+ (s->interp_ctrl[core]
+ [rp2040_sio_interp_index(0, 0)] &
+ SIO_INTERP0_CTRL_BLEND) ?
+ (s->interp_ctrl[core]
+ [rp2040_sio_interp_index(0, 1)] &
+ SIO_INTERP_CTRL_SIGNED) : false;
+
+ for (base = 0; base < 2; base++) {
+ uint16_t half = value >> (base * 16);
+ bool sign = base01_signed ||
+ (s->interp_ctrl[core]
+ [rp2040_sio_interp_index(interp,
+ base)] &
+ SIO_INTERP_CTRL_SIGNED);
+
+ s->interp_base[core][rp2040_sio_interp_base_index(interp, base)] =
+ sign ? (uint32_t)(int16_t)half : half;
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t rp2040_sio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040SioState *s = opaque;
+ unsigned core = rp2040_sio_current_core();
+ unsigned interp;
+ unsigned index;
+ unsigned shift = (addr & 3) * 8;
+ hwaddr interp_reg;
+ hwaddr reg = addr & ~3ULL;
+ uint64_t value;
+
+ if (rp2040_sio_interp_offset(reg, &interp, &interp_reg)) {
+ value = rp2040_sio_interp_read(s, core, interp, interp_reg);
+ return extract64(value, shift, size * 8);
+ }
+
+ switch (reg) {
+ case SIO_CPUID:
+ value = core;
+ break;
+ case SIO_GPIO_IN:
+ value = s->gpio_in;
+ break;
+ case SIO_GPIO_HI_IN:
+ value = s->gpio_hi_in;
+ break;
+ case SIO_GPIO_OUT:
+ value = s->gpio_out;
+ break;
+ case SIO_GPIO_OE:
+ value = s->gpio_oe;
+ break;
+ case SIO_GPIO_HI_OUT:
+ value = s->gpio_hi_out;
+ break;
+ case SIO_GPIO_HI_OE:
+ value = s->gpio_hi_oe;
+ break;
+ case SIO_FIFO_ST:
+ value = rp2040_sio_fifo_status(s, core);
+ break;
+ case SIO_FIFO_RD:
+ value = rp2040_sio_fifo_pop(s, core);
+ break;
+ case SIO_SPINLOCK_ST:
+ value = s->spinlock_st;
+ break;
+ case SIO_DIV_UDIVIDEND:
+ case SIO_DIV_SDIVIDEND:
+ value = s->div_dividend[core];
+ break;
+ case SIO_DIV_UDIVISOR:
+ case SIO_DIV_SDIVISOR:
+ value = s->div_divisor[core];
+ break;
+ case SIO_DIV_QUOTIENT:
+ value = s->div_quotient[core];
+ s->div_dirty[core] = false;
+ break;
+ case SIO_DIV_REMAINDER:
+ value = s->div_remainder[core];
+ break;
+ case SIO_DIV_CSR:
+ value = rp2040_sio_div_csr(s, core);
+ break;
+ default:
+ if (rp2040_sio_spinlock_offset(reg, &index)) {
+ value = (s->spinlock_st & BIT(index)) ? 0 : BIT(index);
+ s->spinlock_st |= BIT(index);
+ } else {
+ value = 0;
+ rp2040_log_unimplemented_read("sio", size,
+ RP2040_SIO_BASE + addr, addr,
+ value);
+ }
+ break;
+ }
+
+ return extract64(value, shift, size * 8);
+}
+
+static void rp2040_sio_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040SioState *s = opaque;
+ unsigned core = rp2040_sio_current_core();
+ unsigned interp;
+ unsigned index;
+ unsigned shift = (addr & 3) * 8;
+ hwaddr interp_reg;
+ hwaddr reg = addr & ~3ULL;
+ uint32_t value = (uint32_t)value64 << shift;
+
+ if (rp2040_sio_interp_offset(reg, &interp, &interp_reg)) {
+ rp2040_sio_interp_write(s, core, interp, interp_reg, value);
+ return;
+ }
+
+ switch (reg) {
+ case SIO_GPIO_OUT:
+ s->gpio_out = value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OUT_SET:
+ s->gpio_out |= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OUT_CLR:
+ s->gpio_out &= ~(value & SIO_GPIO_MASK);
+ break;
+ case SIO_GPIO_OUT_XOR:
+ s->gpio_out ^= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OE:
+ s->gpio_oe = value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OE_SET:
+ s->gpio_oe |= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_OE_CLR:
+ s->gpio_oe &= ~(value & SIO_GPIO_MASK);
+ break;
+ case SIO_GPIO_OE_XOR:
+ s->gpio_oe ^= value & SIO_GPIO_MASK;
+ break;
+ case SIO_GPIO_HI_OUT:
+ s->gpio_hi_out = value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OUT_SET:
+ s->gpio_hi_out |= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OUT_CLR:
+ s->gpio_hi_out &= ~(value & SIO_GPIO_HI_MASK);
+ break;
+ case SIO_GPIO_HI_OUT_XOR:
+ s->gpio_hi_out ^= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OE:
+ s->gpio_hi_oe = value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OE_SET:
+ s->gpio_hi_oe |= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_GPIO_HI_OE_CLR:
+ s->gpio_hi_oe &= ~(value & SIO_GPIO_HI_MASK);
+ break;
+ case SIO_GPIO_HI_OE_XOR:
+ s->gpio_hi_oe ^= value & SIO_GPIO_HI_MASK;
+ break;
+ case SIO_FIFO_ST:
+ s->fifo_sticky[core] &= ~(value & SIO_FIFO_ST_WC_MASK);
+ break;
+ case SIO_FIFO_WR:
+ rp2040_sio_fifo_push(s, core, value);
+ break;
+ case SIO_DIV_UDIVIDEND:
+ s->div_dividend[core] = value;
+ rp2040_sio_divide(s, core, false);
+ break;
+ case SIO_DIV_UDIVISOR:
+ s->div_divisor[core] = value;
+ rp2040_sio_divide(s, core, false);
+ break;
+ case SIO_DIV_SDIVIDEND:
+ s->div_dividend[core] = value;
+ rp2040_sio_divide(s, core, true);
+ break;
+ case SIO_DIV_SDIVISOR:
+ s->div_divisor[core] = value;
+ rp2040_sio_divide(s, core, true);
+ break;
+ case SIO_DIV_QUOTIENT:
+ s->div_quotient[core] = value;
+ s->div_dirty[core] = true;
+ break;
+ case SIO_DIV_REMAINDER:
+ s->div_remainder[core] = value;
+ s->div_dirty[core] = true;
+ break;
+ default:
+ if (rp2040_sio_spinlock_offset(reg, &index)) {
+ s->spinlock_st &= ~BIT(index);
+ } else {
+ rp2040_log_unimplemented_write("sio", size,
+ RP2040_SIO_BASE + addr, addr,
+ value64);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_sio_ops = {
+ .read = rp2040_sio_read,
+ .write = rp2040_sio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false,
+ },
+};
+
+static void rp2040_sio_reset(DeviceState *dev)
+{
+ RP2040SioState *s = RP2040_SIO(dev);
+
+ s->gpio_in &= SIO_GPIO_MASK;
+ s->gpio_hi_in &= SIO_GPIO_HI_MASK;
+ s->gpio_out = 0;
+ s->gpio_oe = 0;
+ s->gpio_hi_out = 0;
+ s->gpio_hi_oe = 0;
+ memset(s->fifo, 0, sizeof(s->fifo));
+ memset(s->fifo_rptr, 0, sizeof(s->fifo_rptr));
+ memset(s->fifo_wptr, 0, sizeof(s->fifo_wptr));
+ memset(s->fifo_level, 0, sizeof(s->fifo_level));
+ memset(s->fifo_sticky, 0, sizeof(s->fifo_sticky));
+ memset(s->div_dividend, 0, sizeof(s->div_dividend));
+ memset(s->div_divisor, 0, sizeof(s->div_divisor));
+ memset(s->div_quotient, 0, sizeof(s->div_quotient));
+ memset(s->div_remainder, 0, sizeof(s->div_remainder));
+ memset(s->div_dirty, 0, sizeof(s->div_dirty));
+ memset(s->interp_accum, 0, sizeof(s->interp_accum));
+ memset(s->interp_base, 0, sizeof(s->interp_base));
+ memset(s->interp_ctrl, 0, sizeof(s->interp_ctrl));
+ s->spinlock_st = 0;
+ rp2040_sio_update_fifo_irq(s);
+}
+
+static void rp2040_sio_init(Object *obj)
+{
+ RP2040SioState *s = RP2040_SIO(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_sio_ops, s,
+ TYPE_RP2040_SIO, 0x1000);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->fifo_irq[0]);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->fifo_irq[1]);
+}
+
+static const VMStateDescription vmstate_rp2040_sio = {
+ .name = TYPE_RP2040_SIO,
+ .version_id = 3,
+ .minimum_version_id = 2,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(gpio_out, RP2040SioState),
+ VMSTATE_UINT32(gpio_in, RP2040SioState),
+ VMSTATE_UINT32(gpio_hi_in, RP2040SioState),
+ VMSTATE_UINT32(gpio_oe, RP2040SioState),
+ VMSTATE_UINT32(gpio_hi_out, RP2040SioState),
+ VMSTATE_UINT32(gpio_hi_oe, RP2040SioState),
+ VMSTATE_UINT32_2DARRAY(fifo, RP2040SioState, RP2040_SIO_NUM_CORES,
+ RP2040_SIO_FIFO_DEPTH),
+ VMSTATE_UINT8_ARRAY(fifo_rptr, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT8_ARRAY(fifo_wptr, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT8_ARRAY(fifo_level, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(fifo_sticky, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_dividend, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_divisor, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_quotient, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_ARRAY(div_remainder, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_BOOL_ARRAY(div_dirty, RP2040SioState,
+ RP2040_SIO_NUM_CORES),
+ VMSTATE_UINT32_2DARRAY_V(interp_accum, RP2040SioState,
+ RP2040_SIO_NUM_CORES,
+ RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES, 3),
+ VMSTATE_UINT32_2DARRAY_V(interp_base, RP2040SioState,
+ RP2040_SIO_NUM_CORES,
+ RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_BASES, 3),
+ VMSTATE_UINT32_2DARRAY_V(interp_ctrl, RP2040SioState,
+ RP2040_SIO_NUM_CORES,
+ RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES, 3),
+ VMSTATE_UINT32(spinlock_st, RP2040SioState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property rp2040_sio_properties[] = {
+ DEFINE_PROP_UINT32("gpio-in", RP2040SioState, gpio_in, 0),
+ DEFINE_PROP_UINT32("gpio-hi-in", RP2040SioState, gpio_hi_in, 0),
+};
+
+static void rp2040_sio_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_sio_reset);
+ device_class_set_props(dc, rp2040_sio_properties);
+ dc->vmsd = &vmstate_rp2040_sio;
+}
+
+static const TypeInfo rp2040_sio_info = {
+ .name = TYPE_RP2040_SIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040SioState),
+ .instance_init = rp2040_sio_init,
+ .class_init = rp2040_sio_class_init,
+};
+
+static void rp2040_sio_register_types(void)
+{
+ type_register_static(&rp2040_sio_info);
+}
+type_init(rp2040_sio_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index aa4b6f4736..5c0047c0f7 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -21,6 +21,7 @@
#include "hw/misc/rp2040_psm.h"
#include "hw/misc/rp2040_resets.h"
#include "hw/misc/rp2040_rosc.h"
+#include "hw/misc/rp2040_sio.h"
#include "hw/misc/rp2040_syscfg.h"
#include "hw/misc/rp2040_sysinfo.h"
#include "hw/misc/rp2040_tbman.h"
@@ -41,12 +42,13 @@ OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
#define RP2040_SRAM5_BASE 0x20041000
#define RP2040_SRAM_HI_SIZE (4 * KiB)
+#define RP2040_NUM_CORES 2
#define RP2040_NUM_IRQS 32
struct RP2040State {
SysBusDevice parent_obj;
- ARMv7MState armv7m;
+ ARMv7MState armv7m[RP2040_NUM_CORES];
PL011State uart[2];
RP2040ClocksState clocks;
RP2040IoBank0State iobank0;
@@ -60,12 +62,14 @@ struct RP2040State {
RP2040SysCfgState syscfg;
RP2040SysInfoState sysinfo;
RP2040RoscState rosc;
+ RP2040SioState sio;
RP2040TbmanState tbman;
RP2040VregState vreg;
RP2040WatchdogState watchdog;
RP2040XoscState xosc;
MemoryRegion *board_memory;
+ MemoryRegion cpu_memory[RP2040_NUM_CORES];
MemoryRegion rom;
MemoryRegion rom_poweroff;
MemoryRegion sram[6];
@@ -73,9 +77,9 @@ struct RP2040State {
char *bootrom_file;
qemu_irq *irq;
- qemu_irq cpu_irq[RP2040_NUM_IRQS];
- qemu_irq nmi_irq;
- bool irq_level[RP2040_NUM_IRQS];
+ qemu_irq cpu_irq[RP2040_NUM_CORES][RP2040_NUM_IRQS];
+ qemu_irq nmi_irq[RP2040_NUM_CORES];
+ bool irq_level[RP2040_NUM_CORES][RP2040_NUM_IRQS];
bool mempowerdown_ready;
bool strict_uart_pins;
bool uart0_tx_pin_enabled;
diff --git a/include/hw/misc/rp2040_sio.h b/include/hw/misc/rp2040_sio.h
new file mode 100644
index 0000000000..e2a79a8a34
--- /dev/null
+++ b/include/hw/misc/rp2040_sio.h
@@ -0,0 +1,57 @@
+/*
+ * RP2040 single-cycle IO block emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_SIO_H
+#define HW_MISC_RP2040_SIO_H
+
+#include "hw/core/irq.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_SIO "rp2040-sio"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040SioState, RP2040_SIO)
+
+#define RP2040_SIO_BASE 0xd0000000
+#define RP2040_SIO_NUM_CORES 2
+#define RP2040_SIO_FIFO_DEPTH 8
+#define RP2040_SIO_NUM_INTERPS 2
+#define RP2040_SIO_INTERP_NUM_LANES 2
+#define RP2040_SIO_INTERP_NUM_BASES 3
+
+struct RP2040SioState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq fifo_irq[RP2040_SIO_NUM_CORES];
+ uint32_t gpio_in;
+ uint32_t gpio_hi_in;
+ uint32_t gpio_out;
+ uint32_t gpio_oe;
+ uint32_t gpio_hi_out;
+ uint32_t gpio_hi_oe;
+ uint32_t fifo[RP2040_SIO_NUM_CORES][RP2040_SIO_FIFO_DEPTH];
+ uint8_t fifo_rptr[RP2040_SIO_NUM_CORES];
+ uint8_t fifo_wptr[RP2040_SIO_NUM_CORES];
+ uint8_t fifo_level[RP2040_SIO_NUM_CORES];
+ uint32_t fifo_sticky[RP2040_SIO_NUM_CORES];
+ uint32_t div_dividend[RP2040_SIO_NUM_CORES];
+ uint32_t div_divisor[RP2040_SIO_NUM_CORES];
+ uint32_t div_quotient[RP2040_SIO_NUM_CORES];
+ uint32_t div_remainder[RP2040_SIO_NUM_CORES];
+ bool div_dirty[RP2040_SIO_NUM_CORES];
+ uint32_t interp_accum[RP2040_SIO_NUM_CORES]
+ [RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t interp_base[RP2040_SIO_NUM_CORES]
+ [RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_BASES];
+ uint32_t interp_ctrl[RP2040_SIO_NUM_CORES]
+ [RP2040_SIO_NUM_INTERPS *
+ RP2040_SIO_INTERP_NUM_LANES];
+ uint32_t spinlock_st;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index b093f5da8a..3054809620 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -260,8 +260,10 @@ qtests_arm = \
'rp2040-iobank0-test',
'rp2040-ioqspi-test',
'rp2040-pads-test',
+ 'rp2040-psm-test',
'rp2040-resets-test',
'rp2040-rosc-test',
+ 'rp2040-sio-test',
'rp2040-tbman-test',
'rp2040-uart-test',
'rp2040-vreg-test',
diff --git a/tests/qtest/rp2040-psm-test.c b/tests/qtest/rp2040-psm-test.c
new file mode 100644
index 0000000000..aa04880a89
--- /dev/null
+++ b/tests/qtest/rp2040-psm-test.c
@@ -0,0 +1,81 @@
+/*
+ * QTest testcase for the RP2040 power-on state machine.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define PSM_BASE 0x40010000
+#define PSM_FRCE_ON 0x00
+#define PSM_FRCE_OFF 0x04
+#define PSM_WDSEL 0x08
+#define PSM_DONE 0x0c
+#define PSM_VALID_MASK 0x0001ffff
+#define PSM_PROC1 BIT(16)
+
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static QTestState *rp2040_psm_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_reset_values(void)
+{
+ QTestState *qts = rp2040_psm_start();
+
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_ON), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_WDSEL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE), ==,
+ PSM_VALID_MASK);
+
+ qtest_quit(qts);
+}
+
+static void test_proc1_force_off(void)
+{
+ QTestState *qts = rp2040_psm_start();
+
+ qtest_writel(qts, PSM_BASE + ATOMIC_SET + PSM_FRCE_OFF, PSM_PROC1);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), ==,
+ PSM_PROC1);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE) & PSM_PROC1, ==,
+ 0);
+
+ qtest_writel(qts, PSM_BASE + ATOMIC_CLR + PSM_FRCE_OFF, PSM_PROC1);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE) & PSM_PROC1, ==,
+ PSM_PROC1);
+
+ qtest_quit(qts);
+}
+
+static void test_register_masks(void)
+{
+ QTestState *qts = rp2040_psm_start();
+
+ qtest_writel(qts, PSM_BASE + PSM_FRCE_ON, UINT32_MAX);
+ qtest_writel(qts, PSM_BASE + PSM_WDSEL, UINT32_MAX);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_ON), ==,
+ PSM_VALID_MASK);
+ g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_WDSEL), ==,
+ PSM_VALID_MASK);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-psm/reset-values", test_reset_values);
+ qtest_add_func("/rp2040-psm/proc1-force-off", test_proc1_force_off);
+ qtest_add_func("/rp2040-psm/register-masks", test_register_masks);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/rp2040-sio-test.c b/tests/qtest/rp2040-sio-test.c
new file mode 100644
index 0000000000..13ee513ebe
--- /dev/null
+++ b/tests/qtest/rp2040-sio-test.c
@@ -0,0 +1,270 @@
+/*
+ * QTest testcase for the RP2040 SIO block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define SIO_BASE 0xd0000000
+#define SIO_CPUID 0x000
+#define SIO_GPIO_IN 0x004
+#define SIO_GPIO_HI_IN 0x008
+#define SIO_GPIO_OUT 0x010
+#define SIO_GPIO_OUT_SET 0x014
+#define SIO_GPIO_OUT_CLR 0x018
+#define SIO_GPIO_OUT_XOR 0x01c
+#define SIO_GPIO_HI_OUT 0x030
+#define SIO_GPIO_HI_OUT_SET 0x034
+#define SIO_GPIO_HI_OUT_CLR 0x038
+#define SIO_GPIO_HI_OUT_XOR 0x03c
+#define SIO_FIFO_ST 0x050
+#define SIO_FIFO_RD 0x058
+#define SIO_SPINLOCK_ST 0x05c
+#define SIO_SPINLOCK0 0x100
+#define SIO_DIV_UDIVIDEND 0x060
+#define SIO_DIV_UDIVISOR 0x064
+#define SIO_DIV_SDIVIDEND 0x068
+#define SIO_DIV_SDIVISOR 0x06c
+#define SIO_DIV_QUOTIENT 0x070
+#define SIO_DIV_REMAINDER 0x074
+#define SIO_DIV_CSR 0x078
+#define SIO_INTERP0_ACCUM0 0x080
+#define SIO_INTERP0_ACCUM1 0x084
+#define SIO_INTERP0_BASE0 0x088
+#define SIO_INTERP0_BASE1 0x08c
+#define SIO_INTERP0_POP_LANE1 0x098
+#define SIO_INTERP0_PEEK_LANE0 0x0a0
+#define SIO_INTERP0_PEEK_LANE1 0x0a4
+#define SIO_INTERP0_CTRL_LANE0 0x0ac
+#define SIO_INTERP0_CTRL_LANE1 0x0b0
+#define SIO_INTERP0_ACCUM0_ADD 0x0b4
+#define SIO_INTERP0_BASE_1AND0 0x0bc
+#define SIO_INTERP1_ACCUM0 0x0c0
+#define SIO_INTERP1_BASE0 0x0c8
+#define SIO_INTERP1_BASE1 0x0cc
+#define SIO_INTERP1_PEEK_LANE0 0x0e0
+#define SIO_INTERP1_CTRL_LANE0 0x0ec
+
+#define INTERP_CTRL_SHIFT(n) (n)
+#define INTERP_CTRL_MASK(lsb, msb) (((lsb) << 5) | ((msb) << 10))
+#define INTERP_CTRL_SIGNED BIT(15)
+#define INTERP_CTRL_CROSS_RESULT BIT(17)
+#define INTERP0_CTRL_BLEND BIT(21)
+#define INTERP1_CTRL_CLAMP BIT(22)
+
+#define DIV_CSR_READY BIT(0)
+#define DIV_CSR_DIRTY BIT(1)
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_sio_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_CPUID), ==, 0);
+ g_assert_cmphex(qtest_readb(qts, SIO_BASE + SIO_CPUID), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_IN), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_IN), ==, BIT(1));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_OUT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_OUT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), ==, BIT(1));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_gpio_alias_registers(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT, BIT(3));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT_SET, BIT(5));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT_CLR, BIT(3));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_OUT_XOR, BIT(7));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_OUT), ==,
+ BIT(5) | BIT(7));
+
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT, BIT(0));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT_SET, BIT(2));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT_CLR, BIT(0));
+ qtest_writel(qts, SIO_BASE + SIO_GPIO_HI_OUT_XOR, BIT(5));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_OUT), ==,
+ BIT(2) | BIT(5));
+
+ qtest_quit(qts);
+}
+
+static void test_sio_fifo_empty_read_sets_roe(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_readl(qts, SIO_BASE + SIO_FIFO_RD);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), ==,
+ BIT(3) | BIT(1));
+
+ qtest_writel(qts, SIO_BASE + SIO_FIFO_ST, BIT(3));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), ==, BIT(1));
+
+ qtest_quit(qts);
+}
+
+static void test_sio_spinlock_claim_release(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK0), ==, BIT(0));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK0), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), ==, BIT(0));
+
+ qtest_writel(qts, SIO_BASE + SIO_SPINLOCK0, 0);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_divider(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_DIV_UDIVIDEND, 100);
+ qtest_writel(qts, SIO_BASE + SIO_DIV_UDIVISOR, 7);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_DIV_CSR), ==,
+ DIV_CSR_READY | DIV_CSR_DIRTY);
+ g_assert_cmpuint(qtest_readl(qts, SIO_BASE + SIO_DIV_REMAINDER), ==, 2);
+ g_assert_cmpuint(qtest_readl(qts, SIO_BASE + SIO_DIV_QUOTIENT), ==, 14);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_DIV_CSR), ==,
+ DIV_CSR_READY);
+
+ qtest_writel(qts, SIO_BASE + SIO_DIV_SDIVIDEND, -100);
+ qtest_writel(qts, SIO_BASE + SIO_DIV_SDIVISOR, 7);
+ g_assert_cmpint((int32_t)qtest_readl(qts,
+ SIO_BASE + SIO_DIV_QUOTIENT),
+ ==, -14);
+ g_assert_cmpint((int32_t)qtest_readl(qts,
+ SIO_BASE + SIO_DIV_REMAINDER),
+ ==, -2);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_mask_and_sign(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM0, 0x1234abcd);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ INTERP_CTRL_MASK(4, 7));
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_ACCUM0_ADD),
+ ==, 0x000000c0);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ INTERP_CTRL_MASK(4, 7) | INTERP_CTRL_SIGNED);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_ACCUM0_ADD),
+ ==, 0xffffffc0);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_pop_cross_result(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t full_mask = INTERP_CTRL_MASK(0, 31);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM0, 123);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM1, 456);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE0, 1);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE1, 0);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ full_mask | INTERP_CTRL_CROSS_RESULT);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE1,
+ full_mask | INTERP_CTRL_CROSS_RESULT);
+
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE0),
+ ==, 124);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_POP_LANE1),
+ ==, 456);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE0),
+ ==, 457);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_blend(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t full_mask = INTERP_CTRL_MASK(0, 31);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE0,
+ full_mask | INTERP0_CTRL_BLEND);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE1, full_mask);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_ACCUM1, 128);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE_1AND0, 0x30005000);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE1),
+ ==, 0x00004000);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE_1AND0, 0xe000f000);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE1),
+ ==, 0x0000e800);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_CTRL_LANE1,
+ full_mask | INTERP_CTRL_SIGNED);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE_1AND0, 0xe000f000);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE1),
+ ==, 0xffffe800);
+
+ qtest_quit(qts);
+}
+
+static void test_sio_interp_clamp(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_CTRL_LANE0,
+ INTERP1_CTRL_CLAMP | INTERP_CTRL_SIGNED |
+ INTERP_CTRL_SHIFT(2) | INTERP_CTRL_MASK(0, 29));
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_BASE0, 0);
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_BASE1, 255);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, -1024);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0),
+ ==, 0);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, 512);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0),
+ ==, 128);
+
+ qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, 1024);
+ g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0),
+ ==, 255);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-sio/reset-values", test_sio_reset_values);
+ qtest_add_func("/rp2040-sio/gpio-alias-registers",
+ test_sio_gpio_alias_registers);
+ qtest_add_func("/rp2040-sio/fifo-empty-read-sets-roe",
+ test_sio_fifo_empty_read_sets_roe);
+ qtest_add_func("/rp2040-sio/spinlock-claim-release",
+ test_sio_spinlock_claim_release);
+ qtest_add_func("/rp2040-sio/divider", test_sio_divider);
+ qtest_add_func("/rp2040-sio/interp-mask-and-sign",
+ test_sio_interp_mask_and_sign);
+ qtest_add_func("/rp2040-sio/interp-pop-cross-result",
+ test_sio_interp_pop_cross_result);
+ qtest_add_func("/rp2040-sio/interp-blend", test_sio_interp_blend);
+ qtest_add_func("/rp2040-sio/interp-clamp", test_sio_interp_clamp);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 17/30] tests/tcg/arm: test RP2040 SIO and multicore
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (15 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 16/30] hw/misc: add RP2040 SIO and multicore support Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 18/30] hw/timer: add RP2040 timer and alarms Gilles Grimaud
` (13 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
tests/tcg/arm/Makefile.softmmu-target | 13 +-
tests/tcg/arm/system/rp2040-core1-launch.S | 216 +++++++++++++++++++
tests/tcg/arm/system/rp2040-core1-launch.ref | 4 +
tests/tcg/arm/system/rp2040-psm-proc1.S | 143 ++++++++++++
tests/tcg/arm/system/rp2040-psm-proc1.ref | 5 +
tests/tcg/arm/system/rp2040-sio-divider.S | 169 +++++++++++++++
tests/tcg/arm/system/rp2040-sio-divider.ref | 2 +
tests/tcg/arm/system/rp2040-sio-fifo.S | 154 +++++++++++++
tests/tcg/arm/system/rp2040-sio-fifo.ref | 2 +
9 files changed, 705 insertions(+), 3 deletions(-)
create mode 100644 tests/tcg/arm/system/rp2040-core1-launch.S
create mode 100644 tests/tcg/arm/system/rp2040-core1-launch.ref
create mode 100644 tests/tcg/arm/system/rp2040-psm-proc1.S
create mode 100644 tests/tcg/arm/system/rp2040-psm-proc1.ref
create mode 100644 tests/tcg/arm/system/rp2040-sio-divider.S
create mode 100644 tests/tcg/arm/system/rp2040-sio-divider.ref
create mode 100644 tests/tcg/arm/system/rp2040-sio-fifo.S
create mode 100644 tests/tcg/arm/system/rp2040-sio-fifo.ref
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index 429416ca58..28e4c5774a 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -20,7 +20,9 @@ run-test-armv6m-undef: QEMU_OPTS=-semihosting-config enable=on,target=native,cha
ARM_TESTS+=test-armv6m-undef
-RP2040_TESTS=rp2040-boot rp2040-uart rp2040-mpu
+RP2040_TESTS=rp2040-boot rp2040-uart rp2040-mpu \
+ rp2040-sio-fifo rp2040-sio-divider rp2040-psm-proc1 \
+ rp2040-core1-launch
$(RP2040_TESTS): %: %.S rp2040-minimal.ld
$(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
@@ -31,11 +33,16 @@ $(RP2040_TESTS): %: %.S rp2040-minimal.ld
run-rp2040-boot: QEMU_OPTS=-M raspi-pico \
-semihosting-config enable=on,target=native -kernel
-run-rp2040-uart run-rp2040-mpu: QEMU_OPTS=-M raspi-pico \
+run-rp2040-uart run-rp2040-mpu \
+run-rp2040-sio-fifo run-rp2040-sio-divider \
+run-rp2040-psm-proc1 run-rp2040-core1-launch: \
+ QEMU_OPTS=-M raspi-pico,strict-uart-pins=off \
-serial chardev:output \
-semihosting-config enable=on,target=native -kernel
-run-rp2040-uart run-rp2040-mpu: run-rp2040-%: rp2040-%
+run-rp2040-uart run-rp2040-mpu \
+run-rp2040-sio-fifo run-rp2040-sio-divider \
+run-rp2040-psm-proc1 run-rp2040-core1-launch: run-rp2040-%: rp2040-%
$(call run-test, $<, \
$(QEMU) -monitor none -display none \
-chardev file$(COMMA)path=$<.out$(COMMA)id=output \
diff --git a/tests/tcg/arm/system/rp2040-core1-launch.S b/tests/tcg/arm/system/rp2040-core1-launch.S
new file mode 100644
index 0000000000..a280014b3c
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-core1-launch.S
@@ -0,0 +1,216 @@
+/*
+ * Raspberry Pi Pico core 1 mask-ROM launch protocol test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define CORE1_STACK 0x20041000
+#define UART0_DR 0x40034000
+#define SIO_BASE 0xd0000000
+#define SIO_CPUID 0x00
+#define SIO_FIFO_ST 0x50
+#define SIO_FIFO_WR 0x54
+#define SIO_FIFO_RD 0x58
+#define SIO_FIFO_ST_VLD 0x01
+#define SIO_FIFO_ST_RDY 0x02
+#define CORE1_ACK 0x0000d01e
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ ldr r4, =SIO_BASE
+ ldr r0, [r4, SIO_CPUID]
+ cmp r0, 0
+ bne fail_core0_id
+
+ ldr r6, =launch_sequence
+ movs r7, 0
+
+launch_next:
+ ldr r0, [r6, r7]
+ cmp r0, 0
+ bne launch_send
+ bl fifo_drain
+ sev
+
+launch_send:
+ bl fifo_push
+ bl fifo_pop
+ ldr r1, [r6, r7]
+ cmp r0, r1
+ bne fail_echo
+ adds r7, 4
+ cmp r7, 24
+ bne launch_next
+
+ ldr r0, =echo_message
+ bl puts
+
+ bl fifo_pop
+ ldr r1, =CORE1_ACK
+ cmp r0, r1
+ bne fail_ack
+
+ ldr r0, =ack_message
+ bl puts
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+.thumb_func
+core1_entry:
+ ldr r4, =SIO_BASE
+ ldr r0, [r4, SIO_CPUID]
+ cmp r0, 1
+ bne core1_failed
+ bl core1_send_ack
+
+2:
+ b 2b
+
+.thumb_func
+core1_send_ack:
+ push {lr}
+ ldr r0, =CORE1_ACK
+ bl fifo_push
+ pop {pc}
+
+core1_failed:
+ ldr r0, =0xbad10001
+ bl fifo_push
+
+3:
+ b 3b
+
+.thumb_func
+fifo_drain:
+ push {r0, r1, lr}
+
+4:
+ ldr r0, [r4, SIO_FIFO_ST]
+ movs r1, SIO_FIFO_ST_VLD
+ tst r0, r1
+ beq 5f
+ ldr r0, [r4, SIO_FIFO_RD]
+ b 4b
+
+5:
+ pop {r0, r1, pc}
+
+.thumb_func
+fifo_push:
+ push {r1, r2, lr}
+
+6:
+ ldr r1, [r4, SIO_FIFO_ST]
+ movs r2, SIO_FIFO_ST_RDY
+ tst r1, r2
+ beq 6b
+ str r0, [r4, SIO_FIFO_WR]
+ sev
+ pop {r1, r2, pc}
+
+.thumb_func
+fifo_pop:
+ push {r1, lr}
+
+7:
+ ldr r1, [r4, SIO_FIFO_ST]
+ movs r0, SIO_FIFO_ST_VLD
+ tst r1, r0
+ beq 7b
+ ldr r0, [r4, SIO_FIFO_RD]
+ pop {r1, pc}
+
+fail_core0_id:
+ ldr r0, =fail_core0_id_message
+ b test_failed
+
+fail_echo:
+ ldr r0, =fail_echo_message
+ b test_failed
+
+fail_ack:
+ ldr r0, =fail_ack_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+8:
+ b 8b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+9:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 10f
+ str r1, [r2]
+ adds r0, 1
+ b 9b
+
+10:
+ bx lr
+
+.section .rodata, "a", %progbits
+.align 2
+launch_sequence:
+ .word 0
+ .word 0
+ .word 1
+ .word vector_table
+ .word CORE1_STACK
+ .word core1_entry + 1
+
+start_message:
+ .asciz "PICO TCG CORE1 START\n"
+echo_message:
+ .asciz "PICO TCG CORE1 ECHO OK\n"
+ack_message:
+ .asciz "PICO TCG CORE1 ACK OK\n"
+ok_message:
+ .asciz "PICO TCG CORE1 OK\n"
+fail_core0_id_message:
+ .asciz "PICO TCG CORE1 FAIL: CORE0 ID\n"
+fail_echo_message:
+ .asciz "PICO TCG CORE1 FAIL: ECHO\n"
+fail_ack_message:
+ .asciz "PICO TCG CORE1 FAIL: ACK\n"
+fail_exception_message:
+ .asciz "PICO TCG CORE1 FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-core1-launch.ref b/tests/tcg/arm/system/rp2040-core1-launch.ref
new file mode 100644
index 0000000000..b9f9d4252d
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-core1-launch.ref
@@ -0,0 +1,4 @@
+PICO TCG CORE1 START
+PICO TCG CORE1 ECHO OK
+PICO TCG CORE1 ACK OK
+PICO TCG CORE1 OK
diff --git a/tests/tcg/arm/system/rp2040-psm-proc1.S b/tests/tcg/arm/system/rp2040-psm-proc1.S
new file mode 100644
index 0000000000..f88166b906
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-psm-proc1.S
@@ -0,0 +1,143 @@
+/*
+ * Raspberry Pi Pico PSM PROC1 power-state test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define PSM_BASE 0x40010000
+#define PSM_FRCE_OFF 0x04
+#define PSM_DONE 0x0c
+#define PSM_SET_ALIAS 0x2000
+#define PSM_CLR_ALIAS 0x3000
+#define PSM_PROC1 0x00010000
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ ldr r4, =(PSM_BASE + PSM_FRCE_OFF)
+ ldr r5, =PSM_PROC1
+ ldr r6, =(PSM_BASE + PSM_DONE)
+
+ ldr r0, [r4]
+ tst r0, r5
+ bne fail_initial
+ ldr r0, [r6]
+ tst r0, r5
+ beq fail_initial
+
+ ldr r0, =initial_message
+ bl puts
+
+ ldr r0, =(PSM_BASE + PSM_SET_ALIAS + PSM_FRCE_OFF)
+ str r5, [r0]
+ ldr r0, [r4]
+ tst r0, r5
+ beq fail_force_off
+ ldr r0, [r6]
+ tst r0, r5
+ bne fail_force_off
+
+ ldr r0, =off_message
+ bl puts
+
+ ldr r0, =(PSM_BASE + PSM_CLR_ALIAS + PSM_FRCE_OFF)
+ str r5, [r0]
+ ldr r0, [r4]
+ tst r0, r5
+ bne fail_restart
+ ldr r0, [r6]
+ tst r0, r5
+ beq fail_restart
+
+ ldr r0, =on_message
+ bl puts
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_initial:
+ ldr r0, =fail_initial_message
+ b test_failed
+
+fail_force_off:
+ ldr r0, =fail_force_off_message
+ b test_failed
+
+fail_restart:
+ ldr r0, =fail_restart_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG PSM PROC1 START\n"
+initial_message:
+ .asciz "PICO TCG PSM PROC1 INITIAL ON\n"
+off_message:
+ .asciz "PICO TCG PSM PROC1 FORCED OFF\n"
+on_message:
+ .asciz "PICO TCG PSM PROC1 RESTARTED\n"
+ok_message:
+ .asciz "PICO TCG PSM PROC1 OK\n"
+fail_initial_message:
+ .asciz "PICO TCG PSM PROC1 FAIL: INITIAL\n"
+fail_force_off_message:
+ .asciz "PICO TCG PSM PROC1 FAIL: FORCE OFF\n"
+fail_restart_message:
+ .asciz "PICO TCG PSM PROC1 FAIL: RESTART\n"
+fail_exception_message:
+ .asciz "PICO TCG PSM PROC1 FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-psm-proc1.ref b/tests/tcg/arm/system/rp2040-psm-proc1.ref
new file mode 100644
index 0000000000..b14da263b9
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-psm-proc1.ref
@@ -0,0 +1,5 @@
+PICO TCG PSM PROC1 START
+PICO TCG PSM PROC1 INITIAL ON
+PICO TCG PSM PROC1 FORCED OFF
+PICO TCG PSM PROC1 RESTARTED
+PICO TCG PSM PROC1 OK
diff --git a/tests/tcg/arm/system/rp2040-sio-divider.S b/tests/tcg/arm/system/rp2040-sio-divider.S
new file mode 100644
index 0000000000..fc924a2f3d
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-sio-divider.S
@@ -0,0 +1,169 @@
+/*
+ * Raspberry Pi Pico SIO hardware divider test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define SIO_BASE 0xd0000000
+#define SIO_DIV_UDIVIDEND 0x60
+#define SIO_DIV_UDIVISOR 0x64
+#define SIO_DIV_SDIVIDEND 0x68
+#define SIO_DIV_SDIVISOR 0x6c
+#define SIO_DIV_QUOTIENT 0x70
+#define SIO_DIV_REMAINDER 0x74
+#define SIO_DIV_CSR 0x78
+#define SIO_DIV_CSR_READY_DIRTY 0x03
+#define SIO_DIV_CSR_READY 0x01
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.macro fail_if_ne reg, expected, target
+ ldr r2, =\expected
+ cmp \reg, r2
+ beq 0f
+ b \target
+0:
+.endm
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+ ldr r4, =SIO_BASE
+
+ /* Unsigned: 100 / 7 = 14 remainder 2. */
+ movs r0, 100
+ str r0, [r4, SIO_DIV_UDIVIDEND]
+ movs r0, 7
+ str r0, [r4, SIO_DIV_UDIVISOR]
+ ldr r0, [r4, SIO_DIV_CSR]
+ fail_if_ne r0, SIO_DIV_CSR_READY_DIRTY, fail_unsigned
+ ldr r0, [r4, SIO_DIV_REMAINDER]
+ fail_if_ne r0, 2, fail_unsigned
+ ldr r0, [r4, SIO_DIV_CSR]
+ fail_if_ne r0, SIO_DIV_CSR_READY_DIRTY, fail_unsigned
+ ldr r0, [r4, SIO_DIV_QUOTIENT]
+ fail_if_ne r0, 14, fail_unsigned
+ ldr r0, [r4, SIO_DIV_CSR]
+ fail_if_ne r0, SIO_DIV_CSR_READY, fail_unsigned
+
+ /* Signed: -100 / 7 = -14 remainder -2. */
+ ldr r0, =-100
+ str r0, [r4, SIO_DIV_SDIVIDEND]
+ movs r0, 7
+ str r0, [r4, SIO_DIV_SDIVISOR]
+ ldr r0, [r4, SIO_DIV_REMAINDER]
+ fail_if_ne r0, -2, fail_signed
+ ldr r0, [r4, SIO_DIV_QUOTIENT]
+ fail_if_ne r0, -14, fail_signed
+
+ /* Division by zero follows the RP2040 result contract. */
+ ldr r0, =0x1234
+ str r0, [r4, SIO_DIV_UDIVIDEND]
+ movs r0, 0
+ str r0, [r4, SIO_DIV_UDIVISOR]
+ ldr r0, [r4, SIO_DIV_REMAINDER]
+ fail_if_ne r0, 0x1234, fail_divzero
+ ldr r0, [r4, SIO_DIV_QUOTIENT]
+ fail_if_ne r0, 0xffffffff, fail_divzero
+
+ /* Result writes restore a saved divider context and set DIRTY. */
+ ldr r0, =0xaabbccdd
+ str r0, [r4, SIO_DIV_REMAINDER]
+ ldr r0, =0x11223344
+ str r0, [r4, SIO_DIV_QUOTIENT]
+ ldr r0, [r4, SIO_DIV_CSR]
+ fail_if_ne r0, SIO_DIV_CSR_READY_DIRTY, fail_restore
+ ldr r0, [r4, SIO_DIV_REMAINDER]
+ fail_if_ne r0, 0xaabbccdd, fail_restore
+ ldr r0, [r4, SIO_DIV_QUOTIENT]
+ fail_if_ne r0, 0x11223344, fail_restore
+ ldr r0, [r4, SIO_DIV_CSR]
+ fail_if_ne r0, SIO_DIV_CSR_READY, fail_restore
+
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_unsigned:
+ ldr r0, =fail_unsigned_message
+ b test_failed
+
+fail_signed:
+ ldr r0, =fail_signed_message
+ b test_failed
+
+fail_divzero:
+ ldr r0, =fail_divzero_message
+ b test_failed
+
+fail_restore:
+ ldr r0, =fail_restore_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG SIO DIVIDER START\n"
+ok_message:
+ .asciz "PICO TCG SIO DIVIDER OK\n"
+fail_unsigned_message:
+ .asciz "PICO TCG SIO DIVIDER FAIL: UNSIGNED\n"
+fail_signed_message:
+ .asciz "PICO TCG SIO DIVIDER FAIL: SIGNED\n"
+fail_divzero_message:
+ .asciz "PICO TCG SIO DIVIDER FAIL: DIVZERO\n"
+fail_restore_message:
+ .asciz "PICO TCG SIO DIVIDER FAIL: RESTORE\n"
+fail_exception_message:
+ .asciz "PICO TCG SIO DIVIDER FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-sio-divider.ref b/tests/tcg/arm/system/rp2040-sio-divider.ref
new file mode 100644
index 0000000000..c03c8338dd
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-sio-divider.ref
@@ -0,0 +1,2 @@
+PICO TCG SIO DIVIDER START
+PICO TCG SIO DIVIDER OK
diff --git a/tests/tcg/arm/system/rp2040-sio-fifo.S b/tests/tcg/arm/system/rp2040-sio-fifo.S
new file mode 100644
index 0000000000..4395167203
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-sio-fifo.S
@@ -0,0 +1,154 @@
+/*
+ * Raspberry Pi Pico SIO FIFO status test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define SIO_BASE 0xd0000000
+#define SIO_CPUID 0x00
+#define SIO_FIFO_ST 0x50
+#define SIO_FIFO_RD 0x58
+#define SIO_FIFO_ST_VLD 0x01
+#define SIO_FIFO_ST_RDY 0x02
+#define SIO_FIFO_ST_ROE 0x08
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ ldr r4, =SIO_BASE
+ ldr r0, [r4, SIO_CPUID]
+ cmp r0, 0
+ bne fail_cpuid
+
+ ldr r0, [r4, SIO_FIFO_ST]
+ movs r1, SIO_FIFO_ST_RDY
+ tst r0, r1
+ beq fail_rdy
+
+ /* The mask ROM on core 1 may have queued its initial ready word. */
+ movs r1, SIO_FIFO_ST_VLD
+ tst r0, r1
+ beq fifo_empty
+ ldr r0, [r4, SIO_FIFO_RD]
+ cmp r0, 0
+ bne fail_ready_word
+
+fifo_empty:
+ ldr r0, [r4, SIO_FIFO_ST]
+ movs r1, SIO_FIFO_ST_VLD
+ tst r0, r1
+ bne fail_vld
+
+ ldr r0, [r4, SIO_FIFO_RD]
+ ldr r0, [r4, SIO_FIFO_ST]
+ movs r1, SIO_FIFO_ST_ROE
+ tst r0, r1
+ beq fail_roe
+
+ str r1, [r4, SIO_FIFO_ST]
+ ldr r0, [r4, SIO_FIFO_ST]
+ tst r0, r1
+ bne fail_roe_clear
+
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_cpuid:
+ ldr r0, =fail_cpuid_message
+ b test_failed
+
+fail_rdy:
+ ldr r0, =fail_rdy_message
+ b test_failed
+
+fail_vld:
+ ldr r0, =fail_vld_message
+ b test_failed
+
+fail_ready_word:
+ ldr r0, =fail_ready_word_message
+ b test_failed
+
+fail_roe:
+ ldr r0, =fail_roe_message
+ b test_failed
+
+fail_roe_clear:
+ ldr r0, =fail_roe_clear_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG SIO FIFO START\n"
+ok_message:
+ .asciz "PICO TCG SIO FIFO OK\n"
+fail_cpuid_message:
+ .asciz "PICO TCG SIO FIFO FAIL: CPUID\n"
+fail_rdy_message:
+ .asciz "PICO TCG SIO FIFO FAIL: RDY\n"
+fail_vld_message:
+ .asciz "PICO TCG SIO FIFO FAIL: VLD\n"
+fail_ready_word_message:
+ .asciz "PICO TCG SIO FIFO FAIL: READY WORD\n"
+fail_roe_message:
+ .asciz "PICO TCG SIO FIFO FAIL: ROE\n"
+fail_roe_clear_message:
+ .asciz "PICO TCG SIO FIFO FAIL: ROE CLEAR\n"
+fail_exception_message:
+ .asciz "PICO TCG SIO FIFO FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-sio-fifo.ref b/tests/tcg/arm/system/rp2040-sio-fifo.ref
new file mode 100644
index 0000000000..c0de9abb9f
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-sio-fifo.ref
@@ -0,0 +1,2 @@
+PICO TCG SIO FIFO START
+PICO TCG SIO FIFO OK
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 18/30] hw/timer: add RP2040 timer and alarms
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (16 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 17/30] tests/tcg/arm: test RP2040 SIO and multicore Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 19/30] tests/tcg/arm: test RP2040 timer alarm interrupt Gilles Grimaud
` (12 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/rp2040.c | 10 +-
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/rp2040_timer.c | 372 ++++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 2 +
include/hw/misc/rp2040_timer.h | 39 ++++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-timer-test.c | 149 +++++++++++++
9 files changed, 577 insertions(+), 1 deletion(-)
create mode 100644 hw/misc/rp2040_timer.c
create mode 100644 include/hw/misc/rp2040_timer.h
create mode 100644 tests/qtest/rp2040-timer-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index a5cbbaef28..81cd84b642 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -391,6 +391,7 @@ config RP2040
select RP2040_SYSCFG
select RP2040_SYSINFO
select RP2040_TBMAN
+ select RP2040_TIMER
select RP2040_VREG
select RP2040_WATCHDOG
select RP2040_XOSC
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 6067d53246..79c7fda151 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -74,7 +74,6 @@ static const struct {
{ "rp2040.i2c1", 0x40048000, 0x4000 },
{ "rp2040.adc", 0x4004c000, 0x4000 },
{ "rp2040.pwm", 0x40050000, 0x4000 },
- { "rp2040.timer", 0x40054000, 0x4000 },
{ "rp2040.rtc", 0x4005c000, 0x4000 },
{ "rp2040.dma", 0x50000000, 0x1000 },
{ "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
@@ -323,6 +322,7 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
object_initialize_child(obj, "sio", &s->sio, TYPE_RP2040_SIO);
object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
+ object_initialize_child(obj, "timer", &s->timer, TYPE_RP2040_TIMER);
object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
object_initialize_child(obj, "watchdog", &s->watchdog,
TYPE_RP2040_WATCHDOG);
@@ -556,6 +556,14 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->tbman), 0, RP2040_TBMAN_BASE);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, RP2040_TIMER_BASE);
+ for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), i, s->irq[i]);
+ }
+
if (!sysbus_realize(SYS_BUS_DEVICE(&s->vreg), errp)) {
return;
}
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 0168f85810..d98b8a4674 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -140,6 +140,9 @@ config RP2040_SYSINFO
config RP2040_TBMAN
bool
+config RP2040_TIMER
+ bool
+
config RP2040_VREG
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 5cdbde9b37..ba2e9489ee 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -111,6 +111,7 @@ system_ss.add(when: 'CONFIG_RP2040_SIO', if_true: files('rp2040_sio.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinfo.c'))
system_ss.add(when: 'CONFIG_RP2040_TBMAN', if_true: files('rp2040_tbman.c'))
+system_ss.add(when: 'CONFIG_RP2040_TIMER', if_true: files('rp2040_timer.c'))
system_ss.add(when: 'CONFIG_RP2040_VREG', if_true: files('rp2040_vreg.c'))
system_ss.add(when: 'CONFIG_RP2040_WATCHDOG', if_true: files('rp2040_watchdog.c'))
system_ss.add(when: 'CONFIG_RP2040_XOSC', if_true: files('rp2040_xosc.c'))
diff --git a/hw/misc/rp2040_timer.c b/hw/misc/rp2040_timer.c
new file mode 100644
index 0000000000..01d2d70793
--- /dev/null
+++ b/hw/misc/rp2040_timer.c
@@ -0,0 +1,372 @@
+/*
+ * RP2040 timer emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/irq.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_timer.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+
+#define TIMER_TIMEHW 0x00
+#define TIMER_TIMELW 0x04
+#define TIMER_TIMEHR 0x08
+#define TIMER_TIMELR 0x0c
+#define TIMER_ALARM0 0x10
+#define TIMER_ALARM3 0x1c
+#define TIMER_ARMED 0x20
+#define TIMER_TIMERAWH 0x24
+#define TIMER_TIMERAWL 0x28
+#define TIMER_DBGPAUSE 0x2c
+#define TIMER_PAUSE 0x30
+#define TIMER_INTR 0x34
+#define TIMER_INTE 0x38
+#define TIMER_INTF 0x3c
+#define TIMER_INTS 0x40
+
+#define TIMER_ALARM_MASK 0x0f
+#define TIMER_DBGPAUSE_MASK (BIT(2) | BIT(1))
+#define TIMER_PAUSE_MASK BIT(0)
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_timer_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 uint64_t rp2040_timer_now_us(RP2040TimerState *s)
+{
+ if (s->pause & TIMER_PAUSE_MASK) {
+ return s->paused_time_us;
+ }
+
+ return qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000 + s->time_offset_us;
+}
+
+static uint32_t rp2040_timer_ints(RP2040TimerState *s)
+{
+ uint32_t ints = (s->intr & s->inte) | s->intf;
+
+ return ints & TIMER_ALARM_MASK;
+}
+
+static void rp2040_timer_update_irq(RP2040TimerState *s)
+{
+ uint32_t ints = rp2040_timer_ints(s);
+ int i;
+
+ for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+ qemu_set_irq(s->irq[i], !!(ints & BIT(i)));
+ }
+}
+
+static void rp2040_timer_update_alarm(RP2040TimerState *s, unsigned alarm)
+{
+ uint64_t now_us;
+ uint32_t delta;
+
+ if (!(s->armed & BIT(alarm)) || (s->pause & TIMER_PAUSE_MASK)) {
+ timer_del(s->alarm_timer[alarm]);
+ return;
+ }
+
+ now_us = rp2040_timer_now_us(s);
+ delta = s->alarm[alarm] - (uint32_t)now_us;
+ if (delta == 0 || delta > INT32_MAX) {
+ timer_del(s->alarm_timer[alarm]);
+ s->armed &= ~BIT(alarm);
+ s->intr |= BIT(alarm);
+ rp2040_timer_update_irq(s);
+ } else {
+ timer_mod(s->alarm_timer[alarm],
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ (int64_t)delta * 1000);
+ }
+}
+
+static void rp2040_timer_update_alarms(RP2040TimerState *s)
+{
+ int i;
+
+ for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+ rp2040_timer_update_alarm(s, i);
+ }
+}
+
+static bool rp2040_timer_alarm_offset(hwaddr offset, unsigned *alarm)
+{
+ if (offset < TIMER_ALARM0 || offset > TIMER_ALARM3 || (offset & 0x3)) {
+ return false;
+ }
+
+ *alarm = (offset - TIMER_ALARM0) / sizeof(uint32_t);
+ return true;
+}
+
+static uint64_t rp2040_timer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040TimerState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t now_us = rp2040_timer_now_us(s);
+ unsigned alarm;
+ uint64_t value;
+
+ switch (offset) {
+ case TIMER_TIMEHR:
+ case TIMER_TIMERAWH:
+ value = now_us >> 32;
+ break;
+ case TIMER_TIMELR:
+ case TIMER_TIMERAWL:
+ value = (uint32_t)now_us;
+ break;
+ case TIMER_ARMED:
+ value = s->armed;
+ break;
+ case TIMER_DBGPAUSE:
+ value = s->dbgpause;
+ break;
+ case TIMER_PAUSE:
+ value = s->pause;
+ break;
+ case TIMER_INTR:
+ value = s->intr;
+ break;
+ case TIMER_INTE:
+ value = s->inte;
+ break;
+ case TIMER_INTF:
+ value = s->intf;
+ break;
+ case TIMER_INTS:
+ value = rp2040_timer_ints(s);
+ break;
+ default:
+ if (rp2040_timer_alarm_offset(offset, &alarm)) {
+ value = s->alarm[alarm];
+ } else {
+ value = 0;
+ rp2040_log_unimplemented_read("timer", size,
+ RP2040_TIMER_BASE + addr, offset,
+ value);
+ }
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_timer_set_time(RP2040TimerState *s, uint64_t value)
+{
+ if (s->pause & TIMER_PAUSE_MASK) {
+ s->paused_time_us = value;
+ } else {
+ s->time_offset_us = value -
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000;
+ }
+ rp2040_timer_update_alarms(s);
+}
+
+static void rp2040_timer_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040TimerState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+ uint32_t old;
+ uint64_t now_us;
+ unsigned alarm;
+
+ switch (offset) {
+ case TIMER_TIMEHW:
+ now_us = rp2040_timer_now_us(s);
+ rp2040_timer_set_time(s, ((uint64_t)value << 32) |
+ (uint32_t)now_us);
+ break;
+ case TIMER_TIMELW:
+ now_us = rp2040_timer_now_us(s);
+ rp2040_timer_set_time(s, (now_us & UINT64_C(0xffffffff00000000)) |
+ value);
+ break;
+ case TIMER_ARMED:
+ s->armed &= ~(value & TIMER_ALARM_MASK);
+ rp2040_timer_update_alarms(s);
+ break;
+ case TIMER_DBGPAUSE:
+ s->dbgpause = rp2040_timer_apply_alias(s->dbgpause, value, alias) &
+ TIMER_DBGPAUSE_MASK;
+ break;
+ case TIMER_PAUSE:
+ old = s->pause;
+ now_us = rp2040_timer_now_us(s);
+ s->pause = rp2040_timer_apply_alias(s->pause, value, alias) &
+ TIMER_PAUSE_MASK;
+ if (!(old & TIMER_PAUSE_MASK) && (s->pause & TIMER_PAUSE_MASK)) {
+ s->paused_time_us = now_us;
+ } else if ((old & TIMER_PAUSE_MASK) &&
+ !(s->pause & TIMER_PAUSE_MASK)) {
+ s->time_offset_us = s->paused_time_us -
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000;
+ rp2040_timer_update_alarms(s);
+ }
+ break;
+ case TIMER_INTR:
+ s->intr &= ~(value & TIMER_ALARM_MASK);
+ rp2040_timer_update_irq(s);
+ break;
+ case TIMER_INTE:
+ s->inte = rp2040_timer_apply_alias(s->inte, value, alias) &
+ TIMER_ALARM_MASK;
+ rp2040_timer_update_irq(s);
+ break;
+ case TIMER_INTF:
+ s->intf = rp2040_timer_apply_alias(s->intf, value, alias) &
+ TIMER_ALARM_MASK;
+ rp2040_timer_update_irq(s);
+ break;
+ default:
+ if (rp2040_timer_alarm_offset(offset, &alarm)) {
+ s->alarm[alarm] = value;
+ s->armed |= BIT(alarm);
+ rp2040_timer_update_alarm(s, alarm);
+ } else {
+ rp2040_log_unimplemented_write("timer", size,
+ RP2040_TIMER_BASE + addr, offset,
+ value64);
+ }
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_timer_ops = {
+ .read = rp2040_timer_read,
+ .write = rp2040_timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_timer_alarm_tick(void *opaque)
+{
+ uintptr_t packed = (uintptr_t)opaque;
+ RP2040TimerState *s = (RP2040TimerState *)(packed & ~(uintptr_t)0x3);
+ unsigned alarm = packed & 0x3;
+
+ s->armed &= ~BIT(alarm);
+ s->intr |= BIT(alarm);
+ rp2040_timer_update_irq(s);
+}
+
+static void rp2040_timer_reset(DeviceState *dev)
+{
+ RP2040TimerState *s = RP2040_TIMER(dev);
+ int i;
+
+ s->time_offset_us = -qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000;
+ s->paused_time_us = 0;
+ memset(s->alarm, 0, sizeof(s->alarm));
+ s->armed = 0;
+ s->dbgpause = TIMER_DBGPAUSE_MASK;
+ s->pause = 0;
+ s->intr = 0;
+ s->inte = 0;
+ s->intf = 0;
+
+ for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+ timer_del(s->alarm_timer[i]);
+ qemu_set_irq(s->irq[i], 0);
+ }
+}
+
+static void rp2040_timer_init(Object *obj)
+{
+ RP2040TimerState *s = RP2040_TIMER(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_timer_ops, s,
+ "rp2040.timer", RP2040_TIMER_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[0]);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[1]);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[2]);
+ sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq[3]);
+}
+
+static void rp2040_timer_realize(DeviceState *dev, Error **errp)
+{
+ RP2040TimerState *s = RP2040_TIMER(dev);
+ int i;
+
+ for (i = 0; i < RP2040_TIMER_NUM_ALARMS; i++) {
+ s->alarm_timer[i] =
+ timer_new_ns(QEMU_CLOCK_VIRTUAL, rp2040_timer_alarm_tick,
+ (void *)((uintptr_t)s | i));
+ }
+}
+
+static const VMStateDescription rp2040_timer_vmstate = {
+ .name = TYPE_RP2040_TIMER,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_TIMER_PTR_ARRAY(alarm_timer, RP2040TimerState,
+ RP2040_TIMER_NUM_ALARMS),
+ VMSTATE_INT64(time_offset_us, RP2040TimerState),
+ VMSTATE_UINT64(paused_time_us, RP2040TimerState),
+ VMSTATE_UINT32_ARRAY(alarm, RP2040TimerState,
+ RP2040_TIMER_NUM_ALARMS),
+ VMSTATE_UINT32(armed, RP2040TimerState),
+ VMSTATE_UINT32(dbgpause, RP2040TimerState),
+ VMSTATE_UINT32(pause, RP2040TimerState),
+ VMSTATE_UINT32(intr, RP2040TimerState),
+ VMSTATE_UINT32(inte, RP2040TimerState),
+ VMSTATE_UINT32(intf, RP2040TimerState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void rp2040_timer_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = rp2040_timer_realize;
+ device_class_set_legacy_reset(dc, rp2040_timer_reset);
+ dc->vmsd = &rp2040_timer_vmstate;
+}
+
+static const TypeInfo rp2040_timer_info = {
+ .name = TYPE_RP2040_TIMER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040TimerState),
+ .instance_init = rp2040_timer_init,
+ .class_init = rp2040_timer_class_init,
+};
+
+static void rp2040_timer_register_types(void)
+{
+ type_register_static(&rp2040_timer_info);
+}
+
+type_init(rp2040_timer_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 5c0047c0f7..16e5ca8846 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -25,6 +25,7 @@
#include "hw/misc/rp2040_syscfg.h"
#include "hw/misc/rp2040_sysinfo.h"
#include "hw/misc/rp2040_tbman.h"
+#include "hw/misc/rp2040_timer.h"
#include "hw/misc/rp2040_vreg.h"
#include "hw/misc/rp2040_watchdog.h"
#include "hw/misc/rp2040_xosc.h"
@@ -64,6 +65,7 @@ struct RP2040State {
RP2040RoscState rosc;
RP2040SioState sio;
RP2040TbmanState tbman;
+ RP2040TimerState timer;
RP2040VregState vreg;
RP2040WatchdogState watchdog;
RP2040XoscState xosc;
diff --git a/include/hw/misc/rp2040_timer.h b/include/hw/misc/rp2040_timer.h
new file mode 100644
index 0000000000..b14bf75d53
--- /dev/null
+++ b/include/hw/misc/rp2040_timer.h
@@ -0,0 +1,39 @@
+/*
+ * RP2040 timer emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_TIMER_H
+#define HW_MISC_RP2040_TIMER_H
+
+#include "hw/core/sysbus.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_TIMER "rp2040-timer"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040TimerState, RP2040_TIMER)
+
+#define RP2040_TIMER_BASE 0x40054000
+#define RP2040_TIMER_SIZE 0x4000
+#define RP2040_TIMER_NUM_ALARMS 4
+
+struct RP2040TimerState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq[RP2040_TIMER_NUM_ALARMS];
+ QEMUTimer *alarm_timer[RP2040_TIMER_NUM_ALARMS];
+
+ int64_t time_offset_us;
+ uint64_t paused_time_us;
+ uint32_t alarm[RP2040_TIMER_NUM_ALARMS];
+ uint32_t armed;
+ uint32_t dbgpause;
+ uint32_t pause;
+ uint32_t intr;
+ uint32_t inte;
+ uint32_t intf;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 3054809620..f0ffa84dd1 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -265,6 +265,7 @@ qtests_arm = \
'rp2040-rosc-test',
'rp2040-sio-test',
'rp2040-tbman-test',
+ 'rp2040-timer-test',
'rp2040-uart-test',
'rp2040-vreg-test',
'rp2040-watchdog-test'] : []) + \
diff --git a/tests/qtest/rp2040-timer-test.c b/tests/qtest/rp2040-timer-test.c
new file mode 100644
index 0000000000..35185415b5
--- /dev/null
+++ b/tests/qtest/rp2040-timer-test.c
@@ -0,0 +1,149 @@
+/*
+ * QTest testcase for the RP2040 timer.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define TIMER_BASE 0x40054000
+#define TIMER_TIMEHW 0x00
+#define TIMER_TIMELW 0x04
+#define TIMER_TIMEHR 0x08
+#define TIMER_TIMELR 0x0c
+#define TIMER_ALARM0 0x10
+#define TIMER_ARMED 0x20
+#define TIMER_TIMERAWH 0x24
+#define TIMER_TIMERAWL 0x28
+#define TIMER_DBGPAUSE 0x2c
+#define TIMER_PAUSE 0x30
+#define TIMER_INTR 0x34
+#define TIMER_INTE 0x38
+#define TIMER_INTF 0x3c
+#define TIMER_INTS 0x40
+
+#define TIMER_SET_ALIAS 0x2000
+#define TIMER_CLR_ALIAS 0x3000
+#define TIMER_ALARM_MASK 0x0f
+#define TIMER_DBGPAUSE_MASK (BIT(2) | BIT(1))
+#define TIMER_PAUSE_MASK BIT(0)
+#define NS_PER_US 1000
+
+static QTestState *rp2040_timer_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_reset_values(void)
+{
+ QTestState *qts = rp2040_timer_start();
+
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_DBGPAUSE), ==,
+ TIMER_DBGPAUSE_MASK);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_PAUSE), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTE), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMEHR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWH), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_counter_and_pause(void)
+{
+ QTestState *qts = rp2040_timer_start();
+ uint32_t before = qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL);
+ uint32_t paused;
+
+ qtest_clock_step(qts, 123 * NS_PER_US);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL) - before,
+ ==, 123);
+
+ qtest_writel(qts, TIMER_BASE + TIMER_PAUSE, TIMER_PAUSE_MASK);
+ paused = qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL);
+ qtest_clock_step(qts, 100 * NS_PER_US);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL), ==,
+ paused);
+
+ qtest_writel(qts, TIMER_BASE + TIMER_TIMEHW, 0);
+ qtest_writel(qts, TIMER_BASE + TIMER_TIMELW, 0x12345678);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL), ==,
+ 0x12345678);
+
+ qtest_writel(qts, TIMER_BASE + TIMER_CLR_ALIAS + TIMER_PAUSE,
+ TIMER_PAUSE_MASK);
+ qtest_clock_step(qts, 10 * NS_PER_US);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMELR), ==,
+ 0x12345682);
+
+ qtest_quit(qts);
+}
+
+static void test_alarm(void)
+{
+ QTestState *qts = rp2040_timer_start();
+ uint32_t now = qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWL);
+
+ qtest_writel(qts, TIMER_BASE + TIMER_INTE, BIT(0));
+ qtest_writel(qts, TIMER_BASE + TIMER_ALARM0, now + 100);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), ==, BIT(0));
+
+ qtest_clock_step(qts, 99 * NS_PER_US);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, 0);
+ qtest_clock_step(qts, NS_PER_US);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, BIT(0));
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, BIT(0));
+
+ qtest_writel(qts, TIMER_BASE + TIMER_INTR, BIT(0));
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_interrupt_force_and_masks(void)
+{
+ QTestState *qts = rp2040_timer_start();
+
+ qtest_writel(qts, TIMER_BASE + TIMER_SET_ALIAS + TIMER_INTE,
+ BIT(2) | BIT(8));
+ qtest_writel(qts, TIMER_BASE + TIMER_SET_ALIAS + TIMER_INTF,
+ BIT(2) | BIT(9));
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTE), ==, BIT(2));
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), ==, BIT(2));
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, BIT(2));
+
+ qtest_writel(qts, TIMER_BASE + TIMER_CLR_ALIAS + TIMER_INTF, BIT(2));
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), ==, 0);
+
+ qtest_writel(qts, TIMER_BASE + TIMER_ARMED, TIMER_ALARM_MASK);
+ qtest_writel(qts, TIMER_BASE + TIMER_DBGPAUSE, UINT32_MAX);
+ qtest_writel(qts, TIMER_BASE + TIMER_PAUSE, UINT32_MAX);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_DBGPAUSE), ==,
+ TIMER_DBGPAUSE_MASK);
+ g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_PAUSE), ==,
+ TIMER_PAUSE_MASK);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-timer/reset-values", test_reset_values);
+ qtest_add_func("/rp2040-timer/counter-and-pause",
+ test_counter_and_pause);
+ qtest_add_func("/rp2040-timer/alarm", test_alarm);
+ qtest_add_func("/rp2040-timer/interrupt-force-and-masks",
+ test_interrupt_force_and_masks);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 19/30] tests/tcg/arm: test RP2040 timer alarm interrupt
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (17 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 18/30] hw/timer: add RP2040 timer and alarms Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 20/30] hw/ssi: add RP2040 XIP flash controller Gilles Grimaud
` (11 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
tests/tcg/arm/Makefile.softmmu-target | 7 +-
tests/tcg/arm/system/rp2040-timer.S | 116 ++++++++++++++++++++++++++
tests/tcg/arm/system/rp2040-timer.ref | 2 +
3 files changed, 122 insertions(+), 3 deletions(-)
create mode 100644 tests/tcg/arm/system/rp2040-timer.S
create mode 100644 tests/tcg/arm/system/rp2040-timer.ref
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index 28e4c5774a..f3efe619d1 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -22,7 +22,7 @@ ARM_TESTS+=test-armv6m-undef
RP2040_TESTS=rp2040-boot rp2040-uart rp2040-mpu \
rp2040-sio-fifo rp2040-sio-divider rp2040-psm-proc1 \
- rp2040-core1-launch
+ rp2040-core1-launch rp2040-timer
$(RP2040_TESTS): %: %.S rp2040-minimal.ld
$(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
@@ -35,14 +35,15 @@ run-rp2040-boot: QEMU_OPTS=-M raspi-pico \
run-rp2040-uart run-rp2040-mpu \
run-rp2040-sio-fifo run-rp2040-sio-divider \
-run-rp2040-psm-proc1 run-rp2040-core1-launch: \
+run-rp2040-psm-proc1 run-rp2040-core1-launch run-rp2040-timer: \
QEMU_OPTS=-M raspi-pico,strict-uart-pins=off \
-serial chardev:output \
-semihosting-config enable=on,target=native -kernel
run-rp2040-uart run-rp2040-mpu \
run-rp2040-sio-fifo run-rp2040-sio-divider \
-run-rp2040-psm-proc1 run-rp2040-core1-launch: run-rp2040-%: rp2040-%
+run-rp2040-psm-proc1 run-rp2040-core1-launch \
+run-rp2040-timer: run-rp2040-%: rp2040-%
$(call run-test, $<, \
$(QEMU) -monitor none -display none \
-chardev file$(COMMA)path=$<.out$(COMMA)id=output \
diff --git a/tests/tcg/arm/system/rp2040-timer.S b/tests/tcg/arm/system/rp2040-timer.S
new file mode 100644
index 0000000000..249df6433a
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-timer.S
@@ -0,0 +1,116 @@
+/*
+ * Raspberry Pi Pico timer alarm interrupt test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define TEST_FLAG 0x20000000
+#define UART0_DR 0x40034000
+#define TIMER_BASE 0x40054000
+#define TIMER_ALARM0 0x10
+#define TIMER_RAWL 0x28
+#define TIMER_INTR 0x34
+#define TIMER_INTE 0x38
+#define NVIC_ISER 0xe000e100
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+ .rept 12
+ .word default_handler + 1
+ .endr
+ .word timer_irq + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ ldr r0, =TEST_FLAG
+ movs r1, 0
+ str r1, [r0]
+
+ ldr r0, =NVIC_ISER
+ movs r1, 1
+ str r1, [r0]
+
+ ldr r4, =TIMER_BASE
+ str r1, [r4, TIMER_INTR]
+ str r1, [r4, TIMER_INTE]
+ ldr r2, [r4, TIMER_RAWL]
+ ldr r3, =1000
+ adds r2, r2, r3
+ str r2, [r4, TIMER_ALARM0]
+
+1:
+ ldr r0, =TEST_FLAG
+ ldr r1, [r0]
+ cmp r1, 0
+ beq 1b
+
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+timer_irq:
+ ldr r0, =TIMER_BASE
+ movs r1, 1
+ str r1, [r0, TIMER_INTR]
+ ldr r0, =TEST_FLAG
+ str r1, [r0]
+ bx lr
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+3:
+ b 3b
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+4:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 5f
+ str r1, [r2]
+ adds r0, 1
+ b 4b
+
+5:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG TIMER START\n"
+ok_message:
+ .asciz "PICO TCG TIMER OK\n"
+fail_message:
+ .asciz "PICO TCG TIMER FAIL\n"
diff --git a/tests/tcg/arm/system/rp2040-timer.ref b/tests/tcg/arm/system/rp2040-timer.ref
new file mode 100644
index 0000000000..32e6e169b6
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-timer.ref
@@ -0,0 +1,2 @@
+PICO TCG TIMER START
+PICO TCG TIMER OK
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 20/30] hw/ssi: add RP2040 XIP flash controller
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (18 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 19/30] tests/tcg/arm: test RP2040 timer alarm interrupt Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 21/30] tests/tcg/arm: test RP2040 flash and XIP behavior Gilles Grimaud
` (10 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
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 <gilles.grimaud@univ-lille.fr>
---
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
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 21/30] tests/tcg/arm: test RP2040 flash and XIP behavior
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (19 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 20/30] hw/ssi: add RP2040 XIP flash controller Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 22/30] hw/dma: add RP2040 DMA controller Gilles Grimaud
` (9 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Exercise write-enable, page programming, sector erase and NOR bit semantics through the RP2040 SSI command path. Verify raw flash persistence across QEMU invocations and IO_QSPI chip-select behavior.\n\nCover XIP accesses while the flash is busy, including direct HardFault delivery, multicore lockout, and faults observed independently by core 0 and core 1.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
tests/tcg/arm/Makefile.softmmu-target | 43 +-
tests/tcg/arm/system/rp2040-core0-xip-fault.S | 193 ++++++++
.../tcg/arm/system/rp2040-core0-xip-fault.ref | 2 +
tests/tcg/arm/system/rp2040-core1-xip-fault.S | 331 +++++++++++++
.../tcg/arm/system/rp2040-core1-xip-fault.ref | 3 +
.../arm/system/rp2040-flash-busy-hardfault.S | 179 ++++++++
.../system/rp2040-flash-busy-hardfault.ref | 2 +
.../arm/system/rp2040-flash-busy-lockout.S | 434 ++++++++++++++++++
.../arm/system/rp2040-flash-busy-lockout.ref | 4 +
tests/tcg/arm/system/rp2040-flash-commands.S | 375 +++++++++++++++
.../tcg/arm/system/rp2040-flash-commands.ref | 6 +
tests/tcg/arm/system/rp2040-flash-ioqspi.S | 212 +++++++++
tests/tcg/arm/system/rp2040-flash-ioqspi.ref | 2 +
.../arm/system/rp2040-flash-persist-reader.S | 87 ++++
.../system/rp2040-flash-persist-reader.ref | 1 +
15 files changed, 1869 insertions(+), 5 deletions(-)
create mode 100644 tests/tcg/arm/system/rp2040-core0-xip-fault.S
create mode 100644 tests/tcg/arm/system/rp2040-core0-xip-fault.ref
create mode 100644 tests/tcg/arm/system/rp2040-core1-xip-fault.S
create mode 100644 tests/tcg/arm/system/rp2040-core1-xip-fault.ref
create mode 100644 tests/tcg/arm/system/rp2040-flash-busy-hardfault.S
create mode 100644 tests/tcg/arm/system/rp2040-flash-busy-hardfault.ref
create mode 100644 tests/tcg/arm/system/rp2040-flash-busy-lockout.S
create mode 100644 tests/tcg/arm/system/rp2040-flash-busy-lockout.ref
create mode 100644 tests/tcg/arm/system/rp2040-flash-commands.S
create mode 100644 tests/tcg/arm/system/rp2040-flash-commands.ref
create mode 100644 tests/tcg/arm/system/rp2040-flash-ioqspi.S
create mode 100644 tests/tcg/arm/system/rp2040-flash-ioqspi.ref
create mode 100644 tests/tcg/arm/system/rp2040-flash-persist-reader.S
create mode 100644 tests/tcg/arm/system/rp2040-flash-persist-reader.ref
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index a07264fd89..c282ff5ad0 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -24,7 +24,14 @@ 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-boot2-w25q080.S rp2040.ld
+RP2040_FLASH_TESTS=rp2040-flash-commands rp2040-flash-ioqspi \
+ rp2040-flash-busy-hardfault rp2040-flash-busy-lockout \
+ rp2040-core0-xip-fault rp2040-core1-xip-fault
+
+RP2040_GUESTS=$(RP2040_TESTS) $(RP2040_FLASH_TESTS) \
+ rp2040-flash-persist-reader
+
+$(RP2040_GUESTS): %: %.S rp2040-boot2-w25q080.S rp2040.ld
$(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
-Wl,--build-id=none -x assembler-with-cpp \
$(ARM_SRC)/rp2040-boot2-w25q080.S $< \
@@ -36,7 +43,10 @@ run-rp2040-boot: QEMU_OPTS=-M raspi-pico \
run-rp2040-uart run-rp2040-mpu \
run-rp2040-sio-fifo run-rp2040-sio-divider \
-run-rp2040-psm-proc1 run-rp2040-core1-launch run-rp2040-timer: \
+run-rp2040-psm-proc1 run-rp2040-core1-launch run-rp2040-timer \
+run-rp2040-flash-commands run-rp2040-flash-ioqspi \
+run-rp2040-flash-busy-hardfault run-rp2040-flash-busy-lockout \
+run-rp2040-core0-xip-fault run-rp2040-core1-xip-fault: \
QEMU_OPTS=-M raspi-pico,strict-uart-pins=off \
-serial chardev:output \
-semihosting-config enable=on,target=native -kernel
@@ -44,14 +54,37 @@ run-rp2040-psm-proc1 run-rp2040-core1-launch run-rp2040-timer: \
run-rp2040-uart run-rp2040-mpu \
run-rp2040-sio-fifo run-rp2040-sio-divider \
run-rp2040-psm-proc1 run-rp2040-core1-launch \
-run-rp2040-timer: run-rp2040-%: rp2040-%
+run-rp2040-timer run-rp2040-flash-ioqspi \
+run-rp2040-flash-busy-hardfault run-rp2040-flash-busy-lockout \
+run-rp2040-core0-xip-fault \
+run-rp2040-core1-xip-fault: run-rp2040-%: rp2040-%
$(call run-test, $<, \
$(QEMU) -monitor none -display none \
-chardev file$(COMMA)path=$<.out$(COMMA)id=output \
$(QEMU_OPTS) $<)
$(call diff-out,$<,$(ARM_SRC)/$<.ref)
-
-ARM_TESTS+=$(RP2040_TESTS)
+run-rp2040-flash-commands: rp2040-flash-commands \
+ rp2040-flash-persist-reader
+ $(call run-test, $<, \
+ $(QEMU) -monitor none -display none \
+ -chardev file$(COMMA)path=$<.out$(COMMA)id=output \
+ $(QEMU_OPTS) $<)
+ $(call diff-out,$<,$(ARM_SRC)/rp2040-flash-commands.ref)
+ $(PYTHON) -c "open('$<.flash', 'wb').write(b'\xff' * 512)"
+ $(QEMU) -monitor none -display none \
+ -M raspi-pico$(COMMA)strict-uart-pins=off$(COMMA)flash-file=$<.flash \
+ -serial null -semihosting-config enable=on$(COMMA)target=native \
+ -kernel $<
+ $(QEMU) -monitor none -display none \
+ -chardev file$(COMMA)path=$<.persist.out$(COMMA)id=output \
+ -M raspi-pico$(COMMA)strict-uart-pins=off$(COMMA)flash-file=$<.flash \
+ -serial chardev:output \
+ -semihosting-config enable=on$(COMMA)target=native \
+ -kernel rp2040-flash-persist-reader
+ $(call diff-out,$<.persist,$(ARM_SRC)/rp2040-flash-persist-reader.ref)
+ rm -f $<.flash $<.persist.out
+
+ARM_TESTS+=$(RP2040_TESTS) $(RP2040_FLASH_TESTS)
# These objects provide the basic boot code and helper functions for all tests
CRT_OBJS=boot.o
diff --git a/tests/tcg/arm/system/rp2040-core0-xip-fault.S b/tests/tcg/arm/system/rp2040-core0-xip-fault.S
new file mode 100644
index 0000000000..675dd231d2
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-core0-xip-fault.S
@@ -0,0 +1,193 @@
+/*
+ * Raspberry Pi Pico core 0 XIP-busy instruction fault test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define SRAM_BASE 0x20000000
+#define UART0_DR 0x40034000
+#define SCB_VTOR 0xe000ed08
+#define SSI_BASE 0x18000000
+#define SSI_CTRLR0 0x00
+#define SSI_SSIENR 0x08
+#define SSI_SER 0x10
+#define SSI_RXFLR 0x24
+#define SSI_DR0 0x60
+#define SSI_CTRLR0_SPI_TXRX 0x00070000
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl xip_puts
+ ldr r0, =SRAM_BASE
+ ldr r1, =sram_start
+ ldr r2, =sram_end
+
+1:
+ cmp r1, r2
+ bhs 2f
+ ldr r3, [r1]
+ str r3, [r0]
+ adds r1, 4
+ adds r0, 4
+ b 1b
+
+2:
+ ldr r0, =SRAM_BASE
+ adds r0, 1
+ bx r0
+
+.thumb_func
+default_handler:
+ b default_handler
+
+.thumb_func
+xip_probe:
+ ldr r0, =xip_no_fault_message
+ bl xip_puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+3:
+ b 3b
+
+.thumb_func
+xip_puts:
+ ldr r2, =UART0_DR
+
+4:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 5f
+ str r1, [r2]
+ adds r0, 1
+ b 4b
+
+5:
+ bx lr
+
+.align 2
+sram_start:
+ ldr r0, =SRAM_BASE
+ ldr r1, =SRAM_END
+ str r1, [r0]
+ adr r1, sram_entry
+ adds r1, 1
+ str r1, [r0, 4]
+ movs r1, 0
+ str r1, [r0, 8]
+ adr r1, sram_hardfault
+ adds r1, 1
+ str r1, [r0, 12]
+ ldr r2, =SCB_VTOR
+ str r0, [r2]
+
+.align 2
+.thumb_func
+sram_entry:
+ ldr r4, =SSI_BASE
+ movs r0, 0
+ str r0, [r4, SSI_SSIENR]
+ ldr r0, =SSI_CTRLR0_SPI_TXRX
+ str r0, [r4, SSI_CTRLR0]
+ movs r0, 1
+ str r0, [r4, SSI_SSIENR]
+
+ str r0, [r4, SSI_SER]
+ movs r0, 0x06
+ bl sram_ssi_txrx
+ movs r0, 0
+ str r0, [r4, SSI_SER]
+
+ movs r0, 1
+ str r0, [r4, SSI_SER]
+ movs r0, 0x02
+ bl sram_ssi_txrx
+ movs r0, 0x00
+ bl sram_ssi_txrx
+ movs r0, 0x20
+ bl sram_ssi_txrx
+ movs r0, 0x00
+ bl sram_ssi_txrx
+ movs r0, 0x5a
+ bl sram_ssi_txrx
+ movs r0, 0
+ str r0, [r4, SSI_SER]
+
+ ldr r0, =xip_probe
+ adds r0, 1
+ bx r0
+
+.thumb_func
+sram_ssi_txrx:
+ ldr r2, =(SSI_BASE + SSI_DR0)
+ str r0, [r2]
+ ldr r3, =(SSI_BASE + SSI_RXFLR)
+
+6:
+ ldr r0, [r3]
+ cmp r0, 0
+ beq 6b
+ ldr r0, [r2]
+ bx lr
+
+.align 2
+.thumb_func
+sram_hardfault:
+ adr r0, sram_ok_message
+ bl sram_puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+7:
+ b 7b
+
+.thumb_func
+sram_puts:
+ ldr r2, =UART0_DR
+
+8:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 9f
+ str r1, [r2]
+ adds r0, 1
+ b 8b
+
+9:
+ bx lr
+
+.align 2
+sram_ok_message:
+ .asciz "PICO TCG CORE0 XIP HARDFAULT OK\n"
+
+.ltorg
+.align 2
+sram_end:
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG CORE0 XIP START\n"
+xip_no_fault_message:
+ .asciz "PICO TCG CORE0 XIP FAIL: NO FAULT\n"
diff --git a/tests/tcg/arm/system/rp2040-core0-xip-fault.ref b/tests/tcg/arm/system/rp2040-core0-xip-fault.ref
new file mode 100644
index 0000000000..22232ee864
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-core0-xip-fault.ref
@@ -0,0 +1,2 @@
+PICO TCG CORE0 XIP START
+PICO TCG CORE0 XIP HARDFAULT OK
diff --git a/tests/tcg/arm/system/rp2040-core1-xip-fault.S b/tests/tcg/arm/system/rp2040-core1-xip-fault.S
new file mode 100644
index 0000000000..5f8a1217eb
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-core1-xip-fault.S
@@ -0,0 +1,331 @@
+/*
+ * Raspberry Pi Pico core 1 XIP-busy instruction fault test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define SRAM_BASE 0x20000000
+#define CORE1_STACK 0x20041000
+#define UART0_DR 0x40034000
+#define SIO_BASE 0xd0000000
+#define SIO_CPUID 0x00
+#define SIO_FIFO_ST 0x50
+#define SIO_FIFO_WR 0x54
+#define SIO_FIFO_RD 0x58
+#define SIO_FIFO_ST_VLD 0x01
+#define SIO_FIFO_ST_RDY 0x02
+#define SCB_VTOR 0xe000ed08
+#define SSI_BASE 0x18000000
+#define SSI_CTRLR0 0x00
+#define SSI_SSIENR 0x08
+#define SSI_SER 0x10
+#define SSI_RXFLR 0x24
+#define SSI_DR0 0x60
+#define SSI_CTRLR0_SPI_TXRX 0x00070000
+#define CORE1_READY 0xc01e1001
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl xip_puts
+ ldr r0, =SRAM_BASE
+ ldr r1, =sram_start
+ ldr r2, =sram_end
+
+1:
+ cmp r1, r2
+ bhs 2f
+ ldr r3, [r1]
+ str r3, [r0]
+ adds r1, 4
+ adds r0, 4
+ b 1b
+
+2:
+ ldr r0, =SRAM_BASE
+ adds r0, sram_core0_main - sram_start + 1
+ bx r0
+
+.thumb_func
+default_handler:
+ b default_handler
+
+.thumb_func
+xip_core1_loop:
+ ldr r0, =CORE1_READY
+ bl xip_fifo_push
+
+3:
+ b 3b
+
+.thumb_func
+xip_fifo_push:
+ ldr r1, =(SIO_BASE + SIO_FIFO_ST)
+
+4:
+ ldr r2, [r1]
+ movs r3, SIO_FIFO_ST_RDY
+ tst r2, r3
+ beq 4b
+ ldr r1, =(SIO_BASE + SIO_FIFO_WR)
+ str r0, [r1]
+ bx lr
+
+.thumb_func
+xip_puts:
+ ldr r2, =UART0_DR
+
+5:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 6f
+ str r1, [r2]
+ adds r0, 1
+ b 5b
+
+6:
+ bx lr
+
+.align 2
+sram_start:
+ .word SRAM_END
+ .word SRAM_BASE + (sram_core0_main - sram_start) + 1
+ .word 0
+ .word SRAM_BASE + (sram_hardfault - sram_start) + 1
+
+.align 2
+.thumb_func
+sram_core0_main:
+ ldr r0, =SCB_VTOR
+ ldr r1, =SRAM_BASE
+ str r1, [r0]
+ bl sram_launch_core1
+ bl sram_fifo_pop
+ ldr r1, =CORE1_READY
+ cmp r0, r1
+ bne sram_fail_ready
+
+ adr r0, sram_ready_message
+ bl sram_puts
+ bl sram_start_flash_program
+
+7:
+ b 7b
+
+.thumb_func
+sram_launch_core1:
+ push {r4, r5, lr}
+ adr r4, sram_launch_sequence
+ movs r5, 6
+
+8:
+ ldr r0, [r4]
+ cmp r0, 0
+ bne 9f
+ bl sram_fifo_drain
+ sev
+ ldr r0, [r4]
+
+9:
+ bl sram_fifo_push
+ sev
+ bl sram_fifo_pop
+ ldr r1, [r4]
+ cmp r0, r1
+ bne sram_fail_launch
+ adds r4, 4
+ subs r5, 1
+ bne 8b
+ pop {r4, r5, pc}
+
+.thumb_func
+sram_fifo_push:
+ ldr r1, =(SIO_BASE + SIO_FIFO_ST)
+
+10:
+ ldr r2, [r1]
+ movs r3, SIO_FIFO_ST_RDY
+ tst r2, r3
+ beq 10b
+ ldr r1, =(SIO_BASE + SIO_FIFO_WR)
+ str r0, [r1]
+ bx lr
+
+.thumb_func
+sram_fifo_pop:
+ ldr r1, =(SIO_BASE + SIO_FIFO_ST)
+
+11:
+ ldr r2, [r1]
+ movs r3, SIO_FIFO_ST_VLD
+ tst r2, r3
+ beq 11b
+ ldr r1, =(SIO_BASE + SIO_FIFO_RD)
+ ldr r0, [r1]
+ bx lr
+
+.thumb_func
+sram_fifo_drain:
+ push {r0, r1, r2, r3, lr}
+ ldr r1, =(SIO_BASE + SIO_FIFO_ST)
+
+12:
+ ldr r2, [r1]
+ movs r3, SIO_FIFO_ST_VLD
+ tst r2, r3
+ beq 13f
+ ldr r0, =(SIO_BASE + SIO_FIFO_RD)
+ ldr r0, [r0]
+ b 12b
+
+13:
+ pop {r0, r1, r2, r3, pc}
+
+.thumb_func
+sram_start_flash_program:
+ push {lr}
+ ldr r4, =SSI_BASE
+ movs r0, 0
+ str r0, [r4, SSI_SSIENR]
+ ldr r0, =SSI_CTRLR0_SPI_TXRX
+ str r0, [r4, SSI_CTRLR0]
+ movs r0, 1
+ str r0, [r4, SSI_SSIENR]
+
+ str r0, [r4, SSI_SER]
+ movs r0, 0x06
+ bl sram_ssi_txrx
+ movs r0, 0
+ str r0, [r4, SSI_SER]
+
+ movs r0, 1
+ str r0, [r4, SSI_SER]
+ movs r0, 0x02
+ bl sram_ssi_txrx
+ movs r0, 0x00
+ bl sram_ssi_txrx
+ movs r0, 0x30
+ bl sram_ssi_txrx
+ movs r0, 0x00
+ bl sram_ssi_txrx
+ movs r0, 0xa5
+ bl sram_ssi_txrx
+ movs r0, 0
+ str r0, [r4, SSI_SER]
+ pop {pc}
+
+.thumb_func
+sram_ssi_txrx:
+ ldr r2, =(SSI_BASE + SSI_DR0)
+ str r0, [r2]
+ ldr r3, =(SSI_BASE + SSI_RXFLR)
+
+14:
+ ldr r0, [r3]
+ cmp r0, 0
+ beq 14b
+ ldr r0, [r2]
+ bx lr
+
+.align 2
+.thumb_func
+sram_hardfault:
+ ldr r0, =SIO_BASE
+ ldr r0, [r0, SIO_CPUID]
+ cmp r0, 1
+ bne sram_fail_core
+ adr r0, sram_ok_message
+ bl sram_puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+15:
+ b 15b
+
+sram_fail_launch:
+ adr r0, sram_fail_launch_message
+ b sram_test_failed
+
+sram_fail_ready:
+ adr r0, sram_fail_ready_message
+ b sram_test_failed
+
+sram_fail_core:
+ adr r0, sram_fail_core_message
+
+sram_test_failed:
+ bl sram_puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+16:
+ b 16b
+
+.thumb_func
+sram_puts:
+ ldr r2, =UART0_DR
+
+17:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 18f
+ str r1, [r2]
+ adds r0, 1
+ b 17b
+
+18:
+ bx lr
+
+.align 2
+sram_launch_sequence:
+ .word 0
+ .word 0
+ .word 1
+ .word SRAM_BASE
+ .word CORE1_STACK
+ .word xip_core1_loop + 1
+
+.align 2
+sram_ready_message:
+ .asciz "PICO TCG CORE1 XIP READY\n"
+.align 2
+sram_ok_message:
+ .asciz "PICO TCG CORE1 XIP HARDFAULT OK\n"
+.align 2
+sram_fail_launch_message:
+ .asciz "PICO TCG CORE1 XIP FAIL: LAUNCH\n"
+.align 2
+sram_fail_ready_message:
+ .asciz "PICO TCG CORE1 XIP FAIL: READY\n"
+.align 2
+sram_fail_core_message:
+ .asciz "PICO TCG CORE1 XIP FAIL: CPUID\n"
+
+.ltorg
+.align 2
+sram_end:
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG CORE1 XIP START\n"
diff --git a/tests/tcg/arm/system/rp2040-core1-xip-fault.ref b/tests/tcg/arm/system/rp2040-core1-xip-fault.ref
new file mode 100644
index 0000000000..4488763747
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-core1-xip-fault.ref
@@ -0,0 +1,3 @@
+PICO TCG CORE1 XIP START
+PICO TCG CORE1 XIP READY
+PICO TCG CORE1 XIP HARDFAULT OK
diff --git a/tests/tcg/arm/system/rp2040-flash-busy-hardfault.S b/tests/tcg/arm/system/rp2040-flash-busy-hardfault.S
new file mode 100644
index 0000000000..81bcafe260
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-busy-hardfault.S
@@ -0,0 +1,179 @@
+/*
+ * Raspberry Pi Pico XIP access while flash is busy test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_BASE 0x20000000
+#define SRAM_VTOR 0x20001000
+#define SRAM_END 0x20042000
+#define VTOR 0xe000ed08
+#define UART0_DR 0x40034000
+#define SSI_SER 0x18000010
+#define SSI_RXFLR 0x18000024
+#define SSI_DR0 0x18000060
+#define XIP_TARGET 0x10001400
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =SRAM_BASE
+ ldr r1, =ram_code_start
+ ldr r2, =ram_code_end
+
+copy_loop:
+ cmp r1, r2
+ bhs copy_done
+ ldr r3, [r1]
+ str r3, [r0]
+ adds r0, 4
+ adds r1, 4
+ b copy_loop
+
+copy_done:
+ ldr r0, =SRAM_BASE + 1
+ bx r0
+
+.thumb_func
+default_handler:
+ b default_handler
+
+.align 2
+ram_code_start:
+.thumb_func
+ram_main:
+ ldr r0, =SRAM_VTOR
+ ldr r1, =SRAM_END
+ str r1, [r0]
+ ldr r1, =SRAM_BASE + 1
+ str r1, [r0, 4]
+ movs r1, 0
+ str r1, [r0, 8]
+ adr r1, hardfault_handler
+ adds r1, 1
+ str r1, [r0, 12]
+ ldr r1, =VTOR
+ str r0, [r1]
+
+ adr r0, start_message
+ bl puts
+ bl write_enable
+ bl select_flash
+ movs r0, 0x02
+ bl ssi_byte
+ movs r0, 0x00
+ bl ssi_byte
+ movs r0, 0x14
+ bl ssi_byte
+ movs r0, 0x00
+ bl ssi_byte
+ movs r0, 'X'
+ bl ssi_byte
+ bl deselect_flash
+
+ ldr r1, =XIP_TARGET
+faulting_access:
+ ldrb r0, [r1]
+ adr r0, fail_message
+ b test_failed
+
+.thumb_func
+write_enable:
+ push {lr}
+ bl select_flash
+ movs r0, 0x06
+ bl ssi_byte
+ bl deselect_flash
+ pop {pc}
+
+.thumb_func
+select_flash:
+ ldr r1, =SSI_SER
+ movs r0, 1
+ str r0, [r1]
+ bx lr
+
+.thumb_func
+deselect_flash:
+ ldr r1, =SSI_SER
+ movs r0, 0
+ str r0, [r1]
+ bx lr
+
+.thumb_func
+ssi_byte:
+ ldr r2, =SSI_DR0
+ str r0, [r2]
+ ldr r3, =SSI_RXFLR
+
+1:
+ ldr r0, [r3]
+ cmp r0, 0
+ beq 1b
+ ldr r0, [r2]
+ bx lr
+
+.align 2
+.thumb_func
+hardfault_handler:
+ adr r0, ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+2:
+ b 2b
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+3:
+ b 3b
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+4:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 5f
+ str r1, [r2]
+ adds r0, 1
+ b 4b
+
+5:
+ bx lr
+
+.align 2
+start_message:
+ .asciz "PICO TCG FLASH XIP BUSY START\n"
+.align 2
+ok_message:
+ .asciz "PICO TCG FLASH XIP BUSY HARDFAULT OK\n"
+.align 2
+fail_message:
+ .asciz "PICO TCG FLASH XIP BUSY FAIL: NO HARDFAULT\n"
+.align 2
+.ltorg
+ram_code_end:
diff --git a/tests/tcg/arm/system/rp2040-flash-busy-hardfault.ref b/tests/tcg/arm/system/rp2040-flash-busy-hardfault.ref
new file mode 100644
index 0000000000..ee43e3ae15
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-busy-hardfault.ref
@@ -0,0 +1,2 @@
+PICO TCG FLASH XIP BUSY START
+PICO TCG FLASH XIP BUSY HARDFAULT OK
diff --git a/tests/tcg/arm/system/rp2040-flash-busy-lockout.S b/tests/tcg/arm/system/rp2040-flash-busy-lockout.S
new file mode 100644
index 0000000000..a0397d7a1d
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-busy-lockout.S
@@ -0,0 +1,434 @@
+/*
+ * Raspberry Pi Pico multicore flash-busy lockout test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define CORE0_SRAM_VTOR 0x20000000
+#define CORE0_SRAM_CODE 0x20000100
+#define PAGE_BUFFER 0x20000400
+#define CORE1_SRAM_VTOR 0x20001000
+#define CORE1_STACK 0x20041000
+#define UART0_DR 0x40034000
+#define SIO_BASE 0xd0000000
+#define SIO_FIFO_ST 0x50
+#define SIO_FIFO_WR 0x54
+#define SIO_FIFO_RD 0x58
+#define SIO_SPINLOCK0 0x100
+#define SIO_FIFO_ST_VLD 0x01
+#define SIO_FIFO_ST_RDY 0x02
+#define NVIC_ISER 0xe000e100
+#define SCB_VTOR 0xe000ed08
+#define SIO_IRQ_PROC1_MASK 0x00010000
+#define SSI_BASE 0x18000000
+#define SSI_SSIENR 0x08
+#define SSI_SER 0x10
+#define SSI_DR0 0x60
+#define FLASH_ADDRESS 0x10101000
+#define LOCK_REQUEST 0xfacefeed
+#define LOCK_ACK 0xface0001
+#define BAD_ACK 0xbad10003
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+ bl copy_core1_sram
+ bl launch_core1
+
+ ldr r0, =launch_message
+ bl puts
+ bl claim_spinlock
+ ldr r0, =LOCK_REQUEST
+ bl fifo_push
+ sev
+ ldr r0, =LOCK_ACK
+ bl fifo_pop_expect
+
+ ldr r0, =locked_message
+ bl puts
+ bl copy_and_run_core0_sram
+ ldr r0, =fail_return_message
+ b test_failed
+
+.thumb_func
+launch_core1:
+ push {r4, r5, lr}
+ ldr r4, =launch_sequence
+ movs r5, 6
+
+1:
+ ldr r0, [r4]
+ cmp r0, 0
+ bne 2f
+ bl fifo_drain
+ sev
+ ldr r0, [r4]
+
+2:
+ bl fifo_push
+ sev
+ bl fifo_pop
+ ldr r1, [r4]
+ cmp r0, r1
+ bne fail_launch
+ adds r4, 4
+ subs r5, 1
+ bne 1b
+ pop {r4, r5, pc}
+
+.thumb_func
+copy_core1_sram:
+ ldr r0, =core1_sram_start
+ ldr r1, =CORE1_SRAM_VTOR
+ ldr r2, =core1_sram_end
+
+3:
+ cmp r0, r2
+ bhs 4f
+ ldr r3, [r0]
+ str r3, [r1]
+ adds r0, 4
+ adds r1, 4
+ b 3b
+
+4:
+ bx lr
+
+.thumb_func
+claim_spinlock:
+ ldr r0, =(SIO_BASE + SIO_SPINLOCK0)
+ ldr r1, [r0]
+ cmp r1, 0
+ beq fail_spinlock
+ bx lr
+
+.thumb_func
+copy_and_run_core0_sram:
+ ldr r0, =core0_sram_start
+ ldr r1, =CORE0_SRAM_CODE
+ ldr r2, =core0_sram_end
+
+5:
+ cmp r0, r2
+ bhs 6f
+ ldr r3, [r0]
+ str r3, [r1]
+ adds r0, 4
+ adds r1, 4
+ b 5b
+
+6:
+ ldr r0, =CORE0_SRAM_CODE
+ adds r0, 1
+ bx r0
+
+.thumb_func
+fifo_push:
+ push {r1, r2, r3, lr}
+ ldr r1, =SIO_BASE
+
+7:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_RDY
+ tst r2, r3
+ beq 7b
+ str r0, [r1, SIO_FIFO_WR]
+ pop {r1, r2, r3, pc}
+
+.thumb_func
+fifo_pop:
+ push {r1, r2, r3, lr}
+ ldr r1, =SIO_BASE
+
+8:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_VLD
+ tst r2, r3
+ beq 8b
+ ldr r0, [r1, SIO_FIFO_RD]
+ pop {r1, r2, r3, pc}
+
+.thumb_func
+fifo_drain:
+ push {r0, r1, r2, r3, lr}
+ ldr r1, =SIO_BASE
+
+9:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_VLD
+ tst r2, r3
+ beq 10f
+ ldr r0, [r1, SIO_FIFO_RD]
+ b 9b
+
+10:
+ pop {r0, r1, r2, r3, pc}
+
+.thumb_func
+fifo_pop_expect:
+ push {r4, lr}
+ mov r4, r0
+ bl fifo_pop
+ cmp r0, r4
+ bne fail_fifo
+ pop {r4, pc}
+
+fail_launch:
+ ldr r0, =fail_launch_message
+ b test_failed
+
+fail_spinlock:
+ ldr r0, =fail_spinlock_message
+ b test_failed
+
+fail_fifo:
+ ldr r0, =fail_fifo_message
+ b test_failed
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+11:
+ b 11b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ push {r1, r2, lr}
+ ldr r1, =UART0_DR
+
+12:
+ ldrb r2, [r0]
+ cmp r2, 0
+ beq 13f
+ str r2, [r1]
+ adds r0, 1
+ b 12b
+
+13:
+ pop {r1, r2, pc}
+
+.align 2
+core0_sram_start:
+ ldr r0, =CORE0_SRAM_VTOR
+ ldr r1, =SRAM_END
+ str r1, [r0]
+ movs r1, 0
+ str r1, [r0, 4]
+ ldr r1, =core0_sram_hardfault
+ ldr r2, =core0_sram_start
+ subs r1, r2
+ ldr r2, =CORE0_SRAM_CODE
+ adds r1, r1, r2
+ str r1, [r0, 12]
+ ldr r0, =SCB_VTOR
+ ldr r1, =CORE0_SRAM_VTOR
+ str r1, [r0]
+
+ ldr r0, =PAGE_BUFFER
+ movs r1, 0x5a
+ str r1, [r0]
+
+ ldr r4, =SSI_BASE
+ movs r0, 1
+ str r0, [r4, SSI_SSIENR]
+ str r0, [r4, SSI_SER]
+ movs r0, 0x06
+ str r0, [r4, SSI_DR0]
+ movs r0, 0
+ str r0, [r4, SSI_SER]
+
+ movs r0, 1
+ str r0, [r4, SSI_SER]
+ movs r0, 0x02
+ str r0, [r4, SSI_DR0]
+ movs r0, 0x10
+ str r0, [r4, SSI_DR0]
+ str r0, [r4, SSI_DR0]
+ movs r0, 0
+ str r0, [r4, SSI_DR0]
+ movs r0, 0x5a
+ str r0, [r4, SSI_DR0]
+ movs r0, 0
+ str r0, [r4, SSI_SER]
+
+ ldr r0, =FLASH_ADDRESS
+ ldrb r0, [r0]
+ ldr r0, =core0_sram_no_fault_message
+ ldr r1, =core0_sram_start
+ subs r0, r1
+ ldr r1, =CORE0_SRAM_CODE
+ adds r0, r1
+ bl core0_sram_puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+14:
+ b 14b
+
+.thumb_func
+core0_sram_hardfault:
+ ldr r0, =core0_sram_ok_message
+ ldr r1, =core0_sram_start
+ subs r0, r1
+ ldr r1, =CORE0_SRAM_CODE
+ adds r0, r1
+ bl core0_sram_puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+15:
+ b 15b
+
+.thumb_func
+core0_sram_puts:
+ ldr r1, =UART0_DR
+
+16:
+ ldrb r2, [r0]
+ cmp r2, 0
+ beq 17f
+ str r2, [r1]
+ adds r0, 1
+ b 16b
+
+17:
+ bx lr
+
+.align 2
+core0_sram_ok_message:
+ .asciz "PICO TCG FLASH BUSY LOCKOUT OK\n"
+core0_sram_no_fault_message:
+ .asciz "PICO TCG FLASH BUSY LOCKOUT FAIL: NO FAULT\n"
+
+.ltorg
+.align 2
+core0_sram_end:
+
+core1_sram_start:
+ .word CORE1_STACK
+ .word CORE1_SRAM_VTOR + (core1_sram_entry - core1_sram_start) + 1
+ .word 0
+ .word 0
+ .space 0x80 - 16
+ .word CORE1_SRAM_VTOR + (core1_sram_fifo_irq - core1_sram_start) + 1
+ .space 0x100 - 0x84
+
+.thumb_func
+core1_sram_entry:
+ ldr r0, =SCB_VTOR
+ ldr r1, =CORE1_SRAM_VTOR
+ str r1, [r0]
+ ldr r0, =NVIC_ISER
+ ldr r1, =SIO_IRQ_PROC1_MASK
+ str r1, [r0]
+ cpsie i
+
+18:
+ wfe
+ b 18b
+
+.thumb_func
+core1_sram_fifo_irq:
+ push {r0, r1, r2, r3, lr}
+ bl core1_sram_fifo_pop
+ ldr r1, =LOCK_REQUEST
+ cmp r0, r1
+ bne core1_sram_bad_request
+ ldr r0, =LOCK_ACK
+ bl core1_sram_fifo_push
+
+19:
+ wfe
+ b 19b
+
+core1_sram_bad_request:
+ ldr r0, =BAD_ACK
+ bl core1_sram_fifo_push
+
+20:
+ b 20b
+
+.thumb_func
+core1_sram_fifo_push:
+ ldr r1, =SIO_BASE
+
+21:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_RDY
+ tst r2, r3
+ beq 21b
+ str r0, [r1, SIO_FIFO_WR]
+ bx lr
+
+.thumb_func
+core1_sram_fifo_pop:
+ ldr r1, =SIO_BASE
+
+22:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_VLD
+ tst r2, r3
+ beq 22b
+ ldr r0, [r1, SIO_FIFO_RD]
+ bx lr
+
+.ltorg
+.align 2
+core1_sram_end:
+
+.section .rodata, "a", %progbits
+.align 2
+launch_sequence:
+ .word 0
+ .word 0
+ .word 1
+ .word CORE1_SRAM_VTOR
+ .word CORE1_STACK
+ .word CORE1_SRAM_VTOR + (core1_sram_entry - core1_sram_start) + 1
+
+start_message:
+ .asciz "PICO TCG FLASH BUSY START\n"
+launch_message:
+ .asciz "PICO TCG FLASH BUSY CORE1 SRAM OK\n"
+locked_message:
+ .asciz "PICO TCG FLASH BUSY LOCKED\n"
+fail_launch_message:
+ .asciz "PICO TCG FLASH BUSY FAIL: LAUNCH\n"
+fail_spinlock_message:
+ .asciz "PICO TCG FLASH BUSY FAIL: SPINLOCK\n"
+fail_fifo_message:
+ .asciz "PICO TCG FLASH BUSY FAIL: FIFO\n"
+fail_return_message:
+ .asciz "PICO TCG FLASH BUSY FAIL: RETURN\n"
+fail_exception_message:
+ .asciz "PICO TCG FLASH BUSY FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-flash-busy-lockout.ref b/tests/tcg/arm/system/rp2040-flash-busy-lockout.ref
new file mode 100644
index 0000000000..53b85daabd
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-busy-lockout.ref
@@ -0,0 +1,4 @@
+PICO TCG FLASH BUSY START
+PICO TCG FLASH BUSY CORE1 SRAM OK
+PICO TCG FLASH BUSY LOCKED
+PICO TCG FLASH BUSY LOCKOUT OK
diff --git a/tests/tcg/arm/system/rp2040-flash-commands.S b/tests/tcg/arm/system/rp2040-flash-commands.S
new file mode 100644
index 0000000000..1245c88c4e
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-commands.S
@@ -0,0 +1,375 @@
+/*
+ * Raspberry Pi Pico flash command and NOR semantics test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_BASE 0x20000000
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define SSI_SER 0x18000010
+#define SSI_RXFLR 0x18000024
+#define SSI_DR0 0x18000060
+#define XIP_TARGET 0x10001000
+#define FLASH_TARGET_OFFSET 0x00001000
+#define FLASH_STATUS_WIP 0x01
+#define FLASH_STATUS_WEL 0x02
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =SRAM_BASE
+ ldr r1, =ram_code_start
+ ldr r2, =ram_code_end
+
+copy_loop:
+ cmp r1, r2
+ bhs copy_done
+ ldr r3, [r1]
+ str r3, [r0]
+ adds r0, 4
+ adds r1, 4
+ b copy_loop
+
+copy_done:
+ ldr r0, =SRAM_BASE + 1
+ bx r0
+
+.thumb_func
+default_handler:
+ b default_handler
+
+.align 2
+ram_code_start:
+.thumb_func
+ram_main:
+ ldr r0, =start_message
+ bl puts
+
+ bl write_enable
+ bl expect_wel
+ ldr r0, =FLASH_TARGET_OFFSET
+ bl erase_sector
+ bl expect_busy_wel_clear
+ bl expect_target_erased
+
+ bl write_enable
+ bl expect_wel
+ ldr r0, =FLASH_TARGET_OFFSET
+ ldr r1, =flash_message
+ movs r2, 5
+ bl program_data
+ bl expect_busy_wel_clear
+ bl expect_flash_message
+ ldr r0, =program_ok_message
+ bl puts
+
+ bl write_enable
+ ldr r0, =FLASH_TARGET_OFFSET
+ bl erase_sector
+ bl expect_busy_wel_clear
+ bl expect_target_erased
+ ldr r0, =erase_ok_message
+ bl puts
+
+ bl write_enable
+ ldr r0, =FLASH_TARGET_OFFSET
+ ldr r1, =flash_message
+ movs r2, 5
+ bl program_data
+ bl expect_busy_wel_clear
+ bl expect_flash_message
+
+ ldr r0, =FLASH_TARGET_OFFSET + 8
+ ldr r1, =no_wel_value
+ movs r2, 1
+ bl program_data
+ bl read_status
+ movs r1, FLASH_STATUS_WIP | FLASH_STATUS_WEL
+ tst r0, r1
+ beq 8f
+ b fail_no_wel
+8:
+ ldr r1, =XIP_TARGET + 8
+ ldrb r0, [r1]
+ cmp r0, 0xff
+ beq 9f
+ b fail_no_wel
+9:
+ ldr r0, =no_wel_ok_message
+ bl puts
+
+ bl write_enable
+ ldr r0, =FLASH_TARGET_OFFSET + 16
+ ldr r1, =zero_value
+ movs r2, 1
+ bl program_data
+ bl expect_busy_wel_clear
+ bl write_enable
+ ldr r0, =FLASH_TARGET_OFFSET + 16
+ ldr r1, =erased_value
+ movs r2, 1
+ bl program_data
+ bl expect_busy_wel_clear
+ ldr r1, =XIP_TARGET + 16
+ ldrb r0, [r1]
+ cmp r0, 0
+ beq 10f
+ b fail_old_and_new
+10:
+ ldr r0, =old_and_new_ok_message
+ bl puts
+
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+.thumb_func
+write_enable:
+ push {lr}
+ bl select_flash
+ movs r0, 0x06
+ bl ssi_byte
+ bl deselect_flash
+ pop {pc}
+
+.thumb_func
+read_status:
+ push {r4, lr}
+ bl select_flash
+ movs r0, 0x05
+ bl ssi_byte
+ mov r4, r0
+ bl deselect_flash
+ mov r0, r4
+ pop {r4, pc}
+
+.thumb_func
+expect_wel:
+ push {lr}
+ bl read_status
+ movs r1, FLASH_STATUS_WEL
+ tst r0, r1
+ beq fail_wel
+ pop {pc}
+
+.thumb_func
+expect_busy_wel_clear:
+ push {lr}
+ bl read_status
+ movs r1, FLASH_STATUS_WIP
+ tst r0, r1
+ beq fail_status
+ movs r1, FLASH_STATUS_WEL
+ tst r0, r1
+ bne fail_status
+ pop {pc}
+
+.thumb_func
+erase_sector:
+ push {r4, lr}
+ mov r4, r0
+ bl select_flash
+ movs r0, 0x20
+ bl ssi_byte
+ lsrs r0, r4, 16
+ bl ssi_byte
+ lsrs r0, r4, 8
+ bl ssi_byte
+ mov r0, r4
+ bl ssi_byte
+ bl deselect_flash
+ pop {r4, pc}
+
+.thumb_func
+program_data:
+ push {r4, r5, r6, lr}
+ mov r4, r0
+ mov r5, r1
+ mov r6, r2
+ bl select_flash
+ movs r0, 0x02
+ bl ssi_byte
+ lsrs r0, r4, 16
+ bl ssi_byte
+ lsrs r0, r4, 8
+ bl ssi_byte
+ mov r0, r4
+ bl ssi_byte
+
+program_loop:
+ cmp r6, 0
+ beq program_done
+ ldrb r0, [r5]
+ bl ssi_byte
+ adds r5, 1
+ subs r6, 1
+ b program_loop
+
+program_done:
+ bl deselect_flash
+ pop {r4, r5, r6, pc}
+
+.thumb_func
+select_flash:
+ ldr r1, =SSI_SER
+ movs r0, 1
+ str r0, [r1]
+ bx lr
+
+.thumb_func
+deselect_flash:
+ ldr r1, =SSI_SER
+ movs r0, 0
+ str r0, [r1]
+ bx lr
+
+.thumb_func
+ssi_byte:
+ ldr r2, =SSI_DR0
+ str r0, [r2]
+ ldr r3, =SSI_RXFLR
+
+2:
+ ldr r0, [r3]
+ cmp r0, 0
+ beq 2b
+ ldr r0, [r2]
+ bx lr
+
+.thumb_func
+expect_target_erased:
+ ldr r1, =XIP_TARGET
+ movs r2, 20
+
+3:
+ ldrb r0, [r1]
+ cmp r0, 0xff
+ bne fail_erase
+ adds r1, 1
+ subs r2, 1
+ bne 3b
+ bx lr
+
+.thumb_func
+expect_flash_message:
+ ldr r0, =XIP_TARGET
+ ldr r1, =flash_message
+ movs r2, 5
+
+4:
+ ldrb r3, [r0]
+ ldrb r4, [r1]
+ cmp r3, r4
+ bne fail_program
+ adds r0, 1
+ adds r1, 1
+ subs r2, 1
+ bne 4b
+ bx lr
+
+fail_wel:
+ ldr r0, =fail_wel_message
+ b test_failed
+
+fail_status:
+ ldr r0, =fail_status_message
+ b test_failed
+
+fail_program:
+ ldr r0, =fail_program_message
+ b test_failed
+
+fail_erase:
+ ldr r0, =fail_erase_message
+ b test_failed
+
+fail_no_wel:
+ ldr r0, =fail_no_wel_message
+ b test_failed
+
+fail_old_and_new:
+ ldr r0, =fail_old_and_new_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+5:
+ b 5b
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+6:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 7f
+ str r1, [r2]
+ adds r0, 1
+ b 6b
+
+7:
+ bx lr
+
+.align 2
+flash_message:
+ .ascii "FLASH"
+no_wel_value:
+ .byte 0xa5
+zero_value:
+ .byte 0x00
+erased_value:
+ .byte 0xff
+start_message:
+ .asciz "PICO TCG FLASH COMMANDS START\n"
+program_ok_message:
+ .asciz "PICO TCG FLASH PROGRAM OK\n"
+erase_ok_message:
+ .asciz "PICO TCG FLASH ERASE OK\n"
+no_wel_ok_message:
+ .asciz "PICO TCG FLASH NO WEL OK\n"
+old_and_new_ok_message:
+ .asciz "PICO TCG FLASH OLD AND NEW OK\n"
+ok_message:
+ .asciz "PICO TCG FLASH COMMANDS OK\n"
+fail_wel_message:
+ .asciz "PICO TCG FLASH COMMANDS FAIL: WEL\n"
+fail_status_message:
+ .asciz "PICO TCG FLASH COMMANDS FAIL: STATUS\n"
+fail_program_message:
+ .asciz "PICO TCG FLASH COMMANDS FAIL: PROGRAM\n"
+fail_erase_message:
+ .asciz "PICO TCG FLASH COMMANDS FAIL: ERASE\n"
+fail_no_wel_message:
+ .asciz "PICO TCG FLASH COMMANDS FAIL: NO WEL\n"
+fail_old_and_new_message:
+ .asciz "PICO TCG FLASH COMMANDS FAIL: OLD AND NEW\n"
+.align 2
+.ltorg
+ram_code_end:
diff --git a/tests/tcg/arm/system/rp2040-flash-commands.ref b/tests/tcg/arm/system/rp2040-flash-commands.ref
new file mode 100644
index 0000000000..ac90dcb404
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-commands.ref
@@ -0,0 +1,6 @@
+PICO TCG FLASH COMMANDS START
+PICO TCG FLASH PROGRAM OK
+PICO TCG FLASH ERASE OK
+PICO TCG FLASH NO WEL OK
+PICO TCG FLASH OLD AND NEW OK
+PICO TCG FLASH COMMANDS OK
diff --git a/tests/tcg/arm/system/rp2040-flash-ioqspi.S b/tests/tcg/arm/system/rp2040-flash-ioqspi.S
new file mode 100644
index 0000000000..081c643529
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-ioqspi.S
@@ -0,0 +1,212 @@
+/*
+ * Raspberry Pi Pico IO_QSPI chip-select flash command test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_BASE 0x20000000
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define SSI_CTRLR0 0x18000000
+#define SSI_SSIENR 0x18000008
+#define SSI_RXFLR 0x18000024
+#define SSI_DR0 0x18000060
+#define IO_QSPI_SS_CTRL 0x4001800c
+#define XIP_TARGET 0x10001200
+#define FLASH_TARGET_OFFSET 0x00001200
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =SRAM_BASE
+ ldr r1, =ram_code_start
+ ldr r2, =ram_code_end
+
+copy_loop:
+ cmp r1, r2
+ bhs copy_done
+ ldr r3, [r1]
+ str r3, [r0]
+ adds r0, 4
+ adds r1, 4
+ b copy_loop
+
+copy_done:
+ ldr r0, =SRAM_BASE + 1
+ bx r0
+
+.thumb_func
+default_handler:
+ b default_handler
+
+.align 2
+ram_code_start:
+.thumb_func
+ram_main:
+ ldr r0, =start_message
+ bl puts
+ ldr r1, =SSI_SSIENR
+ movs r0, 0
+ str r0, [r1]
+ ldr r1, =SSI_CTRLR0
+ ldr r0, =0x00070000
+ str r0, [r1]
+ ldr r1, =SSI_SSIENR
+ movs r0, 1
+ str r0, [r1]
+
+ bl write_enable
+ bl cs_low
+ movs r0, 0x20
+ bl ssi_byte
+ movs r0, 0x00
+ bl ssi_byte
+ movs r0, 0x12
+ bl ssi_byte
+ movs r0, 0x00
+ bl ssi_byte
+ bl cs_high
+ bl read_status
+
+ bl write_enable
+ bl cs_low
+ movs r0, 0x02
+ bl ssi_byte
+ movs r0, 0x00
+ bl ssi_byte
+ movs r0, 0x12
+ bl ssi_byte
+ movs r0, 0x00
+ bl ssi_byte
+ movs r0, 'Q'
+ bl ssi_byte
+ movs r0, 'S'
+ bl ssi_byte
+ movs r0, 'P'
+ bl ssi_byte
+ movs r0, 'I'
+ bl ssi_byte
+ bl cs_high
+ bl read_status
+
+ ldr r1, =XIP_TARGET
+ ldrb r0, [r1]
+ cmp r0, 'Q'
+ bne test_failed
+ ldrb r0, [r1, 1]
+ cmp r0, 'S'
+ bne test_failed
+ ldrb r0, [r1, 2]
+ cmp r0, 'P'
+ bne test_failed
+ ldrb r0, [r1, 3]
+ cmp r0, 'I'
+ bne test_failed
+
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+.thumb_func
+write_enable:
+ push {lr}
+ bl cs_low
+ movs r0, 0x06
+ bl ssi_byte
+ bl cs_high
+ pop {pc}
+
+.thumb_func
+read_status:
+ push {lr}
+ bl cs_low
+ movs r0, 0x05
+ bl ssi_byte
+ bl cs_high
+ pop {pc}
+
+.thumb_func
+ssi_byte:
+ ldr r2, =SSI_DR0
+ str r0, [r2]
+ ldr r3, =SSI_RXFLR
+
+2:
+ ldr r0, [r3]
+ cmp r0, 0
+ beq 2b
+ ldr r0, [r2]
+ bx lr
+
+.thumb_func
+cs_low:
+ ldr r1, =IO_QSPI_SS_CTRL
+ movs r0, 2
+ lsls r0, r0, 8
+ str r0, [r1]
+ bx lr
+
+.thumb_func
+cs_high:
+ ldr r1, =IO_QSPI_SS_CTRL
+ movs r0, 3
+ lsls r0, r0, 8
+ str r0, [r1]
+ bx lr
+
+test_failed:
+ ldr r0, =fail_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+3:
+ b 3b
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+4:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 5f
+ str r1, [r2]
+ adds r0, 1
+ b 4b
+
+5:
+ bx lr
+
+.align 2
+start_message:
+ .asciz "PICO TCG IO_QSPI FLASH START\n"
+ok_message:
+ .asciz "PICO TCG IO_QSPI FLASH OK\n"
+fail_message:
+ .asciz "PICO TCG IO_QSPI FLASH FAIL\n"
+.align 2
+.ltorg
+ram_code_end:
diff --git a/tests/tcg/arm/system/rp2040-flash-ioqspi.ref b/tests/tcg/arm/system/rp2040-flash-ioqspi.ref
new file mode 100644
index 0000000000..8261199083
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-ioqspi.ref
@@ -0,0 +1,2 @@
+PICO TCG IO_QSPI FLASH START
+PICO TCG IO_QSPI FLASH OK
diff --git a/tests/tcg/arm/system/rp2040-flash-persist-reader.S b/tests/tcg/arm/system/rp2040-flash-persist-reader.S
new file mode 100644
index 0000000000..1232664370
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-persist-reader.S
@@ -0,0 +1,87 @@
+/*
+ * Raspberry Pi Pico persistent flash reader test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define XIP_TARGET 0x10001000
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =XIP_TARGET
+ ldr r1, =expected_message
+ movs r2, 5
+
+check_loop:
+ ldrb r3, [r0]
+ ldrb r4, [r1]
+ cmp r3, r4
+ bne test_failed
+ adds r0, 1
+ adds r1, 1
+ subs r2, 1
+ bne check_loop
+
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+test_failed:
+ ldr r0, =fail_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+default_handler:
+ b default_handler
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+expected_message:
+ .ascii "FLASH"
+ok_message:
+ .asciz "PICO TCG FLASH PERSIST OK\n"
+fail_message:
+ .asciz "PICO TCG FLASH PERSIST FAIL\n"
diff --git a/tests/tcg/arm/system/rp2040-flash-persist-reader.ref b/tests/tcg/arm/system/rp2040-flash-persist-reader.ref
new file mode 100644
index 0000000000..0ac066b0cf
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-persist-reader.ref
@@ -0,0 +1 @@
+PICO TCG FLASH PERSIST OK
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 22/30] hw/dma: add RP2040 DMA controller
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (20 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 21/30] tests/tcg/arm: test RP2040 flash and XIP behavior Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 23/30] tests/tcg/arm: test RP2040 DMA transfers Gilles Grimaud
` (8 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Model the twelve RP2040 DMA channels, register aliases, interrupts, chaining, ring wrapping, aborts, sniff checksums and AHB error status. Pace transfers from DREQ inputs and the four virtual DMA timers.\n\nWire the controller to the SoC memory, IRQ lines, XIP controller and both UARTs. Add focused qtests for XIP pacing, rings, aborts, sniffing, errors, timer requests and re-entrant control blocks.\n\nThe model provides deterministic functional behavior rather than bus-cycle accurate timing.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/rp2040.c | 37 +-
hw/dma/Kconfig | 5 +-
hw/dma/meson.build | 1 +
hw/dma/rp2040_dma.c | 1032 +++++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 2 +
include/hw/dma/rp2040_dma.h | 77 +++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-dma-test.c | 374 ++++++++++++
9 files changed, 1528 insertions(+), 2 deletions(-)
create mode 100644 hw/dma/rp2040_dma.c
create mode 100644 include/hw/dma/rp2040_dma.h
create mode 100644 tests/qtest/rp2040-dma-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index dad91b368d..30965e9ede 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -379,6 +379,7 @@ config RP2040
select ARM_V7M
select PL011
select RP2040_CLOCKS
+ select RP2040_DMA
select RP2040_IOBANK0
select RP2040_IOQSPI
select RP2040_NYI
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 3a8c8f88af..e7ba55bd1f 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -24,6 +24,8 @@
#define RP2040_UART0_IRQ 20
#define RP2040_UART1_BASE 0x40038000
#define RP2040_UART1_IRQ 21
+#define RP2040_DMA_IRQ_0 11
+#define RP2040_DMA_IRQ_1 12
#define RP2040_IO_IRQ_BANK0 13
#define RP2040_SIO_IRQ_PROC0 15
#define RP2040_SIO_IRQ_PROC1 16
@@ -148,7 +150,6 @@ static const struct {
{ "rp2040.adc", 0x4004c000, 0x4000 },
{ "rp2040.pwm", 0x40050000, 0x4000 },
{ "rp2040.rtc", 0x4005c000, 0x4000 },
- { "rp2040.dma", 0x50000000, 0x1000 },
{ "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
{ "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
{ "rp2040.pio0", 0x50200000, 0x10000 },
@@ -369,6 +370,7 @@ static void rp2040_soc_init(Object *obj)
object_initialize_child(obj, "xip", &s->xip, TYPE_RP2040_XIP);
object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS);
+ object_initialize_child(obj, "dma", &s->dma, TYPE_RP2040_DMA);
object_initialize_child(obj, "iobank0", &s->iobank0,
TYPE_RP2040_IOBANK0);
object_initialize_child(obj, "ioqspi", &s->ioqspi,
@@ -529,6 +531,29 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
RP2040_XIP_NOCACHE_NOALLOC_BASE,
&s->xip.xip_nocache_noalloc);
+ object_property_set_link(OBJECT(&s->dma), "memory",
+ OBJECT(s->board_memory), &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->dma), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->dma), 0, RP2040_DMA_BASE);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->dma), 0,
+ s->irq[RP2040_DMA_IRQ_0]);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->dma), 1,
+ s->irq[RP2040_DMA_IRQ_1]);
+ qdev_connect_gpio_out_named(DEVICE(&s->xip), "dreq-rx", 0,
+ qdev_get_gpio_in_named(DEVICE(&s->dma),
+ "dreq",
+ RP2040_DREQ_XIP_SSIRX));
+ qdev_connect_gpio_out_named(DEVICE(&s->xip), "dreq-stream", 0,
+ qdev_get_gpio_in_named(DEVICE(&s->dma),
+ "dreq",
+ RP2040_DREQ_XIP_STREAM));
+
for (i = 0; i < ARRAY_SIZE(rp2040_unimplemented); i++) {
create_unimplemented_device(rp2040_unimplemented[i].name,
rp2040_unimplemented[i].base,
@@ -702,6 +727,16 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
uart_base[i] + 0x1000);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart[i]), 0,
s->irq[uart_irq[i]]);
+ qdev_connect_gpio_out_named(
+ DEVICE(&s->uart[i]), "dreq-tx", 0,
+ qdev_get_gpio_in_named(DEVICE(&s->dma), "dreq",
+ i ? RP2040_DREQ_UART1_TX :
+ RP2040_DREQ_UART0_TX));
+ qdev_connect_gpio_out_named(
+ DEVICE(&s->uart[i]), "dreq-rx", 0,
+ qdev_get_gpio_in_named(DEVICE(&s->dma), "dreq",
+ i ? RP2040_DREQ_UART1_RX :
+ RP2040_DREQ_UART0_RX));
}
rp2040_update_uart_pins(s);
}
diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig
index 0a23e7d7b8..79e36c429e 100644
--- a/hw/dma/Kconfig
+++ b/hw/dma/Kconfig
@@ -32,4 +32,7 @@ config XLNX_CSU_DMA
select REGISTER
config K230_GSDMA
- bool
\ No newline at end of file
+ bool
+
+config RP2040_DMA
+ bool
diff --git a/hw/dma/meson.build b/hw/dma/meson.build
index 45db5e7357..43d2e50d0b 100644
--- a/hw/dma/meson.build
+++ b/hw/dma/meson.build
@@ -10,6 +10,7 @@ system_ss.add(when: 'CONFIG_XLNX_ZYNQMP_ARM', if_true: files('xlnx_dpdma.c'))
system_ss.add(when: 'CONFIG_XLNX_ZDMA', if_true: files('xlnx-zdma.c'))
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_dma.c', 'soc_dma.c'))
system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c'))
+system_ss.add(when: 'CONFIG_RP2040_DMA', if_true: files('rp2040_dma.c'))
system_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c'))
system_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c'))
system_ss.add(when: 'CONFIG_K230_GSDMA', if_true: files('k230_gsdma.c'))
diff --git a/hw/dma/rp2040_dma.c b/hw/dma/rp2040_dma.c
new file mode 100644
index 0000000000..5a3f720207
--- /dev/null
+++ b/hw/dma/rp2040_dma.c
@@ -0,0 +1,1032 @@
+/*
+ * RP2040 DMA emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/dma/rp2040_dma.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/host-utils.h"
+#include "qemu/log.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+
+#define DMA_CH_SIZE 0x40
+#define DMA_CH_READ_ADDR 0x00
+#define DMA_CH_WRITE_ADDR 0x04
+#define DMA_CH_TRANS_COUNT 0x08
+#define DMA_CH_CTRL_TRIG 0x0c
+#define DMA_CH_AL1_CTRL 0x10
+#define DMA_CH_AL1_READ_ADDR 0x14
+#define DMA_CH_AL1_WRITE_ADDR 0x18
+#define DMA_CH_AL1_TRANS_COUNT 0x1c
+#define DMA_CH_AL2_CTRL 0x20
+#define DMA_CH_AL2_TRANS_COUNT 0x24
+#define DMA_CH_AL2_READ_ADDR 0x28
+#define DMA_CH_AL2_WRITE_ADDR 0x2c
+#define DMA_CH_AL3_CTRL 0x30
+#define DMA_CH_AL3_WRITE_ADDR 0x34
+#define DMA_CH_AL3_TRANS_COUNT 0x38
+#define DMA_CH_AL3_READ_ADDR 0x3c
+
+#define DMA_INTR 0x400
+#define DMA_INTE0 0x404
+#define DMA_INTF0 0x408
+#define DMA_INTS0 0x40c
+#define DMA_INTE1 0x414
+#define DMA_INTF1 0x418
+#define DMA_INTS1 0x41c
+#define DMA_TIMER0 0x420
+#define DMA_MULTI_CHAN_TRIGGER 0x430
+#define DMA_SNIFF_CTRL 0x434
+#define DMA_SNIFF_DATA 0x438
+#define DMA_FIFO_LEVELS 0x440
+#define DMA_CHAN_ABORT 0x444
+
+#define DMA_CTRL_AHB_ERROR BIT(31)
+#define DMA_CTRL_READ_ERROR BIT(30)
+#define DMA_CTRL_WRITE_ERROR BIT(29)
+#define DMA_CTRL_BUSY BIT(24)
+#define DMA_CTRL_SNIFF_EN BIT(23)
+#define DMA_CTRL_BSWAP BIT(22)
+#define DMA_CTRL_IRQ_QUIET BIT(21)
+#define DMA_CTRL_TREQ_SEL_SHIFT 15
+#define DMA_CTRL_TREQ_SEL_MASK (0x3f << DMA_CTRL_TREQ_SEL_SHIFT)
+#define DMA_CTRL_CHAIN_TO_SHIFT 11
+#define DMA_CTRL_CHAIN_TO_MASK (0xf << DMA_CTRL_CHAIN_TO_SHIFT)
+#define DMA_CTRL_RING_SEL BIT(10)
+#define DMA_CTRL_RING_SIZE_SHIFT 6
+#define DMA_CTRL_RING_SIZE_MASK (0xf << 6)
+#define DMA_CTRL_INCR_WRITE BIT(5)
+#define DMA_CTRL_INCR_READ BIT(4)
+#define DMA_CTRL_DATA_SIZE_SHIFT 2
+#define DMA_CTRL_DATA_SIZE_MASK (0x3 << DMA_CTRL_DATA_SIZE_SHIFT)
+#define DMA_CTRL_EN BIT(0)
+#define DMA_CTRL_ERROR_MASK (DMA_CTRL_AHB_ERROR | \
+ DMA_CTRL_READ_ERROR | \
+ DMA_CTRL_WRITE_ERROR)
+
+#define DMA_SNIFF_CTRL_OUT_INV BIT(11)
+#define DMA_SNIFF_CTRL_OUT_REV BIT(10)
+#define DMA_SNIFF_CTRL_BSWAP BIT(9)
+#define DMA_SNIFF_CTRL_CALC_SHIFT 5
+#define DMA_SNIFF_CTRL_CALC_MASK (0xf << DMA_SNIFF_CTRL_CALC_SHIFT)
+#define DMA_SNIFF_CTRL_DMACH_SHIFT 1
+#define DMA_SNIFF_CTRL_DMACH_MASK (0xf << DMA_SNIFF_CTRL_DMACH_SHIFT)
+#define DMA_SNIFF_CTRL_EN BIT(0)
+#define DMA_SNIFF_CTRL_MASK 0xfff
+
+#define DMA_SNIFF_CALC_CRC32 0x0
+#define DMA_SNIFF_CALC_CRC32R 0x1
+#define DMA_SNIFF_CALC_CRC16 0x2
+#define DMA_SNIFF_CALC_CRC16R 0x3
+#define DMA_SNIFF_CALC_EVEN 0xe
+#define DMA_SNIFF_CALC_SUM 0xf
+
+#define DMA_CTRL_WRITABLE_MASK (DMA_CTRL_SNIFF_EN | DMA_CTRL_BSWAP | \
+ DMA_CTRL_IRQ_QUIET | \
+ DMA_CTRL_TREQ_SEL_MASK | \
+ DMA_CTRL_CHAIN_TO_MASK | \
+ DMA_CTRL_RING_SEL | \
+ DMA_CTRL_RING_SIZE_MASK | \
+ DMA_CTRL_INCR_WRITE | \
+ DMA_CTRL_INCR_READ | \
+ DMA_CTRL_DATA_SIZE_MASK | \
+ BIT(1) | DMA_CTRL_EN)
+#define DMA_CHANNEL_MASK ((1u << RP2040_DMA_NUM_CHANNELS) - 1)
+#define DMA_PACING_SYSCLK_HZ 125000000ULL
+#define DMA_PACING_SYSCLK_NS 8ULL
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_dma_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 uint32_t rp2040_dma_ints(RP2040DmaState *s, unsigned irq)
+{
+ return ((s->intr & s->inte[irq]) | s->intf[irq]) & DMA_CHANNEL_MASK;
+}
+
+static void rp2040_dma_update_irq(RP2040DmaState *s)
+{
+ int i;
+
+ for (i = 0; i < RP2040_DMA_NUM_IRQS; i++) {
+ qemu_set_irq(s->irq[i], rp2040_dma_ints(s, i) != 0);
+ }
+}
+
+static unsigned rp2040_dma_transfer_size(RP2040DmaChannel *ch)
+{
+ switch ((ch->ctrl & DMA_CTRL_DATA_SIZE_MASK) >> DMA_CTRL_DATA_SIZE_SHIFT) {
+ case 0:
+ return 1;
+ case 1:
+ return 2;
+ case 2:
+ return 4;
+ default:
+ return 4;
+ }
+}
+
+static void rp2040_dma_request_start(RP2040DmaState *s, unsigned index);
+static void rp2040_dma_dreq(void *opaque, int n, int level);
+static void rp2040_dma_dreq_pulse(RP2040DmaState *s, uint32_t dreq);
+static void rp2040_dma_dreq_bh(void *opaque);
+static bool rp2040_dma_dreq_has_busy_channel(RP2040DmaState *s, uint32_t dreq,
+ unsigned except);
+static void rp2040_dma_write_reg(RP2040DmaState *s, hwaddr addr,
+ uint64_t value64, unsigned size);
+
+static uint32_t rp2040_dma_treq(RP2040DmaChannel *ch)
+{
+ return (ch->ctrl & DMA_CTRL_TREQ_SEL_MASK) >> DMA_CTRL_TREQ_SEL_SHIFT;
+}
+
+static bool rp2040_dma_treq_is_ready_sink(uint32_t treq)
+{
+ /*
+ * The current XIP/SSI TX model does not have a finite TX FIFO: writes to
+ * its data register are accepted immediately.
+ */
+ return treq == RP2040_DREQ_XIP_SSITX;
+}
+
+static bool rp2040_dma_treq_is_connected_level(uint32_t treq)
+{
+ return treq == RP2040_DREQ_UART0_TX || treq == RP2040_DREQ_UART0_RX ||
+ treq == RP2040_DREQ_UART1_TX || treq == RP2040_DREQ_UART1_RX ||
+ treq == RP2040_DREQ_XIP_STREAM;
+}
+
+static bool rp2040_dma_treq_is_timer(uint32_t treq)
+{
+ return treq >= RP2040_DREQ_DMA_TIMER0 &&
+ treq <= RP2040_DREQ_DMA_TIMER3;
+}
+
+static uint64_t rp2040_dma_timer_period_ns(uint32_t value)
+{
+ uint32_t x = value >> 16;
+ uint32_t y = value & 0xffff;
+ uint64_t period;
+
+ if (x == 0 || y == 0) {
+ return 0;
+ }
+
+ /*
+ * The RP2040 fractional timer emits TREQs at (X/Y) * sys_clk, capped
+ * at one TREQ per sys_clk. Use the Pico's nominal 125 MHz system clock
+ * as the virtual pacing source.
+ */
+ if (x >= y) {
+ return DMA_PACING_SYSCLK_NS;
+ }
+
+ period = DIV_ROUND_UP((uint64_t)y * NANOSECONDS_PER_SECOND,
+ (uint64_t)x * DMA_PACING_SYSCLK_HZ);
+ return MAX(period, 1);
+}
+
+static void rp2040_dma_timer_update(RP2040DmaState *s, unsigned index)
+{
+ RP2040DmaPacingTimer *pt = &s->pacing_timer[index];
+
+ pt->period_ns = rp2040_dma_timer_period_ns(s->timer[index]);
+ if (pt->period_ns == 0) {
+ timer_del(pt->timer);
+ return;
+ }
+
+ timer_mod(pt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ pt->period_ns);
+}
+
+static void rp2040_dma_timer_cb(void *opaque)
+{
+ RP2040DmaPacingTimer *pt = opaque;
+ RP2040DmaState *s = pt->dma;
+ uint32_t dreq = RP2040_DREQ_DMA_TIMER0 + pt->index;
+
+ if (rp2040_dma_dreq_has_busy_channel(s, dreq, RP2040_DMA_NUM_CHANNELS)) {
+ rp2040_dma_dreq_pulse(s, dreq);
+ }
+ if (pt->period_ns != 0) {
+ timer_mod(pt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ pt->period_ns);
+ }
+}
+
+static uint32_t rp2040_dma_next_addr(RP2040DmaChannel *ch, uint32_t addr,
+ unsigned width, bool write)
+{
+ uint32_t ring_size;
+ bool ring_write;
+ uint32_t mask;
+
+ if (!(ch->ctrl & (write ? DMA_CTRL_INCR_WRITE : DMA_CTRL_INCR_READ))) {
+ return addr;
+ }
+
+ ring_size = (ch->ctrl & DMA_CTRL_RING_SIZE_MASK) >>
+ DMA_CTRL_RING_SIZE_SHIFT;
+ ring_write = ch->ctrl & DMA_CTRL_RING_SEL;
+ if (ring_size == 0 || ring_write != write) {
+ return addr + width;
+ }
+
+ mask = (1u << ring_size) - 1;
+ return (addr & ~mask) | ((addr + width) & mask);
+}
+
+static uint32_t rp2040_dma_sniff_word(const uint8_t *buf, unsigned width)
+{
+ uint32_t value = buf[0];
+
+ if (width >= 2) {
+ value |= (uint32_t)buf[1] << 8;
+ }
+ if (width >= 4) {
+ value |= (uint32_t)buf[2] << 16;
+ value |= (uint32_t)buf[3] << 24;
+ }
+ return value;
+}
+
+static void rp2040_dma_sniff_bswap(uint8_t *buf, unsigned width)
+{
+ if (width == 2) {
+ uint8_t t = buf[0];
+ buf[0] = buf[1];
+ buf[1] = t;
+ } else if (width == 4) {
+ uint8_t t = buf[0];
+ buf[0] = buf[3];
+ buf[3] = t;
+ t = buf[1];
+ buf[1] = buf[2];
+ buf[2] = t;
+ }
+}
+
+static uint32_t rp2040_dma_sniff_crc32(uint32_t crc, const uint8_t *buf,
+ unsigned width, bool reverse)
+{
+ int i;
+ int bit;
+
+ for (i = 0; i < width; i++) {
+ uint8_t byte = reverse ? revbit8(buf[i]) : buf[i];
+
+ crc ^= (uint32_t)byte << 24;
+ for (bit = 0; bit < 8; bit++) {
+ crc = (crc & BIT(31)) ? (crc << 1) ^ 0x04c11db7 : crc << 1;
+ }
+ }
+ return crc;
+}
+
+static uint32_t rp2040_dma_sniff_crc16(uint32_t crc, const uint8_t *buf,
+ unsigned width, bool reverse)
+{
+ uint16_t crc16 = crc;
+ int i;
+ int bit;
+
+ for (i = 0; i < width; i++) {
+ uint8_t byte = reverse ? revbit8(buf[i]) : buf[i];
+
+ crc16 ^= (uint16_t)byte << 8;
+ for (bit = 0; bit < 8; bit++) {
+ crc16 = (crc16 & BIT(15)) ? (crc16 << 1) ^ 0x1021 : crc16 << 1;
+ }
+ }
+ return (crc & 0xffff0000) | crc16;
+}
+
+static void rp2040_dma_sniff_update(RP2040DmaState *s, unsigned index,
+ const uint8_t *buf, unsigned width)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+ uint8_t sniff_buf[4] = { 0 };
+ uint32_t channel;
+ uint32_t calc;
+ uint32_t value;
+
+ if (!(s->sniff_ctrl & DMA_SNIFF_CTRL_EN) ||
+ !(ch->ctrl & DMA_CTRL_SNIFF_EN)) {
+ return;
+ }
+
+ channel = (s->sniff_ctrl & DMA_SNIFF_CTRL_DMACH_MASK) >>
+ DMA_SNIFF_CTRL_DMACH_SHIFT;
+ if (channel != index) {
+ return;
+ }
+
+ memcpy(sniff_buf, buf, width);
+ if (s->sniff_ctrl & DMA_SNIFF_CTRL_BSWAP) {
+ rp2040_dma_sniff_bswap(sniff_buf, width);
+ }
+
+ calc = (s->sniff_ctrl & DMA_SNIFF_CTRL_CALC_MASK) >>
+ DMA_SNIFF_CTRL_CALC_SHIFT;
+ switch (calc) {
+ case DMA_SNIFF_CALC_CRC32:
+ s->sniff_data = rp2040_dma_sniff_crc32(s->sniff_data, sniff_buf,
+ width, false);
+ break;
+ case DMA_SNIFF_CALC_CRC32R:
+ s->sniff_data = rp2040_dma_sniff_crc32(s->sniff_data, sniff_buf,
+ width, true);
+ break;
+ case DMA_SNIFF_CALC_CRC16:
+ s->sniff_data = rp2040_dma_sniff_crc16(s->sniff_data, sniff_buf,
+ width, false);
+ break;
+ case DMA_SNIFF_CALC_CRC16R:
+ s->sniff_data = rp2040_dma_sniff_crc16(s->sniff_data, sniff_buf,
+ width, true);
+ break;
+ case DMA_SNIFF_CALC_EVEN:
+ value = rp2040_dma_sniff_word(sniff_buf, width);
+ s->sniff_data = (s->sniff_data ^ ctpop32(value)) & 1;
+ break;
+ case DMA_SNIFF_CALC_SUM:
+ s->sniff_data += rp2040_dma_sniff_word(sniff_buf, width);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint32_t rp2040_dma_sniff_read_data(RP2040DmaState *s)
+{
+ uint32_t value = s->sniff_data;
+
+ if (s->sniff_ctrl & DMA_SNIFF_CTRL_OUT_REV) {
+ value = revbit32(value);
+ }
+ if (s->sniff_ctrl & DMA_SNIFF_CTRL_OUT_INV) {
+ value = ~value;
+ }
+ return value;
+}
+
+static void rp2040_dma_finish_channel(RP2040DmaState *s, unsigned index)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+ uint32_t chain_to;
+
+ ch->ctrl &= ~DMA_CTRL_BUSY;
+ if (!(ch->ctrl & DMA_CTRL_IRQ_QUIET)) {
+ s->intr |= BIT(index);
+ rp2040_dma_update_irq(s);
+ }
+
+ chain_to = (ch->ctrl & DMA_CTRL_CHAIN_TO_MASK) >> DMA_CTRL_CHAIN_TO_SHIFT;
+ if (!(ch->ctrl & DMA_CTRL_ERROR_MASK) &&
+ chain_to < RP2040_DMA_NUM_CHANNELS && chain_to != index) {
+ rp2040_dma_request_start(s, chain_to);
+ }
+}
+
+static void rp2040_dma_run_beats(RP2040DmaState *s, unsigned index,
+ uint32_t beats)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+ uint32_t count;
+ unsigned width;
+
+ if (!(ch->ctrl & DMA_CTRL_EN) || ch->trans_count == 0) {
+ return;
+ }
+
+ width = rp2040_dma_transfer_size(ch);
+ count = MIN(ch->trans_count, beats);
+
+ while (count--) {
+ uint8_t buf[4] = { 0 };
+ MemTxResult result;
+
+ result = address_space_rw(&s->dma_as, ch->read_addr,
+ MEMTXATTRS_UNSPECIFIED, buf, width, false);
+ if (result != MEMTX_OK) {
+ ch->ctrl |= DMA_CTRL_READ_ERROR | DMA_CTRL_AHB_ERROR;
+ break;
+ }
+
+ if (ch->ctrl & DMA_CTRL_BSWAP) {
+ if (width == 2) {
+ uint8_t t = buf[0];
+ buf[0] = buf[1];
+ buf[1] = t;
+ } else if (width == 4) {
+ uint8_t t = buf[0];
+ buf[0] = buf[3];
+ buf[3] = t;
+ t = buf[1];
+ buf[1] = buf[2];
+ buf[2] = t;
+ }
+ }
+ rp2040_dma_sniff_update(s, index, buf, width);
+
+ if (ch->write_addr >= RP2040_DMA_BASE &&
+ ch->write_addr < RP2040_DMA_BASE + RP2040_DMA_SIZE) {
+ if (width == 4) {
+ rp2040_dma_write_reg(s, ch->write_addr - RP2040_DMA_BASE,
+ ldl_le_p(buf), width);
+ result = MEMTX_OK;
+ } else {
+ result = MEMTX_ERROR;
+ }
+ } else {
+ result = address_space_rw(&s->dma_as, ch->write_addr,
+ MEMTXATTRS_UNSPECIFIED, buf, width,
+ true);
+ }
+ if (result != MEMTX_OK) {
+ ch->ctrl |= DMA_CTRL_WRITE_ERROR | DMA_CTRL_AHB_ERROR;
+ break;
+ }
+
+ ch->trans_count--;
+ ch->read_addr = rp2040_dma_next_addr(ch, ch->read_addr, width, false);
+ ch->write_addr = rp2040_dma_next_addr(ch, ch->write_addr, width, true);
+ }
+
+ if ((ch->ctrl & DMA_CTRL_ERROR_MASK) || ch->trans_count == 0) {
+ rp2040_dma_finish_channel(s, index);
+ }
+}
+
+static void rp2040_dma_start_channel(RP2040DmaState *s, unsigned index)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+ uint32_t treq;
+
+ if (!(ch->ctrl & DMA_CTRL_EN)) {
+ return;
+ }
+
+ if (ch->trans_count == 0 && ch->reload_count != 0) {
+ ch->trans_count = ch->reload_count;
+ }
+
+ if (ch->trans_count == 0) {
+ if (ch->ctrl & DMA_CTRL_IRQ_QUIET) {
+ s->intr |= BIT(index);
+ rp2040_dma_update_irq(s);
+ }
+ return;
+ }
+
+ ch->ctrl |= DMA_CTRL_BUSY;
+ ch->ctrl &= ~DMA_CTRL_ERROR_MASK;
+ ch->paced_nyi_logged = false;
+
+ treq = rp2040_dma_treq(ch);
+ if (treq == RP2040_DREQ_FORCE || rp2040_dma_treq_is_ready_sink(treq)) {
+ rp2040_dma_run_beats(s, index, UINT32_MAX);
+ } else if ((rp2040_dma_treq_is_connected_level(treq) ||
+ treq == RP2040_DREQ_XIP_SSIRX) && s->dreq_level[treq]) {
+ rp2040_dma_dreq_pulse(s, treq);
+ } else if (treq != RP2040_DREQ_XIP_SSIRX &&
+ !rp2040_dma_treq_is_connected_level(treq) &&
+ !rp2040_dma_treq_is_timer(treq) &&
+ !ch->paced_nyi_logged) {
+ rp2040_log_nyi("dma", "paced transfer",
+ "DREQ source is not connected yet");
+ ch->paced_nyi_logged = true;
+ }
+}
+
+static void rp2040_dma_drain_starts(RP2040DmaState *s)
+{
+ if (s->engine_active) {
+ return;
+ }
+
+ s->engine_active = true;
+ while (s->pending_start) {
+ unsigned index = ctz32(s->pending_start);
+
+ s->pending_start &= ~BIT(index);
+ rp2040_dma_start_channel(s, index);
+ }
+ s->engine_active = false;
+}
+
+static void rp2040_dma_request_start(RP2040DmaState *s, unsigned index)
+{
+ if (index >= RP2040_DMA_NUM_CHANNELS) {
+ return;
+ }
+
+ s->pending_start |= BIT(index);
+ rp2040_dma_drain_starts(s);
+}
+
+static void rp2040_dma_dreq_pulse(RP2040DmaState *s, uint32_t dreq)
+{
+ if (dreq >= RP2040_DMA_NUM_DREQS) {
+ return;
+ }
+
+ if (s->pending_dreq[dreq] != UINT32_MAX) {
+ s->pending_dreq[dreq]++;
+ }
+ qemu_bh_schedule(s->dreq_bh);
+}
+
+static void rp2040_dma_dreq(void *opaque, int n, int level)
+{
+ RP2040DmaState *s = opaque;
+
+ if (n < 0 || n >= RP2040_DMA_NUM_DREQS) {
+ return;
+ }
+
+ s->dreq_level[n] = level;
+ if (level) {
+ rp2040_dma_dreq_pulse(s, n);
+ if (rp2040_dma_treq_is_connected_level(n)) {
+ rp2040_dma_dreq_bh(s);
+ }
+ }
+}
+
+static void rp2040_dma_dreq_bh(void *opaque)
+{
+ RP2040DmaState *s = opaque;
+ int i;
+ int dreq;
+
+ if (s->dreq_servicing) {
+ return;
+ }
+
+ s->dreq_servicing = true;
+ for (dreq = 0; dreq < RP2040_DMA_NUM_DREQS; dreq++) {
+ while (s->pending_dreq[dreq] > 0) {
+ s->pending_dreq[dreq]--;
+ for (i = 0; i < RP2040_DMA_NUM_CHANNELS; i++) {
+ RP2040DmaChannel *ch = &s->chan[i];
+
+ if ((ch->ctrl & DMA_CTRL_BUSY) && rp2040_dma_treq(ch) == dreq) {
+ s->engine_active = true;
+ rp2040_dma_run_beats(s, i, 1);
+ s->engine_active = false;
+ rp2040_dma_drain_starts(s);
+ }
+ }
+ if (s->dreq_level[dreq] &&
+ rp2040_dma_dreq_has_busy_channel(s, dreq,
+ RP2040_DMA_NUM_CHANNELS)) {
+ rp2040_dma_dreq_pulse(s, dreq);
+ }
+ }
+ }
+ s->dreq_servicing = false;
+}
+
+static bool rp2040_dma_dreq_has_busy_channel(RP2040DmaState *s, uint32_t dreq,
+ unsigned except)
+{
+ int i;
+
+ for (i = 0; i < RP2040_DMA_NUM_CHANNELS; i++) {
+ RP2040DmaChannel *ch = &s->chan[i];
+
+ if (i != except && (ch->ctrl & DMA_CTRL_BUSY) &&
+ rp2040_dma_treq(ch) == dreq) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void rp2040_dma_abort_channel(RP2040DmaState *s, unsigned index)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+ uint32_t treq = rp2040_dma_treq(ch);
+
+ /*
+ * RP2040 CHAN_ABORT clears the transfer counter and leaves the channel
+ * inactive. QEMU does not model in-flight bus-transfer latency, so the
+ * abort status bit is clear as soon as the write completes.
+ */
+ ch->ctrl &= ~DMA_CTRL_BUSY;
+ ch->trans_count = 0;
+ if (treq < RP2040_DMA_NUM_DREQS &&
+ !rp2040_dma_dreq_has_busy_channel(s, treq, index)) {
+ s->pending_dreq[treq] = 0;
+ }
+}
+
+static uint32_t rp2040_dma_read_channel(RP2040DmaState *s, unsigned index,
+ hwaddr offset)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+
+ switch (offset) {
+ case DMA_CH_READ_ADDR:
+ case DMA_CH_AL1_READ_ADDR:
+ case DMA_CH_AL2_READ_ADDR:
+ case DMA_CH_AL3_READ_ADDR:
+ return ch->read_addr;
+ case DMA_CH_WRITE_ADDR:
+ case DMA_CH_AL1_WRITE_ADDR:
+ case DMA_CH_AL2_WRITE_ADDR:
+ case DMA_CH_AL3_WRITE_ADDR:
+ return ch->write_addr;
+ case DMA_CH_TRANS_COUNT:
+ case DMA_CH_AL1_TRANS_COUNT:
+ case DMA_CH_AL2_TRANS_COUNT:
+ case DMA_CH_AL3_TRANS_COUNT:
+ return ch->trans_count;
+ case DMA_CH_CTRL_TRIG:
+ case DMA_CH_AL1_CTRL:
+ case DMA_CH_AL2_CTRL:
+ case DMA_CH_AL3_CTRL:
+ return ch->ctrl;
+ default:
+ return 0;
+ }
+}
+
+static void rp2040_dma_write_ctrl(RP2040DmaState *s, unsigned index,
+ uint32_t value, bool trigger)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+ uint32_t errors = ch->ctrl & DMA_CTRL_ERROR_MASK;
+
+ errors &= ~(value & (DMA_CTRL_READ_ERROR | DMA_CTRL_WRITE_ERROR));
+ if (errors & (DMA_CTRL_READ_ERROR | DMA_CTRL_WRITE_ERROR)) {
+ errors |= DMA_CTRL_AHB_ERROR;
+ } else {
+ errors &= ~DMA_CTRL_AHB_ERROR;
+ }
+ ch->ctrl = (value & DMA_CTRL_WRITABLE_MASK) | errors;
+ if (trigger) {
+ rp2040_dma_request_start(s, index);
+ }
+}
+
+static void rp2040_dma_write_channel(RP2040DmaState *s, unsigned index,
+ hwaddr offset, uint32_t value)
+{
+ RP2040DmaChannel *ch = &s->chan[index];
+
+ switch (offset) {
+ case DMA_CH_READ_ADDR:
+ case DMA_CH_AL1_READ_ADDR:
+ case DMA_CH_AL2_READ_ADDR:
+ ch->read_addr = value;
+ break;
+ case DMA_CH_AL3_READ_ADDR:
+ ch->read_addr = value;
+ rp2040_dma_request_start(s, index);
+ break;
+ case DMA_CH_WRITE_ADDR:
+ case DMA_CH_AL1_WRITE_ADDR:
+ case DMA_CH_AL3_WRITE_ADDR:
+ ch->write_addr = value;
+ break;
+ case DMA_CH_AL2_WRITE_ADDR:
+ ch->write_addr = value;
+ rp2040_dma_request_start(s, index);
+ break;
+ case DMA_CH_TRANS_COUNT:
+ case DMA_CH_AL2_TRANS_COUNT:
+ case DMA_CH_AL3_TRANS_COUNT:
+ ch->trans_count = value;
+ ch->reload_count = value;
+ break;
+ case DMA_CH_AL1_TRANS_COUNT:
+ ch->trans_count = value;
+ ch->reload_count = value;
+ rp2040_dma_request_start(s, index);
+ break;
+ case DMA_CH_CTRL_TRIG:
+ rp2040_dma_write_ctrl(s, index, value, true);
+ break;
+ case DMA_CH_AL1_CTRL:
+ case DMA_CH_AL2_CTRL:
+ case DMA_CH_AL3_CTRL:
+ rp2040_dma_write_ctrl(s, index, value, false);
+ break;
+ default:
+ break;
+ }
+}
+
+static uint64_t rp2040_dma_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040DmaState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value;
+
+ if (offset < RP2040_DMA_NUM_CHANNELS * DMA_CH_SIZE) {
+ value = rp2040_dma_read_channel(s, offset / DMA_CH_SIZE,
+ offset % DMA_CH_SIZE);
+ } else {
+ switch (offset) {
+ case DMA_INTR:
+ value = s->intr;
+ break;
+ case DMA_INTE0:
+ value = s->inte[0];
+ break;
+ case DMA_INTF0:
+ value = s->intf[0];
+ break;
+ case DMA_INTS0:
+ value = rp2040_dma_ints(s, 0);
+ break;
+ case DMA_INTE1:
+ value = s->inte[1];
+ break;
+ case DMA_INTF1:
+ value = s->intf[1];
+ break;
+ case DMA_INTS1:
+ value = rp2040_dma_ints(s, 1);
+ break;
+ case DMA_TIMER0 ... DMA_TIMER0 + 3 * sizeof(uint32_t):
+ value = s->timer[(offset - DMA_TIMER0) / sizeof(uint32_t)];
+ break;
+ case DMA_SNIFF_CTRL:
+ value = s->sniff_ctrl;
+ break;
+ case DMA_SNIFF_DATA:
+ value = rp2040_dma_sniff_read_data(s);
+ break;
+ case DMA_FIFO_LEVELS:
+ value = 0;
+ break;
+ case DMA_CHAN_ABORT:
+ value = 0;
+ break;
+ default:
+ value = 0;
+ qemu_log_mask(LOG_UNIMP, "rp2040.dma: unimplemented read "
+ "(size %d, addr 0x%08" HWADDR_PRIx
+ ", offset 0x%04" HWADDR_PRIx
+ ") -> 0x%0*" PRIx32 "\n",
+ size, RP2040_DMA_BASE + addr, offset,
+ size << 1, value);
+ break;
+ }
+ }
+
+ return value;
+}
+
+static void rp2040_dma_write_reg(RP2040DmaState *s, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+ uint32_t old;
+ int i;
+
+ if (offset < RP2040_DMA_NUM_CHANNELS * DMA_CH_SIZE) {
+ rp2040_dma_write_channel(s, offset / DMA_CH_SIZE,
+ offset % DMA_CH_SIZE, value);
+ } else {
+ switch (offset) {
+ case DMA_INTR:
+ s->intr &= ~(value & DMA_CHANNEL_MASK);
+ rp2040_dma_update_irq(s);
+ break;
+ case DMA_INTE0:
+ s->inte[0] = rp2040_dma_apply_alias(s->inte[0], value, alias) &
+ DMA_CHANNEL_MASK;
+ rp2040_dma_update_irq(s);
+ break;
+ case DMA_INTF0:
+ s->intf[0] = rp2040_dma_apply_alias(s->intf[0], value, alias) &
+ DMA_CHANNEL_MASK;
+ rp2040_dma_update_irq(s);
+ break;
+ case DMA_INTS0:
+ s->intr &= ~(value & DMA_CHANNEL_MASK);
+ rp2040_dma_update_irq(s);
+ break;
+ case DMA_INTE1:
+ s->inte[1] = rp2040_dma_apply_alias(s->inte[1], value, alias) &
+ DMA_CHANNEL_MASK;
+ rp2040_dma_update_irq(s);
+ break;
+ case DMA_INTF1:
+ s->intf[1] = rp2040_dma_apply_alias(s->intf[1], value, alias) &
+ DMA_CHANNEL_MASK;
+ rp2040_dma_update_irq(s);
+ break;
+ case DMA_INTS1:
+ s->intr &= ~(value & DMA_CHANNEL_MASK);
+ rp2040_dma_update_irq(s);
+ break;
+ case DMA_TIMER0 ... DMA_TIMER0 + 3 * sizeof(uint32_t):
+ i = (offset - DMA_TIMER0) / sizeof(uint32_t);
+ s->timer[i] = rp2040_dma_apply_alias(s->timer[i], value, alias);
+ rp2040_dma_timer_update(s, i);
+ break;
+ case DMA_MULTI_CHAN_TRIGGER:
+ value &= DMA_CHANNEL_MASK;
+ for (i = 0; i < RP2040_DMA_NUM_CHANNELS; i++) {
+ if (value & BIT(i)) {
+ rp2040_dma_request_start(s, i);
+ }
+ }
+ break;
+ case DMA_SNIFF_CTRL:
+ old = s->sniff_ctrl;
+ s->sniff_ctrl = rp2040_dma_apply_alias(old, value, alias) &
+ DMA_SNIFF_CTRL_MASK;
+ break;
+ case DMA_SNIFF_DATA:
+ s->sniff_data = rp2040_dma_apply_alias(s->sniff_data, value,
+ alias);
+ break;
+ case DMA_CHAN_ABORT:
+ value &= DMA_CHANNEL_MASK;
+ for (i = 0; i < RP2040_DMA_NUM_CHANNELS; i++) {
+ if (value & BIT(i)) {
+ rp2040_dma_abort_channel(s, i);
+ }
+ }
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "rp2040.dma: unimplemented write "
+ "(size %d, addr 0x%08" HWADDR_PRIx
+ ", offset 0x%04" HWADDR_PRIx
+ ", value 0x%0*" PRIx64 ")\n",
+ size, RP2040_DMA_BASE + addr, offset, size << 1,
+ value64);
+ break;
+ }
+ }
+}
+
+static void rp2040_dma_write(void *opaque, hwaddr addr, uint64_t value64,
+ unsigned size)
+{
+ rp2040_dma_write_reg(opaque, addr, value64, size);
+}
+
+static const MemoryRegionOps rp2040_dma_ops = {
+ .read = rp2040_dma_read,
+ .write = rp2040_dma_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_dma_reset(DeviceState *dev)
+{
+ RP2040DmaState *s = RP2040_DMA(dev);
+ int i;
+
+ for (i = 0; i < RP2040_DMA_NUM_CHANNELS; i++) {
+ s->chan[i].read_addr = 0;
+ s->chan[i].write_addr = 0;
+ s->chan[i].trans_count = 0;
+ s->chan[i].reload_count = 0;
+ s->chan[i].ctrl = i << DMA_CTRL_CHAIN_TO_SHIFT;
+ s->chan[i].paced_nyi_logged = false;
+ }
+ s->intr = 0;
+ s->inte[0] = 0;
+ s->inte[1] = 0;
+ s->intf[0] = 0;
+ s->intf[1] = 0;
+ memset(s->timer, 0, sizeof(s->timer));
+ for (i = 0; i < RP2040_DMA_NUM_TIMERS; i++) {
+ s->pacing_timer[i].period_ns = 0;
+ timer_del(s->pacing_timer[i].timer);
+ }
+ memset(s->dreq_level, 0, sizeof(s->dreq_level));
+ memset(s->pending_dreq, 0, sizeof(s->pending_dreq));
+ qemu_bh_cancel(s->dreq_bh);
+ s->engine_active = false;
+ s->pending_start = 0;
+ s->sniff_ctrl = 0;
+ s->sniff_data = 0;
+ rp2040_dma_update_irq(s);
+}
+
+static void rp2040_dma_init(Object *obj)
+{
+ RP2040DmaState *s = RP2040_DMA(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ int i;
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_dma_ops, s,
+ TYPE_RP2040_DMA, RP2040_DMA_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ for (i = 0; i < RP2040_DMA_NUM_IRQS; i++) {
+ sysbus_init_irq(sbd, &s->irq[i]);
+ }
+ s->dreq_bh = qemu_bh_new(rp2040_dma_dreq_bh, s);
+ qdev_init_gpio_in_named(DEVICE(obj), rp2040_dma_dreq, "dreq",
+ RP2040_DMA_NUM_DREQS);
+ for (i = 0; i < RP2040_DMA_NUM_TIMERS; i++) {
+ s->pacing_timer[i].dma = s;
+ s->pacing_timer[i].index = i;
+ s->pacing_timer[i].timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+ rp2040_dma_timer_cb,
+ &s->pacing_timer[i]);
+ }
+}
+
+static void rp2040_dma_finalize(Object *obj)
+{
+ RP2040DmaState *s = RP2040_DMA(obj);
+ int i;
+
+ for (i = 0; i < RP2040_DMA_NUM_TIMERS; i++) {
+ timer_free(s->pacing_timer[i].timer);
+ }
+ qemu_bh_delete(s->dreq_bh);
+}
+
+static void rp2040_dma_realize(DeviceState *dev, Error **errp)
+{
+ RP2040DmaState *s = RP2040_DMA(dev);
+
+ if (!s->dma_mr) {
+ error_setg(errp, "memory property was not set");
+ return;
+ }
+
+ address_space_init(&s->dma_as, s->dma_mr, "rp2040-dma-memory");
+}
+
+static const VMStateDescription rp2040_dma_vmstate = {
+ .name = TYPE_RP2040_DMA,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const Property rp2040_dma_properties[] = {
+ DEFINE_PROP_LINK("memory", RP2040DmaState, dma_mr, TYPE_MEMORY_REGION,
+ MemoryRegion *),
+};
+
+static void rp2040_dma_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = rp2040_dma_realize;
+ device_class_set_legacy_reset(dc, rp2040_dma_reset);
+ dc->vmsd = &rp2040_dma_vmstate;
+ device_class_set_props(dc, rp2040_dma_properties);
+}
+
+static const TypeInfo rp2040_dma_info = {
+ .name = TYPE_RP2040_DMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040DmaState),
+ .instance_init = rp2040_dma_init,
+ .instance_finalize = rp2040_dma_finalize,
+ .class_init = rp2040_dma_class_init,
+};
+
+static void rp2040_dma_register_types(void)
+{
+ type_register_static(&rp2040_dma_info);
+}
+type_init(rp2040_dma_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index cb108bd5f1..d52c8856b0 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -13,6 +13,7 @@
#include "hw/char/pl011.h"
#include "hw/core/clock.h"
#include "hw/core/sysbus.h"
+#include "hw/dma/rp2040_dma.h"
#include "hw/misc/rp2040_clocks.h"
#include "hw/misc/rp2040_iobank0.h"
#include "hw/misc/rp2040_ioqspi.h"
@@ -56,6 +57,7 @@ struct RP2040State {
ARMv7MState armv7m[RP2040_NUM_CORES];
PL011State uart[2];
RP2040ClocksState clocks;
+ RP2040DmaState dma;
RP2040IoBank0State iobank0;
RP2040IoQspiState ioqspi;
RP2040PadsBank0State pads_bank0;
diff --git a/include/hw/dma/rp2040_dma.h b/include/hw/dma/rp2040_dma.h
new file mode 100644
index 0000000000..4b4dae4097
--- /dev/null
+++ b/include/hw/dma/rp2040_dma.h
@@ -0,0 +1,77 @@
+/*
+ * RP2040 DMA emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_DMA_RP2040_DMA_H
+#define HW_DMA_RP2040_DMA_H
+
+#include "hw/core/sysbus.h"
+#include "system/memory.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_DMA "rp2040-dma"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040DmaState, RP2040_DMA)
+
+#define RP2040_DMA_BASE 0x50000000
+#define RP2040_DMA_SIZE 0x4000
+#define RP2040_DMA_NUM_CHANNELS 12
+#define RP2040_DMA_NUM_IRQS 2
+#define RP2040_DMA_NUM_DREQS 64
+#define RP2040_DMA_NUM_TIMERS 4
+
+#define RP2040_DREQ_UART0_TX 20
+#define RP2040_DREQ_UART0_RX 21
+#define RP2040_DREQ_UART1_TX 22
+#define RP2040_DREQ_UART1_RX 23
+#define RP2040_DREQ_XIP_STREAM 37
+#define RP2040_DREQ_XIP_SSITX 38
+#define RP2040_DREQ_XIP_SSIRX 39
+#define RP2040_DREQ_DMA_TIMER0 59
+#define RP2040_DREQ_DMA_TIMER1 60
+#define RP2040_DREQ_DMA_TIMER2 61
+#define RP2040_DREQ_DMA_TIMER3 62
+#define RP2040_DREQ_FORCE 63
+
+typedef struct RP2040DmaChannel {
+ uint32_t read_addr;
+ uint32_t write_addr;
+ uint32_t trans_count;
+ uint32_t reload_count;
+ uint32_t ctrl;
+ bool paced_nyi_logged;
+} RP2040DmaChannel;
+
+typedef struct RP2040DmaPacingTimer {
+ void *dma;
+ QEMUTimer *timer;
+ uint64_t period_ns;
+ unsigned index;
+} RP2040DmaPacingTimer;
+
+struct RP2040DmaState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ MemoryRegion *dma_mr;
+ AddressSpace dma_as;
+ qemu_irq irq[RP2040_DMA_NUM_IRQS];
+
+ RP2040DmaChannel chan[RP2040_DMA_NUM_CHANNELS];
+ uint32_t intr;
+ uint32_t inte[RP2040_DMA_NUM_IRQS];
+ uint32_t intf[RP2040_DMA_NUM_IRQS];
+ uint32_t timer[RP2040_DMA_NUM_TIMERS];
+ RP2040DmaPacingTimer pacing_timer[RP2040_DMA_NUM_TIMERS];
+ uint32_t sniff_ctrl;
+ uint32_t sniff_data;
+ QEMUBH *dreq_bh;
+ bool dreq_servicing;
+ bool engine_active;
+ uint32_t pending_start;
+ bool dreq_level[RP2040_DMA_NUM_DREQS];
+ uint32_t pending_dreq[RP2040_DMA_NUM_DREQS];
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 9bc7540aee..89e8f3ac32 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -257,6 +257,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
'rp2040-clocks-test',
+ 'rp2040-dma-test',
'rp2040-iobank0-test',
'rp2040-ioqspi-test',
'rp2040-pads-test',
diff --git a/tests/qtest/rp2040-dma-test.c b/tests/qtest/rp2040-dma-test.c
new file mode 100644
index 0000000000..61ad2fac40
--- /dev/null
+++ b/tests/qtest/rp2040-dma-test.c
@@ -0,0 +1,374 @@
+/*
+ * QTest testcase for the RP2040 DMA block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define DMA_BASE 0x50000000
+#define DMA_CH_READ_ADDR 0x00
+#define DMA_CH_WRITE_ADDR 0x04
+#define DMA_CH_TRANS_COUNT 0x08
+#define DMA_CH_CTRL_TRIG 0x0c
+#define DMA_CH_AL1_CTRL 0x10
+#define DMA_CH_AL3_TRANS_COUNT 0x38
+#define DMA_CH_AL3_READ_ADDR 0x3c
+#define DMA_CH_SIZE 0x40
+#define DMA_INTR 0x400
+#define DMA_INTE0 0x404
+#define DMA_INTS0 0x40c
+#define DMA_TIMER0 0x420
+#define DMA_SNIFF_CTRL 0x434
+#define DMA_SNIFF_DATA 0x438
+#define DMA_CHAN_ABORT 0x444
+
+#define DMA_CTRL_AHB_ERROR BIT(31)
+#define DMA_CTRL_READ_ERROR BIT(30)
+#define DMA_CTRL_WRITE_ERROR BIT(29)
+#define DMA_CTRL_BUSY BIT(24)
+#define DMA_CTRL_SNIFF_EN BIT(23)
+#define DMA_CTRL_IRQ_QUIET BIT(21)
+#define DMA_CTRL_TREQ_SEL_SHIFT 15
+#define DMA_CTRL_CHAIN_TO_SHIFT 11
+#define DMA_CTRL_RING_SEL BIT(10)
+#define DMA_CTRL_RING_SIZE_SHIFT 6
+#define DMA_CTRL_INCR_WRITE BIT(5)
+#define DMA_CTRL_INCR_READ BIT(4)
+#define DMA_CTRL_DATA_SIZE_8 (0 << 2)
+#define DMA_CTRL_DATA_SIZE_32 (2 << 2)
+#define DMA_CTRL_EN BIT(0)
+
+#define DMA_SNIFF_CTRL_OUT_INV BIT(11)
+#define DMA_SNIFF_CTRL_OUT_REV BIT(10)
+#define DMA_SNIFF_CTRL_CALC_SUM (0xf << 5)
+#define DMA_SNIFF_CTRL_EN BIT(0)
+
+#define DREQ_DMA_TIMER0 59
+#define DREQ_XIP_SSIRX 39
+#define DREQ_FORCE 63
+
+#define XIP_SSI_BASE 0x18000000
+#define XIP_SSI_DR0 0x60
+
+#define SRAM_BASE 0x20000000
+#define BAD_DMA_ADDR 0x60000000
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_dma_xip_ssi_rx_dreq(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t ctrl = DMA_CTRL_EN | DMA_CTRL_INCR_WRITE | DMA_CTRL_DATA_SIZE_8 |
+ (DREQ_XIP_SSIRX << DMA_CTRL_TREQ_SEL_SHIFT);
+
+ qtest_writel(qts, SRAM_BASE, 0xffffffff);
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, XIP_SSI_BASE + XIP_SSI_DR0);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, SRAM_BASE);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, 4);
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl);
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ DMA_CTRL_BUSY, ==, DMA_CTRL_BUSY);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 4);
+
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x03);
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x00);
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x00);
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x00);
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ DMA_CTRL_BUSY, ==, 0);
+ g_assert_cmphex(qtest_readl(qts, SRAM_BASE), ==, 0);
+
+ qtest_quit(qts);
+}
+
+static void test_dma_write_ring_wrap(void)
+{
+ QTestState *qts = rp2040_start();
+ const uint8_t source[] = { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 };
+ const uint8_t initial[] = { 0xaa, 0xaa, 0xaa, 0xaa };
+ uint8_t dest[sizeof(initial)];
+ uint32_t read_addr = SRAM_BASE + 0x80;
+ uint32_t write_addr = SRAM_BASE + 0x100;
+ uint32_t ctrl = DMA_CTRL_EN | DMA_CTRL_INCR_READ | DMA_CTRL_INCR_WRITE |
+ DMA_CTRL_RING_SEL | DMA_CTRL_DATA_SIZE_8 |
+ (2 << DMA_CTRL_RING_SIZE_SHIFT) |
+ (DREQ_FORCE << DMA_CTRL_TREQ_SEL_SHIFT);
+
+ qtest_memwrite(qts, read_addr, source, sizeof(source));
+ qtest_memwrite(qts, write_addr, initial, sizeof(initial));
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, read_addr);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, write_addr);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, sizeof(source));
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl);
+
+ qtest_memread(qts, write_addr, dest, sizeof(dest));
+ g_assert_cmphex(dest[0], ==, 0x14);
+ g_assert_cmphex(dest[1], ==, 0x15);
+ g_assert_cmphex(dest[2], ==, 0x12);
+ g_assert_cmphex(dest[3], ==, 0x13);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_READ_ADDR), ==,
+ read_addr + sizeof(source));
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_WRITE_ADDR), ==,
+ write_addr + 2);
+
+ qtest_quit(qts);
+}
+
+static void test_dma_channel_abort(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t ctrl = DMA_CTRL_EN | DMA_CTRL_INCR_WRITE | DMA_CTRL_DATA_SIZE_8 |
+ (DREQ_XIP_SSIRX << DMA_CTRL_TREQ_SEL_SHIFT);
+
+ qtest_writel(qts, SRAM_BASE, 0xffffffff);
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, XIP_SSI_BASE + XIP_SSI_DR0);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, SRAM_BASE);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, 4);
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl);
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ DMA_CTRL_BUSY, ==, DMA_CTRL_BUSY);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 4);
+
+ qtest_writel(qts, DMA_BASE + DMA_CHAN_ABORT, BIT(0));
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CHAN_ABORT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ DMA_CTRL_BUSY, ==, 0);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 0);
+
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x03);
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x00);
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x00);
+ qtest_writel(qts, XIP_SSI_BASE + XIP_SSI_DR0, 0x00);
+
+ g_assert_cmphex(qtest_readl(qts, SRAM_BASE), ==, 0xffffffff);
+
+ qtest_quit(qts);
+}
+
+static void test_dma_sniff_sum(void)
+{
+ QTestState *qts = rp2040_start();
+ const uint8_t source[] = { 1, 2, 3, 4 };
+ uint32_t read_addr = SRAM_BASE + 0x180;
+ uint32_t write_addr = SRAM_BASE + 0x190;
+ uint32_t ctrl = DMA_CTRL_EN | DMA_CTRL_INCR_READ | DMA_CTRL_INCR_WRITE |
+ DMA_CTRL_SNIFF_EN | DMA_CTRL_DATA_SIZE_8 |
+ (DREQ_FORCE << DMA_CTRL_TREQ_SEL_SHIFT);
+
+ qtest_memwrite(qts, read_addr, source, sizeof(source));
+ qtest_writel(qts, DMA_BASE + DMA_SNIFF_DATA, 0x100);
+ qtest_writel(qts, DMA_BASE + DMA_SNIFF_CTRL,
+ DMA_SNIFF_CTRL_EN | DMA_SNIFF_CTRL_CALC_SUM);
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, read_addr);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, write_addr);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, sizeof(source));
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl);
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_SNIFF_DATA), ==, 0x10a);
+
+ qtest_writel(qts, DMA_BASE + DMA_SNIFF_CTRL,
+ DMA_SNIFF_CTRL_EN | DMA_SNIFF_CTRL_CALC_SUM |
+ DMA_SNIFF_CTRL_OUT_REV);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_SNIFF_DATA), ==,
+ 0x50800000);
+
+ qtest_writel(qts, DMA_BASE + DMA_SNIFF_CTRL,
+ DMA_SNIFF_CTRL_EN | DMA_SNIFF_CTRL_CALC_SUM |
+ DMA_SNIFF_CTRL_OUT_INV);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_SNIFF_DATA), ==,
+ 0xfffffef5);
+
+ qtest_quit(qts);
+}
+
+static void test_dma_error_status(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t ctrl = DMA_CTRL_EN | DMA_CTRL_DATA_SIZE_32 |
+ (DREQ_FORCE << DMA_CTRL_TREQ_SEL_SHIFT);
+
+ qtest_writel(qts, SRAM_BASE, 0x12345678);
+ qtest_writel(qts, DMA_BASE + DMA_INTR, BIT(0));
+ qtest_writel(qts, DMA_BASE + DMA_INTE0, BIT(0));
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, BAD_DMA_ADDR);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, SRAM_BASE + 4);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, 1);
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl);
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ (DMA_CTRL_AHB_ERROR | DMA_CTRL_READ_ERROR |
+ DMA_CTRL_WRITE_ERROR | DMA_CTRL_BUSY), ==,
+ DMA_CTRL_AHB_ERROR | DMA_CTRL_READ_ERROR);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 1);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR), ==, BIT(0));
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTS0), ==, BIT(0));
+
+ qtest_writel(qts, DMA_BASE + DMA_INTS0, BIT(0));
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTS0), ==, 0);
+
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG,
+ DMA_CTRL_READ_ERROR | DMA_CTRL_WRITE_ERROR);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ (DMA_CTRL_AHB_ERROR | DMA_CTRL_READ_ERROR |
+ DMA_CTRL_WRITE_ERROR), ==, 0);
+
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, SRAM_BASE);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, BAD_DMA_ADDR);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, 1);
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl);
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ (DMA_CTRL_AHB_ERROR | DMA_CTRL_READ_ERROR |
+ DMA_CTRL_WRITE_ERROR | DMA_CTRL_BUSY), ==,
+ DMA_CTRL_AHB_ERROR | DMA_CTRL_WRITE_ERROR);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 1);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR), ==, BIT(0));
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTS0), ==, BIT(0));
+
+ qtest_quit(qts);
+}
+
+static void test_dma_timer_dreq(void)
+{
+ QTestState *qts = rp2040_start();
+ const uint8_t source[] = { 0xa1, 0xb2, 0xc3 };
+ uint8_t dest[sizeof(source)];
+ uint32_t read_addr = SRAM_BASE + 0x280;
+ uint32_t write_addr = SRAM_BASE + 0x290;
+ uint32_t ctrl = DMA_CTRL_EN | DMA_CTRL_INCR_READ | DMA_CTRL_INCR_WRITE |
+ DMA_CTRL_DATA_SIZE_8 |
+ (DREQ_DMA_TIMER0 << DMA_CTRL_TREQ_SEL_SHIFT);
+
+ qtest_memwrite(qts, read_addr, source, sizeof(source));
+ memset(dest, 0, sizeof(dest));
+ qtest_memwrite(qts, write_addr, dest, sizeof(dest));
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, read_addr);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, write_addr);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, sizeof(source));
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl);
+
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ DMA_CTRL_BUSY, ==, DMA_CTRL_BUSY);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==,
+ sizeof(source));
+
+ /*
+ * X/Y = 1/1000 on a nominal 125 MHz sys_clk gives one DREQ every
+ * 8000 ns of QEMU virtual time.
+ */
+ qtest_writel(qts, DMA_BASE + DMA_TIMER0, (1u << 16) | 1000u);
+ qtest_clock_step(qts, 7999);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==,
+ sizeof(source));
+
+ qtest_clock_step(qts, 1);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 2);
+ g_assert_cmphex(qtest_readb(qts, write_addr), ==, source[0]);
+
+ qtest_clock_step(qts, 8000);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 1);
+ g_assert_cmphex(qtest_readb(qts, write_addr + 1), ==, source[1]);
+
+ qtest_clock_step(qts, 8000);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) &
+ DMA_CTRL_BUSY, ==, 0);
+ qtest_memread(qts, write_addr, dest, sizeof(dest));
+ g_assert_cmpmem(dest, sizeof(dest), source, sizeof(source));
+
+ qtest_quit(qts);
+}
+
+static void test_dma_control_blocks(void)
+{
+ QTestState *qts = rp2040_start();
+ const uint8_t source[] = { 0x44, 0x4d, 0x41 };
+ uint8_t dest[sizeof(source)];
+ uint32_t blocks[] = {
+ sizeof(source), SRAM_BASE + 0x380,
+ 0, 0,
+ };
+ uint32_t block_addr = SRAM_BASE + 0x300;
+ uint32_t read_addr = SRAM_BASE + 0x380;
+ uint32_t write_addr = SRAM_BASE + 0x390;
+ uint32_t chan1 = DMA_BASE + DMA_CH_SIZE;
+ uint32_t ctrl_chan_ctrl;
+ uint32_t data_chan_ctrl;
+
+ qtest_memwrite(qts, block_addr, blocks, sizeof(blocks));
+ qtest_memwrite(qts, read_addr, source, sizeof(source));
+
+ /*
+ * Channel 0 writes two 32-bit words into channel 1's alias 3
+ * TRANS_COUNT/READ_ADDR registers. The second write triggers channel 1.
+ */
+ ctrl_chan_ctrl = DMA_CTRL_EN | DMA_CTRL_INCR_READ |
+ DMA_CTRL_INCR_WRITE | DMA_CTRL_RING_SEL |
+ DMA_CTRL_DATA_SIZE_32 |
+ (3 << DMA_CTRL_RING_SIZE_SHIFT) |
+ (DREQ_FORCE << DMA_CTRL_TREQ_SEL_SHIFT);
+
+ /*
+ * Channel 1 copies the configured block and chains back to channel 0.
+ * The final zero-length control block sets channel 1's interrupt via
+ * IRQ_QUIET, matching the SDK control_blocks example's termination.
+ */
+ data_chan_ctrl = DMA_CTRL_EN | DMA_CTRL_INCR_READ |
+ DMA_CTRL_INCR_WRITE | DMA_CTRL_IRQ_QUIET |
+ (DREQ_FORCE << DMA_CTRL_TREQ_SEL_SHIFT) |
+ (0 << DMA_CTRL_CHAIN_TO_SHIFT);
+
+ qtest_writel(qts, chan1 + DMA_CH_WRITE_ADDR, write_addr);
+ qtest_writel(qts, chan1 + DMA_CH_AL1_CTRL, data_chan_ctrl);
+
+ qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, block_addr);
+ qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR,
+ chan1 + DMA_CH_AL3_TRANS_COUNT);
+ qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, 2);
+ qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl_chan_ctrl);
+
+ qtest_memread(qts, write_addr, dest, sizeof(dest));
+ g_assert_cmpmem(dest, sizeof(dest), source, sizeof(source));
+ g_assert_cmphex(qtest_readl(qts, chan1 + DMA_CH_TRANS_COUNT), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, chan1 + DMA_CH_CTRL_TRIG) &
+ DMA_CTRL_BUSY, ==, 0);
+ g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR) & BIT(1), ==,
+ BIT(1));
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-dma/xip-ssi-rx-dreq",
+ test_dma_xip_ssi_rx_dreq);
+ qtest_add_func("/rp2040-dma/write-ring-wrap",
+ test_dma_write_ring_wrap);
+ qtest_add_func("/rp2040-dma/channel-abort",
+ test_dma_channel_abort);
+ qtest_add_func("/rp2040-dma/sniff-sum",
+ test_dma_sniff_sum);
+ qtest_add_func("/rp2040-dma/error-status",
+ test_dma_error_status);
+ qtest_add_func("/rp2040-dma/timer-dreq",
+ test_dma_timer_dreq);
+ qtest_add_func("/rp2040-dma/control-blocks",
+ test_dma_control_blocks);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 23/30] tests/tcg/arm: test RP2040 DMA transfers
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (21 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 22/30] hw/dma: add RP2040 DMA controller Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 24/30] hw/ssi: load RP2040 UF2 flash images Gilles Grimaud
` (7 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Add source-built bare-metal tests for memory copy and fill transfers, completion status and interrupt acknowledgement.\n\nExercise UART0 and UART1 transmit and receive pacing through their DMA request lines. Each UART test waits for DMA IRQ completion and validates looped-back input before reporting success.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
tests/tcg/arm/Makefile.softmmu-target | 34 ++-
tests/tcg/arm/system/rp2040-dma-memory.S | 198 +++++++++++++++
tests/tcg/arm/system/rp2040-dma-memory.ref | 4 +
tests/tcg/arm/system/rp2040-dma-uart.S | 267 +++++++++++++++++++++
tests/tcg/arm/system/rp2040-dma-uart0.ref | 3 +
tests/tcg/arm/system/rp2040-dma-uart1.ref | 3 +
6 files changed, 505 insertions(+), 4 deletions(-)
create mode 100644 tests/tcg/arm/system/rp2040-dma-memory.S
create mode 100644 tests/tcg/arm/system/rp2040-dma-memory.ref
create mode 100644 tests/tcg/arm/system/rp2040-dma-uart.S
create mode 100644 tests/tcg/arm/system/rp2040-dma-uart0.ref
create mode 100644 tests/tcg/arm/system/rp2040-dma-uart1.ref
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index c282ff5ad0..acb90458be 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -28,8 +28,10 @@ RP2040_FLASH_TESTS=rp2040-flash-commands rp2040-flash-ioqspi \
rp2040-flash-busy-hardfault rp2040-flash-busy-lockout \
rp2040-core0-xip-fault rp2040-core1-xip-fault
+RP2040_DMA_TESTS=rp2040-dma-memory rp2040-dma-uart0 rp2040-dma-uart1
+
RP2040_GUESTS=$(RP2040_TESTS) $(RP2040_FLASH_TESTS) \
- rp2040-flash-persist-reader
+ rp2040-dma-memory rp2040-flash-persist-reader
$(RP2040_GUESTS): %: %.S rp2040-boot2-w25q080.S rp2040.ld
$(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
@@ -46,7 +48,8 @@ run-rp2040-sio-fifo run-rp2040-sio-divider \
run-rp2040-psm-proc1 run-rp2040-core1-launch run-rp2040-timer \
run-rp2040-flash-commands run-rp2040-flash-ioqspi \
run-rp2040-flash-busy-hardfault run-rp2040-flash-busy-lockout \
-run-rp2040-core0-xip-fault run-rp2040-core1-xip-fault: \
+run-rp2040-core0-xip-fault run-rp2040-core1-xip-fault \
+run-rp2040-dma-memory: \
QEMU_OPTS=-M raspi-pico,strict-uart-pins=off \
-serial chardev:output \
-semihosting-config enable=on,target=native -kernel
@@ -57,7 +60,8 @@ run-rp2040-psm-proc1 run-rp2040-core1-launch \
run-rp2040-timer run-rp2040-flash-ioqspi \
run-rp2040-flash-busy-hardfault run-rp2040-flash-busy-lockout \
run-rp2040-core0-xip-fault \
-run-rp2040-core1-xip-fault: run-rp2040-%: rp2040-%
+run-rp2040-core1-xip-fault \
+run-rp2040-dma-memory: run-rp2040-%: rp2040-%
$(call run-test, $<, \
$(QEMU) -monitor none -display none \
-chardev file$(COMMA)path=$<.out$(COMMA)id=output \
@@ -84,7 +88,29 @@ run-rp2040-flash-commands: rp2040-flash-commands \
$(call diff-out,$<.persist,$(ARM_SRC)/rp2040-flash-persist-reader.ref)
rm -f $<.flash $<.persist.out
-ARM_TESTS+=$(RP2040_TESTS) $(RP2040_FLASH_TESTS)
+rp2040-dma-uart0 rp2040-dma-uart1: rp2040-dma-uart%: \
+ rp2040-dma-uart.S rp2040-boot2-w25q080.S rp2040.ld
+ $(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
+ -DUART_INDEX=$* -Wl,--build-id=none -x assembler-with-cpp \
+ $(ARM_SRC)/rp2040-boot2-w25q080.S $< \
+ -o $@ -nostdlib -static \
+ -T $(ARM_SRC)/rp2040.ld
+
+run-rp2040-dma-uart0: QEMU_OPTS=-M raspi-pico,strict-uart-pins=off \
+ -serial chardev:output \
+ -semihosting-config enable=on,target=native -kernel
+run-rp2040-dma-uart1: QEMU_OPTS=-M raspi-pico,strict-uart-pins=off \
+ -serial null -serial chardev:output \
+ -semihosting-config enable=on,target=native -kernel
+
+run-rp2040-dma-uart0 run-rp2040-dma-uart1: run-%: %
+ $(call run-test, $<, \
+ $(QEMU) -monitor none -display none \
+ -chardev file$(COMMA)path=$<.out$(COMMA)id=output \
+ $(QEMU_OPTS) $<)
+ $(call diff-out,$<,$(ARM_SRC)/$<.ref)
+
+ARM_TESTS+=$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS)
# These objects provide the basic boot code and helper functions for all tests
CRT_OBJS=boot.o
diff --git a/tests/tcg/arm/system/rp2040-dma-memory.S b/tests/tcg/arm/system/rp2040-dma-memory.S
new file mode 100644
index 0000000000..015c7241dc
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-dma-memory.S
@@ -0,0 +1,198 @@
+/*
+ * Raspberry Pi Pico DMA memory transfer test.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define DMA_BASE 0x50000000
+#define DMA_READ_ADDR 0x000
+#define DMA_WRITE_ADDR 0x004
+#define DMA_TRANS_COUNT 0x008
+#define DMA_CTRL_TRIG 0x00c
+#define DMA_INTR 0x400
+#define DMA_CTRL_WORD_COPY 0x001f8039
+#define DMA_CTRL_WORD_FILL 0x001f8029
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.macro fail_if_ne reg, expected, target
+ ldr r2, =\expected
+ cmp \reg, r2
+ beq 0f
+ b \target
+0:
+.endm
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ ldr r5, =copy_source
+ ldr r0, =0x11223344
+ str r0, [r5, #0]
+ ldr r0, =0xaabbccdd
+ str r0, [r5, #4]
+ ldr r0, =0x01020304
+ str r0, [r5, #8]
+ ldr r0, =0xfeedc0de
+ str r0, [r5, #12]
+ ldr r5, =fill_source
+ ldr r0, =0x55aa55aa
+ str r0, [r5]
+
+ ldr r4, =DMA_BASE
+
+ ldr r0, =copy_source
+ str r0, [r4, DMA_READ_ADDR]
+ ldr r0, =copy_destination
+ str r0, [r4, DMA_WRITE_ADDR]
+ movs r0, 4
+ str r0, [r4, DMA_TRANS_COUNT]
+ ldr r0, =DMA_CTRL_WORD_COPY
+ str r0, [r4, DMA_CTRL_TRIG]
+
+ ldr r5, =copy_source
+ ldr r6, =copy_destination
+ movs r3, 4
+
+copy_check_loop:
+ ldr r0, [r5]
+ ldr r1, [r6]
+ cmp r0, r1
+ bne fail_copy
+ adds r5, 4
+ adds r6, 4
+ subs r3, 1
+ bne copy_check_loop
+
+ ldr r6, =DMA_BASE + DMA_INTR
+ ldr r0, [r6]
+ movs r1, 1
+ tst r0, r1
+ beq fail_irq
+ str r1, [r6]
+ ldr r0, [r6]
+ tst r0, r1
+ bne fail_irq
+
+ ldr r0, =copy_ok_message
+ bl puts
+
+ ldr r0, =fill_source
+ str r0, [r4, DMA_READ_ADDR]
+ ldr r0, =fill_destination
+ str r0, [r4, DMA_WRITE_ADDR]
+ movs r0, 4
+ str r0, [r4, DMA_TRANS_COUNT]
+ ldr r0, =DMA_CTRL_WORD_FILL
+ str r0, [r4, DMA_CTRL_TRIG]
+
+ ldr r5, =fill_destination
+ ldr r1, =0x55aa55aa
+ movs r3, 4
+
+fill_check_loop:
+ ldr r0, [r5]
+ cmp r0, r1
+ bne fail_fill
+ adds r5, 4
+ subs r3, 1
+ bne fill_check_loop
+
+ ldr r0, =fill_ok_message
+ bl puts
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_copy:
+ ldr r0, =fail_copy_message
+ b test_failed
+
+fail_fill:
+ ldr r0, =fail_fill_message
+ b test_failed
+
+fail_irq:
+ ldr r0, =fail_irq_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .bss, "aw", %nobits
+.align 2
+copy_source:
+ .space 16
+copy_destination:
+ .space 16
+fill_source:
+ .space 4
+fill_destination:
+ .space 16
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG DMA MEMORY START\n"
+copy_ok_message:
+ .asciz "PICO TCG DMA MEMORY COPY OK\n"
+fill_ok_message:
+ .asciz "PICO TCG DMA MEMORY FILL OK\n"
+ok_message:
+ .asciz "PICO TCG DMA MEMORY OK\n"
+fail_copy_message:
+ .asciz "PICO TCG DMA MEMORY FAIL: COPY\n"
+fail_fill_message:
+ .asciz "PICO TCG DMA MEMORY FAIL: FILL\n"
+fail_irq_message:
+ .asciz "PICO TCG DMA MEMORY FAIL: IRQ\n"
+fail_exception_message:
+ .asciz "PICO TCG DMA MEMORY FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-dma-memory.ref b/tests/tcg/arm/system/rp2040-dma-memory.ref
new file mode 100644
index 0000000000..5960220ead
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-dma-memory.ref
@@ -0,0 +1,4 @@
+PICO TCG DMA MEMORY START
+PICO TCG DMA MEMORY COPY OK
+PICO TCG DMA MEMORY FILL OK
+PICO TCG DMA MEMORY OK
diff --git a/tests/tcg/arm/system/rp2040-dma-uart.S b/tests/tcg/arm/system/rp2040-dma-uart.S
new file mode 100644
index 0000000000..36972bbc98
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-dma-uart.S
@@ -0,0 +1,267 @@
+/*
+ * Raspberry Pi Pico UART-paced DMA transfer test.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#ifndef UART_INDEX
+#error UART_INDEX must select UART0 or UART1
+#elif UART_INDEX == 0
+#define UART_BASE 0x40034000
+#define DREQ_UART_TX 20
+#define DREQ_UART_RX 21
+#define UART_NAME "UART0"
+#elif UART_INDEX == 1
+#define UART_BASE 0x40038000
+#define DREQ_UART_TX 22
+#define DREQ_UART_RX 23
+#define UART_NAME "UART1"
+#else
+#error Unsupported UART_INDEX
+#endif
+
+#define SRAM_END 0x20042000
+#define UART_LCR_H 0x2c
+#define UART_CR 0x30
+#define UART_DMACR 0x48
+#define UART_CR_ENABLED 0x301
+#define UART_CR_LOOPBACK 0x381
+#define DMA_BASE 0x50000000
+#define DMA_READ_ADDR 0x000
+#define DMA_WRITE_ADDR 0x004
+#define DMA_TRANS_COUNT 0x008
+#define DMA_CTRL_TRIG 0x00c
+#define DMA_INTR 0x400
+#define DMA_INTE0 0x404
+#define DMA_CTRL_EN 0x00000001
+#define DMA_CTRL_INCR_R 0x00000010
+#define DMA_CTRL_INCR_W 0x00000020
+#define DMA_CTRL_TX (DMA_CTRL_EN | DMA_CTRL_INCR_R | \
+ (DREQ_UART_TX << 15))
+#define DMA_CTRL_RX (DMA_CTRL_EN | DMA_CTRL_INCR_W | \
+ (DREQ_UART_RX << 15))
+#define DMA_IRQ_0 11
+#define NVIC_ISER 0xe000e100
+#define NVIC_ICER 0xe000e180
+#define NVIC_ICPR 0xe000e280
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+ .rept 12
+ .word default_handler + 1
+ .endr
+ .rept DMA_IRQ_0
+ .word default_handler + 1
+ .endr
+ .word dma_irq_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ cpsid i
+ ldr r0, =UART_BASE
+ movs r1, 0x10
+ str r1, [r0, UART_LCR_H]
+ ldr r1, =UART_CR_ENABLED
+ str r1, [r0, UART_CR]
+ movs r1, 3
+ str r1, [r0, UART_DMACR]
+
+ ldr r0, =NVIC_ICER
+ movs r2, 0
+ mvns r2, r2
+ str r2, [r0]
+ ldr r0, =NVIC_ICPR
+ str r2, [r0]
+ ldr r0, =NVIC_ISER
+ ldr r1, =(1 << DMA_IRQ_0)
+ str r1, [r0]
+ cpsie i
+
+ ldr r0, =start_message
+ movs r1, START_MESSAGE_LEN
+ bl dma_tx
+
+ ldr r0, =UART_BASE
+ ldr r1, =UART_CR_LOOPBACK
+ str r1, [r0, UART_CR]
+ ldr r0, =hello_message
+ movs r1, 5
+ bl dma_tx
+ ldr r0, =UART_BASE
+ ldr r1, =UART_CR_ENABLED
+ str r1, [r0, UART_CR]
+ ldr r0, =newline_message
+ bl puts
+
+ ldr r0, =rx_buffer
+ movs r1, 5
+ bl dma_rx
+
+ ldr r0, =rx_buffer
+ ldr r1, =hello_message
+ movs r2, 5
+
+check_loop:
+ ldrb r3, [r0]
+ ldrb r4, [r1]
+ cmp r3, r4
+ bne fail_data
+ adds r0, 1
+ adds r1, 1
+ subs r2, 1
+ bne check_loop
+
+ ldr r0, =ok_message
+ movs r1, OK_MESSAGE_LEN
+ bl dma_tx
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_data:
+ ldr r0, =fail_data_message
+ b test_failed
+
+fail_irq:
+ ldr r0, =fail_irq_message
+ b test_failed
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+dma_tx:
+ push {r4, lr}
+ mov r4, r1
+ bl clear_dma_irq
+ ldr r2, =DMA_BASE
+ str r0, [r2, DMA_READ_ADDR]
+ ldr r3, =UART_BASE
+ str r3, [r2, DMA_WRITE_ADDR]
+ str r4, [r2, DMA_TRANS_COUNT]
+ ldr r3, =DMA_CTRL_TX
+ str r3, [r2, DMA_CTRL_TRIG]
+ bl wait_dma_irq
+ pop {r4, pc}
+
+.thumb_func
+dma_rx:
+ push {r4, lr}
+ mov r4, r1
+ bl clear_dma_irq
+ ldr r2, =DMA_BASE
+ ldr r3, =UART_BASE
+ str r3, [r2, DMA_READ_ADDR]
+ str r0, [r2, DMA_WRITE_ADDR]
+ str r4, [r2, DMA_TRANS_COUNT]
+ ldr r3, =DMA_CTRL_RX
+ str r3, [r2, DMA_CTRL_TRIG]
+ bl wait_dma_irq
+ pop {r4, pc}
+
+.thumb_func
+clear_dma_irq:
+ ldr r2, =irq_flag
+ movs r3, 0
+ str r3, [r2]
+ ldr r2, =DMA_BASE + DMA_INTR
+ movs r3, 1
+ str r3, [r2]
+ ldr r2, =DMA_BASE + DMA_INTE0
+ str r3, [r2]
+ bx lr
+
+.thumb_func
+wait_dma_irq:
+ ldr r2, =irq_flag
+
+3:
+ ldr r3, [r2]
+ movs r4, 1
+ tst r3, r4
+ bne 4f
+ b 3b
+
+4:
+ bx lr
+
+.thumb_func
+dma_irq_handler:
+ ldr r0, =DMA_BASE + DMA_INTR
+ ldr r1, [r0]
+ movs r2, 1
+ tst r1, r2
+ beq fail_irq
+ str r1, [r0]
+ ldr r0, =irq_flag
+ str r1, [r0]
+ bx lr
+
+.thumb_func
+puts:
+ ldr r2, =UART_BASE
+
+5:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 6f
+ str r1, [r2]
+ adds r0, 1
+ b 5b
+
+6:
+ bx lr
+
+.section .bss, "aw", %nobits
+.align 2
+irq_flag:
+ .word 0
+rx_buffer:
+ .space 8
+
+.section .rodata, "a", %progbits
+start_message:
+ .ascii "PICO TCG DMA " UART_NAME " START\n"
+start_message_end:
+hello_message:
+ .ascii "HELLO"
+newline_message:
+ .asciz "\n"
+ok_message:
+ .ascii "PICO TCG DMA " UART_NAME " TX/RX OK\n"
+ok_message_end:
+.equ START_MESSAGE_LEN, start_message_end - start_message
+.equ OK_MESSAGE_LEN, ok_message_end - ok_message
+fail_data_message:
+ .asciz "PICO TCG DMA " UART_NAME " FAIL: DATA\n"
+fail_irq_message:
+ .asciz "PICO TCG DMA " UART_NAME " FAIL: IRQ\n"
+fail_exception_message:
+ .asciz "PICO TCG DMA " UART_NAME " FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-dma-uart0.ref b/tests/tcg/arm/system/rp2040-dma-uart0.ref
new file mode 100644
index 0000000000..5cb57c56cc
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-dma-uart0.ref
@@ -0,0 +1,3 @@
+PICO TCG DMA UART0 START
+HELLO
+PICO TCG DMA UART0 TX/RX OK
diff --git a/tests/tcg/arm/system/rp2040-dma-uart1.ref b/tests/tcg/arm/system/rp2040-dma-uart1.ref
new file mode 100644
index 0000000000..1509dc034a
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-dma-uart1.ref
@@ -0,0 +1,3 @@
+PICO TCG DMA UART1 START
+HELLO
+PICO TCG DMA UART1 TX/RX OK
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 24/30] hw/ssi: load RP2040 UF2 flash images
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (22 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 23/30] tests/tcg/arm: test RP2040 DMA transfers Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 25/30] hw/misc: add RP2040 bus controller Gilles Grimaud
` (6 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Accept RP2040-family UF2 files through the Raspberry Pi Pico -kernel path. Validate block framing, family IDs, payload sizes and XIP flash bounds before overlaying payloads onto flash storage.\n\nAdd a focused qtest for multi-block UF2 loading and provide a host-side converter for producing raw flash images from Pico UF2 files.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
MAINTAINERS | 1 +
hw/ssi/rp2040_xip.c | 109 ++++++++
scripts/uf2-to-flash.py | 462 ++++++++++++++++++++++++++++++++++
tests/qtest/rp2040-xip-test.c | 61 +++++
4 files changed, 633 insertions(+)
create mode 100755 scripts/uf2-to-flash.py
diff --git a/MAINTAINERS b/MAINTAINERS
index fd35521947..41494eff2a 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1042,6 +1042,7 @@ S: Maintained
F: hw/*/rp2040*
F: include/hw/*/rp2040*
F: hw/arm/raspi_pico.c
+F: scripts/uf2-to-flash.py
F: tests/qtest/rp2040*
F: tests/tcg/arm/system/rp2040*
diff --git a/hw/ssi/rp2040_xip.c b/hw/ssi/rp2040_xip.c
index d517df41ff..57cdd3c3c7 100644
--- a/hw/ssi/rp2040_xip.c
+++ b/hw/ssi/rp2040_xip.c
@@ -90,6 +90,17 @@
#define FLASH_PAGE_SIZE 256
#define FLASH_SECTOR_SIZE 4096
+#define UF2_MAGIC_START0 0x0a324655
+#define UF2_MAGIC_START1 0x9e5d5157
+#define UF2_MAGIC_END 0x0ab16f30
+#define UF2_BLOCK_SIZE 512
+#define UF2_HEADER_SIZE 32
+#define UF2_MAX_PAYLOAD 476
+
+#define UF2_FLAG_NOT_MAIN_FLASH BIT(0)
+#define UF2_FLAG_FAMILY_ID_PRESENT BIT(13)
+#define UF2_RP2040_FAMILY_ID 0xe48bff56
+
#define ATOMIC_ALIAS_MASK 0x3000
#define ATOMIC_XOR 0x1000
#define ATOMIC_SET 0x2000
@@ -1121,6 +1132,92 @@ static bool rp2040_xip_load_elf(RP2040XipState *s, const char *filename,
return true;
}
+static bool rp2040_xip_load_uf2(RP2040XipState *s, const char *filename,
+ Error **errp)
+{
+ g_autofree gchar *contents = NULL;
+ gsize len;
+ unsigned blocks;
+ unsigned i;
+ bool copied = false;
+
+ if (!g_file_get_contents(filename, &contents, &len, NULL)) {
+ error_setg(errp, "could not load flash image '%s'", filename);
+ return true;
+ }
+
+ if (len < UF2_BLOCK_SIZE ||
+ ldl_le_p(contents) != UF2_MAGIC_START0 ||
+ ldl_le_p(contents + 4) != UF2_MAGIC_START1) {
+ return false;
+ }
+
+ if (len % UF2_BLOCK_SIZE != 0) {
+ error_setg(errp, "invalid UF2 image '%s': size is not a multiple "
+ "of %u bytes", filename, UF2_BLOCK_SIZE);
+ return true;
+ }
+
+ blocks = len / UF2_BLOCK_SIZE;
+ for (i = 0; i < blocks; i++) {
+ const uint8_t *block = (uint8_t *)contents + i * UF2_BLOCK_SIZE;
+ uint32_t flags;
+ uint32_t target;
+ uint32_t payload_size;
+ uint32_t family_id;
+ uint32_t offset;
+
+ if (ldl_le_p(block) != UF2_MAGIC_START0 ||
+ ldl_le_p(block + 4) != UF2_MAGIC_START1 ||
+ ldl_le_p(block + UF2_BLOCK_SIZE - 4) != UF2_MAGIC_END) {
+ error_setg(errp, "invalid UF2 image '%s': bad magic in block %u",
+ filename, i);
+ return true;
+ }
+
+ flags = ldl_le_p(block + 8);
+ if (flags & UF2_FLAG_NOT_MAIN_FLASH) {
+ continue;
+ }
+
+ if (!(flags & UF2_FLAG_FAMILY_ID_PRESENT)) {
+ error_setg(errp, "invalid RP2040 UF2 image '%s': block %u has "
+ "no family ID", filename, i);
+ return true;
+ }
+
+ family_id = ldl_le_p(block + 28);
+ if (family_id != UF2_RP2040_FAMILY_ID) {
+ error_setg(errp, "unsupported UF2 family ID 0x%08" PRIx32
+ " in '%s'", family_id, filename);
+ return true;
+ }
+
+ target = ldl_le_p(block + 12);
+ payload_size = ldl_le_p(block + 16);
+ if (payload_size > UF2_MAX_PAYLOAD ||
+ target < RP2040_XIP_FLASH_BASE ||
+ target - RP2040_XIP_FLASH_BASE > s->flash_size ||
+ payload_size > s->flash_size -
+ (target - RP2040_XIP_FLASH_BASE)) {
+ error_setg(errp, "UF2 block %u in '%s' is outside XIP flash",
+ i, filename);
+ return true;
+ }
+
+ offset = target - RP2040_XIP_FLASH_BASE;
+ memcpy(s->storage + offset, block + UF2_HEADER_SIZE, payload_size);
+ copied = true;
+ }
+
+ if (!copied) {
+ error_setg(errp, "UF2 image '%s' contains no RP2040 flash payload",
+ filename);
+ }
+
+ return true;
+}
+
void rp2040_xip_load_image(RP2040XipState *s, const char *filename,
Error **errp)
{
@@ -1143,6 +1240,18 @@ void rp2040_xip_load_image(RP2040XipState *s, const char *filename,
return;
}
+ if (rp2040_xip_load_uf2(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);
diff --git a/scripts/uf2-to-flash.py b/scripts/uf2-to-flash.py
new file mode 100755
index 0000000000..f1c0fcbbbb
--- /dev/null
+++ b/scripts/uf2-to-flash.py
@@ -0,0 +1,462 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+"""
+uf2-to-flash.py
+
+Convert a Raspberry Pi Pico UF2 file into a raw flash image.
+
+This tool implements the simple and practical conversion model used for
+QEMU-style emulation:
+
+ UF2 target address 0x10000000 + offset -> flash image offset
+
+The output file is not a FAT image. It is a raw representation of the
+external QSPI flash contents after UF2 programming.
+
+Supported input model:
+ - RP2040 / Pico 1 UF2 files, family ID 0xE48BFF56.
+ - RP2350 / Pico 2 UF2 files are detected and converted linearly,
+ but RP2350 partition remapping is not implemented.
+
+Usage:
+ scripts/uf2-to-flash.py firmware.uf2 flash.img
+ scripts/uf2-to-flash.py flash.img firmware.uf2
+
+If the two arguments are provided in reverse order, the .uf2 extension is
+used to detect the input file.
+"""
+
+import os
+import sys
+import struct
+
+
+UF2_MAGIC_START0 = 0x0A324655
+UF2_MAGIC_START1 = 0x9E5D5157
+UF2_MAGIC_END = 0x0AB16F30
+
+UF2_BLOCK_SIZE = 512
+UF2_HEADER_SIZE = 32
+
+FLAG_NOT_MAIN_FLASH = 0x00000001
+FLAG_FAMILY_ID_PRESENT = 0x00002000
+
+RP2040_FAMILY_ID = 0xE48BFF56
+
+# Usual RP2350 / Pico 2 family IDs.
+# They are detected for diagnostics. This script does not implement
+# RP2350 partition table remapping.
+RP2350_FAMILY_IDS = {
+ 0xE48BFF58: "rp2350-arm-secure",
+ 0xE48BFF59: "rp2350-arm-nonsecure",
+ 0xE48BFF5A: "rp2350-riscv",
+ 0xE48BFF5B: "data-or-absolute",
+}
+
+XIP_BASE = 0x10000000
+
+# RP2040 UF2 flash window: 0x10000000 .. 0x11000000
+PICO1_FLASH_WINDOW_SIZE = 16 * 1024 * 1024
+
+# RP2350 UF2 flash window: 0x10000000 .. 0x12000000
+PICO2_FLASH_WINDOW_SIZE = 32 * 1024 * 1024
+
+
+def print_help() -> None:
+ """Print a minimal usage message."""
+
+ program_name = os.path.basename(sys.argv[0])
+
+ print("Usage:")
+ print(f" {program_name} firmware.uf2 flash.img")
+ print()
+ print("Convert a Raspberry Pi Pico UF2 file into a raw flash image.")
+ print("Exactly one argument must have the .uf2 extension, the other")
+ print("is used as the raw flash output file.")
+ print()
+ print("Examples:")
+ print(f" {program_name} blink.uf2 flash.img")
+ print(f" {program_name} flash.raw blink.uf2")
+
+
+def get_extension(path: str) -> str:
+ """Return the lowercase extension of a path."""
+
+ return os.path.splitext(path)[1].lower()
+
+
+def detect_input_output(arguments: list[str]) -> tuple[str, str]:
+ """
+ Detect the UF2 input file and the raw image output file.
+
+ The arguments may be passed in either order, provided exactly one of them
+ has the .uf2 extension.
+ """
+
+ if len(arguments) != 2:
+ print_help()
+ sys.exit(2)
+
+ first_path, second_path = arguments
+ first_ext = get_extension(first_path)
+ second_ext = get_extension(second_path)
+
+ if first_ext == ".uf2" and second_ext != ".uf2":
+ return first_path, second_path
+
+ if second_ext == ".uf2" and first_ext != ".uf2":
+ print("Reversed arguments detected: using the .uf2 file as input.")
+ return second_path, first_path
+
+ print("Error: unable to determine which file is the UF2 input.")
+ print("Exactly one argument must have the .uf2 extension.")
+ print()
+ print_help()
+ sys.exit(2)
+
+
+def read_uf2_blocks(path: str):
+ """
+ Yield all 512-byte blocks from a UF2 file.
+
+ Raises ValueError if the file size is not a multiple of 512 bytes.
+ """
+
+ file_size = os.path.getsize(path)
+
+ if file_size == 0:
+ raise ValueError("empty file")
+
+ if file_size % UF2_BLOCK_SIZE != 0:
+ raise ValueError(
+ f"invalid UF2 file size: {file_size} bytes; "
+ f"expected a multiple of {UF2_BLOCK_SIZE}"
+ )
+
+ with open(path, "rb") as input_file:
+ physical_block_index = 0
+
+ while True:
+ block = input_file.read(UF2_BLOCK_SIZE)
+
+ if not block:
+ break
+
+ physical_block_index += 1
+
+ if len(block) != UF2_BLOCK_SIZE:
+ raise ValueError(
+ f"incomplete block at physical index {physical_block_index}"
+ )
+
+ yield physical_block_index, block
+
+
+def analyze_uf2(uf2_path: str) -> dict:
+ """
+ Analyze a UF2 file and extract main-flash pages.
+
+ Returns a dictionary containing:
+ - pages: mapping from flash offset to payload bytes
+ - family_ids: detected family IDs
+ - warnings: non-fatal problems
+ - errors: fatal format problems
+ - min_offset / max_end_offset: occupied flash range
+ """
+
+ pages: dict[int, bytes] = {}
+ warnings: list[str] = []
+ errors: list[str] = []
+
+ family_ids: set[int] = set()
+ valid_block_count = 0
+ non_uf2_block_count = 0
+ ignored_not_main_flash_count = 0
+
+ min_offset = None
+ max_end_offset = 0
+
+ announced_num_blocks = None
+ seen_block_numbers: set[int] = set()
+
+ for physical_index, block in read_uf2_blocks(uf2_path):
+ (
+ magic_start0,
+ magic_start1,
+ flags,
+ target_address,
+ payload_size,
+ block_number,
+ num_blocks,
+ family_or_file_size,
+ ) = struct.unpack_from("<IIIIIIII", block, 0)
+
+ (magic_end,) = struct.unpack_from("<I", block, UF2_BLOCK_SIZE - 4)
+
+ if (
+ magic_start0 != UF2_MAGIC_START0
+ or magic_start1 != UF2_MAGIC_START1
+ or magic_end != UF2_MAGIC_END
+ ):
+ non_uf2_block_count += 1
+ errors.append(
+ f"block {physical_index}: invalid UF2 magic values"
+ )
+ continue
+
+ valid_block_count += 1
+
+ if announced_num_blocks is None:
+ announced_num_blocks = num_blocks
+ elif num_blocks != announced_num_blocks:
+ warnings.append(
+ f"block {physical_index}: inconsistent numBlocks "
+ f"({num_blocks}, expected {announced_num_blocks})"
+ )
+
+ if block_number in seen_block_numbers:
+ warnings.append(
+ f"block {physical_index}: duplicated blockNo {block_number}"
+ )
+
+ seen_block_numbers.add(block_number)
+
+ if flags & FLAG_NOT_MAIN_FLASH:
+ ignored_not_main_flash_count += 1
+ continue
+
+ if not (flags & FLAG_FAMILY_ID_PRESENT):
+ errors.append(
+ f"block {physical_index}: missing family ID; Pico boot ROM "
+ f"expects a family ID"
+ )
+ continue
+
+ family_id = family_or_file_size
+ family_ids.add(family_id)
+
+ if payload_size != 256:
+ errors.append(
+ f"block {physical_index}: payloadSize={payload_size}; "
+ f"expected 256 for Pico UF2 files"
+ )
+ continue
+
+ if target_address % 256 != 0:
+ errors.append(
+ f"block {physical_index}: target address is not "
+ f"256-byte aligned: 0x{target_address:08x}"
+ )
+ continue
+
+ pico2_window_end = XIP_BASE + PICO2_FLASH_WINDOW_SIZE
+ if not (XIP_BASE <= target_address < pico2_window_end):
+ errors.append(
+ f"block {physical_index}: target address outside Pico "
+ f"flash window: 0x{target_address:08x}"
+ )
+ continue
+
+ flash_offset = target_address - XIP_BASE
+
+ if flash_offset + payload_size > PICO2_FLASH_WINDOW_SIZE:
+ errors.append(
+ f"block {physical_index}: page exceeds Pico 2 flash window: "
+ f"offset=0x{flash_offset:x}, size={payload_size}"
+ )
+ continue
+
+ if flash_offset in pages:
+ warnings.append(
+ f"block {physical_index}: duplicated flash page at offset "
+ f"0x{flash_offset:x}; last occurrence wins"
+ )
+
+ payload = block[UF2_HEADER_SIZE:UF2_HEADER_SIZE + payload_size]
+ pages[flash_offset] = payload
+
+ if min_offset is None or flash_offset < min_offset:
+ min_offset = flash_offset
+
+ if flash_offset + payload_size > max_end_offset:
+ max_end_offset = flash_offset + payload_size
+
+ if valid_block_count == 0:
+ errors.append("no valid UF2 block found")
+
+ if (announced_num_blocks is not None
+ and len(seen_block_numbers) != announced_num_blocks):
+ warnings.append(
+ f"observed {len(seen_block_numbers)} distinct blockNo values, "
+ f"but numBlocks announces {announced_num_blocks}"
+ )
+
+ return {
+ "pages": pages,
+ "family_ids": family_ids,
+ "warnings": warnings,
+ "errors": errors,
+ "valid_block_count": valid_block_count,
+ "non_uf2_block_count": non_uf2_block_count,
+ "ignored_not_main_flash_count": ignored_not_main_flash_count,
+ "min_offset": min_offset,
+ "max_end_offset": max_end_offset,
+ }
+
+
+def diagnose_family_ids(
+ family_ids: set[int],
+ warnings: list[str],
+ errors: list[str],
+) -> None:
+ """
+ Diagnose UF2 family IDs with respect to Pico 1 and Pico 2.
+
+ RP2040 is accepted as the strict Pico 1 case.
+ RP2350 IDs are accepted with a warning because partition remapping is not
+ implemented.
+ """
+
+ if not family_ids:
+ errors.append("no usable family ID found")
+ return
+
+ if len(family_ids) > 1:
+ family_list = ", ".join(
+ f"0x{value:08x}" for value in sorted(family_ids)
+ )
+ warnings.append(f"multiple family IDs found: {family_list}")
+
+ for family_id in sorted(family_ids):
+ if family_id == RP2040_FAMILY_ID:
+ continue
+
+ if family_id in RP2350_FAMILY_IDS:
+ warnings.append(
+ f"family ID 0x{family_id:08x} detected "
+ f"({RP2350_FAMILY_IDS[family_id]}): this is probably a "
+ f"Pico 2 / RP2350 UF2 file. Linear conversion is performed, "
+ f"but RP2350 partition remapping is not implemented."
+ )
+ else:
+ warnings.append(
+ f"unknown or non-Pico family ID: 0x{family_id:08x}"
+ )
+
+ if RP2040_FAMILY_ID not in family_ids:
+ warnings.append(
+ "no RP2040 / Pico 1 family ID found (0xe48bff56); "
+ "the output may not be a strict Pico 1 flash image."
+ )
+
+
+def build_flash_image(pages: dict[int, bytes], occupied_size: int) -> bytearray:
+ """
+ Build a raw flash image initialized with erased flash bytes, 0xff.
+
+ Only the occupied part is emitted:
+ output size = highest written offset + payload size
+ """
+
+ image = bytearray([0xFF] * occupied_size)
+
+ for flash_offset, payload in pages.items():
+ image[flash_offset:flash_offset + len(payload)] = payload
+
+ return image
+
+
+def human_size(byte_count: int) -> str:
+ """Return a human-readable size string."""
+
+ if byte_count < 1024:
+ return f"{byte_count} B"
+
+ if byte_count < 1024 * 1024:
+ return f"{byte_count / 1024:.1f} KiB"
+
+ return f"{byte_count / (1024 * 1024):.2f} MiB"
+
+
+def main() -> None:
+ """Program entry point."""
+
+ uf2_path, image_path = detect_input_output(sys.argv[1:])
+
+ try:
+ analysis = analyze_uf2(uf2_path)
+ except OSError as error:
+ print(f"UF2 file access error: {error}")
+ sys.exit(1)
+ except ValueError as error:
+ print(f"UF2 format error: {error}")
+ sys.exit(1)
+
+ pages = analysis["pages"]
+ family_ids = analysis["family_ids"]
+ warnings = analysis["warnings"]
+ errors = analysis["errors"]
+
+ diagnose_family_ids(family_ids, warnings, errors)
+
+ if not pages:
+ errors.append("no main-flash page to write")
+
+ if analysis["max_end_offset"] > PICO1_FLASH_WINDOW_SIZE:
+ warnings.append(
+ "image exceeds the Pico 1 flash window "
+ "0x10000000..0x11000000; it is not strict Pico 1 compatible"
+ )
+
+ if errors:
+ print("Conversion refused: UF2 format problems were found.")
+ for error in errors:
+ print(f"ERROR: {error}")
+ for warning in warnings:
+ print(f"WARNING: {warning}")
+ sys.exit(1)
+
+ occupied_size = analysis["max_end_offset"]
+ image = build_flash_image(pages, occupied_size)
+
+ try:
+ with open(image_path, "wb") as output_file:
+ output_file.write(image)
+ except OSError as error:
+ print(f"Output image write error: {error}")
+ sys.exit(1)
+
+ programmed_size = sum(len(payload) for payload in pages.values())
+
+ print(f"UF2 input: {uf2_path}")
+ print(f"Raw output: {image_path}")
+ print(f"Valid UF2 blocks: {analysis['valid_block_count']}")
+ print("Ignored NOT_MAIN_FLASH blocks: "
+ f"{analysis['ignored_not_main_flash_count']}")
+ print(f"Written flash pages: {len(pages)}")
+ print(
+ f"Actually programmed bytes: {programmed_size} "
+ f"({human_size(programmed_size)})"
+ )
+ print(
+ f"Occupied flash size: {occupied_size} "
+ f"({human_size(occupied_size)})"
+ )
+
+ if family_ids:
+ family_id_list = ", ".join(
+ f"0x{value:08x}" for value in sorted(family_ids)
+ )
+ print(f"Family ID(s): {family_id_list}")
+
+ if warnings:
+ print()
+ print("Warnings:")
+ for warning in warnings:
+ print(f"- {warning}")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tests/qtest/rp2040-xip-test.c b/tests/qtest/rp2040-xip-test.c
index 8e5b719332..351c8ef706 100644
--- a/tests/qtest/rp2040-xip-test.c
+++ b/tests/qtest/rp2040-xip-test.c
@@ -31,6 +31,15 @@
#define XIP_STAT_FIFO_EMPTY BIT(1)
#define XIP_STAT_FLUSH_READY BIT(0)
+#define UF2_BLOCK_SIZE 512
+#define UF2_PAYLOAD_OFFSET 32
+#define UF2_PAYLOAD_SIZE 256
+#define UF2_MAGIC_START0 0x0a324655
+#define UF2_MAGIC_START1 0x9e5d5157
+#define UF2_MAGIC_END 0x0ab16f30
+#define UF2_FLAG_FAMILY_ID 0x00002000
+#define UF2_RP2040_FAMILY_ID 0xe48bff56
+
static QTestState *rp2040_start(const char *machine_args)
{
if (machine_args) {
@@ -39,6 +48,57 @@ static QTestState *rp2040_start(const char *machine_args)
return qtest_init("-machine raspi-pico");
}
+static void build_uf2_block(uint8_t *block, uint32_t target, uint32_t index,
+ const uint8_t *payload)
+{
+ memset(block, 0, UF2_BLOCK_SIZE);
+ stl_le_p(block, UF2_MAGIC_START0);
+ stl_le_p(block + 4, UF2_MAGIC_START1);
+ stl_le_p(block + 8, UF2_FLAG_FAMILY_ID);
+ stl_le_p(block + 12, target);
+ stl_le_p(block + 16, UF2_PAYLOAD_SIZE);
+ stl_le_p(block + 20, index);
+ stl_le_p(block + 24, 2);
+ stl_le_p(block + 28, UF2_RP2040_FAMILY_ID);
+ memcpy(block + UF2_PAYLOAD_OFFSET, payload, UF2_PAYLOAD_SIZE);
+ stl_le_p(block + UF2_BLOCK_SIZE - 4, UF2_MAGIC_END);
+}
+
+static void test_uf2_kernel_load(void)
+{
+ g_autoptr(GError) error = NULL;
+ g_autofree char *path = NULL;
+ uint8_t uf2[2 * UF2_BLOCK_SIZE];
+ uint8_t boot2[UF2_PAYLOAD_SIZE];
+ uint8_t payload[UF2_PAYLOAD_SIZE];
+ uint8_t actual[UF2_PAYLOAD_SIZE];
+ QTestState *qts;
+ int fd;
+ size_t i;
+
+ memset(boot2, 0x5a, sizeof(boot2));
+ for (i = 0; i < ARRAY_SIZE(payload); i++) {
+ payload[i] = i ^ 0xa5;
+ }
+ build_uf2_block(uf2, XIP_BASE, 0, boot2);
+ build_uf2_block(uf2 + UF2_BLOCK_SIZE, XIP_BASE + 0x2000, 1, payload);
+
+ fd = g_file_open_tmp("qemu-rp2040-uf2-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 *)uf2, sizeof(uf2),
+ &error));
+ g_assert_no_error(error);
+
+ qts = qtest_initf("-machine raspi-pico -kernel %s", path);
+ qtest_memread(qts, XIP_BASE + 0x2000, actual, sizeof(actual));
+ g_assert_cmpmem(actual, sizeof(actual), payload, sizeof(payload));
+ qtest_quit(qts);
+
+ unlink(path);
+}
+
static void read_flash_uid(QTestState *qts, uint8_t *uid)
{
int i;
@@ -280,6 +340,7 @@ int main(int argc, char **argv)
test_flash_uid_default);
qtest_add_func("/rp2040-xip/flash-uid-machine-option",
test_flash_uid_machine_option);
+ qtest_add_func("/rp2040-xip/uf2-kernel-load", test_uf2_kernel_load);
qtest_add_func("/rp2040-xip/flash-program-erase",
test_flash_program_erase);
qtest_add_func("/rp2040-xip/flash-persistence",
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 25/30] hw/misc: add RP2040 bus controller
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (23 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 24/30] hw/ssi: load RP2040 UF2 flash images Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 26/30] hw/arm: add shallow RP2040 USB controller Gilles Grimaud
` (5 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/Kconfig | 1 +
hw/arm/rp2040.c | 8 +-
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/rp2040_busctrl.c | 208 ++++++++++++++++++++++++++++++
include/hw/arm/rp2040.h | 2 +
include/hw/misc/rp2040_busctrl.h | 29 +++++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-busctrl-test.c | 92 +++++++++++++
9 files changed, 344 insertions(+), 1 deletion(-)
create mode 100644 hw/misc/rp2040_busctrl.c
create mode 100644 include/hw/misc/rp2040_busctrl.h
create mode 100644 tests/qtest/rp2040-busctrl-test.c
diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 30965e9ede..517e39d81b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -378,6 +378,7 @@ config RP2040
bool
select ARM_V7M
select PL011
+ select RP2040_BUSCTRL
select RP2040_CLOCKS
select RP2040_DMA
select RP2040_IOBANK0
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index e7ba55bd1f..ca27b3c8ef 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -142,7 +142,6 @@ static const struct {
hwaddr base;
hwaddr size;
} rp2040_unimplemented[] = {
- { "rp2040.busctrl", 0x40030000, 0x4000 },
{ "rp2040.spi0", 0x4003c000, 0x4000 },
{ "rp2040.spi1", 0x40040000, 0x4000 },
{ "rp2040.i2c0", 0x40044000, 0x4000 },
@@ -368,6 +367,8 @@ static void rp2040_soc_init(Object *obj)
}
object_initialize_child(obj, "xip", &s->xip, TYPE_RP2040_XIP);
+ object_initialize_child(obj, "busctrl", &s->busctrl,
+ TYPE_RP2040_BUSCTRL);
object_initialize_child(obj, "clocks", &s->clocks, TYPE_RP2040_CLOCKS);
object_initialize_child(obj, "dma", &s->dma, TYPE_RP2040_DMA);
@@ -531,6 +532,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
RP2040_XIP_NOCACHE_NOALLOC_BASE,
&s->xip.xip_nocache_noalloc);
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->busctrl), errp)) {
+ return;
+ }
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->busctrl), 0, RP2040_BUSCTRL_BASE);
+
object_property_set_link(OBJECT(&s->dma), "memory",
OBJECT(s->board_memory), &err);
if (err) {
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index d98b8a4674..228b313da0 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -101,6 +101,9 @@ config FSL_IMX8MP_ANALOG
config FSL_IMX8MP_CCM
bool
+config RP2040_BUSCTRL
+ bool
+
config RP2040_CLOCKS
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index ba2e9489ee..96b5fb9c46 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -98,6 +98,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_cprman.c',
'bcm2835_powermgt.c',
))
+system_ss.add(when: 'CONFIG_RP2040_BUSCTRL', if_true: files('rp2040_busctrl.c'))
system_ss.add(when: 'CONFIG_RP2040_CLOCKS', if_true: files('rp2040_clocks.c'))
system_ss.add(when: 'CONFIG_RP2040_IOBANK0', if_true: files('rp2040_iobank0.c'))
system_ss.add(when: 'CONFIG_RP2040_IOQSPI', if_true: files('rp2040_ioqspi.c'))
diff --git a/hw/misc/rp2040_busctrl.c b/hw/misc/rp2040_busctrl.c
new file mode 100644
index 0000000000..505047609c
--- /dev/null
+++ b/hw/misc/rp2040_busctrl.c
@@ -0,0 +1,208 @@
+/*
+ * RP2040 bus fabric control emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/rp2040_busctrl.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/module.h"
+
+#define BUSCTRL_PRIORITY 0x00
+#define BUSCTRL_PRIORITY_ACK 0x04
+#define BUSCTRL_PERFCTR_BASE 0x08
+#define BUSCTRL_PERFSEL_BASE 0x0c
+#define BUSCTRL_PERF_STRIDE 0x08
+#define BUSCTRL_NUM_COUNTERS 4
+
+#define BUSCTRL_PRIORITY_MASK 0x00001111
+#define BUSCTRL_PRIORITY_ACKED BIT(0)
+#define BUSCTRL_PERFCTR_MASK 0x00ffffff
+#define BUSCTRL_PERFSEL_RESET 0x1f
+#define BUSCTRL_PERFSEL_MASK 0x1f
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
+
+static uint32_t rp2040_busctrl_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 uint32_t rp2040_busctrl_read_counter(RP2040BusCtrlState *s,
+ unsigned index)
+{
+ if (s->perfsel[index] != BUSCTRL_PERFSEL_RESET &&
+ s->perfctr[index] != BUSCTRL_PERFCTR_MASK) {
+ s->perfctr[index]++;
+ }
+
+ return s->perfctr[index];
+}
+
+static uint64_t rp2040_busctrl_read(void *opaque, hwaddr addr, unsigned size)
+{
+ RP2040BusCtrlState *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint64_t value;
+
+ if (offset >= BUSCTRL_PERFCTR_BASE &&
+ offset < BUSCTRL_PERFCTR_BASE + BUSCTRL_NUM_COUNTERS *
+ BUSCTRL_PERF_STRIDE &&
+ (offset - BUSCTRL_PERFCTR_BASE) % BUSCTRL_PERF_STRIDE == 0) {
+ index = (offset - BUSCTRL_PERFCTR_BASE) / BUSCTRL_PERF_STRIDE;
+ return rp2040_busctrl_read_counter(s, index);
+ }
+
+ if (offset >= BUSCTRL_PERFSEL_BASE &&
+ offset < BUSCTRL_PERFSEL_BASE + BUSCTRL_NUM_COUNTERS *
+ BUSCTRL_PERF_STRIDE &&
+ (offset - BUSCTRL_PERFSEL_BASE) % BUSCTRL_PERF_STRIDE == 0) {
+ index = (offset - BUSCTRL_PERFSEL_BASE) / BUSCTRL_PERF_STRIDE;
+ return s->perfsel[index];
+ }
+
+ switch (offset) {
+ case BUSCTRL_PRIORITY:
+ value = s->priority;
+ break;
+ case BUSCTRL_PRIORITY_ACK:
+ value = BUSCTRL_PRIORITY_ACKED;
+ break;
+ default:
+ value = 0;
+ rp2040_log_unimplemented_read("busctrl", size,
+ RP2040_BUSCTRL_BASE + addr, offset,
+ value);
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_busctrl_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040BusCtrlState *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ unsigned index;
+ uint32_t value = value64;
+
+ if (offset >= BUSCTRL_PERFCTR_BASE &&
+ offset < BUSCTRL_PERFCTR_BASE + BUSCTRL_NUM_COUNTERS *
+ BUSCTRL_PERF_STRIDE &&
+ (offset - BUSCTRL_PERFCTR_BASE) % BUSCTRL_PERF_STRIDE == 0) {
+ index = (offset - BUSCTRL_PERFCTR_BASE) / BUSCTRL_PERF_STRIDE;
+ s->perfctr[index] =
+ rp2040_busctrl_apply_alias(s->perfctr[index], value, alias) &
+ BUSCTRL_PERFCTR_MASK;
+ return;
+ }
+
+ if (offset >= BUSCTRL_PERFSEL_BASE &&
+ offset < BUSCTRL_PERFSEL_BASE + BUSCTRL_NUM_COUNTERS *
+ BUSCTRL_PERF_STRIDE &&
+ (offset - BUSCTRL_PERFSEL_BASE) % BUSCTRL_PERF_STRIDE == 0) {
+ index = (offset - BUSCTRL_PERFSEL_BASE) / BUSCTRL_PERF_STRIDE;
+ s->perfsel[index] =
+ rp2040_busctrl_apply_alias(s->perfsel[index], value, alias) &
+ BUSCTRL_PERFSEL_MASK;
+ return;
+ }
+
+ switch (offset) {
+ case BUSCTRL_PRIORITY:
+ s->priority = rp2040_busctrl_apply_alias(s->priority, value, alias) &
+ BUSCTRL_PRIORITY_MASK;
+ break;
+ case BUSCTRL_PRIORITY_ACK:
+ break;
+ default:
+ rp2040_log_unimplemented_write("busctrl", size,
+ RP2040_BUSCTRL_BASE + addr, offset,
+ value64);
+ break;
+ }
+}
+
+static const MemoryRegionOps rp2040_busctrl_ops = {
+ .read = rp2040_busctrl_read,
+ .write = rp2040_busctrl_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void rp2040_busctrl_reset(DeviceState *dev)
+{
+ RP2040BusCtrlState *s = RP2040_BUSCTRL(dev);
+ int i;
+
+ s->priority = 0;
+ for (i = 0; i < BUSCTRL_NUM_COUNTERS; i++) {
+ s->perfctr[i] = 0;
+ s->perfsel[i] = BUSCTRL_PERFSEL_RESET;
+ }
+}
+
+static void rp2040_busctrl_init(Object *obj)
+{
+ RP2040BusCtrlState *s = RP2040_BUSCTRL(obj);
+
+ memory_region_init_io(&s->iomem, obj, &rp2040_busctrl_ops, s,
+ "rp2040.busctrl", RP2040_BUSCTRL_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_busctrl_vmstate = {
+ .name = TYPE_RP2040_BUSCTRL,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32(priority, RP2040BusCtrlState),
+ VMSTATE_UINT32_ARRAY(perfctr, RP2040BusCtrlState, 4),
+ VMSTATE_UINT32_ARRAY(perfsel, RP2040BusCtrlState, 4),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void rp2040_busctrl_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_legacy_reset(dc, rp2040_busctrl_reset);
+ dc->vmsd = &rp2040_busctrl_vmstate;
+}
+
+static const TypeInfo rp2040_busctrl_info = {
+ .name = TYPE_RP2040_BUSCTRL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(RP2040BusCtrlState),
+ .instance_init = rp2040_busctrl_init,
+ .class_init = rp2040_busctrl_class_init,
+};
+
+static void rp2040_busctrl_register_types(void)
+{
+ type_register_static(&rp2040_busctrl_info);
+}
+type_init(rp2040_busctrl_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index d52c8856b0..eeec45efbe 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -14,6 +14,7 @@
#include "hw/core/clock.h"
#include "hw/core/sysbus.h"
#include "hw/dma/rp2040_dma.h"
+#include "hw/misc/rp2040_busctrl.h"
#include "hw/misc/rp2040_clocks.h"
#include "hw/misc/rp2040_iobank0.h"
#include "hw/misc/rp2040_ioqspi.h"
@@ -56,6 +57,7 @@ struct RP2040State {
ARMv7MState armv7m[RP2040_NUM_CORES];
PL011State uart[2];
+ RP2040BusCtrlState busctrl;
RP2040ClocksState clocks;
RP2040DmaState dma;
RP2040IoBank0State iobank0;
diff --git a/include/hw/misc/rp2040_busctrl.h b/include/hw/misc/rp2040_busctrl.h
new file mode 100644
index 0000000000..5c4a6d6d2b
--- /dev/null
+++ b/include/hw/misc/rp2040_busctrl.h
@@ -0,0 +1,29 @@
+/*
+ * RP2040 bus fabric control emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_BUSCTRL_H
+#define HW_MISC_RP2040_BUSCTRL_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_BUSCTRL "rp2040-busctrl"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040BusCtrlState, RP2040_BUSCTRL)
+
+#define RP2040_BUSCTRL_BASE 0x40030000
+#define RP2040_BUSCTRL_SIZE 0x4000
+
+struct RP2040BusCtrlState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t priority;
+ uint32_t perfctr[4];
+ uint32_t perfsel[4];
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 89e8f3ac32..c11f35e427 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -256,6 +256,7 @@ qtests_arm = \
(config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \
(config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
(config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-syscfg-test',
+ 'rp2040-busctrl-test',
'rp2040-clocks-test',
'rp2040-dma-test',
'rp2040-iobank0-test',
diff --git a/tests/qtest/rp2040-busctrl-test.c b/tests/qtest/rp2040-busctrl-test.c
new file mode 100644
index 0000000000..a86fd8a20e
--- /dev/null
+++ b/tests/qtest/rp2040-busctrl-test.c
@@ -0,0 +1,92 @@
+/*
+ * QTest testcase for the RP2040 bus fabric control block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define BUSCTRL_BASE 0x40030000
+#define BUSCTRL_PRIORITY 0x00
+#define BUSCTRL_PRIORITY_ACK 0x04
+#define BUSCTRL_PERFCTR0 0x08
+#define BUSCTRL_PERFSEL0 0x0c
+#define BUSCTRL_PERFCTR1 0x10
+#define BUSCTRL_PERFSEL1 0x14
+#define BUSCTRL_PRIORITY_MASK 0x00001111
+#define BUSCTRL_PERFCTR_MASK 0x00ffffff
+#define BUSCTRL_PERFSEL_RESET 0x1f
+#define BUSCTRL_PERFSEL_SRAM5 0x05
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_busctrl_reset_values(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY_ACK),
+ ==, 1);
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL0),
+ ==, BUSCTRL_PERFSEL_RESET);
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL1),
+ ==, BUSCTRL_PERFSEL_RESET);
+
+ qtest_quit(qts);
+}
+
+static void test_busctrl_priority(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY, 0xffffffff);
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY),
+ ==, BUSCTRL_PRIORITY_MASK);
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY_ACK),
+ ==, 1);
+
+ qtest_quit(qts);
+}
+
+static void test_busctrl_perf_counter(void)
+{
+ QTestState *qts = rp2040_start();
+ uint32_t first;
+ uint32_t second;
+
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0),
+ ==, 0);
+ qtest_writel(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL0,
+ BUSCTRL_PERFSEL_SRAM5);
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL0),
+ ==, BUSCTRL_PERFSEL_SRAM5);
+
+ first = qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0);
+ second = qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0);
+ g_assert_cmphex(first, ==, 1);
+ g_assert_cmphex(second, ==, 2);
+
+ qtest_writel(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0,
+ BUSCTRL_PERFCTR_MASK);
+ g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0),
+ ==, BUSCTRL_PERFCTR_MASK);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-busctrl/reset-values",
+ test_busctrl_reset_values);
+ qtest_add_func("/rp2040-busctrl/priority", test_busctrl_priority);
+ qtest_add_func("/rp2040-busctrl/perf-counter",
+ test_busctrl_perf_counter);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 26/30] hw/arm: add shallow RP2040 USB controller
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (24 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 25/30] hw/misc: add RP2040 bus controller Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 27/30] hw/arm: add RP2040 synthetic boot ROM service tables Gilles Grimaud
` (4 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/rp2040.c | 125 +++++++++++++++++++++++++++++++++-
include/hw/arm/rp2040.h | 8 +++
tests/qtest/meson.build | 1 +
tests/qtest/rp2040-usb-test.c | 80 ++++++++++++++++++++++
4 files changed, 212 insertions(+), 2 deletions(-)
create mode 100644 tests/qtest/rp2040-usb-test.c
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index ca27b3c8ef..10a79c7daa 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -14,6 +14,7 @@
#include "hw/core/qdev-clock.h"
#include "hw/core/qdev-properties.h"
#include "hw/core/irq.h"
+#include "hw/misc/rp2040_nyi.h"
#include "hw/misc/unimp.h"
#include "qemu/datadir.h"
#include "system/address-spaces.h"
@@ -29,6 +30,21 @@
#define RP2040_IO_IRQ_BANK0 13
#define RP2040_SIO_IRQ_PROC0 15
#define RP2040_SIO_IRQ_PROC1 16
+
+#define USBCTRL_SIE_STATUS 0x50
+#define USBCTRL_BUFF_CPU_HANDLE 0x5c
+#define USBCTRL_EP_ABORT_DONE 0x64
+#define USBCTRL_INTR 0x8c
+#define USBCTRL_INTE 0x90
+#define USBCTRL_INTF 0x94
+#define USBCTRL_INTS 0x98
+
+#define USBCTRL_SIE_STATUS_VBUS_DETECTED BIT(11)
+
+#define ATOMIC_ALIAS_MASK 0x3000
+#define ATOMIC_XOR 0x1000
+#define ATOMIC_SET 0x2000
+#define ATOMIC_CLR 0x3000
#define RP2040_PROC1 1
/*
@@ -149,8 +165,6 @@ static const struct {
{ "rp2040.adc", 0x4004c000, 0x4000 },
{ "rp2040.pwm", 0x40050000, 0x4000 },
{ "rp2040.rtc", 0x4005c000, 0x4000 },
- { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
- { "rp2040.usbctrl_regs", 0x50110000, 0x10000 },
{ "rp2040.pio0", 0x50200000, 0x10000 },
{ "rp2040.pio1", 0x50300000, 0x10000 },
};
@@ -180,6 +194,21 @@ static const MemoryRegionOps rp2040_powered_off_ops = {
},
};
+static uint32_t rp2040_apply_atomic_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_update_mempowerdown(RP2040State *s)
{
uint32_t mempowerdown;
@@ -194,9 +223,78 @@ static void rp2040_update_mempowerdown(RP2040State *s)
memory_region_set_enabled(&s->sram_poweroff[i],
mempowerdown & BIT(i));
}
+ memory_region_set_enabled(&s->usbctrl_dpram_poweroff,
+ mempowerdown & BIT(6));
memory_region_set_enabled(&s->rom_poweroff, mempowerdown & BIT(7));
}
+static uint64_t rp2040_usbctrl_regs_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ RP2040State *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint64_t value;
+
+ switch (offset) {
+ case USBCTRL_SIE_STATUS:
+ value = s->usbctrl_reg[offset / sizeof(uint32_t)] |
+ USBCTRL_SIE_STATUS_VBUS_DETECTED;
+ break;
+ case USBCTRL_BUFF_CPU_HANDLE:
+ case USBCTRL_EP_ABORT_DONE:
+ case USBCTRL_INTR:
+ value = s->usbctrl_reg[offset / sizeof(uint32_t)];
+ break;
+ case USBCTRL_INTS:
+ value = (s->usbctrl_reg[USBCTRL_INTR / sizeof(uint32_t)] |
+ s->usbctrl_reg[USBCTRL_INTF / sizeof(uint32_t)]) &
+ s->usbctrl_reg[USBCTRL_INTE / sizeof(uint32_t)];
+ break;
+ default:
+ if (offset < sizeof(s->usbctrl_reg)) {
+ value = s->usbctrl_reg[offset / sizeof(uint32_t)];
+ } else {
+ value = 0;
+ rp2040_log_unimplemented_read("usbctrl_regs", size,
+ RP2040_USBCTRL_REGS_BASE + addr,
+ offset, value);
+ }
+ break;
+ }
+
+ return value;
+}
+
+static void rp2040_usbctrl_regs_write(void *opaque, hwaddr addr,
+ uint64_t value64, unsigned size)
+{
+ RP2040State *s = opaque;
+ hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+ hwaddr offset = addr & 0xfff;
+ uint32_t value = value64;
+ uint32_t old;
+
+ if (offset < sizeof(s->usbctrl_reg)) {
+ old = s->usbctrl_reg[offset / sizeof(uint32_t)];
+ s->usbctrl_reg[offset / sizeof(uint32_t)] =
+ rp2040_apply_atomic_alias(old, value, alias);
+ } else {
+ rp2040_log_unimplemented_write("usbctrl_regs", size,
+ RP2040_USBCTRL_REGS_BASE + addr,
+ offset, value64);
+ }
+}
+
+static const MemoryRegionOps rp2040_usbctrl_regs_ops = {
+ .read = rp2040_usbctrl_regs_read,
+ .write = rp2040_usbctrl_regs_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
static void rp2040_update_nmi(RP2040State *s)
{
uint32_t nmi_mask = rp2040_syscfg_get_proc0_nmi_mask(&s->syscfg);
@@ -475,6 +573,29 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
&s->sram_poweroff[5], 1);
memory_region_set_enabled(&s->sram_poweroff[5], false);
+ if (!memory_region_init_ram(&s->usbctrl_dpram, OBJECT(s),
+ "rp2040.usbctrl_dpram",
+ RP2040_USBCTRL_DPRAM_SIZE, errp)) {
+ return false;
+ }
+ memory_region_add_subregion(s->board_memory, RP2040_USBCTRL_DPRAM_BASE,
+ &s->usbctrl_dpram);
+ memory_region_init_io(&s->usbctrl_dpram_poweroff, OBJECT(s),
+ &rp2040_powered_off_ops, s,
+ "rp2040.usbctrl_dpram.poweroff",
+ RP2040_USBCTRL_DPRAM_SIZE);
+ memory_region_add_subregion_overlap(s->board_memory,
+ RP2040_USBCTRL_DPRAM_BASE,
+ &s->usbctrl_dpram_poweroff, 1);
+ memory_region_set_enabled(&s->usbctrl_dpram_poweroff, false);
+
+ memory_region_init_io(&s->usbctrl_regs, OBJECT(s),
+ &rp2040_usbctrl_regs_ops, s,
+ "rp2040.usbctrl_regs",
+ RP2040_USBCTRL_REGS_SIZE);
+ memory_region_add_subregion(s->board_memory, RP2040_USBCTRL_REGS_BASE,
+ &s->usbctrl_regs);
+
s->mempowerdown_ready = true;
return true;
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index eeec45efbe..5c5026d65a 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -48,6 +48,10 @@ OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
#define RP2040_SRAM4_BASE 0x20040000
#define RP2040_SRAM5_BASE 0x20041000
#define RP2040_SRAM_HI_SIZE (4 * KiB)
+#define RP2040_USBCTRL_DPRAM_BASE 0x50100000
+#define RP2040_USBCTRL_DPRAM_SIZE (4 * KiB)
+#define RP2040_USBCTRL_REGS_BASE 0x50110000
+#define RP2040_USBCTRL_REGS_SIZE 0x4000
#define RP2040_NUM_CORES 2
#define RP2040_NUM_IRQS 32
@@ -85,6 +89,10 @@ struct RP2040State {
MemoryRegion rom_poweroff;
MemoryRegion sram[6];
MemoryRegion sram_poweroff[6];
+ MemoryRegion usbctrl_dpram;
+ MemoryRegion usbctrl_dpram_poweroff;
+ MemoryRegion usbctrl_regs;
+ uint32_t usbctrl_reg[0x100 / sizeof(uint32_t)];
char *bootrom_file;
qemu_irq *irq;
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index c11f35e427..23ca1b6161 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -268,6 +268,7 @@ qtests_arm = \
'rp2040-sio-test',
'rp2040-tbman-test',
'rp2040-timer-test',
+ 'rp2040-usb-test',
'rp2040-uart-test',
'rp2040-vreg-test',
'rp2040-watchdog-test',
diff --git a/tests/qtest/rp2040-usb-test.c b/tests/qtest/rp2040-usb-test.c
new file mode 100644
index 0000000000..e56b6f9066
--- /dev/null
+++ b/tests/qtest/rp2040-usb-test.c
@@ -0,0 +1,80 @@
+/*
+ * QTest testcase for the shallow RP2040 USB controller model.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+
+#define USBCTRL_DPRAM_BASE 0x50100000
+#define USBCTRL_REGS_BASE 0x50110000
+#define USBCTRL_ALIAS_XOR 0x1000
+#define USBCTRL_ALIAS_SET 0x2000
+#define USBCTRL_ALIAS_CLR 0x3000
+
+#define USBCTRL_SIE_STATUS 0x50
+#define USBCTRL_USB_MUXING 0x74
+#define USBCTRL_INTR 0x8c
+#define USBCTRL_INTE 0x90
+#define USBCTRL_INTF 0x94
+#define USBCTRL_INTS 0x98
+
+#define USBCTRL_VBUS_DETECTED (1U << 11)
+
+static QTestState *rp2040_start(void)
+{
+ return qtest_init("-machine raspi-pico");
+}
+
+static void test_usb_dpram(void)
+{
+ QTestState *qts = rp2040_start();
+
+ qtest_writel(qts, USBCTRL_DPRAM_BASE + 0x20, 0x12345678);
+ g_assert_cmphex(qtest_readl(qts, USBCTRL_DPRAM_BASE + 0x20),
+ ==, 0x12345678);
+
+ qtest_quit(qts);
+}
+
+static void test_usb_registers(void)
+{
+ QTestState *qts = rp2040_start();
+
+ g_assert_cmphex(qtest_readl(qts, USBCTRL_REGS_BASE +
+ USBCTRL_SIE_STATUS) &
+ USBCTRL_VBUS_DETECTED, ==, USBCTRL_VBUS_DETECTED);
+
+ qtest_writel(qts, USBCTRL_REGS_BASE + USBCTRL_USB_MUXING, 0x5);
+ qtest_writel(qts, USBCTRL_REGS_BASE + USBCTRL_ALIAS_SET +
+ USBCTRL_USB_MUXING, 0x8);
+ g_assert_cmphex(qtest_readl(qts, USBCTRL_REGS_BASE +
+ USBCTRL_USB_MUXING), ==, 0xd);
+ qtest_writel(qts, USBCTRL_REGS_BASE + USBCTRL_ALIAS_XOR +
+ USBCTRL_USB_MUXING, 0x9);
+ g_assert_cmphex(qtest_readl(qts, USBCTRL_REGS_BASE +
+ USBCTRL_USB_MUXING), ==, 0x4);
+ qtest_writel(qts, USBCTRL_REGS_BASE + USBCTRL_ALIAS_CLR +
+ USBCTRL_USB_MUXING, 0x4);
+ g_assert_cmphex(qtest_readl(qts, USBCTRL_REGS_BASE +
+ USBCTRL_USB_MUXING), ==, 0);
+
+ qtest_writel(qts, USBCTRL_REGS_BASE + USBCTRL_INTR, 0x3);
+ qtest_writel(qts, USBCTRL_REGS_BASE + USBCTRL_INTF, 0x4);
+ qtest_writel(qts, USBCTRL_REGS_BASE + USBCTRL_INTE, 0x6);
+ g_assert_cmphex(qtest_readl(qts, USBCTRL_REGS_BASE + USBCTRL_INTS),
+ ==, 0x6);
+
+ qtest_quit(qts);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/rp2040-usb/dpram", test_usb_dpram);
+ qtest_add_func("/rp2040-usb/registers", test_usb_registers);
+
+ return g_test_run();
+}
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 27/30] hw/arm: add RP2040 synthetic boot ROM service tables
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (25 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 26/30] hw/arm: add shallow RP2040 USB controller Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:15 ` [PATCH RFC v2 28/30] hw/arm: add RP2040 synthetic boot ROM accelerated helpers Gilles Grimaud
` (3 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/rp2040.c | 291 +++++++++++++++++-
include/hw/arm/rp2040.h | 3 +
tests/tcg/arm/Makefile.softmmu-target | 21 ++
tests/tcg/arm/system/rp2040-bootrom-helpers.S | 235 ++++++++++++++
.../tcg/arm/system/rp2040-bootrom-helpers.ref | 5 +
5 files changed, 553 insertions(+), 2 deletions(-)
create mode 100644 tests/tcg/arm/system/rp2040-bootrom-helpers.S
create mode 100644 tests/tcg/arm/system/rp2040-bootrom-helpers.ref
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 10a79c7daa..6c2f62038e 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -47,6 +47,24 @@
#define ATOMIC_CLR 0x3000
#define RP2040_PROC1 1
+#define RP2040_BOOTROM_FUNC_TABLE_PTR_OFFSET 0x14
+#define RP2040_BOOTROM_DATA_TABLE_PTR_OFFSET 0x16
+#define RP2040_BOOTROM_TABLE_LOOKUP_PTR_OFFSET 0x18
+#define RP2040_BOOTROM_LOOKUP_OFFSET 0x0100
+#define RP2040_BOOTROM_STUBS_OFFSET 0x0120
+#define RP2040_BOOTROM_FUNC_TABLE_OFFSET 0x0300
+#define RP2040_BOOTROM_DATA_TABLE_OFFSET 0x0340
+#define RP2040_BOOTROM_FLOAT_NYI_STUB_OFFSET 0x0360
+#define RP2040_BOOTROM_DOUBLE_NYI_STUB_OFFSET 0x0380
+#define RP2040_BOOTROM_FLOAT_TABLE_OFFSET 0x03c0
+#define RP2040_BOOTROM_DOUBLE_TABLE_OFFSET 0x0460
+#define RP2040_BOOTROM_FUNC_TABLE_ENTRY_SIZE 4
+#define RP2040_BOOTROM_DATA_TABLE_ENTRY_SIZE 4
+#define RP2040_BOOTROM_NYI_CODE_LITERAL_OFFSET 20
+#define RP2040_BOOTROM_ROM_VERSION_OFFSET 0x13
+#define RP2040_BOOTROM_SYNTHETIC_ROM_VERSION 2
+#define RP2040_BOOTROM_FLOAT_TABLE_WORDS 32
+
/*
* 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
@@ -169,6 +187,222 @@ static const struct {
{ "rp2040.pio1", 0x50300000, 0x10000 },
};
+typedef struct RP2040BootromFunction {
+ uint16_t code;
+ const char *name;
+ const uint8_t *impl;
+ size_t impl_size;
+} RP2040BootromFunction;
+
+typedef struct RP2040BootromData {
+ uint16_t code;
+ uint16_t offset;
+} RP2040BootromData;
+
+#define RP2040_ROM_TABLE_CODE(c1, c2) ((uint16_t)(c1) | ((uint16_t)(c2) << 8))
+
+static const uint8_t rp2040_bootrom_clz32[] = {
+ 0x00, 0x21, 0x00, 0x28, 0x01, 0xd1, 0x20, 0x20,
+ 0x70, 0x47, 0x00, 0x28, 0x02, 0xd4, 0x40, 0x00,
+ 0x01, 0x31, 0xfa, 0xe7, 0x08, 0x46, 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_ctz32[] = {
+ 0x00, 0x21, 0x00, 0x28, 0x01, 0xd1, 0x20, 0x20,
+ 0x70, 0x47, 0x01, 0x22, 0x10, 0x42, 0x02, 0xd1,
+ 0x40, 0x08, 0x01, 0x31, 0xf9, 0xe7, 0x08, 0x46,
+ 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_popcount32[] = {
+ 0x00, 0x21, 0x00, 0x28, 0x04, 0xd0, 0x01, 0x22,
+ 0x02, 0x40, 0x89, 0x18, 0x40, 0x08, 0xf8, 0xe7,
+ 0x08, 0x46, 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_reverse32[] = {
+ 0x00, 0x21, 0x20, 0x22, 0x49, 0x00, 0x01, 0x23,
+ 0x03, 0x40, 0x19, 0x43, 0x40, 0x08, 0x01, 0x3a,
+ 0xf8, 0xd1, 0x08, 0x46, 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_memcpy[] = {
+ 0x10, 0xb4, 0x03, 0x46, 0x00, 0x2a, 0x05, 0xd0,
+ 0x0c, 0x78, 0x04, 0x70, 0x01, 0x31, 0x01, 0x30,
+ 0x01, 0x3a, 0xf7, 0xe7, 0x18, 0x46, 0x10, 0xbc,
+ 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_memset[] = {
+ 0x03, 0x46, 0x00, 0x2a, 0x03, 0xd0, 0x01, 0x70,
+ 0x01, 0x30, 0x01, 0x3a, 0xf9, 0xe7, 0x18, 0x46,
+ 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_memcpy44[] = {
+ 0x10, 0xb4, 0x03, 0x46, 0x00, 0x2a, 0x05, 0xd0,
+ 0x0c, 0x68, 0x04, 0x60, 0x04, 0x31, 0x04, 0x30,
+ 0x04, 0x3a, 0xf7, 0xe7, 0x18, 0x46, 0x10, 0xbc,
+ 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_memset4[] = {
+ 0x10, 0xb4, 0x03, 0x46, 0xc9, 0xb2, 0x0c, 0x46,
+ 0x24, 0x02, 0x21, 0x43, 0x0c, 0x46, 0x24, 0x04,
+ 0x21, 0x43, 0x00, 0x2a, 0x03, 0xd0, 0x01, 0x60,
+ 0x04, 0x30, 0x04, 0x3a, 0xf9, 0xe7, 0x18, 0x46,
+ 0x10, 0xbc, 0x70, 0x47,
+};
+
+#define RP2040_BOOTROM_IMPL(_code, _name, _impl) \
+ { _code, _name, _impl, sizeof(_impl) }
+#define RP2040_BOOTROM_NYI(_code, _name) \
+ { _code, _name, NULL, 0 }
+
+static const RP2040BootromFunction rp2040_bootrom_functions[] = {
+ RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('C', 'X'),
+ "flash_enter_cmd_xip"),
+ RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('E', 'X'), "flash_exit_xip"),
+ RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('F', 'C'),
+ "flash_flush_cache"),
+ RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('I', 'F'),
+ "connect_internal_flash"),
+ RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('R', 'E'),
+ "flash_range_erase"),
+ RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('R', 'P'),
+ "flash_range_program"),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('C', '4'), "memcpy44",
+ rp2040_bootrom_memcpy44),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('L', '3'), "clz32",
+ rp2040_bootrom_clz32),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('M', 'C'), "memcpy",
+ rp2040_bootrom_memcpy),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('M', 'S'), "memset",
+ rp2040_bootrom_memset),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('P', '3'), "popcount32",
+ rp2040_bootrom_popcount32),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('R', '3'), "reverse32",
+ rp2040_bootrom_reverse32),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('S', '4'), "memset4",
+ rp2040_bootrom_memset4),
+ RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('T', '3'), "ctz32",
+ rp2040_bootrom_ctz32),
+ RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('U', 'B'), "reset_usb_boot"),
+};
+
+static const RP2040BootromData rp2040_bootrom_data[] = {
+ { RP2040_ROM_TABLE_CODE('S', 'F'), RP2040_BOOTROM_FLOAT_TABLE_OFFSET + 2 },
+ { RP2040_ROM_TABLE_CODE('S', 'D'), RP2040_BOOTROM_DOUBLE_TABLE_OFFSET + 2 },
+};
+
+static const uint8_t rp2040_bootrom_lookup[] = {
+ 0x02, 0x88, 0x00, 0x2a, 0x05, 0xd0, 0x91, 0x42,
+ 0x01, 0xd0, 0x04, 0x30, 0xf8, 0xe7, 0x40, 0x88,
+ 0x70, 0x47, 0x5f, 0x22, 0x12, 0x06, 0xff, 0x23,
+ 0x1b, 0x04, 0x1a, 0x43, 0x11, 0x60, 0x00, 0xbe,
+ 0x00, 0x20, 0x70, 0x47,
+};
+
+static const uint8_t rp2040_bootrom_nyi_stub[] = {
+ 0x04, 0x49, 0x5f, 0x20, 0x00, 0x06, 0xff, 0x22,
+ 0x12, 0x04, 0x10, 0x43, 0x01, 0x60, 0x00, 0xbe,
+ 0xfe, 0xe7, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00,
+};
+
+static void rp2040_store_hword(uint8_t *rom, uint32_t offset, uint16_t value)
+{
+ rom[offset] = value;
+ rom[offset + 1] = value >> 8;
+}
+
+static void rp2040_store_word(uint8_t *rom, uint32_t offset, uint32_t value)
+{
+ rom[offset] = value;
+ rom[offset + 1] = value >> 8;
+ rom[offset + 2] = value >> 16;
+ rom[offset + 3] = value >> 24;
+}
+
+static void rp2040_install_synthetic_bootrom(void)
+{
+ g_autofree uint8_t *rom = g_malloc0(RP2040_ROM_SIZE);
+ uint32_t func_base = RP2040_BOOTROM_STUBS_OFFSET;
+ uint32_t func_table = RP2040_BOOTROM_FUNC_TABLE_OFFSET;
+ uint32_t data_table = RP2040_BOOTROM_DATA_TABLE_OFFSET;
+ uint32_t float_nyi = RP2040_BOOTROM_FLOAT_NYI_STUB_OFFSET | 1;
+ uint32_t double_nyi = RP2040_BOOTROM_DOUBLE_NYI_STUB_OFFSET | 1;
+ int i;
+
+ memcpy(rom, rp2040_bootrom, sizeof(rp2040_bootrom));
+ rom[RP2040_BOOTROM_ROM_VERSION_OFFSET] =
+ RP2040_BOOTROM_SYNTHETIC_ROM_VERSION;
+ memcpy(rom + RP2040_BOOTROM_LOOKUP_OFFSET, rp2040_bootrom_lookup,
+ sizeof(rp2040_bootrom_lookup));
+ rp2040_store_hword(rom, RP2040_BOOTROM_FUNC_TABLE_PTR_OFFSET,
+ RP2040_BOOTROM_FUNC_TABLE_OFFSET);
+ rp2040_store_hword(rom, RP2040_BOOTROM_DATA_TABLE_PTR_OFFSET,
+ RP2040_BOOTROM_DATA_TABLE_OFFSET);
+ rp2040_store_hword(rom, RP2040_BOOTROM_TABLE_LOOKUP_PTR_OFFSET,
+ RP2040_BOOTROM_LOOKUP_OFFSET | 1);
+
+ for (i = 0; i < ARRAY_SIZE(rp2040_bootrom_functions); i++) {
+ const RP2040BootromFunction *func = &rp2040_bootrom_functions[i];
+ uint32_t entry = func_base | 1;
+
+ if (func->impl) {
+ memcpy(rom + func_base, func->impl, func->impl_size);
+ func_base += ROUND_UP(func->impl_size, 4);
+ } else {
+ memcpy(rom + func_base, rp2040_bootrom_nyi_stub,
+ sizeof(rp2040_bootrom_nyi_stub));
+ rp2040_store_word(rom,
+ func_base +
+ RP2040_BOOTROM_NYI_CODE_LITERAL_OFFSET,
+ func->code);
+ func_base += ROUND_UP(sizeof(rp2040_bootrom_nyi_stub), 4);
+ }
+ rp2040_store_hword(rom, func_table +
+ i * RP2040_BOOTROM_FUNC_TABLE_ENTRY_SIZE,
+ func->code);
+ rp2040_store_hword(rom, func_table +
+ i * RP2040_BOOTROM_FUNC_TABLE_ENTRY_SIZE + 2,
+ entry);
+ }
+
+ memcpy(rom + RP2040_BOOTROM_FLOAT_NYI_STUB_OFFSET, rp2040_bootrom_nyi_stub,
+ sizeof(rp2040_bootrom_nyi_stub));
+ rp2040_store_word(rom, RP2040_BOOTROM_FLOAT_NYI_STUB_OFFSET +
+ RP2040_BOOTROM_NYI_CODE_LITERAL_OFFSET,
+ RP2040_ROM_TABLE_CODE('S', 'F'));
+ memcpy(rom + RP2040_BOOTROM_DOUBLE_NYI_STUB_OFFSET, rp2040_bootrom_nyi_stub,
+ sizeof(rp2040_bootrom_nyi_stub));
+ rp2040_store_word(rom, RP2040_BOOTROM_DOUBLE_NYI_STUB_OFFSET +
+ RP2040_BOOTROM_NYI_CODE_LITERAL_OFFSET,
+ RP2040_ROM_TABLE_CODE('S', 'D'));
+ rom[RP2040_BOOTROM_FLOAT_TABLE_OFFSET] = RP2040_BOOTROM_FLOAT_TABLE_WORDS;
+ rom[RP2040_BOOTROM_DOUBLE_TABLE_OFFSET] = RP2040_BOOTROM_FLOAT_TABLE_WORDS;
+ for (i = 0; i < RP2040_BOOTROM_FLOAT_TABLE_WORDS; i++) {
+ rp2040_store_word(rom, RP2040_BOOTROM_FLOAT_TABLE_OFFSET + 2 +
+ i * sizeof(uint32_t), float_nyi);
+ rp2040_store_word(rom, RP2040_BOOTROM_DOUBLE_TABLE_OFFSET + 2 +
+ i * sizeof(uint32_t), double_nyi);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(rp2040_bootrom_data); i++) {
+ const RP2040BootromData *data = &rp2040_bootrom_data[i];
+
+ rp2040_store_hword(rom, data_table +
+ i * RP2040_BOOTROM_DATA_TABLE_ENTRY_SIZE,
+ data->code);
+ rp2040_store_hword(rom, data_table +
+ i * RP2040_BOOTROM_DATA_TABLE_ENTRY_SIZE + 2,
+ data->offset);
+ }
+
+ rom_add_blob_fixed("rp2040.bootrom", rom, RP2040_ROM_SIZE,
+ RP2040_ROM_BASE);
+}
+
static MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr,
uint64_t *data, unsigned size,
MemTxAttrs attrs)
@@ -295,6 +529,52 @@ static const MemoryRegionOps rp2040_usbctrl_regs_ops = {
},
};
+static const char *rp2040_bootrom_function_name(uint16_t code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(rp2040_bootrom_functions); i++) {
+ if (rp2040_bootrom_functions[i].code == code) {
+ return rp2040_bootrom_functions[i].name;
+ }
+ }
+ return NULL;
+}
+
+static void rp2040_synthetic_rom_dbg_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ uint16_t code = value;
+ const char *name = rp2040_bootrom_function_name(code);
+ char feature[64];
+ char detail[64];
+
+ snprintf(feature, sizeof(feature), "boot ROM function '%c%c'",
+ code & 0xff, (code >> 8) & 0xff);
+ snprintf(detail, sizeof(detail), "%s%s",
+ name ? "SDK name: " : "",
+ name ? name : "unknown lookup code");
+ rp2040_log_nyi("bootrom", feature, detail);
+}
+
+static uint64_t rp2040_synthetic_rom_dbg_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ rp2040_log_nyi("bootrom", "synthetic diagnostic register read",
+ "write-only QEMU diagnostic channel");
+ return 0;
+}
+
+static const MemoryRegionOps rp2040_synthetic_rom_dbg_ops = {
+ .read = rp2040_synthetic_rom_dbg_read,
+ .write = rp2040_synthetic_rom_dbg_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
static void rp2040_update_nmi(RP2040State *s)
{
uint32_t nmi_mask = rp2040_syscfg_get_proc0_nmi_mask(&s->syscfg);
@@ -596,6 +876,14 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
memory_region_add_subregion(s->board_memory, RP2040_USBCTRL_REGS_BASE,
&s->usbctrl_regs);
+ memory_region_init_io(&s->synthetic_rom_dbg, OBJECT(s),
+ &rp2040_synthetic_rom_dbg_ops, s,
+ "rp2040.synthetic-rom-dbg",
+ RP2040_SYNTHETIC_ROM_DBG_SIZE);
+ memory_region_add_subregion(s->board_memory,
+ RP2040_SYNTHETIC_ROM_DBG_BASE,
+ &s->synthetic_rom_dbg);
+
s->mempowerdown_ready = true;
return true;
@@ -606,8 +894,7 @@ static bool rp2040_load_bootrom(RP2040State *s, Error **errp)
g_autofree char *filename = NULL;
if (!s->bootrom_file) {
- rom_add_blob_fixed("rp2040.bootrom", rp2040_bootrom,
- sizeof(rp2040_bootrom), RP2040_ROM_BASE);
+ rp2040_install_synthetic_bootrom();
return true;
}
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 5c5026d65a..58c524a341 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -52,6 +52,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
#define RP2040_USBCTRL_DPRAM_SIZE (4 * KiB)
#define RP2040_USBCTRL_REGS_BASE 0x50110000
#define RP2040_USBCTRL_REGS_SIZE 0x4000
+#define RP2040_SYNTHETIC_ROM_DBG_BASE 0x5fff0000
+#define RP2040_SYNTHETIC_ROM_DBG_SIZE 0x1000
#define RP2040_NUM_CORES 2
#define RP2040_NUM_IRQS 32
@@ -92,6 +94,7 @@ struct RP2040State {
MemoryRegion usbctrl_dpram;
MemoryRegion usbctrl_dpram_poweroff;
MemoryRegion usbctrl_regs;
+ MemoryRegion synthetic_rom_dbg;
uint32_t usbctrl_reg[0x100 / sizeof(uint32_t)];
char *bootrom_file;
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index acb90458be..eb3de828c5 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -110,6 +110,27 @@ run-rp2040-dma-uart0 run-rp2040-dma-uart1: run-%: %
$(QEMU_OPTS) $<)
$(call diff-out,$<,$(ARM_SRC)/$<.ref)
+rp2040-bootrom-helpers: rp2040-bootrom-helpers.S \
+ rp2040-boot2-w25q080.S rp2040.ld
+ $(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
+ -Wl,--build-id=none -x assembler-with-cpp \
+ $(ARM_SRC)/rp2040-boot2-w25q080.S $< \
+ -o $@ -nostdlib -static \
+ -T $(ARM_SRC)/rp2040.ld
+
+run-rp2040-bootrom-helpers: QEMU_OPTS=\
+ -M raspi-pico,strict-uart-pins=off \
+ -serial chardev:output \
+ -semihosting-config enable=on,target=native -kernel
+run-rp2040-bootrom-helpers: rp2040-bootrom-helpers
+ $(call run-test, $<, \
+ $(QEMU) -monitor none -display none \
+ -chardev file$(COMMA)path=$<.out$(COMMA)id=output \
+ $(QEMU_OPTS) $<)
+ $(call diff-out,$<,$(ARM_SRC)/rp2040-bootrom-helpers.ref)
+
+RP2040_TESTS+=rp2040-bootrom-helpers
+
ARM_TESTS+=$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS)
# These objects provide the basic boot code and helper functions for all tests
diff --git a/tests/tcg/arm/system/rp2040-bootrom-helpers.S b/tests/tcg/arm/system/rp2040-bootrom-helpers.S
new file mode 100644
index 0000000000..e48230a875
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-bootrom-helpers.S
@@ -0,0 +1,235 @@
+/*
+ * Raspberry Pi Pico boot ROM integer and memory helper test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define ROM_FUNC_TABLE_PTR 0x00000014
+#define ROM_TABLE_LOOKUP_PTR 0x00000018
+#define ROM_FUNC_POPCOUNT32 0x3350
+#define ROM_FUNC_CLZ32 0x334c
+#define ROM_FUNC_CTZ32 0x3354
+#define ROM_FUNC_REVERSE32 0x3352
+#define ROM_FUNC_MEMSET 0x534d
+#define ROM_FUNC_MEMCPY 0x434d
+#define ROM_FUNC_MEMSET4 0x3453
+#define ROM_FUNC_MEMCPY44 0x3443
+#define TEST_BUFFER 0x20000000
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.macro lookup code
+ ldr r0, =\code
+ bl rom_func_lookup
+ cmp r0, 0
+ bne 0f
+ b fail_lookup
+0:
+ mov r4, r0
+.endm
+
+.macro fail_if_ne reg, expected, target
+ ldr r2, =\expected
+ cmp \reg, r2
+ beq 0f
+ b \target
+0:
+.endm
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ lookup ROM_FUNC_POPCOUNT32
+ ldr r0, =0xf0f00f0f
+ blx r4
+ fail_if_ne r0, 16, fail_bits
+
+ lookup ROM_FUNC_CLZ32
+ ldr r0, =0x00f00000
+ blx r4
+ fail_if_ne r0, 8, fail_bits
+
+ lookup ROM_FUNC_CTZ32
+ ldr r0, =0x00008000
+ blx r4
+ fail_if_ne r0, 15, fail_bits
+
+ lookup ROM_FUNC_REVERSE32
+ ldr r0, =0x80000001
+ blx r4
+ fail_if_ne r0, 0x80000001, fail_bits
+
+ ldr r0, =bits_ok_message
+ bl puts
+
+ lookup ROM_FUNC_MEMSET
+ ldr r0, =TEST_BUFFER
+ movs r1, 0x5a
+ movs r2, 4
+ blx r4
+ ldr r5, =TEST_BUFFER
+ ldrb r0, [r5]
+ fail_if_ne r0, 0x5a, fail_byte_memory
+ ldrb r0, [r5, 3]
+ fail_if_ne r0, 0x5a, fail_byte_memory
+
+ ldr r5, =TEST_BUFFER + 0x20
+ ldr r0, =0x11223344
+ str r0, [r5]
+ lookup ROM_FUNC_MEMCPY
+ ldr r0, =TEST_BUFFER + 0x10
+ ldr r1, =TEST_BUFFER + 0x20
+ movs r2, 4
+ blx r4
+ ldr r5, =TEST_BUFFER + 0x10
+ ldr r0, [r5]
+ fail_if_ne r0, 0x11223344, fail_byte_memory
+
+ ldr r0, =byte_memory_ok_message
+ bl puts
+
+ lookup ROM_FUNC_MEMSET4
+ ldr r5, =TEST_BUFFER + 0x30
+ ldr r0, =0xdeadbeef
+ str r0, [r5, 8]
+ ldr r0, =TEST_BUFFER + 0x30
+ movs r1, 0x5a
+ movs r2, 8
+ blx r4
+ ldr r5, =TEST_BUFFER + 0x30
+ ldr r0, [r5]
+ fail_if_ne r0, 0x5a5a5a5a, fail_word_memory
+ ldr r0, [r5, 4]
+ fail_if_ne r0, 0x5a5a5a5a, fail_word_memory
+ ldr r0, [r5, 8]
+ fail_if_ne r0, 0xdeadbeef, fail_word_memory
+
+ ldr r5, =TEST_BUFFER + 0x40
+ ldr r0, =0x55667788
+ str r0, [r5]
+ ldr r0, =0x99aabbcc
+ str r0, [r5, 4]
+ ldr r5, =TEST_BUFFER + 0x50
+ ldr r0, =0xdeadbeef
+ str r0, [r5, 8]
+ lookup ROM_FUNC_MEMCPY44
+ ldr r0, =TEST_BUFFER + 0x50
+ ldr r1, =TEST_BUFFER + 0x40
+ movs r2, 8
+ blx r4
+ ldr r5, =TEST_BUFFER + 0x50
+ ldr r0, [r5]
+ fail_if_ne r0, 0x55667788, fail_word_memory
+ ldr r0, [r5, 4]
+ fail_if_ne r0, 0x99aabbcc, fail_word_memory
+ ldr r0, [r5, 8]
+ fail_if_ne r0, 0xdeadbeef, fail_word_memory
+
+ ldr r0, =word_memory_ok_message
+ bl puts
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_lookup:
+ ldr r0, =fail_lookup_message
+ b test_failed
+
+fail_bits:
+ ldr r0, =fail_bits_message
+ b test_failed
+
+fail_byte_memory:
+ ldr r0, =fail_byte_memory_message
+ b test_failed
+
+fail_word_memory:
+ ldr r0, =fail_word_memory_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+rom_func_lookup:
+ push {r4, lr}
+ mov r1, r0
+ ldr r0, =ROM_FUNC_TABLE_PTR
+ ldrh r0, [r0]
+ ldr r4, =ROM_TABLE_LOOKUP_PTR
+ ldrh r4, [r4]
+ blx r4
+ pop {r4, pc}
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG BOOTROM HELPERS START\n"
+bits_ok_message:
+ .asciz "PICO TCG BOOTROM HELPERS BITS OK\n"
+byte_memory_ok_message:
+ .asciz "PICO TCG BOOTROM HELPERS BYTE MEMORY OK\n"
+word_memory_ok_message:
+ .asciz "PICO TCG BOOTROM HELPERS WORD MEMORY OK\n"
+ok_message:
+ .asciz "PICO TCG BOOTROM HELPERS OK\n"
+fail_lookup_message:
+ .asciz "PICO TCG BOOTROM HELPERS FAIL: LOOKUP\n"
+fail_bits_message:
+ .asciz "PICO TCG BOOTROM HELPERS FAIL: BITS\n"
+fail_byte_memory_message:
+ .asciz "PICO TCG BOOTROM HELPERS FAIL: BYTE MEMORY\n"
+fail_word_memory_message:
+ .asciz "PICO TCG BOOTROM HELPERS FAIL: WORD MEMORY\n"
+fail_exception_message:
+ .asciz "PICO TCG BOOTROM HELPERS FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-bootrom-helpers.ref b/tests/tcg/arm/system/rp2040-bootrom-helpers.ref
new file mode 100644
index 0000000000..b1b4346bf4
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-bootrom-helpers.ref
@@ -0,0 +1,5 @@
+PICO TCG BOOTROM HELPERS START
+PICO TCG BOOTROM HELPERS BITS OK
+PICO TCG BOOTROM HELPERS BYTE MEMORY OK
+PICO TCG BOOTROM HELPERS WORD MEMORY OK
+PICO TCG BOOTROM HELPERS OK
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 28/30] hw/arm: add RP2040 synthetic boot ROM accelerated helpers
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (26 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 27/30] hw/arm: add RP2040 synthetic boot ROM service tables Gilles Grimaud
@ 2026-08-30 6:15 ` Gilles Grimaud
2026-08-30 6:16 ` [PATCH RFC v2 29/30] hw/arm: add optional Pico SDK exit handling Gilles Grimaud
` (2 subsequent siblings)
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:15 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Provide float and double boot ROM helpers through the synthetic QEMU helper aperture, and accelerate the boot ROM flash entry points by calling the existing XIP flash model directly.
These shortcuts are installed only when the built-in synthetic ROM is selected. They are not RP2040 hardware interfaces; an external mask ROM continues to exercise the modeled SSI/XIP path.
Add check-tcg coverage for floating-point tables and multicore flash lockout.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/rp2040.c | 930 +++++++++++++++++-
hw/arm/trace-events | 3 +
include/hw/arm/rp2040.h | 5 +
tests/tcg/arm/Makefile.softmmu-target | 21 +
tests/tcg/arm/system/rp2040-bootrom-float.S | 206 ++++
tests/tcg/arm/system/rp2040-bootrom-float.ref | 4 +
tests/tcg/arm/system/rp2040-flash-lockout.S | 387 ++++++++
tests/tcg/arm/system/rp2040-flash-lockout.ref | 6 +
8 files changed, 1520 insertions(+), 42 deletions(-)
create mode 100644 tests/tcg/arm/system/rp2040-bootrom-float.S
create mode 100644 tests/tcg/arm/system/rp2040-bootrom-float.ref
create mode 100644 tests/tcg/arm/system/rp2040-flash-lockout.S
create mode 100644 tests/tcg/arm/system/rp2040-flash-lockout.ref
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 6c2f62038e..6c06570586 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -16,10 +16,15 @@
#include "hw/core/irq.h"
#include "hw/misc/rp2040_nyi.h"
#include "hw/misc/unimp.h"
+#include "fpu/softfloat.h"
#include "qemu/datadir.h"
+#include "qemu/log.h"
#include "system/address-spaces.h"
#include "target/arm/cpu.h"
#include "target/arm/cpu-qom.h"
+#include "trace.h"
+
+#include <math.h>
#define RP2040_UART0_BASE 0x40034000
#define RP2040_UART0_IRQ 20
@@ -64,6 +69,70 @@
#define RP2040_BOOTROM_ROM_VERSION_OFFSET 0x13
#define RP2040_BOOTROM_SYNTHETIC_ROM_VERSION 2
#define RP2040_BOOTROM_FLOAT_TABLE_WORDS 32
+#define RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFSET 8
+#define RP2040_BOOTROM_HELPER_ARGS4_CODE_LITERAL_OFFSET 24
+#define RP2040_BOOTROM_HELPER_ARGS3_CODE_LITERAL_OFFSET 20
+#define RP2040_BOOTROM_FP_STUB_CODE_LITERAL_OFFSET 32
+#define RP2040_BOOTROM_FLOAT_STUBS_OFFSET 0x0600
+#define RP2040_BOOTROM_DOUBLE_STUBS_OFFSET 0x0b00
+#define RP2040_BOOTROM_FP_STUB_SIZE 36
+
+#define RP2040_SYNTHETIC_ROM_DBG_CMD 0x00
+#define RP2040_SYNTHETIC_ROM_DBG_ARG0 0x04
+#define RP2040_SYNTHETIC_ROM_DBG_ARG1 0x08
+#define RP2040_SYNTHETIC_ROM_DBG_ARG2 0x0c
+#define RP2040_SYNTHETIC_ROM_DBG_ARG3 0x10
+#define RP2040_SYNTHETIC_ROM_DBG_RESULT0 0x14
+#define RP2040_SYNTHETIC_ROM_DBG_RESULT1 0x18
+#define RP2040_SYNTHETIC_ROM_DBG_RESULT2 0x1c
+#define RP2040_SYNTHETIC_ROM_DBG_RESULT3 0x20
+#define RP2040_SYNTHETIC_ROM_DBG_FLASH_COUNT0 0x40
+
+#define RP2040_SYNTHETIC_FP_CMD_MASK 0xffffff00
+#define RP2040_SYNTHETIC_FP_CMD_FLOAT 0x80000000
+#define RP2040_SYNTHETIC_FP_CMD_DOUBLE 0x80000100
+
+enum RP2040SyntheticFlashHelper {
+ RP2040_SYNTHETIC_FLASH_CONNECT_INTERNAL_FLASH,
+ RP2040_SYNTHETIC_FLASH_EXIT_XIP,
+ RP2040_SYNTHETIC_FLASH_FLUSH_CACHE,
+ RP2040_SYNTHETIC_FLASH_ENTER_CMD_XIP,
+ RP2040_SYNTHETIC_FLASH_RANGE_ERASE,
+ RP2040_SYNTHETIC_FLASH_RANGE_PROGRAM,
+};
+
+#define RP2040_SF_TABLE_FADD 0x00
+#define RP2040_SF_TABLE_FSUB 0x04
+#define RP2040_SF_TABLE_FMUL 0x08
+#define RP2040_SF_TABLE_FDIV 0x0c
+#define RP2040_SF_TABLE_FCMP_FAST 0x10
+#define RP2040_SF_TABLE_FCMP_FAST_FLAGS 0x14
+#define RP2040_SF_TABLE_FSQRT 0x18
+#define RP2040_SF_TABLE_FLOAT2INT 0x1c
+#define RP2040_SF_TABLE_FLOAT2FIX 0x20
+#define RP2040_SF_TABLE_FLOAT2UINT 0x24
+#define RP2040_SF_TABLE_FLOAT2UFIX 0x28
+#define RP2040_SF_TABLE_INT2FLOAT 0x2c
+#define RP2040_SF_TABLE_FIX2FLOAT 0x30
+#define RP2040_SF_TABLE_UINT2FLOAT 0x34
+#define RP2040_SF_TABLE_UFIX2FLOAT 0x38
+#define RP2040_SF_TABLE_FCOS 0x3c
+#define RP2040_SF_TABLE_FSIN 0x40
+#define RP2040_SF_TABLE_FTAN 0x44
+#define RP2040_SF_TABLE_V3_FSINCOS 0x48
+#define RP2040_SF_TABLE_FEXP 0x4c
+#define RP2040_SF_TABLE_FLN 0x50
+#define RP2040_SF_TABLE_FCMP_BASIC 0x54
+#define RP2040_SF_TABLE_FATAN2 0x58
+#define RP2040_SF_TABLE_INT642FLOAT 0x5c
+#define RP2040_SF_TABLE_FIX642FLOAT 0x60
+#define RP2040_SF_TABLE_UINT642FLOAT 0x64
+#define RP2040_SF_TABLE_UFIX642FLOAT 0x68
+#define RP2040_SF_TABLE_FLOAT2INT64 0x6c
+#define RP2040_SF_TABLE_FLOAT2FIX64 0x70
+#define RP2040_SF_TABLE_FLOAT2UINT64 0x74
+#define RP2040_SF_TABLE_FLOAT2UFIX64 0x78
+#define RP2040_SF_TABLE_FLOAT2DOUBLE 0x7c
/*
* Temporary boot ROM used until a faithful RP2040 boot ROM is requested. It
@@ -192,6 +261,7 @@ typedef struct RP2040BootromFunction {
const char *name;
const uint8_t *impl;
size_t impl_size;
+ uint32_t code_literal_offset;
} RP2040BootromFunction;
typedef struct RP2040BootromData {
@@ -254,23 +324,98 @@ static const uint8_t rp2040_bootrom_memset4[] = {
0x10, 0xbc, 0x70, 0x47,
};
+static const uint8_t rp2040_bootrom_flash_noarg[] = {
+ /*
+ * Synthetic-ROM-only acceleration. These stubs call the QEMU helper
+ * aperture directly; they do not model RP2040 SSI transactions.
+ */
+ 0x01, 0x49, /* ldr r1, [pc, #4] ; function code */
+ 0x02, 0x48, /* ldr r0, [pc, #8] ; debug base */
+ 0x01, 0x60, /* str r1, [r0] */
+ 0x70, 0x47, /* bx lr */
+ 0x00, 0x00, 0x00, 0x00, /* function code literal */
+ 0x00, 0x00, 0xff, 0x5f, /* 0x5fff0000 */
+};
+
+static const uint8_t rp2040_bootrom_flash_args4[] = {
+ 0x10, 0xb5, /* push {r4, lr} */
+ 0x04, 0x4c, /* ldr r4, [pc, #16] ; debug base */
+ 0x60, 0x60, /* str r0, [r4, #4] */
+ 0xa1, 0x60, /* str r1, [r4, #8] */
+ 0xe2, 0x60, /* str r2, [r4, #12] */
+ 0x23, 0x61, /* str r3, [r4, #16] */
+ 0x02, 0x48, /* ldr r0, [pc, #8] ; function code */
+ 0x20, 0x60, /* str r0, [r4] */
+ 0x10, 0xbd, /* pop {r4, pc} */
+ 0xc0, 0x46, /* nop; align literal */
+ 0x00, 0x00, 0xff, 0x5f, /* 0x5fff0000 */
+ 0x00, 0x00, 0x00, 0x00, /* function code literal */
+};
+
+static const uint8_t rp2040_bootrom_flash_args3[] = {
+ 0x10, 0xb5, /* push {r4, lr} */
+ 0x03, 0x4c, /* ldr r4, [pc, #12] ; debug base */
+ 0x60, 0x60, /* str r0, [r4, #4] */
+ 0xa1, 0x60, /* str r1, [r4, #8] */
+ 0xe2, 0x60, /* str r2, [r4, #12] */
+ 0x02, 0x48, /* ldr r0, [pc, #8] ; function code */
+ 0x20, 0x60, /* str r0, [r4] */
+ 0x10, 0xbd, /* pop {r4, pc} */
+ 0x00, 0x00, 0xff, 0x5f, /* 0x5fff0000 */
+ 0x00, 0x00, 0x00, 0x00, /* function code literal */
+};
+
+static const uint8_t rp2040_bootrom_fp_stub[] = {
+ 0x10, 0xb5, /* push {r4, lr} */
+ 0x06, 0x4c, /* ldr r4, [pc, #24] ; debug base */
+ 0x60, 0x60, /* str r0, [r4, #4] */
+ 0xa1, 0x60, /* str r1, [r4, #8] */
+ 0xe2, 0x60, /* str r2, [r4, #12] */
+ 0x23, 0x61, /* str r3, [r4, #16] */
+ 0x04, 0x48, /* ldr r0, [pc, #16] ; command */
+ 0x20, 0x60, /* str r0, [r4] */
+ 0x60, 0x69, /* ldr r0, [r4, #20] */
+ 0xa1, 0x69, /* ldr r1, [r4, #24] */
+ 0xe2, 0x69, /* ldr r2, [r4, #28] */
+ 0x23, 0x6a, /* ldr r3, [r4, #32] */
+ 0x10, 0xbd, /* pop {r4, pc} */
+ 0xc0, 0x46, /* nop; align literal */
+ 0x00, 0x00, 0xff, 0x5f, /* 0x5fff0000 */
+ 0x00, 0x00, 0x00, 0x00, /* command literal */
+};
+
#define RP2040_BOOTROM_IMPL(_code, _name, _impl) \
- { _code, _name, _impl, sizeof(_impl) }
+ { _code, _name, _impl, sizeof(_impl), UINT32_MAX }
+#define RP2040_BOOTROM_IMPL_CODE(_code, _name, _impl, _offset) \
+ { _code, _name, _impl, sizeof(_impl), _offset }
#define RP2040_BOOTROM_NYI(_code, _name) \
- { _code, _name, NULL, 0 }
+ { _code, _name, NULL, 0, UINT32_MAX }
static const RP2040BootromFunction rp2040_bootrom_functions[] = {
- RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('C', 'X'),
- "flash_enter_cmd_xip"),
- RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('E', 'X'), "flash_exit_xip"),
- RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('F', 'C'),
- "flash_flush_cache"),
- RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('I', 'F'),
- "connect_internal_flash"),
- RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('R', 'E'),
- "flash_range_erase"),
- RP2040_BOOTROM_NYI(RP2040_ROM_TABLE_CODE('R', 'P'),
- "flash_range_program"),
+ RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('C', 'X'),
+ "flash_enter_cmd_xip",
+ rp2040_bootrom_flash_noarg,
+ RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFSET),
+ RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('E', 'X'),
+ "flash_exit_xip",
+ rp2040_bootrom_flash_noarg,
+ RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFSET),
+ RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('F', 'C'),
+ "flash_flush_cache",
+ rp2040_bootrom_flash_noarg,
+ RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFSET),
+ RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('I', 'F'),
+ "connect_internal_flash",
+ rp2040_bootrom_flash_noarg,
+ RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFSET),
+ RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('R', 'E'),
+ "flash_range_erase",
+ rp2040_bootrom_flash_args4,
+ RP2040_BOOTROM_HELPER_ARGS4_CODE_LITERAL_OFFSET),
+ RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('R', 'P'),
+ "flash_range_program",
+ rp2040_bootrom_flash_args3,
+ RP2040_BOOTROM_HELPER_ARGS3_CODE_LITERAL_OFFSET),
RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('C', '4'), "memcpy44",
rp2040_bootrom_memcpy44),
RP2040_BOOTROM_IMPL(RP2040_ROM_TABLE_CODE('L', '3'), "clz32",
@@ -296,17 +441,38 @@ static const RP2040BootromData rp2040_bootrom_data[] = {
};
static const uint8_t rp2040_bootrom_lookup[] = {
- 0x02, 0x88, 0x00, 0x2a, 0x05, 0xd0, 0x91, 0x42,
- 0x01, 0xd0, 0x04, 0x30, 0xf8, 0xe7, 0x40, 0x88,
- 0x70, 0x47, 0x5f, 0x22, 0x12, 0x06, 0xff, 0x23,
- 0x1b, 0x04, 0x1a, 0x43, 0x11, 0x60, 0x00, 0xbe,
- 0x00, 0x20, 0x70, 0x47,
+ 0x02, 0x88, /* ldrh r2, [r0] */
+ 0x00, 0x2a, /* cmp r2, #0 */
+ 0x05, 0xd0, /* beq not_found */
+ 0x91, 0x42, /* cmp r1, r2 */
+ 0x01, 0xd0, /* beq found */
+ 0x04, 0x30, /* adds r0, #4 */
+ 0xf8, 0xe7, /* b loop */
+ 0x40, 0x88, /* found: ldrh r0, [r0, #2] */
+ 0x70, 0x47, /* bx lr */
+ 0x5f, 0x22, /* not_found: movs r2, #0x5f */
+ 0x12, 0x06, /* lsls r2, r2, #24 */
+ 0xff, 0x23, /* movs r3, #0xff */
+ 0x1b, 0x04, /* lsls r3, r3, #16 */
+ 0x1a, 0x43, /* orrs r2, r3 ; 0x5fff0000 */
+ 0x11, 0x60, /* str r1, [r2] */
+ 0x00, 0xbe, /* bkpt #0 */
+ 0x00, 0x20, /* movs r0, #0 */
+ 0x70, 0x47, /* bx lr */
};
static const uint8_t rp2040_bootrom_nyi_stub[] = {
- 0x04, 0x49, 0x5f, 0x20, 0x00, 0x06, 0xff, 0x22,
- 0x12, 0x04, 0x10, 0x43, 0x01, 0x60, 0x00, 0xbe,
- 0xfe, 0xe7, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00,
+ 0x04, 0x49, /* ldr r1, [pc, #16] ; function code */
+ 0x5f, 0x20, /* movs r0, #0x5f */
+ 0x00, 0x06, /* lsls r0, r0, #24 */
+ 0xff, 0x22, /* movs r2, #0xff */
+ 0x12, 0x04, /* lsls r2, r2, #16 */
+ 0x10, 0x43, /* orrs r0, r2 ; 0x5fff0000 */
+ 0x01, 0x60, /* str r1, [r0] */
+ 0x00, 0xbe, /* bkpt #0 */
+ 0xfe, 0xe7, /* b . */
+ 0xc0, 0x46, /* nop; align literal */
+ 0x00, 0x00, 0x00, 0x00, /* function code literal */
};
static void rp2040_store_hword(uint8_t *rom, uint32_t offset, uint16_t value)
@@ -323,6 +489,56 @@ static void rp2040_store_word(uint8_t *rom, uint32_t offset, uint32_t value)
rom[offset + 3] = value >> 24;
}
+static bool rp2040_bootrom_fp_supported(uint32_t offset)
+{
+ switch (offset) {
+ case RP2040_SF_TABLE_FADD:
+ case RP2040_SF_TABLE_FSUB:
+ case RP2040_SF_TABLE_FMUL:
+ case RP2040_SF_TABLE_FDIV:
+ case RP2040_SF_TABLE_FCMP_FAST:
+ case RP2040_SF_TABLE_FCMP_FAST_FLAGS:
+ case RP2040_SF_TABLE_FSQRT:
+ case RP2040_SF_TABLE_FLOAT2INT:
+ case RP2040_SF_TABLE_FLOAT2FIX:
+ case RP2040_SF_TABLE_FLOAT2UINT:
+ case RP2040_SF_TABLE_FLOAT2UFIX:
+ case RP2040_SF_TABLE_INT2FLOAT:
+ case RP2040_SF_TABLE_FIX2FLOAT:
+ case RP2040_SF_TABLE_UINT2FLOAT:
+ case RP2040_SF_TABLE_UFIX2FLOAT:
+ case RP2040_SF_TABLE_FCOS:
+ case RP2040_SF_TABLE_FSIN:
+ case RP2040_SF_TABLE_FTAN:
+ case RP2040_SF_TABLE_V3_FSINCOS:
+ case RP2040_SF_TABLE_FEXP:
+ case RP2040_SF_TABLE_FLN:
+ case RP2040_SF_TABLE_FCMP_BASIC:
+ case RP2040_SF_TABLE_FATAN2:
+ case RP2040_SF_TABLE_INT642FLOAT:
+ case RP2040_SF_TABLE_FIX642FLOAT:
+ case RP2040_SF_TABLE_UINT642FLOAT:
+ case RP2040_SF_TABLE_UFIX642FLOAT:
+ case RP2040_SF_TABLE_FLOAT2INT64:
+ case RP2040_SF_TABLE_FLOAT2FIX64:
+ case RP2040_SF_TABLE_FLOAT2UINT64:
+ case RP2040_SF_TABLE_FLOAT2UFIX64:
+ case RP2040_SF_TABLE_FLOAT2DOUBLE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void rp2040_install_bootrom_fp_stub(uint8_t *rom, uint32_t offset,
+ uint32_t command)
+{
+ memcpy(rom + offset, rp2040_bootrom_fp_stub,
+ sizeof(rp2040_bootrom_fp_stub));
+ rp2040_store_word(rom, offset + RP2040_BOOTROM_FP_STUB_CODE_LITERAL_OFFSET,
+ command);
+}
+
static void rp2040_install_synthetic_bootrom(void)
{
g_autofree uint8_t *rom = g_malloc0(RP2040_ROM_SIZE);
@@ -338,6 +554,7 @@ static void rp2040_install_synthetic_bootrom(void)
RP2040_BOOTROM_SYNTHETIC_ROM_VERSION;
memcpy(rom + RP2040_BOOTROM_LOOKUP_OFFSET, rp2040_bootrom_lookup,
sizeof(rp2040_bootrom_lookup));
+
rp2040_store_hword(rom, RP2040_BOOTROM_FUNC_TABLE_PTR_OFFSET,
RP2040_BOOTROM_FUNC_TABLE_OFFSET);
rp2040_store_hword(rom, RP2040_BOOTROM_DATA_TABLE_PTR_OFFSET,
@@ -351,6 +568,10 @@ static void rp2040_install_synthetic_bootrom(void)
if (func->impl) {
memcpy(rom + func_base, func->impl, func->impl_size);
+ if (func->code_literal_offset != UINT32_MAX) {
+ rp2040_store_word(rom, func_base + func->code_literal_offset,
+ func->code);
+ }
func_base += ROUND_UP(func->impl_size, 4);
} else {
memcpy(rom + func_base, rp2040_bootrom_nyi_stub,
@@ -361,6 +582,7 @@ static void rp2040_install_synthetic_bootrom(void)
func->code);
func_base += ROUND_UP(sizeof(rp2040_bootrom_nyi_stub), 4);
}
+
rp2040_store_hword(rom, func_table +
i * RP2040_BOOTROM_FUNC_TABLE_ENTRY_SIZE,
func->code);
@@ -379,13 +601,34 @@ static void rp2040_install_synthetic_bootrom(void)
rp2040_store_word(rom, RP2040_BOOTROM_DOUBLE_NYI_STUB_OFFSET +
RP2040_BOOTROM_NYI_CODE_LITERAL_OFFSET,
RP2040_ROM_TABLE_CODE('S', 'D'));
+
rom[RP2040_BOOTROM_FLOAT_TABLE_OFFSET] = RP2040_BOOTROM_FLOAT_TABLE_WORDS;
rom[RP2040_BOOTROM_DOUBLE_TABLE_OFFSET] = RP2040_BOOTROM_FLOAT_TABLE_WORDS;
for (i = 0; i < RP2040_BOOTROM_FLOAT_TABLE_WORDS; i++) {
- rp2040_store_word(rom, RP2040_BOOTROM_FLOAT_TABLE_OFFSET + 2 +
- i * sizeof(uint32_t), float_nyi);
- rp2040_store_word(rom, RP2040_BOOTROM_DOUBLE_TABLE_OFFSET + 2 +
- i * sizeof(uint32_t), double_nyi);
+ uint32_t table_offset = i * sizeof(uint32_t);
+
+ if (rp2040_bootrom_fp_supported(table_offset)) {
+ uint32_t float_stub = RP2040_BOOTROM_FLOAT_STUBS_OFFSET +
+ i * RP2040_BOOTROM_FP_STUB_SIZE;
+ uint32_t double_stub = RP2040_BOOTROM_DOUBLE_STUBS_OFFSET +
+ i * RP2040_BOOTROM_FP_STUB_SIZE;
+
+ rp2040_install_bootrom_fp_stub(rom, float_stub,
+ RP2040_SYNTHETIC_FP_CMD_FLOAT |
+ table_offset);
+ rp2040_install_bootrom_fp_stub(rom, double_stub,
+ RP2040_SYNTHETIC_FP_CMD_DOUBLE |
+ table_offset);
+ rp2040_store_word(rom, RP2040_BOOTROM_FLOAT_TABLE_OFFSET + 2 +
+ table_offset, float_stub | 1);
+ rp2040_store_word(rom, RP2040_BOOTROM_DOUBLE_TABLE_OFFSET + 2 +
+ table_offset, double_stub | 1);
+ } else {
+ rp2040_store_word(rom, RP2040_BOOTROM_FLOAT_TABLE_OFFSET + 2 +
+ table_offset, float_nyi);
+ rp2040_store_word(rom, RP2040_BOOTROM_DOUBLE_TABLE_OFFSET + 2 +
+ table_offset, double_nyi);
+ }
}
for (i = 0; i < ARRAY_SIZE(rp2040_bootrom_data); i++) {
@@ -403,6 +646,21 @@ static void rp2040_install_synthetic_bootrom(void)
RP2040_ROM_BASE);
}
+static uint32_t rp2040_apply_atomic_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 MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr,
uint64_t *data, unsigned size,
MemTxAttrs attrs)
@@ -428,20 +686,6 @@ static const MemoryRegionOps rp2040_powered_off_ops = {
},
};
-static uint32_t rp2040_apply_atomic_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_update_mempowerdown(RP2040State *s)
{
@@ -538,30 +782,632 @@ static const char *rp2040_bootrom_function_name(uint16_t code)
return rp2040_bootrom_functions[i].name;
}
}
+
return NULL;
}
+static void rp2040_init_float_status(float_status *status)
+{
+ *status = (float_status) { 0 };
+ set_float_rounding_mode(float_round_nearest_even, status);
+}
+
+static void rp2040_synthetic_fp_nyi(bool is_double, uint32_t offset)
+{
+ g_autofree char *detail = g_strdup_printf("%s table offset 0x%02" PRIx32,
+ is_double ? "double" : "float",
+ offset);
+
+ rp2040_log_nyi("bootrom", "floating-point helper", detail);
+}
+
+static float rp2040_u32_to_host_float(uint32_t value)
+{
+ union {
+ uint32_t u;
+ float f;
+ } v = { .u = value };
+
+ return v.f;
+}
+
+static uint32_t rp2040_host_float_to_u32(float value)
+{
+ union {
+ float f;
+ uint32_t u;
+ } v = { .f = value };
+
+ return v.u;
+}
+
+static double rp2040_u64_to_host_double(uint64_t value)
+{
+ union {
+ uint64_t u;
+ double d;
+ } v = { .u = value };
+
+ return v.d;
+}
+
+static uint64_t rp2040_host_double_to_u64(double value)
+{
+ union {
+ double d;
+ uint64_t u;
+ } v = { .d = value };
+
+ return v.u;
+}
+
+static uint32_t rp2040_synthetic_fp_compare(double a, double b)
+{
+ if (isnan(a) || isnan(b)) {
+ return 1;
+ }
+ if (a < b) {
+ return -1;
+ }
+ if (a > b) {
+ return 1;
+ }
+ return 0;
+}
+
+static int32_t rp2040_synthetic_double_to_i32(double value)
+{
+ if (isnan(value)) {
+ return 0;
+ }
+ if (value >= INT32_MAX) {
+ return INT32_MAX;
+ }
+ if (value <= INT32_MIN) {
+ return INT32_MIN;
+ }
+ return value;
+}
+
+static uint32_t rp2040_synthetic_double_to_u32(double value)
+{
+ if (isnan(value) || value <= 0) {
+ return 0;
+ }
+ if (value >= UINT32_MAX) {
+ return UINT32_MAX;
+ }
+ return value;
+}
+
+static int64_t rp2040_synthetic_double_to_i64(double value)
+{
+ if (isnan(value)) {
+ return 0;
+ }
+ if (value >= 0x1p63) {
+ return INT64_MAX;
+ }
+ if (value <= -0x1p63) {
+ return INT64_MIN;
+ }
+ return value;
+}
+
+static uint64_t rp2040_synthetic_double_to_u64(double value)
+{
+ if (isnan(value) || value <= 0) {
+ return 0;
+ }
+ if (value >= 0x1p64) {
+ return UINT64_MAX;
+ }
+ return value;
+}
+
+static void rp2040_synthetic_float_op(RP2040State *s, uint32_t offset)
+{
+ float_status status;
+ float32 a = make_float32(s->synthetic_rom_dbg_arg[0]);
+ float32 b = make_float32(s->synthetic_rom_dbg_arg[1]);
+ float host_a = rp2040_u32_to_host_float(s->synthetic_rom_dbg_arg[0]);
+ float host_b = rp2040_u32_to_host_float(s->synthetic_rom_dbg_arg[1]);
+ float32 r32;
+ float64 r64;
+ uint64_t r;
+ double scaled;
+
+ rp2040_init_float_status(&status);
+ memset(s->synthetic_rom_dbg_result, 0, sizeof(s->synthetic_rom_dbg_result));
+ s->synthetic_rom_dbg_result[1] = 0;
+
+ switch (offset) {
+ case RP2040_SF_TABLE_FADD:
+ s->synthetic_rom_dbg_result[0] =
+ float32_val(float32_add(a, b, &status));
+ break;
+ case RP2040_SF_TABLE_FSUB:
+ s->synthetic_rom_dbg_result[0] =
+ float32_val(float32_sub(a, b, &status));
+ break;
+ case RP2040_SF_TABLE_FMUL:
+ s->synthetic_rom_dbg_result[0] =
+ float32_val(float32_mul(a, b, &status));
+ break;
+ case RP2040_SF_TABLE_FDIV:
+ s->synthetic_rom_dbg_result[0] =
+ float32_val(float32_div(a, b, &status));
+ break;
+ case RP2040_SF_TABLE_FCMP_FAST:
+ case RP2040_SF_TABLE_FCMP_FAST_FLAGS:
+ case RP2040_SF_TABLE_FCMP_BASIC:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_synthetic_fp_compare(host_a, host_b);
+ break;
+ case RP2040_SF_TABLE_FSQRT:
+ s->synthetic_rom_dbg_result[0] =
+ float32_val(float32_sqrt(a, &status));
+ break;
+ case RP2040_SF_TABLE_FLOAT2INT:
+ s->synthetic_rom_dbg_result[0] =
+ float32_to_int32_round_to_zero(a, &status);
+ break;
+ case RP2040_SF_TABLE_FLOAT2FIX:
+ scaled = ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xff);
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_synthetic_double_to_i32(scaled);
+ break;
+ case RP2040_SF_TABLE_FLOAT2UINT:
+ s->synthetic_rom_dbg_result[0] =
+ float32_to_uint32_round_to_zero(a, &status);
+ break;
+ case RP2040_SF_TABLE_FLOAT2UFIX:
+ scaled = ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xff);
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_synthetic_double_to_u32(scaled);
+ break;
+ case RP2040_SF_TABLE_INT2FLOAT:
+ s->synthetic_rom_dbg_result[0] =
+ float32_val(int32_to_float32(s->synthetic_rom_dbg_arg[0],
+ &status));
+ break;
+ case RP2040_SF_TABLE_FIX2FLOAT:
+ r32 = float64_to_float32(make_float64(rp2040_host_double_to_u64(
+ ldexp((double)(int32_t)
+ s->synthetic_rom_dbg_arg[0],
+ -(int)(s->synthetic_rom_dbg_arg[1] &
+ 0xff)))),
+ &status);
+ s->synthetic_rom_dbg_result[0] = float32_val(r32);
+ break;
+ case RP2040_SF_TABLE_UINT2FLOAT:
+ s->synthetic_rom_dbg_result[0] =
+ float32_val(uint32_to_float32(s->synthetic_rom_dbg_arg[0],
+ &status));
+ break;
+ case RP2040_SF_TABLE_UFIX2FLOAT:
+ r32 = float64_to_float32(make_float64(rp2040_host_double_to_u64(
+ ldexp((double)
+ s->synthetic_rom_dbg_arg[0],
+ -(int)(s->synthetic_rom_dbg_arg[1] &
+ 0xff)))),
+ &status);
+ s->synthetic_rom_dbg_result[0] = float32_val(r32);
+ break;
+ case RP2040_SF_TABLE_FCOS:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_host_float_to_u32(cosf(host_a));
+ break;
+ case RP2040_SF_TABLE_FSIN:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_host_float_to_u32(sinf(host_a));
+ s->synthetic_rom_dbg_result[1] =
+ rp2040_host_float_to_u32(cosf(host_a));
+ break;
+ case RP2040_SF_TABLE_FTAN:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_host_float_to_u32(tanf(host_a));
+ break;
+ case RP2040_SF_TABLE_V3_FSINCOS:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_host_float_to_u32(sinf(host_a));
+ s->synthetic_rom_dbg_result[1] =
+ rp2040_host_float_to_u32(cosf(host_a));
+ break;
+ case RP2040_SF_TABLE_FEXP:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_host_float_to_u32(expf(host_a));
+ break;
+ case RP2040_SF_TABLE_FLN:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_host_float_to_u32(logf(host_a));
+ break;
+ case RP2040_SF_TABLE_FATAN2:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_host_float_to_u32(atan2f(host_a, host_b));
+ break;
+ case RP2040_SF_TABLE_INT642FLOAT:
+ r = deposit64(s->synthetic_rom_dbg_arg[0], 32, 32,
+ s->synthetic_rom_dbg_arg[1]);
+ r32 = int64_to_float32((int64_t)r, &status);
+ s->synthetic_rom_dbg_result[0] = float32_val(r32);
+ break;
+ case RP2040_SF_TABLE_FIX642FLOAT:
+ r = deposit64(s->synthetic_rom_dbg_arg[0], 32, 32,
+ s->synthetic_rom_dbg_arg[1]);
+ r32 = float64_to_float32(make_float64(rp2040_host_double_to_u64(
+ ldexp((double)(int64_t)r,
+ -(int)(s->synthetic_rom_dbg_arg[2] &
+ 0xff)))),
+ &status);
+ s->synthetic_rom_dbg_result[0] = float32_val(r32);
+ break;
+ case RP2040_SF_TABLE_UINT642FLOAT:
+ r = deposit64(s->synthetic_rom_dbg_arg[0], 32, 32,
+ s->synthetic_rom_dbg_arg[1]);
+ r32 = uint64_to_float32(r, &status);
+ s->synthetic_rom_dbg_result[0] = float32_val(r32);
+ break;
+ case RP2040_SF_TABLE_UFIX642FLOAT:
+ r = deposit64(s->synthetic_rom_dbg_arg[0], 32, 32,
+ s->synthetic_rom_dbg_arg[1]);
+ r32 = float64_to_float32(make_float64(rp2040_host_double_to_u64(
+ ldexp((double)r,
+ -(int)(s->synthetic_rom_dbg_arg[2] &
+ 0xff)))),
+ &status);
+ s->synthetic_rom_dbg_result[0] = float32_val(r32);
+ break;
+ case RP2040_SF_TABLE_FLOAT2INT64:
+ r = float32_to_int64_round_to_zero(a, &status);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2FIX64:
+ scaled = ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xff);
+ r = rp2040_synthetic_double_to_i64(scaled);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2UINT64:
+ r = float32_to_uint64_round_to_zero(a, &status);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2UFIX64:
+ scaled = ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xff);
+ r = rp2040_synthetic_double_to_u64(scaled);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2DOUBLE:
+ r64 = float32_to_float64(a, &status);
+ r = float64_val(r64);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ default:
+ s->synthetic_rom_dbg_result[0] = 0;
+ rp2040_synthetic_fp_nyi(false, offset);
+ break;
+ }
+}
+
+static void rp2040_synthetic_double_op(RP2040State *s, uint32_t offset)
+{
+ float_status status;
+ uint64_t av = deposit64(s->synthetic_rom_dbg_arg[0], 32, 32,
+ s->synthetic_rom_dbg_arg[1]);
+ uint64_t bv = deposit64(s->synthetic_rom_dbg_arg[2], 32, 32,
+ s->synthetic_rom_dbg_arg[3]);
+ float64 a = make_float64(av);
+ float64 b = make_float64(bv);
+ double host_a = rp2040_u64_to_host_double(av);
+ double host_b = rp2040_u64_to_host_double(bv);
+ float64 r64;
+ float32 r32;
+ uint64_t r;
+ double scaled;
+
+ rp2040_init_float_status(&status);
+ memset(s->synthetic_rom_dbg_result, 0, sizeof(s->synthetic_rom_dbg_result));
+
+ switch (offset) {
+ case RP2040_SF_TABLE_FADD:
+ r64 = float64_add(a, b, &status);
+ goto return_double;
+ case RP2040_SF_TABLE_FSUB:
+ r64 = float64_sub(a, b, &status);
+ goto return_double;
+ case RP2040_SF_TABLE_FMUL:
+ r64 = float64_mul(a, b, &status);
+ goto return_double;
+ case RP2040_SF_TABLE_FDIV:
+ r64 = float64_div(a, b, &status);
+ goto return_double;
+ case RP2040_SF_TABLE_FCMP_FAST:
+ case RP2040_SF_TABLE_FCMP_FAST_FLAGS:
+ case RP2040_SF_TABLE_FCMP_BASIC:
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_synthetic_fp_compare(host_a, host_b);
+ s->synthetic_rom_dbg_result[1] = 0;
+ break;
+ case RP2040_SF_TABLE_FSQRT:
+ r64 = float64_sqrt(a, &status);
+ goto return_double;
+ case RP2040_SF_TABLE_FLOAT2INT:
+ s->synthetic_rom_dbg_result[0] =
+ float64_to_int32_round_to_zero(a, &status);
+ s->synthetic_rom_dbg_result[1] = 0;
+ break;
+ case RP2040_SF_TABLE_FLOAT2FIX:
+ scaled = ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff);
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_synthetic_double_to_i32(scaled);
+ s->synthetic_rom_dbg_result[1] = 0;
+ break;
+ case RP2040_SF_TABLE_FLOAT2UINT:
+ s->synthetic_rom_dbg_result[0] =
+ float64_to_uint32_round_to_zero(a, &status);
+ s->synthetic_rom_dbg_result[1] = 0;
+ break;
+ case RP2040_SF_TABLE_FLOAT2UFIX:
+ scaled = ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff);
+ s->synthetic_rom_dbg_result[0] =
+ rp2040_synthetic_double_to_u32(scaled);
+ s->synthetic_rom_dbg_result[1] = 0;
+ break;
+ case RP2040_SF_TABLE_INT2FLOAT:
+ r64 = int32_to_float64(s->synthetic_rom_dbg_arg[0], &status);
+ goto return_double;
+ case RP2040_SF_TABLE_FIX2FLOAT:
+ r64 = make_float64(rp2040_host_double_to_u64(
+ ldexp((double)(int32_t)s->synthetic_rom_dbg_arg[0],
+ -(int)(s->synthetic_rom_dbg_arg[1] & 0xff))));
+ goto return_double;
+ case RP2040_SF_TABLE_UINT2FLOAT:
+ r64 = uint32_to_float64(s->synthetic_rom_dbg_arg[0], &status);
+ goto return_double;
+ case RP2040_SF_TABLE_UFIX2FLOAT:
+ r64 = make_float64(rp2040_host_double_to_u64(
+ ldexp((double)s->synthetic_rom_dbg_arg[0],
+ -(int)(s->synthetic_rom_dbg_arg[1] & 0xff))));
+ goto return_double;
+ case RP2040_SF_TABLE_FCOS:
+ r64 = make_float64(rp2040_host_double_to_u64(cos(host_a)));
+ goto return_double;
+ case RP2040_SF_TABLE_FSIN:
+ r64 = make_float64(rp2040_host_double_to_u64(sin(host_a)));
+ goto return_double;
+ case RP2040_SF_TABLE_FTAN:
+ r64 = make_float64(rp2040_host_double_to_u64(tan(host_a)));
+ goto return_double;
+ case RP2040_SF_TABLE_V3_FSINCOS:
+ r = rp2040_host_double_to_u64(sin(host_a));
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ r = rp2040_host_double_to_u64(cos(host_a));
+ s->synthetic_rom_dbg_result[2] = r;
+ s->synthetic_rom_dbg_result[3] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FEXP:
+ r64 = make_float64(rp2040_host_double_to_u64(exp(host_a)));
+ goto return_double;
+ case RP2040_SF_TABLE_FLN:
+ r64 = make_float64(rp2040_host_double_to_u64(log(host_a)));
+ goto return_double;
+ case RP2040_SF_TABLE_FATAN2:
+ r64 = make_float64(rp2040_host_double_to_u64(atan2(host_a, host_b)));
+ goto return_double;
+ case RP2040_SF_TABLE_INT642FLOAT:
+ r64 = int64_to_float64((int64_t)av, &status);
+ goto return_double;
+ case RP2040_SF_TABLE_FIX642FLOAT:
+ r64 = make_float64(rp2040_host_double_to_u64(
+ ldexp((double)(int64_t)av,
+ -(int)(s->synthetic_rom_dbg_arg[2] & 0xff))));
+ goto return_double;
+ case RP2040_SF_TABLE_UINT642FLOAT:
+ r64 = uint64_to_float64(av, &status);
+ goto return_double;
+ case RP2040_SF_TABLE_UFIX642FLOAT:
+ r64 = make_float64(rp2040_host_double_to_u64(
+ ldexp((double)av,
+ -(int)(s->synthetic_rom_dbg_arg[2] & 0xff))));
+ goto return_double;
+ case RP2040_SF_TABLE_FLOAT2INT64:
+ r = float64_to_int64_round_to_zero(a, &status);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2FIX64:
+ scaled = ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff);
+ r = rp2040_synthetic_double_to_i64(scaled);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2UINT64:
+ r = float64_to_uint64_round_to_zero(a, &status);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2UFIX64:
+ scaled = ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff);
+ r = rp2040_synthetic_double_to_u64(scaled);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+ break;
+ case RP2040_SF_TABLE_FLOAT2DOUBLE:
+ r32 = float64_to_float32(a, &status);
+ s->synthetic_rom_dbg_result[0] = float32_val(r32);
+ s->synthetic_rom_dbg_result[1] = 0;
+ break;
+ default:
+ s->synthetic_rom_dbg_result[0] = 0;
+ s->synthetic_rom_dbg_result[1] = 0;
+ rp2040_synthetic_fp_nyi(true, offset);
+ break;
+ }
+ return;
+
+return_double:
+ r = float64_val(r64);
+ s->synthetic_rom_dbg_result[0] = r;
+ s->synthetic_rom_dbg_result[1] = r >> 32;
+}
+
+static bool rp2040_synthetic_fp_op(RP2040State *s, uint32_t command)
+{
+ uint32_t offset = command & 0xff;
+
+ switch (command & RP2040_SYNTHETIC_FP_CMD_MASK) {
+ case RP2040_SYNTHETIC_FP_CMD_FLOAT:
+ rp2040_synthetic_float_op(s, offset);
+ return true;
+ case RP2040_SYNTHETIC_FP_CMD_DOUBLE:
+ rp2040_synthetic_double_op(s, offset);
+ return true;
+ default:
+ return false;
+ }
+}
+
+static void rp2040_synthetic_flash_helper_hit(RP2040State *s,
+ unsigned int helper,
+ const char *name)
+{
+ uint32_t count;
+
+ g_assert(helper < RP2040_SYNTHETIC_ROM_FLASH_HELPER_COUNT);
+
+ count = ++s->synthetic_rom_flash_helper_count[helper];
+ trace_rp2040_synthetic_flash_helper(name, count);
+}
+
static void rp2040_synthetic_rom_dbg_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
+ RP2040State *s = opaque;
+ hwaddr offset = addr & 0xfff;
+ uint32_t command = value;
uint16_t code = value;
const char *name = rp2040_bootrom_function_name(code);
char feature[64];
char detail[64];
+ Error *local_err = NULL;
+
+ switch (offset) {
+ case RP2040_SYNTHETIC_ROM_DBG_ARG0:
+ case RP2040_SYNTHETIC_ROM_DBG_ARG1:
+ case RP2040_SYNTHETIC_ROM_DBG_ARG2:
+ case RP2040_SYNTHETIC_ROM_DBG_ARG3:
+ s->synthetic_rom_dbg_arg[(offset - RP2040_SYNTHETIC_ROM_DBG_ARG0) /
+ sizeof(uint32_t)] = value;
+ return;
+ case RP2040_SYNTHETIC_ROM_DBG_CMD:
+ break;
+ default:
+ rp2040_log_nyi("bootrom", "synthetic diagnostic register write",
+ "unknown register");
+ return;
+ }
+
+ if (rp2040_synthetic_fp_op(s, command)) {
+ return;
+ }
+
+ switch (code) {
+ case RP2040_ROM_TABLE_CODE('C', 'X'):
+ rp2040_synthetic_flash_helper_hit(
+ s, RP2040_SYNTHETIC_FLASH_ENTER_CMD_XIP,
+ "flash_enter_cmd_xip");
+ rp2040_log_nyi("bootrom", "flash_enter_cmd_xip",
+ "synthetic helper does not reconfigure SSI hardware");
+ return;
+ case RP2040_ROM_TABLE_CODE('E', 'X'):
+ rp2040_synthetic_flash_helper_hit(
+ s, RP2040_SYNTHETIC_FLASH_EXIT_XIP, "flash_exit_xip");
+ rp2040_log_nyi("bootrom", "flash_exit_xip",
+ "synthetic helper does not send serial flash commands");
+ return;
+ case RP2040_ROM_TABLE_CODE('F', 'C'):
+ rp2040_synthetic_flash_helper_hit(
+ s, RP2040_SYNTHETIC_FLASH_FLUSH_CACHE, "flash_flush_cache");
+ rp2040_log_nyi("bootrom", "flash_flush_cache",
+ "XIP cache is not modeled");
+ return;
+ case RP2040_ROM_TABLE_CODE('I', 'F'):
+ rp2040_synthetic_flash_helper_hit(
+ s, RP2040_SYNTHETIC_FLASH_CONNECT_INTERNAL_FLASH,
+ "connect_internal_flash");
+ rp2040_log_nyi("bootrom", "connect_internal_flash",
+ "synthetic helper assumes the QSPI flash is connected");
+ return;
+ case RP2040_ROM_TABLE_CODE('R', 'E'):
+ rp2040_synthetic_flash_helper_hit(
+ s, RP2040_SYNTHETIC_FLASH_RANGE_ERASE, "flash_range_erase");
+ if (!rp2040_xip_flash_range_erase(&s->xip,
+ s->synthetic_rom_dbg_arg[0],
+ s->synthetic_rom_dbg_arg[1],
+ s->synthetic_rom_dbg_arg[2],
+ s->synthetic_rom_dbg_arg[3],
+ &local_err)) {
+ warn_report_err(local_err);
+ }
+ return;
+ case RP2040_ROM_TABLE_CODE('R', 'P'):
+ rp2040_synthetic_flash_helper_hit(
+ s, RP2040_SYNTHETIC_FLASH_RANGE_PROGRAM, "flash_range_program");
+ if (!rp2040_xip_flash_range_program(&s->xip,
+ s->synthetic_rom_dbg_arg[0],
+ s->synthetic_rom_dbg_arg[1],
+ s->synthetic_rom_dbg_arg[2],
+ &local_err)) {
+ warn_report_err(local_err);
+ }
+ return;
+ }
snprintf(feature, sizeof(feature), "boot ROM function '%c%c'",
code & 0xff, (code >> 8) & 0xff);
- snprintf(detail, sizeof(detail), "%s%s",
+ snprintf(detail, sizeof(detail), "%s%s%s",
name ? "SDK name: " : "",
- name ? name : "unknown lookup code");
+ name ? name : "unknown lookup code",
+ name ? "" : "");
rp2040_log_nyi("bootrom", feature, detail);
}
static uint64_t rp2040_synthetic_rom_dbg_read(void *opaque, hwaddr addr,
unsigned size)
{
+ RP2040State *s = opaque;
+ hwaddr offset = addr & 0xfff;
+
+ if (offset >= RP2040_SYNTHETIC_ROM_DBG_ARG0 &&
+ offset <= RP2040_SYNTHETIC_ROM_DBG_ARG3 &&
+ QEMU_IS_ALIGNED(offset, sizeof(uint32_t))) {
+ return s->synthetic_rom_dbg_arg[(offset -
+ RP2040_SYNTHETIC_ROM_DBG_ARG0) /
+ sizeof(uint32_t)];
+ }
+ if (offset >= RP2040_SYNTHETIC_ROM_DBG_RESULT0 &&
+ offset <= RP2040_SYNTHETIC_ROM_DBG_RESULT3 &&
+ QEMU_IS_ALIGNED(offset, sizeof(uint32_t))) {
+ return s->synthetic_rom_dbg_result[(offset -
+ RP2040_SYNTHETIC_ROM_DBG_RESULT0) /
+ sizeof(uint32_t)];
+ }
+ if (offset >= RP2040_SYNTHETIC_ROM_DBG_FLASH_COUNT0 &&
+ offset < RP2040_SYNTHETIC_ROM_DBG_FLASH_COUNT0 +
+ sizeof(s->synthetic_rom_flash_helper_count) &&
+ QEMU_IS_ALIGNED(offset, sizeof(uint32_t))) {
+ return s->synthetic_rom_flash_helper_count[
+ (offset - RP2040_SYNTHETIC_ROM_DBG_FLASH_COUNT0) /
+ sizeof(uint32_t)];
+ }
+
rp2040_log_nyi("bootrom", "synthetic diagnostic register read",
- "write-only QEMU diagnostic channel");
+ "unsupported register");
return 0;
}
diff --git a/hw/arm/trace-events b/hw/arm/trace-events
index 1b16f710fe..a888466ca0 100644
--- a/hw/arm/trace-events
+++ b/hw/arm/trace-events
@@ -1,5 +1,8 @@
# See docs/devel/tracing.rst for syntax documentation.
+# rp2040.c
+rp2040_synthetic_flash_helper(const char *name, uint32_t count) "helper %s count %" PRIu32
+
# omap1.c
omap1_pwl_clocking_scheme(const char *scheme) "omap1 CLKM: clocking scheme set to %s"
omap1_pwl_backlight(int output) "omap1 PWL: backlight now at %d/256"
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 58c524a341..29269c913b 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -54,6 +54,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
#define RP2040_USBCTRL_REGS_SIZE 0x4000
#define RP2040_SYNTHETIC_ROM_DBG_BASE 0x5fff0000
#define RP2040_SYNTHETIC_ROM_DBG_SIZE 0x1000
+#define RP2040_SYNTHETIC_ROM_FLASH_HELPER_COUNT 6
#define RP2040_NUM_CORES 2
#define RP2040_NUM_IRQS 32
@@ -108,6 +109,10 @@ struct RP2040State {
bool uart0_rx_pin_enabled;
bool uart1_tx_pin_enabled;
bool uart1_rx_pin_enabled;
+ uint32_t synthetic_rom_dbg_arg[4];
+ uint32_t synthetic_rom_dbg_result[4];
+ uint32_t synthetic_rom_flash_helper_count[
+ RP2040_SYNTHETIC_ROM_FLASH_HELPER_COUNT];
Clock *sysclk;
};
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index eb3de828c5..2f5878c1fa 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -131,6 +131,27 @@ run-rp2040-bootrom-helpers: rp2040-bootrom-helpers
RP2040_TESTS+=rp2040-bootrom-helpers
+rp2040-bootrom-float rp2040-flash-lockout: %: %.S \
+ rp2040-boot2-w25q080.S rp2040.ld
+ $(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
+ -Wl,--build-id=none -x assembler-with-cpp \
+ $(ARM_SRC)/rp2040-boot2-w25q080.S $< \
+ -o $@ -nostdlib -static \
+ -T $(ARM_SRC)/rp2040.ld
+
+run-rp2040-bootrom-float run-rp2040-flash-lockout: QEMU_OPTS=\
+ -M raspi-pico,strict-uart-pins=off \
+ -serial chardev:output \
+ -semihosting-config enable=on,target=native -kernel
+run-rp2040-bootrom-float run-rp2040-flash-lockout: run-%: %
+ $(call run-test, $<, \
+ $(QEMU) -monitor none -display none \
+ -chardev file$(COMMA)path=$<.out$(COMMA)id=output \
+ $(QEMU_OPTS) $<)
+ $(call diff-out,$<,$(ARM_SRC)/$<.ref)
+
+RP2040_TESTS+=rp2040-bootrom-float rp2040-flash-lockout
+
ARM_TESTS+=$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS)
# These objects provide the basic boot code and helper functions for all tests
diff --git a/tests/tcg/arm/system/rp2040-bootrom-float.S b/tests/tcg/arm/system/rp2040-bootrom-float.S
new file mode 100644
index 0000000000..57d60b9fd9
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-bootrom-float.S
@@ -0,0 +1,206 @@
+/*
+ * Raspberry Pi Pico boot ROM floating-point table test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define ROM_DATA_TABLE_PTR 0x00000016
+#define ROM_TABLE_LOOKUP_PTR 0x00000018
+#define ROM_DATA_FLOAT_TABLE 0x4653
+#define ROM_DATA_DOUBLE_TABLE 0x4453
+#define FP_TABLE_FADD 0x00
+#define FP_TABLE_FMUL 0x08
+#define FP_TABLE_FDIV 0x0c
+#define FP_TABLE_FLOAT2DOUBLE 0x7c
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.macro lookup_data code, dst
+ ldr r0, =\code
+ bl rom_data_lookup
+ cmp r0, 0
+ bne 0f
+ b fail_lookup
+0:
+ mov \dst, r0
+.endm
+
+.macro load_table_func basereg, offset
+ mov r4, \basereg
+ adds r4, \offset
+ ldrb r3, [r4]
+ ldrb r5, [r4, 1]
+ lsls r5, 8
+ orrs r3, r5
+ ldrb r5, [r4, 2]
+ lsls r5, 16
+ orrs r3, r5
+ ldrb r5, [r4, 3]
+ lsls r5, 24
+ orrs r3, r5
+ mov r4, r3
+.endm
+
+.macro fail_if_ne reg, expected, target
+ ldr r2, =\expected
+ cmp \reg, r2
+ beq 0f
+ b \target
+0:
+.endm
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+
+ lookup_data ROM_DATA_FLOAT_TABLE, r6
+
+ /* 1.25f + 2.5f = 3.75f. */
+ load_table_func r6, FP_TABLE_FADD
+ ldr r0, =0x3fa00000
+ ldr r1, =0x40200000
+ blx r4
+ fail_if_ne r0, 0x40700000, fail_single
+
+ /* 3.75f * 2.0f = 7.5f. */
+ load_table_func r6, FP_TABLE_FMUL
+ ldr r0, =0x40700000
+ ldr r1, =0x40000000
+ blx r4
+ fail_if_ne r0, 0x40f00000, fail_single
+
+ /* 7.5f / 3.0f = 2.5f. */
+ load_table_func r6, FP_TABLE_FDIV
+ ldr r0, =0x40f00000
+ ldr r1, =0x40400000
+ blx r4
+ fail_if_ne r0, 0x40200000, fail_single
+
+ /* Convert 1.25f to 0x3ff4000000000000. */
+ load_table_func r6, FP_TABLE_FLOAT2DOUBLE
+ ldr r0, =0x3fa00000
+ blx r4
+ fail_if_ne r0, 0x00000000, fail_single
+ fail_if_ne r1, 0x3ff40000, fail_single
+
+ ldr r0, =single_ok_message
+ bl puts
+
+ lookup_data ROM_DATA_DOUBLE_TABLE, r7
+
+ /* 1.5 + 4.0 = 5.5. */
+ load_table_func r7, FP_TABLE_FADD
+ movs r0, 0
+ ldr r1, =0x3ff80000
+ movs r2, 0
+ ldr r3, =0x40100000
+ blx r4
+ fail_if_ne r0, 0x00000000, fail_double
+ fail_if_ne r1, 0x40160000, fail_double
+
+ /* The SD entry at 0x7c converts 1.5 to the float 1.5f. */
+ load_table_func r7, FP_TABLE_FLOAT2DOUBLE
+ movs r0, 0
+ ldr r1, =0x3ff80000
+ blx r4
+ fail_if_ne r0, 0x3fc00000, fail_double
+
+ ldr r0, =double_ok_message
+ bl puts
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_lookup:
+ ldr r0, =fail_lookup_message
+ b test_failed
+
+fail_single:
+ ldr r0, =fail_single_message
+ b test_failed
+
+fail_double:
+ ldr r0, =fail_double_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+rom_data_lookup:
+ push {r4, lr}
+ mov r1, r0
+ ldr r0, =ROM_DATA_TABLE_PTR
+ ldrh r0, [r0]
+ ldr r4, =ROM_TABLE_LOOKUP_PTR
+ ldrh r4, [r4]
+ blx r4
+ pop {r4, pc}
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG BOOTROM FLOAT START\n"
+single_ok_message:
+ .asciz "PICO TCG BOOTROM FLOAT SF OK\n"
+double_ok_message:
+ .asciz "PICO TCG BOOTROM FLOAT SD OK\n"
+ok_message:
+ .asciz "PICO TCG BOOTROM FLOAT OK\n"
+fail_lookup_message:
+ .asciz "PICO TCG BOOTROM FLOAT FAIL: LOOKUP\n"
+fail_single_message:
+ .asciz "PICO TCG BOOTROM FLOAT FAIL: SF\n"
+fail_double_message:
+ .asciz "PICO TCG BOOTROM FLOAT FAIL: SD\n"
+fail_exception_message:
+ .asciz "PICO TCG BOOTROM FLOAT FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-bootrom-float.ref b/tests/tcg/arm/system/rp2040-bootrom-float.ref
new file mode 100644
index 0000000000..d1eb0e7463
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-bootrom-float.ref
@@ -0,0 +1,4 @@
+PICO TCG BOOTROM FLOAT START
+PICO TCG BOOTROM FLOAT SF OK
+PICO TCG BOOTROM FLOAT SD OK
+PICO TCG BOOTROM FLOAT OK
diff --git a/tests/tcg/arm/system/rp2040-flash-lockout.S b/tests/tcg/arm/system/rp2040-flash-lockout.S
new file mode 100644
index 0000000000..a7be6bbb51
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-lockout.S
@@ -0,0 +1,387 @@
+/*
+ * Raspberry Pi Pico multicore flash lockout test.
+ *
+ * Copyright 2026 Gilles Grimaud
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define CORE1_STACK 0x20041000
+#define PAGE_BUFFER 0x20000000
+#define UART0_DR 0x40034000
+#define SIO_BASE 0xd0000000
+#define SIO_FIFO_ST 0x50
+#define SIO_FIFO_WR 0x54
+#define SIO_FIFO_RD 0x58
+#define SIO_SPINLOCK0 0x100
+#define SIO_FIFO_ST_VLD 0x01
+#define SIO_FIFO_ST_RDY 0x02
+#define NVIC_ISER 0xe000e100
+#define SIO_IRQ_PROC1_MASK 0x00010000
+#define DEBUG_BASE 0x5fff0000
+#define DEBUG_CMD 0x00
+#define DEBUG_ARG0 0x04
+#define DEBUG_ARG1 0x08
+#define DEBUG_ARG2 0x0c
+#define DEBUG_ARG3 0x10
+#define DEBUG_COUNT_ERASE 0x50
+#define DEBUG_COUNT_PROGRAM 0x54
+#define DEBUG_FLASH_ERASE 0x4552
+#define DEBUG_FLASH_PROGRAM 0x5052
+#define FLASH_OFFSET 0x00101000
+#define FLASH_ADDRESS 0x10101000
+#define LOCK_REQUEST 0xfacefeed
+#define LOCK_ACK 0xface0001
+#define RELEASE_REQUEST 0xfacebeef
+#define RELEASE_ACK 0xface0002
+#define BAD_ACK 0xbad10002
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word default_handler + 1
+ .rept 28
+ .word default_handler + 1
+ .endr
+ .word core1_fifo_irq + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+ bl launch_core1
+
+ ldr r0, =launch_message
+ bl puts
+ bl claim_spinlock
+ ldr r0, =LOCK_REQUEST
+ bl fifo_push
+ sev
+ ldr r0, =LOCK_ACK
+ bl fifo_pop_expect
+
+ ldr r0, =locked_message
+ bl puts
+ bl flash_erase_program_verify
+
+ ldr r0, =flash_message
+ bl puts
+ ldr r0, =RELEASE_REQUEST
+ bl fifo_push
+ sev
+ ldr r0, =RELEASE_ACK
+ bl fifo_pop_expect
+ bl release_spinlock
+
+ ldr r0, =released_message
+ bl puts
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+.thumb_func
+core1_entry:
+ ldr r0, =NVIC_ISER
+ ldr r1, =SIO_IRQ_PROC1_MASK
+ str r1, [r0]
+ cpsie i
+
+2:
+ wfe
+ b 2b
+
+.thumb_func
+core1_fifo_irq:
+ push {r0, r1, r2, r3, r4, lr}
+ bl fifo_pop
+ ldr r1, =LOCK_REQUEST
+ cmp r0, r1
+ bne core1_bad_request
+ ldr r0, =LOCK_ACK
+ bl fifo_push
+
+3:
+ wfe
+ ldr r0, =SIO_BASE
+ ldr r1, [r0, SIO_FIFO_ST]
+ movs r0, SIO_FIFO_ST_VLD
+ tst r1, r0
+ beq 3b
+ bl fifo_pop
+ ldr r1, =RELEASE_REQUEST
+ cmp r0, r1
+ bne core1_bad_request
+ ldr r0, =RELEASE_ACK
+ bl fifo_push
+ pop {r0, r1, r2, r3, r4, pc}
+
+core1_bad_request:
+ ldr r0, =BAD_ACK
+ bl fifo_push
+
+4:
+ b 4b
+
+.thumb_func
+launch_core1:
+ push {r4, r5, lr}
+ ldr r4, =launch_sequence
+ movs r5, 6
+
+5:
+ ldr r0, [r4]
+ cmp r0, 0
+ bne 6f
+ bl fifo_drain
+ sev
+ ldr r0, [r4]
+
+6:
+ bl fifo_push
+ sev
+ bl fifo_pop
+ ldr r1, [r4]
+ cmp r0, r1
+ bne fail_launch
+ adds r4, 4
+ subs r5, 1
+ bne 5b
+ pop {r4, r5, pc}
+
+.thumb_func
+claim_spinlock:
+ ldr r0, =(SIO_BASE + SIO_SPINLOCK0)
+ ldr r1, [r0]
+ cmp r1, 0
+ beq fail_spinlock
+ bx lr
+
+.thumb_func
+release_spinlock:
+ ldr r0, =(SIO_BASE + SIO_SPINLOCK0)
+ movs r1, 0
+ str r1, [r0]
+ bx lr
+
+.thumb_func
+flash_erase_program_verify:
+ push {r4, r5, r6, lr}
+ ldr r0, =PAGE_BUFFER
+ movs r1, 0
+ movs r2, 0
+ movs r3, 1
+ lsls r3, r3, 8
+
+7:
+ strb r1, [r0, r2]
+ adds r1, 1
+ adds r2, 1
+ cmp r2, r3
+ bne 7b
+
+ ldr r4, =DEBUG_BASE
+ ldr r0, =FLASH_OFFSET
+ str r0, [r4, DEBUG_ARG0]
+ movs r0, 1
+ lsls r0, r0, 12
+ str r0, [r4, DEBUG_ARG1]
+ movs r0, 0
+ str r0, [r4, DEBUG_ARG2]
+ movs r0, 0x20
+ str r0, [r4, DEBUG_ARG3]
+ ldr r0, =DEBUG_FLASH_ERASE
+ str r0, [r4, DEBUG_CMD]
+
+ ldr r0, =FLASH_ADDRESS
+ movs r2, 0
+ movs r3, 1
+ lsls r3, r3, 8
+
+8:
+ ldrb r5, [r0, r2]
+ movs r6, 0xff
+ cmp r5, r6
+ bne fail_flash
+ adds r2, 1
+ cmp r2, r3
+ bne 8b
+
+ ldr r0, =FLASH_OFFSET
+ str r0, [r4, DEBUG_ARG0]
+ ldr r0, =PAGE_BUFFER
+ str r0, [r4, DEBUG_ARG1]
+ movs r0, 1
+ lsls r0, r0, 8
+ str r0, [r4, DEBUG_ARG2]
+ ldr r0, =DEBUG_FLASH_PROGRAM
+ str r0, [r4, DEBUG_CMD]
+
+ ldr r0, =FLASH_ADDRESS
+ movs r1, 0
+ movs r2, 0
+ movs r3, 1
+ lsls r3, r3, 8
+
+9:
+ ldrb r5, [r0, r2]
+ cmp r5, r1
+ bne fail_flash
+ adds r1, 1
+ adds r2, 1
+ cmp r2, r3
+ bne 9b
+
+ ldr r0, [r4, DEBUG_COUNT_ERASE]
+ cmp r0, 1
+ bne fail_flash
+ ldr r0, [r4, DEBUG_COUNT_PROGRAM]
+ cmp r0, 1
+ bne fail_flash
+ pop {r4, r5, r6, pc}
+
+.thumb_func
+fifo_push:
+ push {r1, r2, r3, lr}
+ ldr r1, =SIO_BASE
+
+10:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_RDY
+ tst r2, r3
+ beq 10b
+ str r0, [r1, SIO_FIFO_WR]
+ pop {r1, r2, r3, pc}
+
+.thumb_func
+fifo_pop:
+ push {r1, r2, r3, lr}
+ ldr r1, =SIO_BASE
+
+11:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_VLD
+ tst r2, r3
+ beq 11b
+ ldr r0, [r1, SIO_FIFO_RD]
+ pop {r1, r2, r3, pc}
+
+.thumb_func
+fifo_drain:
+ push {r0, r1, r2, r3, lr}
+ ldr r1, =SIO_BASE
+
+12:
+ ldr r2, [r1, SIO_FIFO_ST]
+ movs r3, SIO_FIFO_ST_VLD
+ tst r2, r3
+ beq 13f
+ ldr r0, [r1, SIO_FIFO_RD]
+ b 12b
+
+13:
+ pop {r0, r1, r2, r3, pc}
+
+.thumb_func
+fifo_pop_expect:
+ push {r4, lr}
+ mov r4, r0
+ bl fifo_pop
+ cmp r0, r4
+ bne fail_fifo
+ pop {r4, pc}
+
+fail_launch:
+ ldr r0, =fail_launch_message
+ b test_failed
+
+fail_spinlock:
+ ldr r0, =fail_spinlock_message
+ b test_failed
+
+fail_fifo:
+ ldr r0, =fail_fifo_message
+ b test_failed
+
+fail_flash:
+ ldr r0, =fail_flash_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+14:
+ b 14b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_exception_message
+ b test_failed
+
+.thumb_func
+puts:
+ push {r1, r2, lr}
+ ldr r1, =UART0_DR
+
+15:
+ ldrb r2, [r0]
+ cmp r2, 0
+ beq 16f
+ str r2, [r1]
+ adds r0, 1
+ b 15b
+
+16:
+ pop {r1, r2, pc}
+
+.section .rodata, "a", %progbits
+.align 2
+launch_sequence:
+ .word 0
+ .word 0
+ .word 1
+ .word vector_table
+ .word CORE1_STACK
+ .word core1_entry + 1
+
+start_message:
+ .asciz "PICO TCG FLASH LOCKOUT START\n"
+launch_message:
+ .asciz "PICO TCG FLASH LOCKOUT CORE1 OK\n"
+locked_message:
+ .asciz "PICO TCG FLASH LOCKOUT LOCKED\n"
+flash_message:
+ .asciz "PICO TCG FLASH LOCKOUT PROGRAM OK\n"
+released_message:
+ .asciz "PICO TCG FLASH LOCKOUT RELEASED\n"
+ok_message:
+ .asciz "PICO TCG FLASH LOCKOUT OK\n"
+fail_launch_message:
+ .asciz "PICO TCG FLASH LOCKOUT FAIL: LAUNCH\n"
+fail_spinlock_message:
+ .asciz "PICO TCG FLASH LOCKOUT FAIL: SPINLOCK\n"
+fail_fifo_message:
+ .asciz "PICO TCG FLASH LOCKOUT FAIL: FIFO\n"
+fail_flash_message:
+ .asciz "PICO TCG FLASH LOCKOUT FAIL: FLASH\n"
+fail_exception_message:
+ .asciz "PICO TCG FLASH LOCKOUT FAIL: EXCEPTION\n"
diff --git a/tests/tcg/arm/system/rp2040-flash-lockout.ref b/tests/tcg/arm/system/rp2040-flash-lockout.ref
new file mode 100644
index 0000000000..300d9b134b
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-flash-lockout.ref
@@ -0,0 +1,6 @@
+PICO TCG FLASH LOCKOUT START
+PICO TCG FLASH LOCKOUT CORE1 OK
+PICO TCG FLASH LOCKOUT LOCKED
+PICO TCG FLASH LOCKOUT PROGRAM OK
+PICO TCG FLASH LOCKOUT RELEASED
+PICO TCG FLASH LOCKOUT OK
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 29/30] hw/arm: add optional Pico SDK exit handling
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (27 preceding siblings ...)
2026-08-30 6:15 ` [PATCH RFC v2 28/30] hw/arm: add RP2040 synthetic boot ROM accelerated helpers Gilles Grimaud
@ 2026-08-30 6:16 ` Gilles Grimaud
2026-08-30 6:16 ` [PATCH RFC v2 30/30] docs/system/arm: document Raspberry Pi Pico Gilles Grimaud
2026-09-02 13:20 ` [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Alex Bennée
30 siblings, 0 replies; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:16 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Add an opt-in raspi-pico machine property that converts the Pico SDK's BKPT #0 exit convention into a QEMU guest shutdown. Keep the default architectural BKPT-to-HardFault behavior unchanged and chain non-BKPT faults to the application handler.
Cover the default HardFault path, explicit semihosting exit, and the optional Pico SDK exit path with compiled check-tcg guests.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
hw/arm/raspi_pico.c | 23 ++++
hw/arm/rp2040.c | 117 ++++++++++++++++++
include/hw/arm/rp2040.h | 7 ++
tests/tcg/arm/Makefile.softmmu-target | 50 ++++++++
tests/tcg/arm/system/rp2040-bkpt-hardfault.S | 94 ++++++++++++++
.../tcg/arm/system/rp2040-bkpt-hardfault.ref | 2 +
tests/tcg/arm/system/rp2040-exit.S | 55 ++++++++
7 files changed, 348 insertions(+)
create mode 100644 tests/tcg/arm/system/rp2040-bkpt-hardfault.S
create mode 100644 tests/tcg/arm/system/rp2040-bkpt-hardfault.ref
create mode 100644 tests/tcg/arm/system/rp2040-exit.S
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
index 2696a24951..2f87ee1a2d 100644
--- a/hw/arm/raspi_pico.c
+++ b/hw/arm/raspi_pico.c
@@ -34,6 +34,7 @@ struct RaspiPicoMachineState {
uint64_t rosc_random_seed_value;
bool rosc_random_seed_set;
bool strict_uart_pins;
+ bool pico_sdk_exit;
};
static char *raspi_pico_get_flash_file(Object *obj, Error **errp)
@@ -164,6 +165,7 @@ static void raspi_pico_init(MachineState *machine)
rp2040_xip_load_image(&s->soc.xip, machine->kernel_filename,
&error_fatal);
+ rp2040_set_pico_sdk_exit(&s->soc, s->pico_sdk_exit, &error_fatal);
armv7m_load_kernel(s->soc.armv7m[0].cpu, NULL,
RP2040_XIP_BASE, 2 * MiB);
rp2040_xip_set_writable(&s->soc.xip, false);
@@ -193,6 +195,21 @@ static void raspi_pico_set_strict_uart_pins(Object *obj, bool value,
s->strict_uart_pins = value;
}
+static bool raspi_pico_get_pico_sdk_exit(Object *obj, Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ return s->pico_sdk_exit;
+}
+
+static void raspi_pico_set_pico_sdk_exit(Object *obj, bool value,
+ Error **errp)
+{
+ RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+ s->pico_sdk_exit = value;
+}
+
static void raspi_pico_machine_initfn(Object *obj)
{
RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
@@ -241,6 +258,12 @@ static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
"Require the RP2040 IO_BANK0 "
"UART pinmux before UART0 or UART1 "
"reaches its host serial backend");
+ object_class_property_add_bool(oc, "pico-sdk-exit",
+ raspi_pico_get_pico_sdk_exit,
+ raspi_pico_set_pico_sdk_exit);
+ object_class_property_set_description(oc, "pico-sdk-exit",
+ "Convert Pico SDK exit BKPT #0 "
+ "HardFaults into QEMU exits");
}
static const TypeInfo raspi_pico_machine_info = {
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index 6c06570586..fc61088d82 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -20,6 +20,7 @@
#include "qemu/datadir.h"
#include "qemu/log.h"
#include "system/address-spaces.h"
+#include "system/runstate.h"
#include "target/arm/cpu.h"
#include "target/arm/cpu-qom.h"
#include "trace.h"
@@ -76,6 +77,8 @@
#define RP2040_BOOTROM_FLOAT_STUBS_OFFSET 0x0600
#define RP2040_BOOTROM_DOUBLE_STUBS_OFFSET 0x0b00
#define RP2040_BOOTROM_FP_STUB_SIZE 36
+#define RP2040_APP_HARDFAULT_VECTOR_OFFSET 0x10c
+#define RP2040_PICO_SDK_EXIT_ORIGINAL_HANDLER_OFFSET 0x38
#define RP2040_SYNTHETIC_ROM_DBG_CMD 0x00
#define RP2040_SYNTHETIC_ROM_DBG_ARG0 0x04
@@ -87,6 +90,7 @@
#define RP2040_SYNTHETIC_ROM_DBG_RESULT2 0x1c
#define RP2040_SYNTHETIC_ROM_DBG_RESULT3 0x20
#define RP2040_SYNTHETIC_ROM_DBG_FLASH_COUNT0 0x40
+#define RP2040_SYNTHETIC_ROM_DBG_CMD_EXIT 0x54495845 /* "EXIT" */
#define RP2040_SYNTHETIC_FP_CMD_MASK 0xffffff00
#define RP2040_SYNTHETIC_FP_CMD_FLOAT 0x80000000
@@ -475,6 +479,17 @@ static const uint8_t rp2040_bootrom_nyi_stub[] = {
0x00, 0x00, 0x00, 0x00, /* function code literal */
};
+static const uint8_t rp2040_pico_sdk_exit_trampoline[] = {
+ 0x73, 0x46, 0x04, 0x22, 0x13, 0x42, 0x02, 0xd0,
+ 0xef, 0xf3, 0x09, 0x80, 0x01, 0xe0, 0xef, 0xf3,
+ 0x08, 0x80, 0x81, 0x69, 0x0a, 0x88, 0x05, 0x4b,
+ 0x9a, 0x42, 0x05, 0xd1, 0x01, 0x68, 0x04, 0x4a,
+ 0x51, 0x60, 0x04, 0x49, 0x11, 0x60, 0xfe, 0xe7,
+ 0x03, 0x48, 0x00, 0x47, 0x00, 0xbe, 0x00, 0x00,
+ 0x00, 0x00, 0xff, 0x5f, 0x45, 0x58, 0x49, 0x54,
+ 0x00, 0x00, 0x00, 0x00,
+};
+
static void rp2040_store_hword(uint8_t *rom, uint32_t offset, uint16_t value)
{
rom[offset] = value;
@@ -1316,6 +1331,12 @@ static void rp2040_synthetic_rom_dbg_write(void *opaque, hwaddr addr,
return;
}
+ if (command == RP2040_SYNTHETIC_ROM_DBG_CMD_EXIT) {
+ qemu_system_shutdown_request_with_code(SHUTDOWN_CAUSE_GUEST_SHUTDOWN,
+ s->synthetic_rom_dbg_arg[0]);
+ return;
+ }
+
switch (code) {
case RP2040_ROM_TABLE_CODE('C', 'X'):
rp2040_synthetic_flash_helper_hit(
@@ -1421,6 +1442,55 @@ static const MemoryRegionOps rp2040_synthetic_rom_dbg_ops = {
},
};
+static MemTxResult rp2040_pico_sdk_exit_page_read(void *opaque, hwaddr addr,
+ uint64_t *data,
+ unsigned size,
+ MemTxAttrs attrs)
+{
+ RP2040State *s = opaque;
+ MemTxResult result;
+ unsigned int i;
+
+ result = rp2040_xip_read_data(&s->xip, addr, data, size);
+ if (result != MEMTX_OK) {
+ return result;
+ }
+
+ for (i = 0; i < size; i++) {
+ hwaddr cur = addr + i;
+
+ if (cur >= RP2040_APP_HARDFAULT_VECTOR_OFFSET &&
+ cur < RP2040_APP_HARDFAULT_VECTOR_OFFSET + sizeof(uint32_t)) {
+ uint8_t byte = extract32(
+ RP2040_PICO_SDK_EXIT_TRAMPOLINE_BASE | 1,
+ (cur - RP2040_APP_HARDFAULT_VECTOR_OFFSET) * 8, 8);
+
+ *data = deposit64(*data, i * 8, 8, byte);
+ }
+ }
+ return MEMTX_OK;
+}
+
+static MemTxResult rp2040_pico_sdk_exit_page_write(void *opaque,
+ hwaddr addr,
+ uint64_t value,
+ unsigned size,
+ MemTxAttrs attrs)
+{
+ return MEMTX_ERROR;
+}
+
+static const MemoryRegionOps rp2040_pico_sdk_exit_page_ops = {
+ .read_with_attrs = rp2040_pico_sdk_exit_page_read,
+ .write_with_attrs = rp2040_pico_sdk_exit_page_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = true,
+ },
+};
+
static void rp2040_update_nmi(RP2040State *s)
{
uint32_t nmi_mask = rp2040_syscfg_get_proc0_nmi_mask(&s->syscfg);
@@ -1729,6 +1799,15 @@ static bool rp2040_init_memory(RP2040State *s, Error **errp)
memory_region_add_subregion(s->board_memory,
RP2040_SYNTHETIC_ROM_DBG_BASE,
&s->synthetic_rom_dbg);
+ if (!memory_region_init_rom(&s->pico_sdk_exit_trampoline, OBJECT(s),
+ "rp2040.pico-sdk-exit-trampoline",
+ RP2040_PICO_SDK_EXIT_TRAMPOLINE_SIZE, errp)) {
+ return false;
+ }
+ memory_region_add_subregion(s->board_memory,
+ RP2040_PICO_SDK_EXIT_TRAMPOLINE_BASE,
+ &s->pico_sdk_exit_trampoline);
+ memory_region_set_enabled(&s->pico_sdk_exit_trampoline, false);
s->mempowerdown_ready = true;
@@ -1778,6 +1857,13 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
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);
+ /* A page-sized overlay avoids QEMU's slower subpage dispatch path. */
+ memory_region_init_io(&s->pico_sdk_exit_page, OBJECT(dev),
+ &rp2040_pico_sdk_exit_page_ops, s,
+ "rp2040.pico-sdk-exit-page", 4 * KiB);
+ memory_region_add_subregion_overlap(
+ s->board_memory, RP2040_XIP_BASE, &s->pico_sdk_exit_page, 1);
+ memory_region_set_enabled(&s->pico_sdk_exit_page, false);
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,
@@ -2001,6 +2087,37 @@ static void rp2040_soc_realize(DeviceState *dev, Error **errp)
rp2040_update_uart_pins(s);
}
+void rp2040_set_pico_sdk_exit(RP2040State *s, bool enabled, Error **errp)
+{
+ uint8_t *trampoline;
+ uint32_t original_handler;
+
+ memory_region_set_enabled(&s->pico_sdk_exit_page, false);
+ memory_region_set_enabled(&s->pico_sdk_exit_trampoline, false);
+ if (!enabled) {
+ return;
+ }
+
+ original_handler = ldl_le_p(
+ s->xip.storage + RP2040_APP_HARDFAULT_VECTOR_OFFSET);
+ if (!(original_handler & 1) || original_handler == UINT32_MAX) {
+ error_setg(errp, "pico-sdk-exit requires a valid Thumb HardFault "
+ "handler at 0x%08x",
+ RP2040_XIP_BASE + RP2040_APP_HARDFAULT_VECTOR_OFFSET);
+ return;
+ }
+
+ trampoline = memory_region_get_ram_ptr(&s->pico_sdk_exit_trampoline);
+ memset(trampoline, 0, RP2040_PICO_SDK_EXIT_TRAMPOLINE_SIZE);
+ memcpy(trampoline, rp2040_pico_sdk_exit_trampoline,
+ sizeof(rp2040_pico_sdk_exit_trampoline));
+ stl_le_p(trampoline + RP2040_PICO_SDK_EXIT_ORIGINAL_HANDLER_OFFSET,
+ original_handler);
+ s->pico_sdk_exit_original_handler = original_handler;
+ memory_region_set_enabled(&s->pico_sdk_exit_trampoline, true);
+ memory_region_set_enabled(&s->pico_sdk_exit_page, true);
+}
+
static const Property rp2040_soc_properties[] = {
DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGION,
MemoryRegion *),
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 29269c913b..2e65e76893 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -54,6 +54,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(RP2040State, RP2040)
#define RP2040_USBCTRL_REGS_SIZE 0x4000
#define RP2040_SYNTHETIC_ROM_DBG_BASE 0x5fff0000
#define RP2040_SYNTHETIC_ROM_DBG_SIZE 0x1000
+#define RP2040_PICO_SDK_EXIT_TRAMPOLINE_BASE 0x00004000
+#define RP2040_PICO_SDK_EXIT_TRAMPOLINE_SIZE 0x1000
#define RP2040_SYNTHETIC_ROM_FLASH_HELPER_COUNT 6
#define RP2040_NUM_CORES 2
@@ -96,6 +98,8 @@ struct RP2040State {
MemoryRegion usbctrl_dpram_poweroff;
MemoryRegion usbctrl_regs;
MemoryRegion synthetic_rom_dbg;
+ MemoryRegion pico_sdk_exit_trampoline;
+ MemoryRegion pico_sdk_exit_page;
uint32_t usbctrl_reg[0x100 / sizeof(uint32_t)];
char *bootrom_file;
@@ -113,8 +117,11 @@ struct RP2040State {
uint32_t synthetic_rom_dbg_result[4];
uint32_t synthetic_rom_flash_helper_count[
RP2040_SYNTHETIC_ROM_FLASH_HELPER_COUNT];
+ uint32_t pico_sdk_exit_original_handler;
Clock *sysclk;
};
+void rp2040_set_pico_sdk_exit(RP2040State *s, bool enabled, Error **errp);
+
#endif
diff --git a/tests/tcg/arm/Makefile.softmmu-target b/tests/tcg/arm/Makefile.softmmu-target
index 2f5878c1fa..19355ee2c4 100644
--- a/tests/tcg/arm/Makefile.softmmu-target
+++ b/tests/tcg/arm/Makefile.softmmu-target
@@ -152,6 +152,56 @@ run-rp2040-bootrom-float run-rp2040-flash-lockout: run-%: %
RP2040_TESTS+=rp2040-bootrom-float rp2040-flash-lockout
+rp2040-bkpt-hardfault: rp2040-bkpt-hardfault.S \
+ rp2040-boot2-w25q080.S rp2040.ld
+ $(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
+ -Wl,--build-id=none -x assembler-with-cpp \
+ $(ARM_SRC)/rp2040-boot2-w25q080.S $< \
+ -o $@ -nostdlib -static \
+ -T $(ARM_SRC)/rp2040.ld
+
+run-rp2040-bkpt-hardfault: QEMU_OPTS=-M raspi-pico,strict-uart-pins=off \
+ -serial chardev:output \
+ -semihosting-config enable=on,target=native -kernel
+run-rp2040-bkpt-hardfault: rp2040-bkpt-hardfault
+ $(call run-test, $<, \
+ $(QEMU) -monitor none -display none \
+ -chardev file$(COMMA)path=$<.out$(COMMA)id=output \
+ $(QEMU_OPTS) $<)
+ $(call diff-out,$<,$(ARM_SRC)/rp2040-bkpt-hardfault.ref)
+
+RP2040_TESTS+=rp2040-bkpt-hardfault
+
+rp2040-semihost-exit: rp2040-exit.S \
+ rp2040-boot2-w25q080.S rp2040.ld
+ $(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
+ -DEXIT_MODE=0 -Wl,--build-id=none -x assembler-with-cpp \
+ $(ARM_SRC)/rp2040-boot2-w25q080.S $< \
+ -o $@ -nostdlib -static \
+ -T $(ARM_SRC)/rp2040.ld
+
+run-rp2040-semihost-exit: rp2040-semihost-exit
+ @$(QEMU) -M raspi-pico -monitor none -display none -serial none \
+ -semihosting-config enable=on$(COMMA)target=native \
+ -kernel $<; rc=$$?; test $$rc -eq 23
+
+RP2040_TESTS+=rp2040-semihost-exit
+
+rp2040-pico-sdk-exit: rp2040-exit.S \
+ rp2040-boot2-w25q080.S rp2040.ld
+ $(CC) -mcpu=cortex-m0plus -mthumb -mfloat-abi=soft \
+ -DEXIT_MODE=1 -Wl,--build-id=none -x assembler-with-cpp \
+ $(ARM_SRC)/rp2040-boot2-w25q080.S $< \
+ -o $@ -nostdlib -static \
+ -T $(ARM_SRC)/rp2040.ld
+
+run-rp2040-pico-sdk-exit: rp2040-pico-sdk-exit
+ @$(QEMU) -M raspi-pico$(COMMA)pico-sdk-exit=on \
+ -monitor none -display none -serial none -kernel $<; \
+ rc=$$?; test $$rc -eq 23
+
+RP2040_TESTS+=rp2040-pico-sdk-exit
+
ARM_TESTS+=$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS)
# These objects provide the basic boot code and helper functions for all tests
diff --git a/tests/tcg/arm/system/rp2040-bkpt-hardfault.S b/tests/tcg/arm/system/rp2040-bkpt-hardfault.S
new file mode 100644
index 0000000000..68873d6a8d
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-bkpt-hardfault.S
@@ -0,0 +1,94 @@
+/*
+ * Raspberry Pi Pico BKPT-to-HardFault test
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#define SRAM_END 0x20042000
+#define UART0_DR 0x40034000
+#define HARDFAULT_EXCEPTION 3
+#define SYS_EXIT 0x18
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define ADP_STOPPED_RUNTIME_ERROR 0x20023
+
+.section .vectors, "a", %progbits
+.global vector_table
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word default_handler + 1
+ .word hardfault_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+ ldr r0, =start_message
+ bl puts
+ bkpt 0
+
+ ldr r0, =fail_return_message
+ b test_failed
+
+.thumb_func
+hardfault_handler:
+ mrs r0, ipsr
+ cmp r0, HARDFAULT_EXCEPTION
+ bne fail_exception
+
+ ldr r0, =ok_message
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_APPLICATION_EXIT
+ bkpt 0xab
+
+1:
+ b 1b
+
+fail_exception:
+ ldr r0, =fail_exception_message
+
+test_failed:
+ bl puts
+ movs r0, SYS_EXIT
+ ldr r1, =ADP_STOPPED_RUNTIME_ERROR
+ bkpt 0xab
+
+2:
+ b 2b
+
+.thumb_func
+default_handler:
+ ldr r0, =fail_vector_message
+ b test_failed
+
+.thumb_func
+puts:
+ ldr r2, =UART0_DR
+
+3:
+ ldrb r1, [r0]
+ cmp r1, 0
+ beq 4f
+ str r1, [r2]
+ adds r0, 1
+ b 3b
+
+4:
+ bx lr
+
+.section .rodata, "a", %progbits
+start_message:
+ .asciz "PICO TCG BKPT START\n"
+ok_message:
+ .asciz "PICO TCG BKPT HARDFAULT OK\n"
+fail_return_message:
+ .asciz "PICO TCG BKPT FAIL: RETURNED\n"
+fail_exception_message:
+ .asciz "PICO TCG BKPT FAIL: IPSR\n"
+fail_vector_message:
+ .asciz "PICO TCG BKPT FAIL: VECTOR\n"
diff --git a/tests/tcg/arm/system/rp2040-bkpt-hardfault.ref b/tests/tcg/arm/system/rp2040-bkpt-hardfault.ref
new file mode 100644
index 0000000000..0f023cf1e7
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-bkpt-hardfault.ref
@@ -0,0 +1,2 @@
+PICO TCG BKPT START
+PICO TCG BKPT HARDFAULT OK
diff --git a/tests/tcg/arm/system/rp2040-exit.S b/tests/tcg/arm/system/rp2040-exit.S
new file mode 100644
index 0000000000..ad6d428bd7
--- /dev/null
+++ b/tests/tcg/arm/system/rp2040-exit.S
@@ -0,0 +1,55 @@
+/*
+ * Raspberry Pi Pico host-exit behavior tests
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+.syntax unified
+.cpu cortex-m0plus
+.thumb
+
+#ifndef EXIT_MODE
+#error EXIT_MODE must select semihosting or Pico SDK exit
+#endif
+
+#define EXIT_MODE_SEMIHOST 0
+#define EXIT_MODE_PICO_SDK 1
+#define SRAM_END 0x20042000
+#define SYS_EXIT_EXTENDED 0x20
+#define ADP_STOPPED_APPLICATION_EXIT 0x20026
+#define TEST_EXIT_STATUS 23
+
+.section .vectors, "a", %progbits
+vector_table:
+ .word SRAM_END
+ .word reset_handler + 1
+ .word hardfault_handler + 1
+ .word hardfault_handler + 1
+
+.section .text, "ax", %progbits
+.thumb_func
+.global reset_handler
+reset_handler:
+#if EXIT_MODE == EXIT_MODE_SEMIHOST
+ movs r0, SYS_EXIT_EXTENDED
+ ldr r1, =semihost_exit_block
+ bkpt 0xab
+#elif EXIT_MODE == EXIT_MODE_PICO_SDK
+ movs r0, TEST_EXIT_STATUS
+ bkpt 0
+#else
+#error Unsupported EXIT_MODE
+#endif
+
+1:
+ b 1b
+
+.thumb_func
+hardfault_handler:
+ b hardfault_handler
+
+.section .rodata, "a", %progbits
+.align 2
+semihost_exit_block:
+ .word ADP_STOPPED_APPLICATION_EXIT
+ .word TEST_EXIT_STATUS
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* [PATCH RFC v2 30/30] docs/system/arm: document Raspberry Pi Pico
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (28 preceding siblings ...)
2026-08-30 6:16 ` [PATCH RFC v2 29/30] hw/arm: add optional Pico SDK exit handling Gilles Grimaud
@ 2026-08-30 6:16 ` Gilles Grimaud
2026-09-02 12:01 ` Alex Bennée
2026-09-02 13:20 ` [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Alex Bennée
30 siblings, 1 reply; 49+ messages in thread
From: Gilles Grimaud @ 2026-08-30 6:16 UTC (permalink / raw)
To: qemu-devel; +Cc: alex.bennee, qemu-arm, peter.maydell
From: gilles grimaud <gilles.grimaud@univ-lille.fr>
Document the RP2040 machine lineage, supported and shallow devices, boot ROM profiles, ELF/UF2/raw loading, persistent flash, serial backends, machine properties, flash policy, Pico SDK exit compatibility, and known limitations.
Add the board page to the Arm system manual and its ownership entry to MAINTAINERS.
Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
---
MAINTAINERS | 1 +
docs/system/arm/raspi-pico.rst | 641 +++++++++++++++++++++++++++++++++
docs/system/target-arm.rst | 1 +
3 files changed, 643 insertions(+)
create mode 100644 docs/system/arm/raspi-pico.rst
diff --git a/MAINTAINERS b/MAINTAINERS
index 41494eff2a..0fa93d28c8 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1043,6 +1043,7 @@ F: hw/*/rp2040*
F: include/hw/*/rp2040*
F: hw/arm/raspi_pico.c
F: scripts/uf2-to-flash.py
+F: docs/system/arm/raspi-pico.rst
F: tests/qtest/rp2040*
F: tests/tcg/arm/system/rp2040*
diff --git a/docs/system/arm/raspi-pico.rst b/docs/system/arm/raspi-pico.rst
new file mode 100644
index 0000000000..d28cd20af8
--- /dev/null
+++ b/docs/system/arm/raspi-pico.rst
@@ -0,0 +1,641 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Raspberry Pi Pico board (``raspi-pico``)
+========================================
+
+The ``raspi-pico`` machine models a minimal Raspberry Pi Pico 1 board based
+on the RP2040 microcontroller. The current model is intended for bare-metal
+bring-up and tests that execute code from the RP2040 external flash XIP
+window.
+
+RFC lineage
+-----------
+
+This machine starts from Alex Bennee's 2022 RP2040/Pico RFC patch series.
+The RP2040 SoC skeleton, Pico machine, and memory map are adapted from that
+series to the current QEMU tree. The RFC ``pc-bios/pipico.rom`` image was
+used for bring-up experiments, but it is not installed as a QEMU firmware
+blob by this machine.
+
+RFC patch 0005's mask ROM loading logic is adapted for the current machine:
+an external boot ROM can be supplied explicitly with ``-bios``. QEMU still
+uses a small synthetic boot ROM for direct XIP bring-up, so existing firmware
+tests remain stable without requiring an external firmware image.
+
+Supported devices
+-----------------
+
+ * Two Cortex-M0+ cores. With QEMU's synthetic ROM, core 1 starts in ROM and
+ waits for the SDK FIFO launch sequence. With an external ROM image, the
+ modeled ``PSM.FRCE_OFF.PROC1`` path can release core 1 at the ROM reset
+ vector, but the external-ROM multicore flow remains a bring-up path.
+ * Cortex-M0+ MPU with eight protection regions
+ * 16 KiB boot ROM window
+ * 264 KiB SRAM
+ * 2 MiB external flash contents mapped through the XIP window and XIP alias
+ windows
+ * XIP/SSI flash command path for read, page program, sector erase, unique ID,
+ XIP stream and SDK-style SSI RX DMA reads
+ * Clock generator, crystal oscillator, ring oscillator, PLL_SYS and PLL_USB
+ programmer-visible registers
+ * Reset controller, PSM, watchdog, timer, SYSINFO, SYSCFG, TBMAN and
+ VREG_AND_CHIP_RESET programmer-visible registers
+ * BUSCTRL priority registers and shallow performance counters
+ * SIO core ID, inter-core FIFO, spinlocks, divider and interpolators
+ * DMA engine with memory, UART, XIP stream, XIP/SSI RX and timer DREQ support
+ * IO_BANK0, IO_QSPI, PADS_BANK0 and PADS_QSPI shallow pin-control models
+ * UART0 and UART1 through QEMU serial backends
+ * USB DPRAM and shallow USB controller register storage
+
+Unsupported or shallow devices
+------------------------------
+
+This machine is not a complete RP2040 model. The following blocks are not
+implemented, or are only present as shallow register storage:
+
+ * PIO state machines, instruction execution, FIFOs, IRQs and DMA pacing are
+ not implemented.
+ * USB device and host transactions, endpoint state machines and BOOTSEL
+ mass-storage programming are not implemented. USB DPRAM and the basic
+ controller register window exist for firmware probing only.
+ * SPI0, SPI1, I2C0, I2C1, PWM, ADC and the temperature sensor are not
+ implemented.
+ * BUSCTRL does not model arbitration. Its performance counters are
+ deterministic programmer-visible counters, not measurements of actual bus
+ events.
+ * The RTC block is not implemented.
+ * General GPIO electrical behaviour, pad-level input sampling, edge
+ detection, external pin wiring and alternate UART pin mappings are not
+ implemented. IO_BANK0 is sufficient for function-select storage and for
+ gating the supported UART0/UART1 pins.
+ * The XIP cache is not modeled. XIP aliases are functional views of the same
+ flash storage, but cache hit/miss counters and cache allocation behaviour
+ are not timing-accurate.
+ * QEMU's normal gdbstub can debug the emulated Cortex-M0+ CPUs. The RP2040
+ SWD/debug fabric itself is not implemented: debug pause inputs and
+ ``DBGFORCE``-style registers are stored where useful, but they do not model
+ an external SWD probe connected to the chip.
+ * DMA is implemented for the paths listed above, but DREQ sources belonging
+ to unimplemented peripherals do not transfer data.
+ * The synthetic ROM provides a useful subset of the RP2040 function and data
+ tables, including memory and bit helpers, floating-point helpers and
+ accelerated flash operations. Missing table entries report an explicit
+ not-implemented diagnostic. The external mask ROM path is available
+ through ``-bios`` for user-provided RP2040 ROM images such as
+ ``pipico.rom``; QEMU does not ship that ROM image, and USB BOOTSEL
+ mass-storage mode is not implemented.
+
+Boot options
+------------
+
+For direct bring-up, a firmware image can be loaded into the XIP window with
+``-kernel``. ELF images linked at ``0x10000000`` and Pico 1 UF2 images are
+accepted, with raw images loaded at ``0x10000000`` as a fallback:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico -kernel firmware.elf -serial stdio
+
+The same option accepts the UF2 image normally copied to the Pico's BOOTSEL
+USB mass-storage device:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico -kernel firmware.uf2 -serial stdio
+
+QEMU does not emulate the Pico BOOTSEL USB mass-storage programming mode. A
+UF2 file is loaded directly by ``-kernel``; guest firmware cannot receive a
+UF2 through an emulated USB drive.
+
+The machine also accepts a raw initial flash image:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico,flash-file=flash.bin -serial stdio
+
+Bytes not provided by the raw flash image are initialized to the NOR erased
+state, ``0xff``.
+
+If both ``flash-file`` and ``-kernel`` are specified, the raw flash file is
+loaded first, then the ``-kernel`` image is overlaid into the emulated XIP
+flash. Only the flash ranges covered by the ELF, UF2 or raw image are
+replaced; the rest of the existing raw flash contents are preserved. The
+complete emulated flash image is written back to the raw file, so a later run
+with only ``flash-file`` restarts from the overlaid image.
+Successful guest sector erase and page program commands are also written back
+to the raw file.
+
+The Pico SDK uses the external SPI NOR flash unique ID as the Pico 1 board
+identifier. The emulated flash reports the stable default ID
+``3eb8a7493fcc0608``. Tests that need a different board identity can override
+it with:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico,flash-uid=0011223344556677 \
+ -kernel firmware.elf -serial stdio
+
+The flash UID is not stored in, nor appended to, ``flash-file``; that file
+remains the raw bytes of the guest-addressable flash array.
+
+The ring oscillator ``RANDOMBIT`` stream is backed by QEMU's guest-visible
+random source by default. For reproducible tests, a deterministic stream can
+be requested with:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico,rosc-random-seed=0x1234 \
+ -kernel firmware.elf -serial stdio
+
+Debugging with GDB uses QEMU's normal gdbstub. For example:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico -kernel firmware.elf \
+ -serial stdio -S -gdb tcp::1234
+
+Then connect an ARM embedded GDB:
+
+.. code-block:: bash
+
+ (gdb) target remote :1234
+
+This debugs the emulated Cortex-M0+ CPU through QEMU. It does not emulate
+the RP2040 SWD debug port or an external SWD probe.
+
+By default, guest ``BKPT`` instructions retain their architectural Cortex-M
+behaviour. In particular, ``BKPT #0`` used by the Pico SDK ``_exit()`` path
+is delivered through the normal DebugMonitor/HardFault rules; merely running
+under QEMU does not turn it into a process exit.
+
+Automated tests that deliberately use the Pico SDK exit convention can opt
+in to a machine-local compatibility path:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico,pico-sdk-exit=on \
+ -kernel firmware.elf -serial stdio
+
+When enabled, QEMU overlays the initial application HardFault vector at
+``0x1000010c`` with a trampoline. A HardFault caused by ``BKPT #0`` requests
+guest shutdown using the stacked ``r0`` value as the exit status. Other
+HardFaults are forwarded to the application's original handler. This option
+is disabled by default, requires a valid Thumb HardFault vector in the loaded
+XIP image, and does not modify ``flash-file``. It is a Pico SDK test
+convenience rather than an RP2040 hardware feature. Standard Arm semihosting
+remains available independently with ``-semihosting-config``.
+
+Pico UF2 images can be converted to this raw flash format with:
+
+.. code-block:: bash
+
+ $ scripts/uf2-to-flash.py firmware.uf2 flash.bin
+
+An RP2040 boot ROM image can be supplied explicitly with ``-bios``:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico -bios pipico.rom -serial stdio
+
+The file name is resolved through QEMU's BIOS search path, the same mechanism
+used by other machines for firmware blobs. QEMU does not ship an RP2040 boot
+ROM image for this machine; use ``-bios`` with a user-provided ROM image when
+testing the external mask-ROM path. Direct XIP bring-up keeps using the
+synthetic boot ROM described above, and explicit ``-bios`` still overrides the
+synthetic ROM.
+
+Mask ROM bring-up tracing
+-------------------------
+
+The real mask ROM path can be explored with QEMU's unimplemented-device log:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico,flash-file=flash.bin \
+ -bios pipico.rom -display none -serial none \
+ -d unimp,guest_errors -D rp2040-bios-mmio.log
+
+The RP2040 model names unimplemented MMIO blocks in the log and includes the
+absolute address, register offset, access size and write value. The XIP/SSI
+register block also logs APB register accesses through the same ``unimp`` log
+mask, without logging every normal XIP instruction fetch.
+
+Most early boot polls that were useful during bring-up are now handled by
+the corresponding shallow device models: clocks, XOSC, PLLs, reset
+controller, watchdog scratch registers, XIP control aliases, SYSINFO,
+SYSCFG, VREG_AND_CHIP_RESET, TBMAN, PSM, SIO and the QSPI IO path. New
+``LOG_UNIMP`` messages from this command are therefore a
+signal that the external mask-ROM path has reached a peripheral or register
+that is still outside the modeled subset.
+
+The external ``-bios`` path is intended for hardware-compatibility bring-up
+and for comparing the synthetic ROM against the real RP2040 boot ROM flow.
+It is more sensitive to missing low-level hardware details than the synthetic
+ROM path, and QEMU does not ship the ``pipico.rom`` image.
+
+The in-tree qtests and ``check-tcg`` programs keep coverage self-contained by
+reproducing important hardware and SDK sequences without depending on the
+Pico SDK or on a user-provided mask ROM image.
+
+Clock and XOSC model
+--------------------
+
+The RP2040 datasheet describes clock generator ``SELECTED`` registers as
+one-hot status registers for glitchless muxes, and notes that software should
+poll them until a source switch completes. See datasheet pages 203 to 216.
+The current QEMU model returns stable one-hot selected values immediately and
+updates QEMU ``Clock`` outputs for ``clk-ref``, ``clk-sys``, ``clk-peri``,
+``clk-usb``, ``clk-adc`` and ``clk-rtc``. It models frequencies and software
+visible register state, not analog transition latency.
+
+The crystal oscillator model follows the XOSC programmer-visible behaviour
+described by the datasheet: XOSC starts disabled, firmware writes the enable
+code to ``CTRL``, and then polls ``STATUS.STABLE`` until the oscillator is
+usable. See datasheet pages 217 to 220. QEMU asserts ``STABLE``
+immediately once XOSC is enabled and awake, keeps ``BADWRITE`` sticky until
+cleared, and implements the documented ``STARTUP``, ``DORMANT`` and
+``COUNT`` registers at the level needed by early boot.
+
+The ring oscillator model follows the programmer-visible register layout
+described by the datasheet: ``CTRL``, ``FREQA``, ``FREQB``, ``DORMANT``,
+``DIV``, ``PHASE``, ``STATUS``, ``RANDOMBIT`` and ``COUNT``. See datasheet
+pages 221 to 227. QEMU models a stable nominal ROSC and updates a QEMU
+``Clock`` output from the visible enable/dormant/divider state. ``COUNT`` is
+derived from QEMU virtual time rather than CPU cycles; in normal execution
+this follows elapsed host time, while in ``icount`` mode it follows QEMU's
+deterministic virtual clock. ``RANDOMBIT`` is not an analog oscillator model:
+without ``rosc-random-seed`` it refills from QEMU's guest-visible random
+source, and with ``rosc-random-seed`` it uses a deterministic pseudo-random
+stream for repeatable tests. The model does not emulate analog frequency
+variation with process, voltage or temperature.
+
+The QSPI IO bank model implements the documented IO_QSPI register layout for
+the six QSPI pins. It stores each pin's ``CTRL`` register, returns stable
+zero ``STATUS`` values, implements shallow interrupt enable/force/status
+registers, supports the RP2040 atomic aliases, and forwards forced
+``GPIO_QSPI_SS`` output changes to the XIP/SSI flash model. This is enough
+for boot firmware to configure the QSPI pin muxing and to bracket serial flash
+commands around the XIP/SSI controller. It does not emulate pad electrical
+behaviour or serial flash transfers; those belong to the pad and XIP/SSI
+models.
+
+Reset controller model
+----------------------
+
+The RP2040 datasheet describes the reset controller at ``0x4000c000`` with
+``RESET``, ``WDSEL`` and ``RESET_DONE`` registers. ``RESET`` holds a
+peripheral in reset while its bit is set, and ``RESET_DONE`` reports that the
+peripheral's registers are ready once reset is deasserted. See datasheet
+pages 175 to 177.
+
+The current QEMU model stores ``RESET`` and ``WDSEL`` for documented bits
+0..24, implements the RP2040 atomic alias windows, and derives
+``RESET_DONE`` immediately as the inverse of ``RESET`` for those bits. It
+does not yet propagate resets into the individual peripheral models or model
+reset completion delays.
+
+VREG and chip reset model
+-------------------------
+
+The RP2040 datasheet describes the shared ``VREG_AND_CHIP_RESET`` register
+window at ``0x40064000`` with ``VREG``, ``BOD`` and ``CHIP_RESET`` registers.
+``CHIP_RESET`` records chip-level reset sources: power-on/brown-out, RUN pin,
+and Rescue Debug Port. See datasheet pages 157 to 158 and 167.
+
+The current QEMU model stores the writable ``VREG`` and ``BOD`` fields,
+reports ``VREG.ROK`` as stable when the regulator is enabled and not in high
+impedance mode, and stores the software-visible Rescue Debug Port flag in
+``CHIP_RESET``. Watchdog reset cause is reported by the watchdog block's
+``REASON`` register; it is not reflected in ``CHIP_RESET`` because the
+documented ``CHIP_RESET`` source fields do not include watchdog reset.
+
+TBMAN model
+-----------
+
+The RP2040 datasheet describes ``TBMAN`` as a testbench manager used during
+chip development simulations. On real hardware it only exposes a
+``PLATFORM`` register indicating that the platform is ASIC; this is duplicated
+by ``SYSINFO.PLATFORM``. See datasheet pages 309 to 310.
+
+The current QEMU model implements this real-chip subset and returns
+``TBMAN.PLATFORM.ASIC`` set. It deliberately does not expose testbench
+simulation controls, because those controls would imply a simulation
+environment outside the RP2040 SoC model.
+
+PSM model
+---------
+
+The RP2040 datasheet describes the power-on state machine at ``0x40010000``
+with ``FRCE_ON``, ``FRCE_OFF``, ``WDSEL`` and ``DONE`` registers. The Pico
+SDK uses ``FRCE_OFF.PROC1`` in ``multicore_reset_core1()`` to hold core 1 off
+and then release it before the ROM FIFO launch protocol. See datasheet pages
+179 to 182.
+
+The current QEMU model stores the documented bits 0..16, implements the
+RP2040 atomic alias windows, and derives ``DONE`` immediately as the inverse
+of ``FRCE_OFF``. On reset, ``FRCE_OFF`` is clear. Setting
+``FRCE_OFF.PROC1`` powers off proc1 in the QEMU model; clearing it powers
+proc1 back on at the ROM reset vector.
+
+PLL model
+---------
+
+The RP2040 datasheet describes ``PLL_SYS`` and ``PLL_USB`` at ``0x40028000``
+and ``0x4002c000``. Each PLL exposes ``CS``, ``PWR``, ``FBDIV_INT`` and
+``PRIM`` registers; firmware powers the PLL, waits for ``CS.LOCK``, and then
+enables the post dividers. The documented output frequency is
+``(FREF / REFDIV) * FBDIV / (POSTDIV1 * POSTDIV2)``. See datasheet pages
+228 to 233.
+
+The current QEMU model is intentionally shallow. It stores the visible
+registers, implements atomic alias writes, reports ``CS.LOCK`` immediately
+when the PLL core is powered, and publishes a calculated QEMU ``Clock``
+output. This does not change instruction execution speed directly. In QEMU,
+``Clock`` objects describe the modeled hardware clock tree; TCG execution
+rate is not a cycle-accurate function of the guest PLL. The RP2040 clock
+generator model still uses fixed PLL_SYS/PLL_USB frequencies, so dynamic PLL
+output wiring is left for a later fidelity step.
+
+RP2040 flash and XIP model
+--------------------------
+
+The RP2040 datasheet describes a 16 KiB mask ROM at ``0x00000000``, 264 KiB
+of SRAM starting at ``0x20000000``, and external flash accessed through the
+QSPI execute-in-place hardware. See the RP2040 datasheet pages 120 to 122.
+
+The flash is not directly attached as an ordinary parallel memory. System
+bus reads to the 16 MiB XIP window starting at ``0x10000000`` are translated
+by the XIP hardware into external serial flash transfers. The XIP block also
+contains a 16 KiB cache and several aliases with different cache behaviour.
+See datasheet pages 122 to 124. The current QEMU model implements the main
+XIP window and the ``NOALLOC``, ``NOCACHE`` and ``NOCACHE_NOALLOC`` aliases as
+functional views of the same flash storage, while leaving cache timing for
+later work.
+
+The RP2040 XIP path is backed by the SSI controller. The datasheet describes
+the SSI as a Synopsys DW_apb_ssi controller connected to the QSPI pins and
+forming part of the XIP block. It can be configured to issue common serial
+flash read sequences, including the standard ``0x03`` read command with a
+24-bit address and the continuation-read path used by the Pico SDK
+``flash/ssi_dma`` example. The SSI DMA registers are modeled sufficiently for
+RX DMA pacing from ``SSI_DR0`` through ``DREQ_XIP_SSIRX``. See datasheet
+pages 567 to 569.
+
+The synthetic boot ROM provides the minimum startup path required by this
+machine. Core 0 copies the 256-byte boot2 image from XIP into SRAM and runs
+it there. Core 1 waits in ROM for the SDK-compatible FIFO launch sequence,
+echoes each word, installs the requested ``VTOR`` and stack pointer, and
+branches to the requested entry point. Higher-level multicore lockout and
+flash protocols remain firmware responsibilities.
+
+The synthetic ROM also publishes RP2040-compatible lookup, function and data
+tables. It implements ``memcpy``, ``memset``, their word-oriented variants,
+count/reverse helpers, and the floating-point and double-precision entries
+covered by the in-tree tests. These routines call a private QEMU service
+region to provide the same functional result more efficiently; they do not
+model ROM instruction timing.
+
+The synthetic flash table entries similarly accelerate connect, XIP mode,
+cache-flush, erase and program operations through the QEMU flash model. Page
+program and sector erase still enforce the modeled NOR rules. These helpers
+bypass the guest-visible SSI command sequence and complete atomically from the
+CPUs' point of view, so they belong to the synthetic-ROM execution profile,
+not to the RP2040 hardware model. Firmware tested with an external
+``pipico.rom`` instead exercises the modeled SIO lockout and SSI/XIP paths.
+Unsupported ROM lookups, including ``reset_usb_boot``, retain an explicit
+not-implemented diagnostic.
+
+The external ``pipico.rom`` mask ROM path executes a user-provided RP2040
+boot ROM image through ``-bios``. It is useful when validating behaviour
+that depends on real ROM hardware interactions, provided those interactions
+stay within the modeled peripheral subset. It is not a USB BOOTSEL
+emulation path.
+
+Firmware intended to terminate QEMU in a test can use standard Arm
+semihosting ``SYS_EXIT`` or ``SYS_EXIT_EXTENDED`` with ``bkpt #0xab`` when
+semihosting is enabled. Ordinary ``bkpt`` instructions otherwise retain
+their architectural Cortex-M exception behaviour unless the explicit
+``pico-sdk-exit`` compatibility option described above is enabled.
+
+The lower-level SSI/XIP command path remains responsible for modelling serial
+flash command state and for raising the documented QEMU HardFault policy when
+guest code executes from XIP while the flash model is busy. Tests for that
+busy/fault behaviour target the SSI/XIP model directly, or the ``pipico.rom``
+path once the relevant ROM/hardware interaction is supported, rather than the
+synthetic ROM's atomic helper shortcuts.
+
+For software-driven flash operations, firmware programs the SSI through its
+APB register interface at ``XIP_SSI_BASE``. The important registers for the
+initial emulation are ``CTRLR0``, ``CTRLR1``, ``SSIENR``, ``SER``, ``BAUDR``,
+``SR`` and the data register window beginning at ``DR0``. The data register
+window feeds the transmit FIFO on writes and pops the receive FIFO on reads.
+See datasheet pages 597 to 602.
+
+Flash programming policy
+------------------------
+
+The emulation models guest-visible flash programming through the RP2040
+XIP/SSI path rather than as a board-private back door. The implemented
+command set is:
+
+ * ``0x01`` write status
+ * ``0x06`` write enable
+ * ``0x05`` read status
+ * ``0x35`` read status register 2
+ * ``0x03`` read
+ * ``0x4b`` read unique ID
+ * ``0xeb`` quad I/O read
+ * ``0xa0`` continuation read
+ * ``0x02`` page program
+ * ``0x20`` sector erase
+
+The flash contents follow NOR semantics:
+
+ * the erased state is ``0xff``;
+ * programming can only clear bits, equivalent to ``old & new``;
+ * page program is limited to 256-byte pages;
+ * sector erase operates on 4096-byte sectors.
+
+Unsupported flash commands are currently ignored. Out-of-range page program
+and sector erase commands have no effect. If such a command consumed write
+enable state, the emulation clears write enable and does not enter the busy
+state.
+
+The datasheet notes that software must consider XIP cache coherence around
+flash programming operations, and describes ROM routines that reconfigure the
+SSI for erase/program flows before restoring a slow XIP read configuration.
+It also notes that, between parts of that call sequence, the SSI is not in a
+state where it can handle XIP accesses. See datasheet pages 122 to 124 and
+the boot ROM flash routine discussion on pages 134 to 135.
+
+QEMU therefore uses the following deterministic policy: while an emulated
+flash page program or sector erase is in progress, any access through the XIP
+memory window to the same flash produces a bus error. On the Cortex-M0+,
+unsuitable instruction fetches or faulting memory accesses are reported via
+HardFault; the RP2040 datasheet describes the Cortex-M0+ default memory map
+and HardFault behaviour on pages 71 to 72. This is an emulation policy chosen
+to make incorrect execute-from-XIP-while-programming behaviour visible and
+testable. It is not intended to model precise flash timing.
+
+UART models
+-----------
+
+The RP2040 datasheet states that each UART instance is based on ARM PrimeCell
+UART PL011 revision r1p5, with 32-byte transmit and receive FIFOs. It also
+states that PL011 modem mode and IrDA mode are not supported by RP2040. See
+datasheet pages 417 to 419.
+
+The current QEMU model therefore wires UART0 at ``0x40034000`` and UART1 at
+``0x40038000`` to QEMU's existing PL011 device. The register list and flag
+register layout match the RP2040 UART programmer's model: ``UARTDR`` is at
+offset ``0x000``, ``UARTRSR/UARTECR`` at ``0x004`` and ``UARTFR`` at
+``0x018``. See datasheet pages 429 to 431.
+
+The RP2040 APB atomic alias windows for both UARTs are also mapped, because
+the Pico SDK uses them when configuring UART registers. ``UARTDMACR`` drives
+UART0 and UART1 TX/RX DREQ lines into the RP2040 DMA model.
+
+The console path uses QEMU's standard serial backends, so the host side can
+still be selected with the usual ``-serial`` or ``-chardev`` options. UART0
+uses the first serial backend and UART1 uses the second one, for example:
+
+.. code-block:: bash
+
+ $ qemu-system-arm -machine raspi-pico -kernel firmware.elf \
+ -serial stdio -serial tcp:127.0.0.1:1234,server,nowait
+
+By default, the Pico machine requires the guest to route UARTs through
+``IO_BANK0`` first: GPIO0/GPIO1 must have ``FUNCSEL=UART`` before UART0 host
+serial transmit/receive is connected, and GPIO4/GPIO5 do the same for UART1.
+This catches firmware that writes UART registers but forgets the Pico GPIO
+function select.
+
+For compatibility with very small bring-up payloads, this check can be
+disabled with ``-machine raspi-pico,strict-uart-pins=off``. ``PADS_BANK0``
+stores the documented pad-control registers separately from this UART path.
+
+For the initial console use case, the documented stable status behaviour is:
+
+ * ``UARTFR.TXFE`` and ``UARTFR.RXFE`` follow QEMU PL011 FIFO state.
+ * ``UARTFR.TXFF`` and ``UARTFR.RXFF`` follow QEMU PL011 FIFO fullness.
+ * ``UARTFR.BUSY`` is not modeled with RP2040 transmission timing.
+ * ``UARTFR.RI``, ``UARTFR.DCD`` and ``UARTFR.DSR`` are treated as absent
+ modem-status inputs and remain deasserted.
+ * ``UARTFR.CTS`` has no GPIO-backed CTS input yet and remains deasserted
+ unless a future RP2040 UART shim connects it to the GPIO model.
+
+This is sufficient for polling transmit firmware that waits for ``TXFF`` to
+clear before writing ``UARTDR``. A dedicated RP2040 UART wrapper can be added
+later if firmware needs GPIO-backed CTS/RTS flow control, precise ``BUSY``
+timing, or stricter masking of unsupported PL011 modem/IrDA features.
+
+Known limitations
+-----------------
+
+ * Core 0 runs normally. Core 1 starts in the synthetic ROM, echoes the
+ SDK-compatible FIFO launch sequence, installs the provided ``VTOR``/stack,
+ and branches to the provided entry point. With an external mask ROM,
+ ``PSM.FRCE_OFF.PROC1`` can release core 1 at the ROM reset vector, but the
+ external-ROM core1 boot flow is not yet covered by an in-tree regression.
+ * UART0 and UART1 currently use QEMU's PL011 model with the RP2040
+ compatibility policy documented above. The strict pin check currently
+ covers the Pico GPIO0/GPIO1 UART0 path and GPIO4/GPIO5 UART1 path only;
+ alternate RP2040 UART pin mappings remain future work.
+ * ``IO_BANK0`` stores GPIO function-select, override and interrupt registers,
+ implements RP2040 atomic aliases, and gates UART host serial I/O for the
+ GPIO0/GPIO1 UART0 path and GPIO4/GPIO5 UART1 path. It is still a
+ simplified routing model: it does not yet derive pad input levels from
+ ``PADS_BANK0`` electrical state, does not route general SIO GPIO outputs to
+ external pins, and does not connect arbitrary peripheral functions through
+ the GPIO matrix. Edge detection and full interrupt source modelling remain
+ future work.
+ * SIO divider and interpolator results are computed immediately when their
+ registers are accessed. QEMU does not model the RP2040 single-cycle timing,
+ divider latency, or cycle-accurate pipeline effects for these datapaths.
+ * ``PADS_BANK0`` and ``PADS_QSPI`` store documented pad-control registers and
+ implement RP2040 atomic aliases. They do not model electrical pad
+ behaviour and do not currently gate UART or XIP operation.
+ * The XIP cache and detailed timing are not yet modeled. ``XIP_FLUSH`` is
+ treated as immediately complete, and ``XIP_CTR_HIT``/``XIP_CTR_ACC`` do not
+ report real cache hit/miss behaviour. The Pico SDK ``cache_perfctr``
+ example is therefore expected to remain a partial validation: ordinary XIP
+ execution works, but cache-performance measurements are not meaningful in
+ this emulation. The XIP streaming FIFO is modeled functionally for direct
+ reads and DMA from ``XIP_AUX_BASE``, but without flash idle-cycle timing.
+ The SSI bulk RX path used by the SDK ``ssi_dma`` example is also modeled
+ functionally, including the 32-bit byte order expected with DMA ``BSWAP``,
+ but it does not model serial-clock throughput or FIFO refill latency.
+ The XIP control and SSI APB register blocks do handle the RP2040 atomic
+ ``XOR``/``SET``/``CLR`` aliases.
+ * The external flash unique ID is modeled as an 8-byte QEMU property exposed
+ through the SPI NOR ``0x4b`` RUID command. It is stable by default and can
+ be overridden with ``flash-uid``; it is not persisted in ``flash-file``.
+ * ``IO_QSPI`` stores pin-control and interrupt registers and forwards forced
+ ``GPIO_QSPI_SS`` changes to the XIP/SSI model. It does not emulate the
+ electrical QSPI pads or a separate serial bus.
+ * The ROSC model exposes stable register behaviour, a nominal clock and a
+ QEMU-backed ``RANDOMBIT`` stream. It does not model analog frequency
+ variation or physical oscillator entropy.
+ * The synthetic ROM supports direct boot2/application launch, the core1 FIFO
+ launch sequence, a subset of the ROM function/data tables, and accelerated
+ floating-point and flash helpers. These helpers are functionally tested
+ but intentionally bypass ROM instruction timing and, for flash operations,
+ the low-level SSI command sequence. The external ``-bios`` path can run a
+ user-provided real RP2040 mask ROM image, but it is limited by the same
+ missing peripheral models as the rest of the machine, notably USB BOOTSEL
+ mass-storage mode.
+ * ``pico-sdk-exit`` is disabled by default. When enabled it overlays only
+ the initial XIP HardFault vector at ``0x1000010c``. Faults not caused by
+ ``BKPT #0`` are chained to the handler found there when the image was
+ loaded. It does
+ not follow a later guest ``VTOR`` relocation or a runtime replacement of
+ that vector, and should therefore be used only as a test convenience for
+ the Pico SDK exit convention.
+ * QEMU does not emulate the Pico BOOTSEL USB mass-storage programming mode.
+ UF2 images are accepted only as host-loaded ``-kernel`` inputs.
+ * The USB model provides DPRAM, stable VBUS detection, register storage,
+ atomic aliases and interrupt mask/force/status calculation. It does not
+ implement packet transactions, endpoint state machines, host mode or a
+ BOOTSEL mass-storage device. PIO and most other peripherals are not yet
+ implemented. DMA supports memory-to-memory transfers, XIP stream and
+ XIP/SSI RX DREQ pacing, UART0/UART1 TX/RX DREQ pacing, DMA timer pacing
+ from QEMU virtual time, read/write ring wrapping,
+ the documented sniff accumulator modes, immediate channel abort, and
+ bus-error status reporting through ``CTRL_TRIG``, ``INTR`` and ``INTS0/1``.
+ UART DREQs are exposed through the current PL011-backed UART models and
+ follow the PL011
+ FIFO occupancy plus ``UARTDMACR`` enable bits; fine-grained UART timing is
+ not modeled. DMA timer pacing uses the documented ``X/Y`` fractional timer
+ registers and the Pico's nominal 125 MHz system clock as the virtual source.
+ DMA bus errors are reported with the documented
+ ``READ_ERROR`` or ``WRITE_ERROR`` plus ``AHB_ERROR`` bits, clear ``BUSY``,
+ keep the remaining transfer count, and raise the raw channel interrupt.
+ QEMU does not model DMA pipeline latency: abort status self-clears
+ immediately, and the reported fault address is the exact attempted address
+ rather than a delayed approximate address.
+ * BUSCTRL stores bus priority and acknowledges updates immediately. Its four
+ performance selectors and counters are programmer-visible, but selected
+ counters advance deterministically when read rather than counting actual
+ crossbar events. Bus arbitration priority does not affect memory timing.
+ * ``SYSINFO`` and ``SYSCFG`` expose the documented register layout used by
+ early firmware. ``PROC0_NMI_MASK`` is wired for interrupt sources routed
+ through the RP2040 IRQ shim, currently including UART0, and
+ ``MEMPOWERDOWN`` makes powered-off ROM and SRAM bank windows return memory
+ transaction errors. ``DBGFORCE`` is stored but not connected to an
+ SWD/debug fabric model.
+ * ``VREG_AND_CHIP_RESET`` stores the voltage-regulator and brown-out detector
+ control fields and exposes stable chip reset status. Analog regulator and
+ brown-out behaviour is not modeled.
+ * ``TBMAN`` exposes only the documented real-chip ``PLATFORM`` register.
+ * ``PSM`` stores force-on, force-off and watchdog-select bits. ``DONE`` is
+ derived immediately from ``FRCE_OFF``; analog power sequencing delays are
+ not modeled.
+ * The watchdog models ``CTRL``, ``LOAD``, ``REASON``, ``SCRATCH`` and
+ ``TICK``, including ``CTRL.TRIGGER`` and the RP2040-E1 double-decrement
+ behaviour. Debug pause inputs are stored but not connected to a debug
+ fabric model.
+ * The TIMER block models the microsecond counter, ``ALARM0`` through
+ ``ALARM3``, ``ARMED``, ``DBGPAUSE``, ``PAUSE`` and the interrupt
+ registers. Alarm outputs are connected to RP2040 IRQs 0 through 3. The
+ counter advances on QEMU virtual time rather than CPU-cycle timing, and
+ debug pause inputs have no external debug-fabric side effects.
diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst
index e34492402f..65faa75138 100644
--- a/docs/system/target-arm.rst
+++ b/docs/system/target-arm.rst
@@ -97,6 +97,7 @@ Board-specific documentation
arm/imx8m
arm/orangepi
arm/raspi
+ arm/raspi-pico
arm/collie
arm/sx1
arm/stellaris
--
2.55.0
^ permalink raw reply related [flat|nested] 49+ messages in thread* Re: [PATCH RFC v2 30/30] docs/system/arm: document Raspberry Pi Pico
2026-08-30 6:16 ` [PATCH RFC v2 30/30] docs/system/arm: document Raspberry Pi Pico Gilles Grimaud
@ 2026-09-02 12:01 ` Alex Bennée
0 siblings, 0 replies; 49+ messages in thread
From: Alex Bennée @ 2026-09-02 12:01 UTC (permalink / raw)
To: Gilles Grimaud; +Cc: qemu-devel, qemu-arm, peter.maydell
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> Document the RP2040 machine lineage, supported and shallow devices, boot ROM profiles, ELF/UF2/raw loading, persistent flash, serial backends, machine properties, flash policy, Pico SDK exit compatibility, and known limitations.
>
> Add the board page to the Arm system manual and its ownership entry to MAINTAINERS.
>
> Signed-off-by: gilles grimaud <gilles.grimaud@univ-lille.fr>
> ---
> MAINTAINERS | 1 +
> docs/system/arm/raspi-pico.rst | 641 +++++++++++++++++++++++++++++++++
> docs/system/target-arm.rst | 1 +
> 3 files changed, 643 insertions(+)
> create mode 100644 docs/system/arm/raspi-pico.rst
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 41494eff2a..0fa93d28c8 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1043,6 +1043,7 @@ F: hw/*/rp2040*
> F: include/hw/*/rp2040*
> F: hw/arm/raspi_pico.c
> F: scripts/uf2-to-flash.py
> +F: docs/system/arm/raspi-pico.rst
> F: tests/qtest/rp2040*
> F: tests/tcg/arm/system/rp2040*
>
> diff --git a/docs/system/arm/raspi-pico.rst b/docs/system/arm/raspi-pico.rst
> new file mode 100644
> index 0000000000..d28cd20af8
> --- /dev/null
> +++ b/docs/system/arm/raspi-pico.rst
> @@ -0,0 +1,641 @@
> +.. SPDX-License-Identifier: GPL-2.0-or-later
> +
> +Raspberry Pi Pico board (``raspi-pico``)
> +========================================
> +
> +The ``raspi-pico`` machine models a minimal Raspberry Pi Pico 1 board based
> +on the RP2040 microcontroller. The current model is intended for bare-metal
> +bring-up and tests that execute code from the RP2040 external flash XIP
> +window.
> +
> +RFC lineage
> +-----------
> +
> +This machine starts from Alex Bennee's 2022 RP2040/Pico RFC patch series.
> +The RP2040 SoC skeleton, Pico machine, and memory map are adapted from that
> +series to the current QEMU tree. The RFC ``pc-bios/pipico.rom`` image was
> +used for bring-up experiments, but it is not installed as a QEMU firmware
> +blob by this machine.
> +
> +RFC patch 0005's mask ROM loading logic is adapted for the current machine:
> +an external boot ROM can be supplied explicitly with ``-bios``. QEMU still
> +uses a small synthetic boot ROM for direct XIP bring-up, so existing firmware
> +tests remain stable without requiring an external firmware image.
I don't think you need to document the lineage in the user facing docs.
You can add Based-on: <msgid> tags to the individual commits where the
patch started out from one of mine if you wish.
The rest is a lot but I can't really complain about comprehensive
documentation. I do wonder how much is repeating stuff that could just
be pointing as RaspberryPi's extensive and good documentation?
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread
* Re: [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine
2026-08-29 23:42 [PATCH RFC v2 00/30] arm: add Raspberry Pi Pico/RP2040 machine Gilles Grimaud
` (29 preceding siblings ...)
2026-08-30 6:16 ` [PATCH RFC v2 30/30] docs/system/arm: document Raspberry Pi Pico Gilles Grimaud
@ 2026-09-02 13:20 ` Alex Bennée
30 siblings, 0 replies; 49+ messages in thread
From: Alex Bennée @ 2026-09-02 13:20 UTC (permalink / raw)
To: Gilles Grimaud; +Cc: qemu-devel, qemu-arm
Gilles Grimaud <gilles.grimaud@univ-lille.fr> writes:
> From: gilles grimaud <gilles.grimaud@univ-lille.fr>
>
> This RFC adds a Raspberry Pi Pico / RP2040 machine to QEMU.
>
> The work continues the RP2040/Pico RFC posted by Alex Bennee in 2022:
>
> https://patchew.org/QEMU/20220110175104.2908956-1-alex.bennee%40linaro.org/
>
> The target is intended primarily for debugging bare-metal Pico firmware and
> for running firmware tests in CI without physical hardware. It provides a
> functional, test-oriented model rather than a cycle-accurate RP2040 model.
>
> The machine supports two boot paths. The default synthetic ROM implements
> the RP2040 boot protocol and selected ROM service tables, with optional
> QEMU-accelerated helpers for firmware testing. Alternatively, users may
> supply an RP2040 mask ROM image with -bios to exercise the modeled hardware
> path. QEMU does not ship that image, and USB BOOTSEL mass storage is not
> currently supported.
I've finished my first pass through, I'll re-review once you post v3.
However some general points:
- checkpatch everything
- format commit messages
- reduce duplication
- ensure only one architectural features being modified per patch
I've also included our experimental AI reviewer so takes it's
suggestions with a pinch of salt:
https://patchpilot.linaro.org/run/b132da76b734#stage=review
but it might be worth a read through none the less.
>
<snip>
--
Alex Bennée
Virtualisation Tech Lead @ Linaro
^ permalink raw reply [flat|nested] 49+ messages in thread