From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047112; cv=none; d=zohomail.com; s=zohoarc; b=FkG/r23DfFOaIjlehnBsW3w3+dWqSA2iYA3O0NgKG4ivePUuma42TnXVXXzvcC65OkqvfAb+e7APRj/Bjnny8JQkI82CiMXiCoxWz/iKOzjmhs2xJ7dWYeXcbuqKcLThUCvQqgK9ZZCHlp+ZPrEm/UD/tyBJSJ6XIcEVQltY7Io= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047112; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=RMucFzJPN2MoGUfPz33012reZYmnxRZy02J6azk5YUM=; b=Uf6eAuxHmgb7NOkMLiGR8ZR19Q+x5afBCh2HNaFrK6vO+k1VSRs9vUt3dI5Cm7k+cfpLuZPz5Xdm1bztfstz62fj2+xSdQ6NzrqRL6LMxtQwB4JfqHR+zV1vP10C3GLuLdX2QDSw6HIae9PnDMhFsnhSQQ/U4JQ5Abw4txHIe6g= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 17880471122359.005042868792316; Sat, 29 Aug 2026 16:45:12 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiB-000814-01; Sat, 29 Aug 2026 19:43:47 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Si9-00080R-7E; Sat, 29 Aug 2026 19:43:45 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Si6-0006BT-Vf; Sat, 29 Aug 2026 19:43:44 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXWzZ5GmLz7xf0; Sun, 30 Aug 2026 01:43:38 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzZ3DfLz7vrV; Sun, 30 Aug 2026 01:43:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047018; bh=RMucFzJPN2MoGUfPz33012reZYmnxRZy02J6azk5YUM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=hBJztcWL0MBIyd8VcJpKa5v9W2R4iF7QKC5JjVwt8uUAvD7ur7cruaF84PMdnpBEW FQVxPheMIQLy2O1UIyP4rLl/e8H5h0aB4RUsygawniD5sPgd5GnLUM57Z+xGPu+oc+ e103lig8xC6Hqo/xAXNYwRzFO8PVt9rT9N7TKDTO4NxaOm4S7BrKAgDH5k/9Yi5BJ6 z0JTRogJMFfR3VI5b7vbHSI9TgDmrA3bQDzH+ElwUOFyhcBWJjgnlbBTi6x2aCNYeG PoGpFe6SNtHL1Xt/LjYWML6gXnfQN2F73YKPMixxpvN7yb/UJLVTcpdIxg2lVBs4ZY ygcN1dHBeRRYw== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Peter Maydell Subject: [PATCH RFC v2 01/30] target/arm: support Cortex-M0+ MPU Date: Sun, 30 Aug 2026 01:42:35 +0200 Message-ID: <20260829234308.33725-2-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047116502158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud 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 pre= sent and clear the region state on reset. Signed-off-by: gilles grimaud --- 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] =3D value & 0xffffff80; + if (!arm_feature(&cpu->env, ARM_FEATURE_M_SECURITY)) { + cpu->env.v7m.vecbase[!attrs.secure] =3D value & 0xffffff80; + } break; case 0xd0c: /* Application Interrupt/Reset Control (AIRCR) */ if ((value >> R_V7M_AIRCR_VECTKEY_SHIFT) =3D=3D 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 t= ype) 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) } =20 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 =3D cpu->pmsav7_dregion; =20 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, S1T= ranslate *ptw, /* PMSAv8 */ ret =3D 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 =3D 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 mask= reg, uint32_t val) !arm_v7m_is_handler_mode(env))) { write_v7m_control_spsel(env, (val & R_V7M_CONTROL_SPSEL_MASK) = !=3D 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 =3D env->v7m.control[env->v7m.secure]; + env->v7m.control[env->v7m.secure] &=3D ~R_V7M_CONTROL_NPRIV_MA= SK; env->v7m.control[env->v7m.secure] |=3D val & R_V7M_CONTROL_NPR= IV_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))) { /* --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047105; cv=none; d=zohomail.com; s=zohoarc; b=Tl7ic94eRL4EdobW5dLwelZrbp5iWKdvwQM48Gvt01koVmKwUEBEZp2n/E9QELDrx+F0cikKkzWADQ3RSwX9wow52b1rlLc0lE75ObL9WmovgsE6huiFiVfaaRW4kKXqmY28q3xEZvOoelYqtl8uFjT9NievZpnP2yebKiwmxJU= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047105; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=mR3cZMhs8jJwtsg810+4NT9bWS2wNRA6jz/tsbJBO84=; b=mTp5EaZYGjz0W40kJ5+ZjQrt70QF5RS3ho2RRTJlFWn/4j8s1mUYtCleO7bhrwUI+jdxE/MahtqadDcXFYjF/5j+vGG750UTFhpurYy3bvqUwywc4T7qSh8dBpnbpgeEzfbsUQ2fVs5EEIak8BMLFx+Ws24rAad7DoEBr/wrJAM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047105167307.20498224590347; Sat, 29 Aug 2026 16:45:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiG-00082B-DI; Sat, 29 Aug 2026 19:43:52 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiE-00081y-Uf; Sat, 29 Aug 2026 19:43:50 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiC-0006CN-WB; Sat, 29 Aug 2026 19:43:50 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzb0T8dzBqXY; Sun, 30 Aug 2026 01:43:39 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzZ52Mjz7v19; Sun, 30 Aug 2026 01:43:38 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047019; bh=mR3cZMhs8jJwtsg810+4NT9bWS2wNRA6jz/tsbJBO84=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZMwjgUdfxt/J1bI5824g7tf7ZMiKSC3IuzSvU91r1CiWy/t9DwQMrXTVXFbqdCUWf ql8z3W+iWLUlY3NsC//KCIWBBSVVS1v0sFVSRUsQ1e2r8A5ywbKGP7nGp179Zvp9g0 w8XZxHvfKoEaEou8F10mi9oxqKNE9qKs+nW71ZXKp3O1uGb8BAADrQfPP8Dv2aqQ96 hafJVP9jYiz6Zcx/ElIEx5snuw2GYgCzEniudvlsxItavEbyO3FV3yB94rbdOpD88B eaIv1Tcgy0Nw3xH5IdrO/N2Gvb9z6Mz6rYhu2A6E/LscLlFsSmJDsnXCAc0VOzNB54 PzQQa05TJcGdA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Peter Maydell , =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= , Paolo Bonzini , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 02/30] hw/char/pl011: expose DMA request outputs Date: Sun, 30 Aug 2026 01:42:36 +0200 Message-ID: <20260829234308.33725-3-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047110636158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Expose named TX and RX DMA request GPIO outputs from the PL011 model. Updat= e them when UARTDMACR or FIFO state changes so SoC DMA controllers can cons= ume PL011 pacing without device-specific callbacks. Add a qtest covering TX and RX request assertion and deassertion. Signed-off-by: gilles grimaud --- 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, Cha= rdev *chr) #define LCR_FEN (1 << 4) #define LCR_BRK (1 << 0) =20 +/* 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; } =20 +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 =3D 0; @@ -190,6 +204,7 @@ static void pl011_fifo_rx_put(void *opaque, uint32_t va= lue) trace_pl011_fifo_rx_full(); s->flags |=3D PL011_FLAG_RXFF; } + pl011_update_dreq(s); if (s->read_count =3D=3D s->read_trigger) { s->int_level |=3D INT_RX; pl011_update(s); @@ -258,6 +273,7 @@ static void pl011_write_txdata(PL011State *s, uint8_t d= ata) pl011_loopback_tx(s, data); s->int_level |=3D INT_TX; pl011_update(s); + pl011_update_dreq(s); } =20 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 =3D 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 =3D 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 =3D value; - if (value & 3) { - qemu_log_mask(LOG_UNIMP, "pl011: DMA not implemented\n"); - } + s->dmacr =3D 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) =20 s->ibrd &=3D IBRD_MASK; s->fbrd &=3D FBRD_MASK; + s->dmacr &=3D DMACR_RXDMAE | DMACR_TXDMAE | DMACR_DMAONERR; + pl011_update_dreq(s); =20 return 0; } @@ -653,6 +671,8 @@ static void pl011_init(Object *obj) for (i =3D 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); =20 s->clk =3D 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 =3D false; pl011_reset_rx_fifo(s); pl011_reset_tx_fifo(s); + pl011_update_dreq(s); } =20 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 =3D \ 'stm32l4x5_usart-test'] =20 qtests_arm =3D \ + (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-time= r-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 =3D NULL; + QListEntry *entry; + QList *children; + + response =3D qtest_qmp(qts, + "{ 'execute': 'qom-list'," + " 'arguments': { 'path': '/machine/unattached' }= }"); + children =3D qdict_get_qlist(response, "return"); + + QLIST_FOREACH_ENTRY(children, entry) { + QDict *child =3D qobject_to(QDict, qlist_entry_obj(entry)); + + if (g_str_equal(qdict_get_str(child, "type"), "child")) { + return g_strdup_printf("/machine/unattached/%s", + qdict_get_str(child, "name")); + } + } + + g_assert_not_reached(); +} + +static void test_tx_dreq(void) +{ + QTestState *qts =3D qtest_init("-M virt"); + g_autofree char *path =3D 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), =3D=3D, + 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 =3D qtest_init("-M virt"); + g_autofree char *path =3D 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), =3D=3D, '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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047168; cv=none; d=zohomail.com; s=zohoarc; b=YmmLY5SlQxmQ1zheFO96FEPxQlYENI1mvE/EWbID+gdV9dAiTki8oVpXvFoIqf9Rd5wTe2qQpKNxp+68Umy1MTYR+LOuMV95+o5kYKC/r43lcaIOD9ux/4axYvu499mRYpQO2Fm/cuqGONDr9P+i9tH818Pje0RBdxphL8Z3itQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047168; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=cBVN2elGMHwvfeXc8aJbqK7Eg2e45SlSAax/Afk10WY=; b=dCNEv7YTHHMm9LxFJHSGIHs00goUCUHhpAkfbqsu02KKaIwmUtOUiNR5K3T7sE+J86mwp+jm3Mz/swX3FqaA5Byl583jvHP85BqG8RLbP2MqJF+VYw+IHH8z2Eg7MqwIpbCOYkfOtc3OHPADNWi3rmDoKDO9YUAFePp2OeE3YyA= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047168027628.6654245304826; Sat, 29 Aug 2026 16:46:08 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiK-000845-6a; Sat, 29 Aug 2026 19:43:56 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiI-00082j-0u; Sat, 29 Aug 2026 19:43:54 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiG-0006Cm-AL; Sat, 29 Aug 2026 19:43:53 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzb28MzzBqXZ; Sun, 30 Aug 2026 01:43:39 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzb0gZBz7vrV; Sun, 30 Aug 2026 01:43:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047019; bh=cBVN2elGMHwvfeXc8aJbqK7Eg2e45SlSAax/Afk10WY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=c/xdt58n9LalEbIA7a3sY8SY8f3T3P9eIFhFcv+j/l7L8JA0Xk3Eigw7mohhVs6Yd 9K60LtgzSqi632fSNhxVYEDnatDPHdgwrSgZgGM887tG5iMpjCHOZI4AY4mlpKXtZn UIRrp7pvOQH9OKbz73Hnvg1PNSmw0agVa9x9a/t6kIzRkGesTNjX8QfL5FSoM6UC3J dmXl5KC5sWDUe3k7fSm+aFQBdXZAuFOPIbgMM5qMJq9lebplzMd/Sfbi9c0wCS+YYJ he1imJDYZm/M3oNBsNGYmASJ+BUAXEBAmaJxVzV0GIfJcPXRCR4WOlLQiJ8SVROU5+ sieCQYHJYvHww== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini Subject: [PATCH RFC v2 03/30] hw/misc: add RP2040 diagnostic helpers Date: Sun, 30 Aug 2026 01:42:37 +0200 Message-ID: <20260829234308.33725-4-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047170073158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Add shared helpers for reporting unimplemented RP2040 features and register= accesses. Keeping the formatting in one place makes shallow peripheral mod= els explicit and gives their diagnostics a consistent component, address, o= ffset and value format. Add a dedicated Kconfig symbol so the helpers remain disabled until selecte= d by the RP2040 SoC. Signed-off-by: gilles grimaud --- 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 =20 +Raspberry Pi Pico / RP2040 +M: Gilles Grimaud +L: qemu-arm@nongnu.org +S: Maintained +F: hw/*/rp2040* +F: include/hw/*/rp2040* + Real View M: Peter Maydell 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 =20 +config RP2040_NYI + bool + config STM32_RCC bool =20 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-ddr= c.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 +=3D 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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047153; cv=none; d=zohomail.com; s=zohoarc; b=FXHKAb+HK+yYKGO7IzftLHslgIz6CiXhWX+5gjhKVlBgGsV4b66wO5PyCNo4KwLZd6J/ZhmGo5RwYJQoYv1DeMsOq0l8+6RxXdk7zArVMF2c2Wb2IUk4L0Y4Pmi0675gwKSbzOdyLuT1RqeAogVvxVliIAlOhvi5m0CCr8SFaQA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047153; h=Content-Type:Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=K0I7dSLo2uyCjbPF75fsz2UPTGyHxqLMIynHXl4S0+c=; b=jxhsTUoJH2sF45PsRpKuYxZpv+L/HmIB0ghdgVAdxIz0yRPqkW0hN9WJ5zstFAge6ETauEnF/Ycv4O5HHgcrePbtWzEzJyT9GM+OJNQAvu8Ky/uWXLOVGGPWffvsPQTxH8AgFF+gJm8mpQvmD8aMMHM8gWgdhXUSVqvEiYLX9lw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047153873631.4330831947241; Sat, 29 Aug 2026 16:45:53 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiK-000848-Iv; Sat, 29 Aug 2026 19:43:56 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiJ-00083n-PN; Sat, 29 Aug 2026 19:43:55 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiH-0006Cs-3M; Sat, 29 Aug 2026 19:43:55 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXWzb4cHJz8376; Sun, 30 Aug 2026 01:43:39 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzb2bpWz7v19; Sun, 30 Aug 2026 01:43:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047019; bh=K0I7dSLo2uyCjbPF75fsz2UPTGyHxqLMIynHXl4S0+c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=veUWk6KH8/RKZUyrvPKdHypotw0yGchhYllnH32ebDmvUg3nyRnq80nzbuMVlulbt 08iRZOJsEsvgbZm/4jFYRJokzyRLdODIjZxF555sNZeJSCb1VapHvbvApPYA8GldMo R1dsoIx1Qcon7aahWmose8n24XU8ME0VZzcbuETK/dAON7m/8hh/fsTJG9fQGdF1yg Maxm3yYupZzYhWeXkibVMyByMD7nQncygMqyEpdC9lPlEflpoffVQq89mw1LYGivCW zJ8hm4Eq8iFe5z1zhmXbBhE4n0kE7ZK8b2uSsaErNPhEgZ5Y+EggI9XS8lMl4PwUGt YrwEYqAVebY/A== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= Subject: [PATCH RFC v2 04/30] hw/arm: add RP2040 SoC and Raspberry Pi Pico machine Date: Sun, 30 Aug 2026 01:42:38 +0200 Message-ID: <20260829234308.33725-5-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047156682158500 From: gilles grimaud Continue the Raspberry Pi Pico machine model introduced by Alex Benn=C3=A9e= '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 u= nimplemented-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 --- 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 =20 Real View M: Peter Maydell 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=3Dn # CONFIG_MPS2=3Dn # CONFIG_RASPI=3Dn +CONFIG_RASPI_PICO=3Dy # CONFIG_DIGIC=3Dn # CONFIG_SABRELITE=3Dn # CONFIG_EMCRAFT_SF2=3Dn 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 =20 +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 =3D RASPI_PICO_MACHINE(machine); + MemoryRegion *system_memory =3D 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 *dat= a) +{ + MachineClass *mc =3D MACHINE_CLASS(oc); + + mc->desc =3D "Raspberry Pi Pico (Cortex-M0+)"; + mc->init =3D raspi_pico_init; + mc->default_cpus =3D 1; + mc->min_cpus =3D 1; + mc->max_cpus =3D 1; + mc->default_ram_size =3D 0; + mc->no_parallel =3D 1; + mc->no_floppy =3D 1; + mc->no_cdrom =3D 1; +} + +static const TypeInfo raspi_pico_machine_info =3D { + .name =3D TYPE_RASPI_PICO_MACHINE, + .parent =3D TYPE_MACHINE, + .instance_size =3D sizeof(RaspiPicoMachineState), + .class_init =3D raspi_pico_machine_class_init, + .interfaces =3D 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[] =3D { + 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[] =3D { + { "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 =3D 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 =3D 0; i < ARRAY_SIZE(s->uart); i++) { + g_autofree char *name =3D g_strdup_printf("uart%d", i); + g_autofree char *property =3D 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 =3D 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 =3D 0; i < 4; i++) { + g_autofree char *name =3D 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 =3D NULL; + + if (!s->bootrom_file) { + rom_add_blob_fixed("rp2040.bootrom", rp2040_bootrom, + sizeof(rp2040_bootrom), RP2040_ROM_BASE); + return true; + } + + filename =3D 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) >=3D 0; +} + +static void rp2040_soc_realize(DeviceState *dev, Error **errp) +{ + RP2040State *s =3D RP2040(dev); + static const hwaddr uart_base[] =3D { RP2040_UART0_BASE, RP2040_UART1_= BASE }; + static const int uart_irq[] =3D { RP2040_UART0_IRQ, RP2040_UART1_IRQ }; + Error *err =3D 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 =3D 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 =3D 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[] =3D { + DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGI= ON, + MemoryRegion *), + DEFINE_PROP_STRING("bootrom-file", RP2040State, bootrom_file), +}; + +static void rp2040_soc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D rp2040_soc_realize; + device_class_set_props(dc, rp2040_soc_properties); +} + +static const TypeInfo rp2040_soc_info =3D { + .name =3D TYPE_RP2040, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040State), + .instance_init =3D rp2040_soc_init, + .class_init =3D 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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047152; cv=none; d=zohomail.com; s=zohoarc; b=KSn/APmxxjy4gEgqz2q1uOvP1uX1gvqDHT+otMJg+Uoa0ikPd1ArGZ3DnvUqdK95qrfR05ZCieHvaJPxz8iF8z50w65io9es5O1I05xLsXBhsbP/m6qcdWoi91c0urqE0MzxpVmyprIqY72OXU/6S3h3EIQX8rP7/9YFDQSPKW8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047152; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=9iAgLGXyWoTTI0QSPHKdEKH5qEMaoF2aVyd4k8UxUSE=; b=b6M1aBs3OHHIxvQE3qJbiTO+toSEKL6IoRHt03EmEs8fQIiau7UvElEnAll+60LDXkk0JVPV/EKrLfHyN57MYaH2GrVqw1vLb/b4GAfFVBe3yYnq44qrKUrzXkEZWaepFdy0EJGwRP2PbxfRPd4nMn2/eAsENOvOCTce+jNQvKA= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047152424828.8377882369984; Sat, 29 Aug 2026 16:45:52 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiN-00085G-9t; Sat, 29 Aug 2026 19:43:59 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiM-00084g-41; Sat, 29 Aug 2026 19:43:58 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiK-0006DI-2P; Sat, 29 Aug 2026 19:43:57 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzb6syjzBqXf; Sun, 30 Aug 2026 01:43:39 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzb53KHz7vrV; Sun, 30 Aug 2026 01:43:39 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047020; bh=9iAgLGXyWoTTI0QSPHKdEKH5qEMaoF2aVyd4k8UxUSE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZcnznBlTAyAsrpNtQ2XyfPHtm0kmGzUo/8n8rw+htDM4SWR6u/oW68BI3GM36y5rK t7k/i9hFX4k1qfJEzlVM/vuwlMJPq1Hbf8QpwDVDfHQcZbFbPW3RnczugpvYeRH1lQ B3tkMOCrw7fev+jPDcXN1nMn5JQIKgX/TB3frA1muuj0PX6GVska0zUaIG9Z8POZQ5 cHVnNVmvCQnE0poz8gK9HBtwSVg5wjTBmOZ7a6YbqT+nAYuGcUjxAf0I5e+tjfVEkv xzWZDSI4CnYNQX88x2OkuKWVou2Gnc0yhQJkozfVTaVb1KSyG2FWykudwlSO+F3Mod KRJSqvBX3MPUQ== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Peter Maydell Subject: [PATCH RFC v2 05/30] tests/tcg/arm: add Raspberry Pi Pico smoke tests Date: Sun, 30 Aug 2026 01:42:39 +0200 Message-ID: <20260829234308.33725-6-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047154554154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Build Cortex-M0+ guest programs from assembly sources to exercise the minim= al Pico boot path, UART0 output, and the Cortex-M0+ MPU. Keep the vector ta= ble at the XIP base so these tests match the initial synthetic boot stub wi= thout depending on boot2 or the later flash controller model. Signed-off-by: gilles grimaud --- 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* =20 Real View M: Peter Maydell 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=3D-semihosting-config e= nable=3Don,target=3Dnative,cha =20 ARM_TESTS+=3Dtest-armv6m-undef =20 +RP2040_TESTS=3Drp2040-boot rp2040-uart rp2040-mpu + +$(RP2040_TESTS): %: %.S rp2040-minimal.ld + $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ + -Wl,--build-id=3Dnone -x assembler-with-cpp \ + $< -o $@ -nostdlib -static \ + -T $(ARM_SRC)/rp2040-minimal.ld + +run-rp2040-boot: QEMU_OPTS=3D-M raspi-pico \ + -semihosting-config enable=3Don,target=3Dnative -kernel + +run-rp2040-uart run-rp2040-mpu: QEMU_OPTS=3D-M raspi-pico \ + -serial chardev:output \ + -semihosting-config enable=3Don,target=3Dnative -kernel + +run-rp2040-uart run-rp2040-mpu: run-rp2040-%: rp2040-% + $(call run-test, $<, \ + $(QEMU) -monitor none -display none \ + -chardev file$(COMMA)path=3D$<.out$(COMMA)id=3Doutput \ + $(QEMU_OPTS) $<) + $(call diff-out,$<,$(ARM_SRC)/$<.ref) + +ARM_TESTS+=3D$(RP2040_TESTS) + # These objects provide the basic boot code and helper functions for all t= ests CRT_OBJS=3Dboot.o =20 diff --git a/tests/tcg/arm/system/rp2040-boot.S b/tests/tcg/arm/system/rp20= 40-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, =3DADP_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 =3D 0x10000000, LENGTH =3D 2M + SRAM (rwx) : ORIGIN =3D 0x20000000, LENGTH =3D 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/rp204= 0-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, =3Dstart_message + bl puts + + ldr r0, =3DMPU_TYPE + ldr r0, [r0] + ldr r1, =3DMPU_TYPE_DREGION_MASK + ands r0, r1 + lsrs r0, r0, MPU_TYPE_DREGION_SHIFT + cmp r0, 8 + bne fail_type + + ldr r0, =3Dtype_message + bl puts + + ldr r4, =3DTEST_ADDRESS + ldr r5, =3DTEST_VALUE + str r5, [r4] + ldr r0, [r4] + cmp r0, r5 + bne fail_plain + + ldr r0, =3Dplain_message + bl puts + + movs r0, 0 + ldr r1, =3D0x20000000 + ldr r2, =3DMPU_RASR_SRAM_FULL_ACCESS + bl configure_region + + movs r0, 1 + movs r1, 0 + ldr r2, =3DMPU_RASR_XIP_READ_ONLY + bl configure_region + + movs r0, 2 + ldr r1, =3DTEST_ADDRESS + ldr r2, =3DMPU_RASR_TEST_PRIV_RW + bl configure_region + + ldr r0, =3DMPU_CTRL + movs r1, MPU_CTRL_ENABLE_PRIVDEFENA + str r1, [r0] + dsb + isb + + ldr r0, [r4] + cmp r0, r5 + bne fail_privileged + + ldr r0, =3Dprivileged_message + bl puts + + movs r0, 1 + msr control, r0 + isb + + ldr r0, [r4] + ldr r0, =3Dfail_no_fault_message + b test_failed + +.thumb_func +configure_region: + ldr r3, =3DMPU_RNR + str r0, [r3] + ldr r3, =3DMPU_RBAR + str r1, [r3] + ldr r3, =3DMPU_RASR + str r2, [r3] + bx lr + +fail_type: + ldr r0, =3Dfail_type_message + b test_failed + +fail_plain: + ldr r0, =3Dfail_plain_message + b test_failed + +fail_privileged: + ldr r0, =3Dfail_privileged_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +1: + b 1b + +.thumb_func +hardfault_handler: + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +2: + b 2b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/rp2= 040-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/rp20= 40-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, =3DUART0_DR + ldr r1, =3Dmessage + +1: + ldrb r2, [r1] + cmp r2, 0 + beq 2f + str r2, [r0] + adds r1, 1 + b 1b + +2: + movs r0, SYS_EXIT + ldr r1, =3DADP_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/rp= 2040-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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047105; cv=none; d=zohomail.com; s=zohoarc; b=PuPeJhGI2AWBkg1TFONWg1G0CtfHY5VlYglfHjfYl1s+JeTKov+LIrALYFhzd+bPd8iW2fIIhpqBwRHZuECsErHhu/AAJWdJXZlBsLwnW5O8Q3IjWFz9PzrmpcH5xq46e+Hzd3xo99gYESYz2Rh3dMttUf6bIpoPymnkXY0O0fM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047105; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=hLD3i3oCRJDl49ntg36l1fO5NhcwYmoduEeC9J/CmiU=; b=O03kJt9YpGolMPbTHYIzFQAQW93Jfn/idJjKVULaj32x4rK/zq5+Wxr2RHPnKkY0NH+9rJ16wn0lLCu9Rcz1QYo1G6DOBiYLli1bpdK20mIKDzkt7JUc0klYnAdVeAoEhJlVF3KZi4qhoU3wPXOzbZ8dwoJJm9siqFyyxU7FGsU= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047105094653.6411970144012; Sat, 29 Aug 2026 16:45:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiS-00086l-Mj; Sat, 29 Aug 2026 19:44:04 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiR-000862-0M; Sat, 29 Aug 2026 19:44:03 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiN-0006Dr-Fh; Sat, 29 Aug 2026 19:44:02 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzc2xq2zBqXm; Sun, 30 Aug 2026 01:43:40 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzc0gnnz7v19; Sun, 30 Aug 2026 01:43:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047020; bh=hLD3i3oCRJDl49ntg36l1fO5NhcwYmoduEeC9J/CmiU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=yFRLdgAaisbizj0BOi428ZfNMv173Hy9WINqzm9+Y0/ESbThTSDqc6atN+CIlsO5l 5ph0TcwkwqnctZab+JKVj0CiMk4KIIJoT6i+JrWujWA4GseT8Slx5kelxd8EeYQmQ2 rSptC3SD+e4HnPpMVQEjOfz9OhdNRxHyAG5sZ2UwigEmLnX30q1ZSG2U/oqPaSOrVF NUHvHlF3heJgkkkPuVd9Acd9AMF7UXF+X4m90WGSVqsxQMm9QLfW9KIvuxz6O+nqJH ywWTKAmXKlu+nG2wW3BTgR79YrtK3fYb+RhupGR3Yxl+kw96TEJFR6lm+V+756FNJJ eHGluTON1fahQ== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 06/30] hw/misc: add RP2040 SYSINFO and SYSCFG Date: Sun, 30 Aug 2026 01:42:40 +0200 Message-ID: <20260829234308.33725-7-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047111569154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the RP2040 identification and system configuration registers, includi= ng APB atomic aliases and documented reset and write masks. Wire SYSCFG int= o the SoC so NMI masks reroute interrupt inputs and MEMPOWERDOWN blocks acc= ess to powered-off ROM and SRAM banks.\n\nAdd focused qtests for register v= alues, aliases, power-down behavior, and UART0 IRQ routing to NMI. Signed-off-by: gilles grimaud --- 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* =20 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 =20 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[] =3D { - { "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 }, }; =20 +static MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr, + uint64_t *data, unsigned size, + MemTxAttrs attrs) +{ + *data =3D 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 =3D { + .read_with_attrs =3D rp2040_powered_off_read, + .write_with_attrs =3D rp2040_powered_off_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + }, +}; + +static void rp2040_update_mempowerdown(RP2040State *s) +{ + uint32_t mempowerdown; + int i; + + if (!s->mempowerdown_ready) { + return; + } + + mempowerdown =3D rp2040_syscfg_get_mempowerdown(&s->syscfg); + for (i =3D 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 =3D rp2040_syscfg_get_proc0_nmi_mask(&s->syscfg); + bool nmi_level =3D false; + int i; + + for (i =3D 0; i < RP2040_NUM_IRQS; i++) { + bool route_to_nmi =3D nmi_mask & BIT(i); + + qemu_set_irq(s->cpu_irq[i], s->irq_level[i] && !route_to_nmi); + nmi_level |=3D s->irq_level[i] && route_to_nmi; + } + qemu_set_irq(s->nmi_irq, nmi_level); +} + +static void rp2040_syscfg_update(void *opaque) +{ + RP2040State *s =3D opaque; + + rp2040_update_mempowerdown(s); + rp2040_update_nmi(s); +} + +static void rp2040_set_irq(void *opaque, int irq, int level) +{ + RP2040State *s =3D opaque; + + assert(irq >=3D 0 && irq < RP2040_NUM_IRQS); + s->irq_level[irq] =3D level; + rp2040_update_nmi(s); +} + static void rp2040_soc_init(Object *obj) { RP2040State *s =3D RP2040(obj); @@ -104,6 +177,10 @@ static void rp2040_soc_init(Object *obj) "chardev"); } =20 + object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG); + object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSIN= FO); + + s->irq =3D qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS); s->sysclk =3D 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); =20 for (i =3D 0; i < 4; i++) { g_autofree char *name =3D g_strdup_printf("rp2040.sram%d", i); + g_autofree char *poweroff_name =3D + g_strdup_printf("rp2040.sram%d.poweroff", i); =20 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); } =20 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); =20 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 =3D true; =20 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 =3D 0; i < RP2040_NUM_IRQS; i++) { + s->cpu_irq[i] =3D qdev_get_gpio_in(DEVICE(&s->armv7m), i); + } + s->nmi_irq =3D 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); =20 for (i =3D 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]]); } } =20 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 =20 +config RP2040_SYSCFG + bool + +config RP2040_SYSINFO + bool + config STM32_RCC bool =20 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_sysinf= o.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-ddr= c.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 =3D update; + s->update_opaque =3D 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 siz= e) +{ + RP2040SysCfgState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case SYSCFG_PROC0_NMI_MASK: + value =3D s->proc0_nmi_mask; + break; + case SYSCFG_PROC1_NMI_MASK: + value =3D s->proc1_nmi_mask; + break; + case SYSCFG_PROC_CONFIG: + value =3D s->proc_config; + break; + case SYSCFG_PROC_IN_SYNC_BYPASS: + value =3D s->proc_in_sync_bypass; + break; + case SYSCFG_PROC_IN_SYNC_BYPASS_HI: + value =3D s->proc_in_sync_bypass_hi; + break; + case SYSCFG_DBGFORCE: + value =3D s->dbgforce; + break; + case SYSCFG_MEMPOWERDOWN: + value =3D s->mempowerdown; + break; + default: + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D value64; + + switch (offset) { + case SYSCFG_PROC0_NMI_MASK: + s->proc0_nmi_mask =3D + rp2040_syscfg_apply_alias(s->proc0_nmi_mask, value, alias); + rp2040_syscfg_update(s); + break; + case SYSCFG_PROC1_NMI_MASK: + s->proc1_nmi_mask =3D + rp2040_syscfg_apply_alias(s->proc1_nmi_mask, value, alias); + break; + case SYSCFG_PROC_CONFIG: + s->proc_config =3D + 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 =3D + 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 =3D + rp2040_syscfg_apply_alias(s->proc_in_sync_bypass_hi, value, + alias) & + PROC_IN_SYNC_BYPASS_HI_MASK; + break; + case SYSCFG_DBGFORCE: + s->dbgforce =3D + rp2040_syscfg_apply_alias(s->dbgforce, value, alias) & + DBGFORCE_RW_MASK; + break; + case SYSCFG_MEMPOWERDOWN: + s->mempowerdown =3D + 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 =3D { + .read =3D rp2040_syscfg_read, + .write =3D rp2040_syscfg_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_syscfg_reset(DeviceState *dev) +{ + RP2040SysCfgState *s =3D RP2040_SYSCFG(dev); + + s->proc0_nmi_mask =3D 0; + s->proc1_nmi_mask =3D 0; + s->proc_config =3D PROC_CONFIG_RESET; + s->proc_in_sync_bypass =3D 0; + s->proc_in_sync_bypass_hi =3D 0; + s->dbgforce =3D DBGFORCE_RESET; + s->mempowerdown =3D 0; + rp2040_syscfg_update(s); +} + +static int rp2040_syscfg_post_load(void *opaque, int version_id) +{ + RP2040SysCfgState *s =3D opaque; + + rp2040_syscfg_update(s); + return 0; +} + +static void rp2040_syscfg_init(Object *obj) +{ + RP2040SysCfgState *s =3D 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 =3D { + .name =3D TYPE_RP2040_SYSCFG, + .version_id =3D 1, + .minimum_version_id =3D 1, + .post_load =3D rp2040_syscfg_post_load, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_syscfg_reset); + dc->vmsd =3D &rp2040_syscfg_vmstate; +} + +static const TypeInfo rp2040_syscfg_info =3D { + .name =3D TYPE_RP2040_SYSCFG, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040SysCfgState), + .instance_init =3D rp2040_syscfg_init, + .class_init =3D 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 si= ze) +{ + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case SYSINFO_CHIP_ID: + case SYSINFO_GITREF_RP2040: + value =3D 0; + break; + case SYSINFO_PLATFORM: + value =3D SYSINFO_PLATFORM_ASIC; + break; + default: + value =3D 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 =3D 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, val= ue); +} + +static const MemoryRegionOps rp2040_sysinfo_ops =3D { + .read =3D rp2040_sysinfo_read, + .write =3D rp2040_sysinfo_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_sysinfo_init(Object *obj) +{ + RP2040SysInfoState *s =3D 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 =3D { + .name =3D TYPE_RP2040_SYSINFO, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_END_OF_LIST() + } +}; + +static void rp2040_sysinfo_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &rp2040_sysinfo_vmstate; +} + +static const TypeInfo rp2040_sysinfo_info =3D { + .name =3D TYPE_RP2040_SYSINFO, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040SysInfoState), + .instance_init =3D rp2040_sysinfo_init, + .class_init =3D 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" =20 #define TYPE_RP2040 "rp2040" @@ -34,12 +36,22 @@ struct RP2040State { =20 ARMv7MState armv7m; PL011State uart[2]; + RP2040SysCfgState syscfg; + RP2040SysInfoState sysinfo; =20 MemoryRegion *board_memory; MemoryRegion rom; + MemoryRegion rom_poweroff; MemoryRegion sram[6]; + MemoryRegion sram_poweroff[6]; char *bootrom_file; =20 + 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; }; =20 diff --git a/include/hw/misc/rp2040_syscfg.h b/include/hw/misc/rp2040_syscf= g.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_sysi= nfo.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 =3D \ (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-sysc= fg-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 =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_CHIP_ID), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_PLATFORM), =3D= =3D, + SYSINFO_PLATFORM_ASIC); + g_assert_cmphex(qtest_readl(qts, SYSINFO_BASE + SYSINFO_GITREF_RP2040), + =3D=3D, 0); + + qtest_quit(qts); +} + +static void test_syscfg_reset_values(void) +{ + QTestState *qts =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK), + =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK), + =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC_CONFIG), = =3D=3D, + PROC_CONFIG_RESET); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_DBGFORCE), =3D= =3D, + DBGFORCE_RESET); + + qtest_quit(qts); +} + +static void test_syscfg_rw_masks(void) +{ + QTestState *qts =3D 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), + =3D=3D, 0xa5a5a5a5); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC1_NMI_MASK), + =3D=3D, 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), + =3D=3D, 0x3fffffff); + g_assert_cmphex(qtest_readl(qts, + SYSCFG_BASE + SYSCFG_PROC_IN_SYNC_BYPASS_H= I), + =3D=3D, 0x0000003f); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_MEMPOWERDOWN), = =3D=3D, + 0x000000ff); + + qtest_quit(qts); +} + +static void test_syscfg_atomic_aliases(void) +{ + QTestState *qts =3D rp2040_start(); + + qtest_writel(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK, 0); + qtest_writel(qts, SYSCFG_BASE + ATOMIC_SET_ALIAS + SYSCFG_PROC0_NMI_MA= SK, + 0x0000000f); + qtest_writel(qts, SYSCFG_BASE + ATOMIC_CLR_ALIAS + SYSCFG_PROC0_NMI_MA= SK, + 0x00000003); + g_assert_cmphex(qtest_readl(qts, SYSCFG_BASE + SYSCFG_PROC0_NMI_MASK), + =3D=3D, 0x0000000c); + + qtest_quit(qts); +} + +static void test_syscfg_mempowerdown_blocks_sram(void) +{ + QTestState *qts =3D rp2040_start(); + + qtest_writel(qts, SRAM0_BASE, 0x11223344); + g_assert_cmphex(qtest_readl(qts, SRAM0_BASE), =3D=3D, 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), =3D=3D, 0x11223344); + + qtest_quit(qts); +} + +static void test_syscfg_nmi_mask_routes_uart0_irq(void) +{ + QTestState *qts =3D 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), =3D=3D, + 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), =3D=3D, = 0); + g_assert_cmphex(qtest_readl(qts, NVIC_ICSR) & NVIC_ICSR_NMIPENDSET, = =3D=3D, + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047145; cv=none; d=zohomail.com; s=zohoarc; b=bKxHlyaQb6OdC4xXeS1dhQwOqYiT1j9qNywErXyNJ321Na7lh53rm9/w/+HdfjJ7vx8nj7EmKtKNXCJYKbBydBKzgOuxuhdw5RLS3C20p/d4oJvUphASSA9zERqQtGk3BzeuF2mJHU8USP+ZEh67TPFYFls1QTrd5stSeSGq2RY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047145; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=Lbwd6FIBHGDSpDurf65+aYtzdyAu4MeoLmODMhgCQMo=; b=SMa/1QAxT7PCOFAEm8I3EsR0Uo1/dp5sfROhHkVEFnzj/aC3wt3X4G04VQuHltaOBE2xJx0wAwVALF7MsmIcYd+DLGrUsvclNZNLeFMYwqu2La0j9vCOkBSADFYggYRsLW8APKFC8xPIEJy1pniJ/2L2CC2zsmZagMfbf0ihYAs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047145826484.66680352150786; Sat, 29 Aug 2026 16:45:45 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiU-00087D-2k; Sat, 29 Aug 2026 19:44:06 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiT-00086u-44; Sat, 29 Aug 2026 19:44:05 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiQ-0006EL-9u; Sat, 29 Aug 2026 19:44:04 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXWzc5mwFz837k; Sun, 30 Aug 2026 01:43:40 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzc3bMNz7vrV; Sun, 30 Aug 2026 01:43:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047020; bh=Lbwd6FIBHGDSpDurf65+aYtzdyAu4MeoLmODMhgCQMo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IJi1AG50TRbMc15RdmzFo2ZUbvfL0UCZQ3vdtLM70bK/zmV5JVGui1njYWs5gQ/wv SLjj1Yl2AYoGnKXx2n4+11USxK3wQYNhiXHthWJJw9/A1BRJxHET99dxw3zR7w4D9Q sRhh+zCLx5jmBny988nzORyE3/vUscByu/kKwZ0h2KMa3RU5xJZkMFe7oG9Cc816xY 9QAl6muA67Sgli1vn+iIuaswQRZBsIi4FSkBzABLMp5hP9ZyrimNfFKvxXmJmKKqIB ZZAdOLCMAcGRZlZMe5H8KpbIZ4BjC+aCY+lmxKnwYG4ultiDkY4jca6FNhvIbsEfNs yPA04fhUJFAOA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 07/30] hw/misc: add RP2040 TBMAN and voltage regulator Date: Sun, 30 Aug 2026 01:42:41 +0200 Message-ID: <20260829234308.33725-8-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047148496154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Replace the TBMAN and VREG/chip-reset placeholders with the RP2040 register= models from the development tree. Expose the ASIC platform indication, doc= umented reset values, write masks, regulator status, and APB atomic aliases= .\n\nAdd focused qtests for both blocks. Signed-off-by: gilles grimaud --- 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 =20 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) =20 object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG); object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSIN= FO); + object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN); + object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG); =20 s->irq =3D qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS); s->sysclk =3D 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); =20 + 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 =3D 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 =20 +config RP2040_TBMAN + bool + +config RP2040_VREG + bool + config STM32_RCC bool =20 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_sysinf= o.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-ddr= c.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 =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case TBMAN_PLATFORM: + value =3D TBMAN_PLATFORM_ASIC; + break; + default: + value =3D 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 =3D addr & 0xfff; + + rp2040_log_unimplemented_write("tbman", size, RP2040_TBMAN_BASE + addr, + offset, value64); +} + +static const MemoryRegionOps rp2040_tbman_ops =3D { + .read =3D rp2040_tbman_read, + .write =3D rp2040_tbman_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_tbman_init(Object *obj) +{ + RP2040TbmanState *s =3D 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 =3D { + .name =3D TYPE_RP2040_TBMAN, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_END_OF_LIST() + } +}; + +static void rp2040_tbman_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->vmsd =3D &rp2040_tbman_vmstate; +} + +static const TypeInfo rp2040_tbman_info =3D { + .name =3D TYPE_RP2040_TBMAN, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040TbmanState), + .instance_init =3D rp2040_tbman_init, + .class_init =3D 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 =3D s->vreg; + + if ((value & VREG_EN) && !(value & VREG_HIZ)) { + value |=3D VREG_ROK; + } + + return value; +} + +static uint64_t rp2040_vreg_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040VregState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case VREG_VREG: + value =3D rp2040_vreg_read_vreg(s); + break; + case VREG_BOD: + value =3D s->bod; + break; + case VREG_CHIP_RESET: + value =3D s->chip_reset; + break; + default: + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D value64; + + switch (offset) { + case VREG_VREG: + s->vreg =3D rp2040_vreg_apply_alias(s->vreg, value, alias) & + VREG_RW_MASK; + break; + case VREG_BOD: + s->bod =3D rp2040_vreg_apply_alias(s->bod, value, alias) & BOD_RW_= MASK; + break; + case VREG_CHIP_RESET: + s->chip_reset =3D + 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 =3D { + .read =3D rp2040_vreg_read, + .write =3D rp2040_vreg_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_vreg_reset(DeviceState *dev) +{ + RP2040VregState *s =3D RP2040_VREG(dev); + + s->vreg =3D VREG_RESET; + s->bod =3D BOD_RESET; + s->chip_reset =3D 0; +} + +static void rp2040_vreg_init(Object *obj) +{ + RP2040VregState *s =3D 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 =3D { + .name =3D TYPE_RP2040_VREG, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_vreg_reset); + dc->vmsd =3D &rp2040_vreg_vmstate; +} + +static const TypeInfo rp2040_vreg_info =3D { + .name =3D TYPE_RP2040_VREG, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040VregState), + .instance_init =3D rp2040_vreg_init, + .class_init =3D 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" =20 #define TYPE_RP2040 "rp2040" @@ -38,6 +40,8 @@ struct RP2040State { PL011State uart[2]; RP2040SysCfgState syscfg; RP2040SysInfoState sysinfo; + RP2040TbmanState tbman; + RP2040VregState vreg; =20 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 =3D \ (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-sysc= fg-test'] : []) + \ + (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-sysc= fg-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-tes= t.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 =3D qtest_init("-machine raspi-pico"); + + g_assert_cmphex(qtest_readl(qts, TBMAN_BASE + TBMAN_PLATFORM), =3D=3D, + 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 =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_VREG), =3D=3D, + VREG_RESET | VREG_ROK); + g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_BOD), =3D=3D, BOD_RE= SET); + g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_CHIP_RESET), =3D=3D,= 0); + + qtest_quit(qts); +} + +static void test_vreg_rw_masks(void) +{ + QTestState *qts =3D 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), =3D=3D, 0x000= 000f3); + g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_BOD), =3D=3D, 0x0000= 00f1); + g_assert_cmphex(qtest_readl(qts, VREG_BASE + VREG_CHIP_RESET), =3D=3D, + CHIP_RESET_RESCUE); + + qtest_quit(qts); +} + +static void test_vreg_atomic_aliases(void) +{ + QTestState *qts =3D 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), =3D=3D, 0x0000= 0001); + + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047114; cv=none; d=zohomail.com; s=zohoarc; b=CX+oGdWjMhk1dt3F7/L/L3ZVxa5nIc9iWbErC4MR6mak2J2YMgI6dz6Ie17tgXI3y2QZ+HP3xy/fkmfLLk+LBTX1IYJz+8j7xenuNJG90uZSRKCm8bXHwDW/5cXuTHVGHXml7+beEw1n/ufB6tFsfP1ZZ2ApkQD6CGh+EbDaoP8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047114; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=GDGPmZqmkdR7qqsy3AZxl8puIuhDK59ALlXB9fHMTjY=; b=ZLqi223n1mtdEVGWq0Bdoku3yDGpK1MDArHrrfy3dzk4NBJOkAYNPKig/FLYO5f6dGBD4YgwdxdSYiEfUEcetCDuMRgNPGXXByevugAV1z5maGWudlRo1Ziev7sfgnbLmjw5BXhQ5TGawlSXhZQeEd09YHlV23+EOdhPxV2/XVg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047114556216.3497748626222; Sat, 29 Aug 2026 16:45:14 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Sij-0008Az-BD; Sat, 29 Aug 2026 19:44:21 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sid-0008AE-9j; Sat, 29 Aug 2026 19:44:15 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiZ-0006FU-BV; Sat, 29 Aug 2026 19:44:15 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXWzd1qKzz8388; Sun, 30 Aug 2026 01:43:41 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzc6YFYz7tLT; Sun, 30 Aug 2026 01:43:40 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047021; bh=GDGPmZqmkdR7qqsy3AZxl8puIuhDK59ALlXB9fHMTjY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dKqHo+TLv/oil7dGO0ORgfPLfVyINCyBB78+lWmMm4KC9Jeq96X1uMu4NqIoqTES9 OZcV3h2NyaKZ0WKmRlN+GRWnH1EfqE+4ZHSutfkN/r5GwXxjhmY/qHreURHPjKzuLh 4vLZ9z+RlwelVzK0fjAPf46i0UhEWpyCWyVvwb5n/Y6k3UvDtJdkf+FZQKCfW4Tyub DiKnSWwdHXoICfxj622pviPWt1qUJOB85iZpjM1TbC4o53jIb+tqOQW1dGg/pE9Ver Kwq+iR0a6zZpRF8Fz7M+r21Gihsi/HyDhGe1MsLCEW+IhgubTpVkBlCnrHflk651f+ Ub9RzdumTddXA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 08/30] hw/misc: add RP2040 crystal and ring oscillators Date: Sun, 30 Aug 2026 01:42:42 +0200 Message-ID: <20260829234308.33725-9-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047118805154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the RP2040 XOSC and ROSC register blocks and expose their clock outpu= ts. Use virtual time for the ROSC COUNT register and provide either QEMU gu= est entropy or a machine-selectable deterministic seed for RANDOMBIT. Add q= tests for the ROSC register behavior and deterministic stream. Signed-off-by: gilles grimaud --- 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 =20 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 { =20 RP2040State soc; MemoryRegion flash; + char *rosc_random_seed; + uint64_t rosc_random_seed_value; + bool rosc_random_seed_set; }; =20 +static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s =3D 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 =3D RASPI_PICO_MACHINE(obj); + uint64_t seed; + + if (!value || !*value) { + g_free(s->rosc_random_seed); + s->rosc_random_seed =3D NULL; + s->rosc_random_seed_value =3D 0; + s->rosc_random_seed_set =3D 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 =3D g_strdup(value); + s->rosc_random_seed_value =3D seed; + s->rosc_random_seed_set =3D true; +} + static void raspi_pico_init(MachineState *machine) { RaspiPicoMachineState *s =3D 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); } =20 +static void raspi_pico_machine_finalize(Object *obj) +{ + RaspiPicoMachineState *s =3D RASPI_PICO_MACHINE(obj); + + g_free(s->rosc_random_seed); +} + static void raspi_pico_machine_class_init(ObjectClass *oc, const void *dat= a) { MachineClass *mc =3D MACHINE_CLASS(oc); @@ -68,6 +115,14 @@ static void raspi_pico_machine_class_init(ObjectClass *= oc, const void *data) mc->no_parallel =3D 1; mc->no_floppy =3D 1; mc->no_cdrom =3D 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 th= e " + "ROSC RANDOMBIT stream; if unset= , " + "QEMU guest entropy is used"); } =20 static const TypeInfo raspi_pico_machine_info =3D { @@ -75,6 +130,7 @@ static const TypeInfo raspi_pico_machine_info =3D { .parent =3D TYPE_MACHINE, .instance_size =3D sizeof(RaspiPicoMachineState), .class_init =3D raspi_pico_machine_class_init, + .instance_finalize =3D raspi_pico_machine_finalize, .interfaces =3D arm_machine_interfaces, }; =20 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) =20 object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG); object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSIN= FO); + 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); =20 s->irq =3D qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS); s->sysclk =3D 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); =20 + 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); =20 + if (!sysbus_realize(SYS_BUS_DEVICE(&s->xosc), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->xosc), 0, RP2040_XOSC_BASE); + for (i =3D 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 =20 +config RP2040_ROSC + bool + config RP2040_SYSCFG bool =20 @@ -116,6 +119,9 @@ config RP2040_TBMAN config RP2040_VREG bool =20 +config RP2040_XOSC + bool + config STM32_RCC bool =20 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_sysinf= o.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-ddr= c.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 =3D=3D ROSC_FREQ_RANGE_LOW || range =3D=3D ROSC_FREQ_RANG= E_MEDIUM || + range =3D=3D ROSC_FREQ_RANGE_HIGH || range =3D=3D ROSC_FREQ_RAN= GE_TOOHIGH; +} + +static bool rp2040_rosc_enabled(RP2040RoscState *s) +{ + return extract32(s->ctrl, 12, 12) !=3D ROSC_ENABLE_DISABLE; +} + +static bool rp2040_rosc_running(RP2040RoscState *s) +{ + return rp2040_rosc_enabled(s) && s->dormant =3D=3D ROSC_WAKE_VALUE; +} + +static uint32_t rp2040_rosc_divisor(RP2040RoscState *s) +{ + uint32_t encoded =3D s->div & 0xfff; + uint32_t div; + + if (encoded < ROSC_DIV_PASS) { + return 31; + } + + div =3D encoded - ROSC_DIV_PASS; + if (div =3D=3D 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 =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->count_start_= ns; + elapsed_ticks =3D elapsed_ns * ROSC_NOMINAL_HZ / NANOSECONDS_PER_SECON= D; + if (elapsed_ticks >=3D s->count) { + s->count =3D 0; + } else { + s->count -=3D elapsed_ticks; + s->count_start_ns =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + } + + return s->count; +} + +static uint32_t rp2040_rosc_status(RP2040RoscState *s) +{ + uint32_t value =3D 0; + + if (rp2040_rosc_enabled(s)) { + value |=3D ROSC_STATUS_ENABLED; + if (s->dormant =3D=3D ROSC_WAKE_VALUE) { + value |=3D ROSC_STATUS_STABLE | ROSC_STATUS_DIV_RUNNING; + } + } + if (s->badwrite) { + value |=3D 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 =3D 64; + } + + bit =3D s->random_pool & 1; + s->random_pool >>=3D 1; + s->random_pool_bits--; + return bit; + } + + x =3D s->random_prng_state; + x ^=3D x >> 12; + x ^=3D x << 25; + x ^=3D x >> 27; + s->random_prng_state =3D x; + return (x * 0x2545f4914f6cdd1dull) >> 63; +} + +static uint64_t rp2040_rosc_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040RoscState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case ROSC_CTRL: + value =3D s->ctrl; + break; + case ROSC_FREQA: + value =3D s->freqa; + break; + case ROSC_FREQB: + value =3D s->freqb; + break; + case ROSC_DORMANT: + value =3D s->dormant; + break; + case ROSC_DIV: + value =3D s->div; + break; + case ROSC_PHASE: + value =3D s->phase; + break; + case ROSC_STATUS: + value =3D rp2040_rosc_status(s); + break; + case ROSC_RANDOMBIT: + value =3D rp2040_rosc_random_bit(s); + break; + case ROSC_COUNT: + value =3D rp2040_rosc_count_read(s); + break; + default: + value =3D 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 =3D extract32(value, 12, 12); + uint32_t range =3D extract32(value, 0, 12); + + if (enable !=3D ROSC_ENABLE_DISABLE && enable !=3D ROSC_ENABLE_ENABLE)= { + s->badwrite =3D true; + value =3D deposit32(value, 12, 12, ROSC_ENABLE_ENABLE); + } + if (!rp2040_rosc_valid_range(range)) { + s->badwrite =3D true; + value =3D deposit32(value, 0, 12, ROSC_FREQ_RANGE_LOW); + } + + s->ctrl =3D 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) !=3D ROSC_FREQ_PASSWD) { + s->badwrite =3D true; + *reg =3D 0; + return; + } + + *reg =3D value & 0x96967777; +} + +static void rp2040_rosc_write_div(RP2040RoscState *s, uint32_t value) +{ + if ((value & 0xfff) < ROSC_DIV_PASS) { + s->badwrite =3D true; + value =3D ROSC_DIV_PASS + 31; + } + + s->div =3D value & 0xfff; + rp2040_rosc_update_clock(s); +} + +static void rp2040_rosc_write_phase(RP2040RoscState *s, uint32_t value) +{ + if (extract32(value, 4, 8) !=3D ROSC_PHASE_PASSWD) { + s->badwrite =3D true; + value =3D 0; + } + + s->phase =3D value & 0x00000fff; +} + +static void rp2040_rosc_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + RP2040RoscState *s =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D 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 =3D value =3D=3D ROSC_DORMANT_VALUE ? value : ROSC_WAKE= _VALUE; + if (value !=3D ROSC_DORMANT_VALUE && value !=3D ROSC_WAKE_VALUE) { + s->badwrite =3D 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 =3D false; + } + break; + case ROSC_COUNT: + s->count =3D value & 0xff; + s->count_start_ns =3D 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 =3D { + .read =3D rp2040_rosc_read, + .write =3D rp2040_rosc_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_rosc_reset(DeviceState *dev) +{ + RP2040RoscState *s =3D RP2040_ROSC(dev); + + s->ctrl =3D (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW; + s->freqa =3D 0; + s->freqb =3D 0; + s->dormant =3D ROSC_WAKE_VALUE; + s->div =3D ROSC_DIV_PASS + 16; + s->phase =3D 0x00000aa8; + s->count =3D 0; + s->count_start_ns =3D 0; + s->random_pool =3D 0; + s->random_pool_bits =3D 0; + s->random_prng_state =3D s->random_seed; + if (!s->random_prng_state) { + s->random_prng_state =3D 0x9e3779b97f4a7c15ull; + } + s->badwrite =3D false; + + rp2040_rosc_update_clock(s); +} + +static void rp2040_rosc_init(Object *obj) +{ + RP2040RoscState *s =3D RP2040_ROSC(obj); + DeviceState *dev =3D DEVICE(obj); + + s->clk =3D 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 =3D { + .name =3D TYPE_RP2040_ROSC, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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[] =3D { + 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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_rosc_reset); + dc->vmsd =3D &rp2040_rosc_vmstate; + device_class_set_props(dc, rp2040_rosc_properties); +} + +static const TypeInfo rp2040_rosc_info =3D { + .name =3D TYPE_RP2040_ROSC, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040RoscState), + .instance_init =3D rp2040_rosc_init, + .class_init =3D 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) !=3D 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 =3D 0; + + if (rp2040_xosc_enabled(s)) { + value |=3D XOSC_STATUS_ENABLED; + if (s->dormant =3D=3D XOSC_WAKE_VALUE) { + value |=3D XOSC_STATUS_STABLE; + } + } + if (s->badwrite) { + value |=3D XOSC_STATUS_BADWRITE; + } + + return value; +} + +static uint64_t rp2040_xosc_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040XoscState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case XOSC_CTRL: + value =3D s->ctrl; + break; + case XOSC_STATUS: + value =3D rp2040_xosc_status(s); + break; + case XOSC_DORMANT: + value =3D s->dormant; + break; + case XOSC_STARTUP: + value =3D s->startup; + break; + case XOSC_COUNT: + value =3D 0; + break; + default: + value =3D 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 =3D extract32(value, 12, 12); + uint32_t freq_range =3D extract32(value, 0, 12); + + if (freq_range !=3D XOSC_FREQ_1_15MHZ || + (enable !=3D XOSC_ENABLE_DISABLE && enable !=3D XOSC_ENABLE_ENABLE= )) { + s->badwrite =3D true; + } + + if (enable !=3D XOSC_ENABLE_DISABLE && enable !=3D XOSC_ENABLE_ENABLE)= { + value =3D deposit32(value, 12, 12, XOSC_ENABLE_ENABLE); + } + value =3D deposit32(value, 0, 12, XOSC_FREQ_1_15MHZ); + + s->ctrl =3D value; + rp2040_xosc_update_clock(s); +} + +static void rp2040_xosc_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + RP2040XoscState *s =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D 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 =3D false; + } + break; + case XOSC_DORMANT: + s->dormant =3D value =3D=3D XOSC_DORMANT_VALUE ? value : XOSC_WAKE= _VALUE; + rp2040_xosc_update_clock(s); + break; + case XOSC_STARTUP: + s->startup =3D value & 0x00103fff; + break; + case XOSC_COUNT: + s->count =3D 0; + break; + default: + rp2040_log_unimplemented_write("xosc", size, + RP2040_XOSC_BASE + addr, offset, + value64); + break; + } +} + +static const MemoryRegionOps rp2040_xosc_ops =3D { + .read =3D rp2040_xosc_read, + .write =3D rp2040_xosc_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_xosc_reset(DeviceState *dev) +{ + RP2040XoscState *s =3D RP2040_XOSC(dev); + + s->ctrl =3D (XOSC_ENABLE_DISABLE << 12) | XOSC_FREQ_1_15MHZ; + s->dormant =3D XOSC_WAKE_VALUE; + s->startup =3D 0x000000c4; + s->count =3D 0; + s->badwrite =3D false; + + rp2040_xosc_update_clock(s); +} + +static void rp2040_xosc_init(Object *obj) +{ + RP2040XoscState *s =3D RP2040_XOSC(obj); + DeviceState *dev =3D DEVICE(obj); + + s->clk =3D 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 =3D { + .name =3D TYPE_RP2040_XOSC, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_xosc_reset); + dc->vmsd =3D &rp2040_xosc_vmstate; +} + +static const TypeInfo rp2040_xosc_info =3D { + .name =3D TYPE_RP2040_XOSC, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040XoscState), + .instance_init =3D rp2040_xosc_init, + .class_init =3D 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" =20 #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; =20 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 =3D \ (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-sysc= fg-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 =3D 0; + unsigned i; + + for (i =3D 0; i < count; i++) { + value |=3D (qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & BIT(0))= << i; + } + + return value; +} + +static void test_rosc_reset_values(void) +{ + QTestState *qts =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_CTRL), =3D=3D, + (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW); + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DORMANT), =3D=3D, + ROSC_WAKE_VALUE); + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), =3D=3D, 0x0000= 0ab0); + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS), =3D=3D, + ROSC_STATUS_RUNNING); + + qtest_quit(qts); +} + +static void test_rosc_badwrite_and_clear(void) +{ + QTestState *qts =3D rp2040_start(); + + qtest_writel(qts, ROSC_BASE + ROSC_FREQA, 0x00007777); + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_FREQA), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) & + ROSC_STATUS_BADWRITE, =3D=3D, 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, =3D=3D, 0); + + qtest_quit(qts); +} + +static void test_rosc_count_reaches_zero(void) +{ + QTestState *qts =3D rp2040_start(); + int i; + + qtest_writel(qts, ROSC_BASE + ROSC_COUNT, 1); + for (i =3D 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), =3D=3D, 0); + + qtest_quit(qts); +} + +static void test_rosc_misc_values(void) +{ + QTestState *qts =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & ~BIT(0), + =3D=3D, 0); + qtest_writel(qts, ROSC_BASE + ROSC_DIV, 0xaa1); + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), =3D=3D, 0xaa1); + qtest_writel(qts, ROSC_BASE + ROSC_PHASE, 0xaa8); + g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_PHASE), =3D=3D, 0xaa= 8); + + 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, =3D=3D, 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 =3D qtest_init("-machine raspi-pico,rosc-random-seed=3D0x1234"); + bits_a =3D read_random_bits(qts_a, 32); + qtest_quit(qts_a); + + qts_b =3D qtest_init("-machine raspi-pico,rosc-random-seed=3D0x1234"); + bits_b =3D read_random_bits(qts_b, 32); + qtest_quit(qts_b); + + g_assert_cmphex(bits_a, =3D=3D, 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047142; cv=none; d=zohomail.com; s=zohoarc; b=iCuaZhX7abhTFbVBcYDFHTrLeW6SQ6nZFSN9JAV2Zesbs9tAMhYolVp+Vm4M8BMfONy+tZjRouJn2X/6jpr2Qi8zOpCLaebO3IrUyhmF3WtMRmnHD+XDfswLRpgXM7b5MVGN7hxAmHZ4cZk4/9/jzI1DnYj6PRmkNFjmU2oaUVY= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047142; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=6/mfah0HA2ojyE6VhI3Frv9jdjbm4ZP2NAVGZrfbtLk=; b=CLNdcF6GfIxEvdgNDJkbpz4xUVbSNTabPXucXiayUPwkIMGxc0MIayWssDsb0EhHuEZE35qUgxuqFUekgf3xCrfOpEvIYOkwTx2ZlisTFp3cAMuBfzxt5tYRd4pwfq8vuxbcU0sziaKkSkhpWwyxmFbR872H1tTddPdrVfGAni8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047142276882.8831266110406; Sat, 29 Aug 2026 16:45:42 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0SiY-00088I-PJ; Sat, 29 Aug 2026 19:44:10 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiW-00087s-GO; Sat, 29 Aug 2026 19:44:08 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiS-0006Ee-Qn; Sat, 29 Aug 2026 19:44:08 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzd4kzszBqY3; Sun, 30 Aug 2026 01:43:41 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzd2Svvz7vrV; Sun, 30 Aug 2026 01:43:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047021; bh=6/mfah0HA2ojyE6VhI3Frv9jdjbm4ZP2NAVGZrfbtLk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=u37fXoWUR45uHdemp3T5hz9CKomFhmNbVTxAxxCEW3O5hXX/QMP/+IfzLc6ggSnco RpVJz1yNkXX7WDewHm5l5O9Hdfe9PZO8YjQ58KpIBgikjLfW6YOvhbh38kg8WGRcLm dFbuRGCJs99QlP4mVI/W+02IQ7EWNuUHdIVhTjXDZo5p+mD7DsjJ7hB9ljqwPGcWyl w4yVayq4LH6/hA0GhAgP1Bf88m24JBTHN2TjTEEZv7f9V6G4NVNh2jhZu5yuY3F4br UOAtGui8mxLnvaBZprL20JymeoxrRUQsWKKBMO6l/L082C1rZHH86SE2MAUMGDS2Gt EoOc23zl15cAQ== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 09/30] hw/misc: add RP2040 PLL and clock controller Date: Sun, 30 Aug 2026 01:42:43 +0200 Message-ID: <20260829234308.33725-10-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047144343154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the RP2040 system and USB PLL register blocks and their clock outputs= . Add the clock mux, divider, selected-source and frequency-counter registe= rs, then route clk-sys to the Cortex-M0+ and clk-peri to both UARTs. Includ= e focused qtests for PLL configuration, clock switching and frequency measu= rement. Signed-off-by: gilles grimaud --- 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" =20 -#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[] =3D { - { "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"); } =20 + 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_SYSIN= FO); object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC); @@ -182,7 +191,6 @@ static void rp2040_soc_init(Object *obj) =20 s->irq =3D qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS); s->sysclk =3D clock_new(obj, "sysclk"); - clock_set_hz(s->sysclk, RP2040_SYSCLK_FRQ); } =20 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); } =20 + 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); =20 for (i =3D 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 =20 +config RP2040_CLOCKS + bool + config RP2040_NYI bool =20 +config RP2040_PLL + bool + config RP2040_ROSC bool =20 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_sysinf= o.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 =3D reg >> 8; + + return div =3D=3D 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 =3D s->regs[CLK_REF_CTRL / 4]; + uint32_t src =3D extract32(ctrl, 0, 2); + unsigned freq; + + switch (src) { + case 0: + freq =3D ROSC_HZ; + break; + case 1: + freq =3D rp2040_clocks_ref_aux_freq(s, + rp2040_clocks_ctrl_auxsrc(ctrl)); + break; + case 2: + freq =3D XOSC_HZ; + break; + default: + freq =3D 0; + break; + } + + return freq / rp2040_clocks_div(s->regs[CLK_REF_DIV / 4]); +} + +static unsigned rp2040_clocks_sys_freq(RP2040ClocksState *s) +{ + uint32_t ctrl =3D s->regs[CLK_SYS_CTRL / 4]; + uint32_t src =3D extract32(ctrl, 0, 1); + unsigned freq; + + if (src) { + freq =3D rp2040_clocks_sys_aux_freq(s, + rp2040_clocks_ctrl_auxsrc(ctrl)); + } else { + freq =3D 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 =3D 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 =3D s->regs[ctrl_off / 4]; + + if (!(ctrl & CTRL_ENABLE)) { + return 0; + } + + return rp2040_clocks_usb_adc_rtc_aux_freq(s, + rp2040_clocks_ctrl_auxsrc(ct= rl)) / + rp2040_clocks_div(s->regs[div_off / 4]); +} + +static void rp2040_clocks_update(RP2040ClocksState *s) +{ + unsigned ref_hz =3D rp2040_clocks_ref_freq(s); + unsigned sys_hz =3D 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 =3D rp2040_clocks_hz(s->pll_sys) / 1000; + break; + case 0x02: + khz =3D rp2040_clocks_hz(s->pll_usb) / 1000; + break; + case 0x03: + case 0x04: + khz =3D ROSC_HZ / 1000; + break; + case 0x05: + khz =3D XOSC_HZ / 1000; + break; + case 0x08: + khz =3D rp2040_clocks_ref_freq(s) / 1000; + break; + case 0x09: + khz =3D rp2040_clocks_sys_freq(s) / 1000; + break; + case 0x0a: + khz =3D clock_get_hz(s->clk_peri) / 1000; + break; + case 0x0b: + khz =3D clock_get_hz(s->clk_usb) / 1000; + break; + case 0x0c: + khz =3D clock_get_hz(s->clk_adc) / 1000; + break; + case 0x0d: + khz =3D clock_get_hz(s->clk_rtc) / 1000; + break; + default: + khz =3D 0; + break; + } + + return khz << 5; +} + +static bool rp2040_clocks_is_selected(hwaddr offset) +{ + return offset =3D=3D CLK_REF_SELECTED || + offset =3D=3D CLK_SYS_SELECTED || + offset =3D=3D CLK_PERI_SELECTED || + offset =3D=3D CLK_USB_SELECTED || + offset =3D=3D CLK_ADC_SELECTED || + offset =3D=3D CLK_RTC_SELECTED; +} + +static uint64_t rp2040_clocks_read(void *opaque, hwaddr addr, unsigned siz= e) +{ + RP2040ClocksState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + if (offset >=3D sizeof(s->regs)) { + value =3D 0; + rp2040_log_unimplemented_read("clocks", size, + RP2040_CLOCKS_BASE + addr, offset, + value); + } else if (rp2040_clocks_is_selected(offset)) { + value =3D rp2040_clocks_selected(s, offset); + } else { + switch (offset) { + case FC0_STATUS: + value =3D BIT(4); + break; + case FC0_RESULT: + value =3D rp2040_clocks_fc0_result(s); + break; + case ENABLED0: + case ENABLED1: + value =3D s->regs[(offset - 0x10) / 4]; + break; + default: + value =3D s->regs[offset / 4]; + break; + } + } + + return value; +} + +static void rp2040_clocks_write(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + RP2040ClocksState *s =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t old; + uint32_t newval =3D value; + + if (offset < sizeof(s->regs) && !rp2040_clocks_is_selected(offset)) { + old =3D s->regs[offset / 4]; + switch (alias) { + case ATOMIC_XOR: + newval =3D old ^ value; + break; + case ATOMIC_SET: + newval =3D old | value; + break; + case ATOMIC_CLR: + newval =3D 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] =3D newval; + rp2040_clocks_update(s); + break; + } + } else if (offset >=3D 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 =3D { + .read =3D rp2040_clocks_read, + .write =3D rp2040_clocks_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_clocks_reset(DeviceState *dev) +{ + RP2040ClocksState *s =3D RP2040_CLOCKS(dev); + + memset(s->regs, 0, sizeof(s->regs)); + + s->regs[CLK_REF_DIV / 4] =3D 0x00000100; + s->regs[CLK_SYS_DIV / 4] =3D 0x00000100; + s->regs[CLK_PERI_CTRL / 4] =3D CTRL_ENABLE; + s->regs[CLK_PERI_DIV / 4] =3D 0x00000100; + s->regs[CLK_USB_CTRL / 4] =3D CTRL_ENABLE | (0x3 << 5); + s->regs[CLK_USB_DIV / 4] =3D 0x00000100; + s->regs[CLK_ADC_CTRL / 4] =3D CTRL_ENABLE | (0x3 << 5); + s->regs[CLK_ADC_DIV / 4] =3D 0x00000100; + s->regs[CLK_RTC_CTRL / 4] =3D CTRL_ENABLE | (0xa << 5); + s->regs[CLK_RTC_DIV / 4] =3D 0x00000100; + s->regs[FC0_REF_KHZ / 4] =3D XOSC_HZ / 1000; + s->regs[WAKE_EN0 / 4] =3D 0xffffffff; + s->regs[WAKE_EN1 / 4] =3D 0xffffffff; + s->regs[SLEEP_EN0 / 4] =3D 0xffffffff; + s->regs[SLEEP_EN1 / 4] =3D 0xffffffff; + + rp2040_clocks_update(s); +} + +static void rp2040_clocks_init(Object *obj) +{ + RP2040ClocksState *s =3D RP2040_CLOCKS(obj); + DeviceState *dev =3D DEVICE(obj); + + s->clk_ref =3D qdev_init_clock_out(dev, "clk-ref"); + s->clk_sys =3D qdev_init_clock_out(dev, "clk-sys"); + s->clk_peri =3D qdev_init_clock_out(dev, "clk-peri"); + s->clk_usb =3D qdev_init_clock_out(dev, "clk-usb"); + s->clk_adc =3D qdev_init_clock_out(dev, "clk-adc"); + s->clk_rtc =3D qdev_init_clock_out(dev, "clk-rtc"); + s->pll_sys =3D qdev_init_clock_in(dev, "pll-sys", + rp2040_clocks_input_update, s, 0); + s->pll_usb =3D 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 =3D { + .name =3D TYPE_RP2040_CLOCKS, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_clocks_reset); + dc->vmsd =3D &rp2040_clocks_vmstate; +} + +static const TypeInfo rp2040_clocks_info =3D { + .name =3D TYPE_RP2040_CLOCKS, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040ClocksState), + .instance_init =3D rp2040_clocks_init, + .class_init =3D 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 =3D s->cs & PLL_CS_REFDIV_MASK; + uint32_t fbdiv =3D s->fbdiv_int & PLL_FBDIV_MASK; + uint32_t postdiv1 =3D extract32(s->prim, 16, 3); + uint32_t postdiv2 =3D extract32(s->prim, 12, 3); + uint64_t hz; + + if (!rp2040_pll_locked(s) || (s->pwr & PLL_PWR_POSTDIVPD)) { + return 0; + } + + refdiv =3D refdiv ? refdiv : 1; + if (s->cs & PLL_CS_BYPASS) { + return XOSC_HZ / refdiv; + } + + if (!fbdiv || !postdiv1 || !postdiv2) { + return s->fallback_hz; + } + + hz =3D XOSC_HZ; + hz =3D 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 =3D opaque; + hwaddr offset =3D addr & 0xfff; + const char *name =3D s->trace_name ? s->trace_name : "rp2040.pll"; + uint64_t value; + + switch (offset) { + case PLL_CS: + value =3D s->cs & ~PLL_CS_LOCK; + if (rp2040_pll_locked(s)) { + value |=3D PLL_CS_LOCK; + } + break; + case PLL_PWR: + value =3D s->pwr; + break; + case PLL_FBDIV_INT: + value =3D s->fbdiv_int; + break; + case PLL_PRIM: + value =3D s->prim; + break; + default: + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + const char *name =3D s->trace_name ? s->trace_name : "rp2040.pll"; + uint32_t value =3D value64; + + switch (offset) { + case PLL_CS: + s->cs =3D rp2040_pll_apply_alias(s->cs, value, alias) & + (PLL_CS_BYPASS | PLL_CS_REFDIV_MASK); + break; + case PLL_PWR: + s->pwr =3D rp2040_pll_apply_alias(s->pwr, value, alias) & PLL_PWR_= MASK; + break; + case PLL_FBDIV_INT: + s->fbdiv_int =3D rp2040_pll_apply_alias(s->fbdiv_int, value, alias= ) & + PLL_FBDIV_MASK; + break; + case PLL_PRIM: + s->prim =3D 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 =3D { + .read =3D rp2040_pll_read, + .write =3D rp2040_pll_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_pll_reset(DeviceState *dev) +{ + RP2040PllState *s =3D RP2040_PLL(dev); + + s->cs =3D 1; + s->pwr =3D PLL_PWR_MASK; + s->fbdiv_int =3D 0; + s->prim =3D (7 << 16) | (7 << 12); + + rp2040_pll_update_clock(s); +} + +static void rp2040_pll_init(Object *obj) +{ + RP2040PllState *s =3D RP2040_PLL(obj); + DeviceState *dev =3D DEVICE(obj); + + s->clk =3D 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 =3D { + .name =3D TYPE_RP2040_PLL, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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[] =3D { + 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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_pll_reset); + device_class_set_props(dc, rp2040_pll_properties); + dc->vmsd =3D &rp2040_pll_vmstate; +} + +static const TypeInfo rp2040_pll_info =3D { + .name =3D TYPE_RP2040_PLL, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040PllState), + .instance_init =3D rp2040_pll_init, + .class_init =3D 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 { =20 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_clock= s.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 =3D \ (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-sysc= fg-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-t= est.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, =3D=3D, FC0_STATUS_DONE); + return qtest_readl(qts, CLOCKS_BASE + FC0_RESULT) >> 5; +} + +static void test_clock_mux_and_frequency_counter(void) +{ + QTestState *qts =3D 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), =3D=3D, 125000); + g_assert_cmpuint(frequency_count_khz(qts, 0x02), =3D=3D, 48000); + g_assert_cmpuint(frequency_count_khz(qts, 0x03), =3D=3D, 6000); + g_assert_cmpuint(frequency_count_khz(qts, 0x09), =3D=3D, 125000); + g_assert_cmpuint(frequency_count_khz(qts, 0x0a), =3D=3D, 125000); + g_assert_cmpuint(frequency_count_khz(qts, 0x0b), =3D=3D, 48000); + g_assert_cmpuint(frequency_count_khz(qts, 0x0c), =3D=3D, 48000); + g_assert_cmpuint(frequency_count_khz(qts, 0x0d), =3D=3D, 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), =3D=3D, 0); + g_assert_cmpuint(frequency_count_khz(qts, 0x09), =3D=3D, 48000); + g_assert_cmpuint(frequency_count_khz(qts, 0x0a), =3D=3D, 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047105; cv=none; d=zohomail.com; s=zohoarc; b=XDTKrHpiygkSsLngXaAkM+DDjCf8jvMNJ4lICK52lRjtXmDOlGi9rpbbX0GWrVolP/pqRAlDrMr0JmWp/q4kDcByhFG9kQ2aVs0tzr2/7OLSExl8m5AHdbR+VKhPrUM6qM13yJSfE8xx6Megu4PaniwKVt1nVUasfI3adNxXcUs= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047105; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=Hi1sHMtAcIkGqN+6fEfecIGe3R/X86Z+Of2u3jYHCAQ=; b=G011f2NylVomDZPMHm1AzHhBzgDudxz7Yz1uYDKUOroWVWyd1EqiSOru/3JOKuwxPpa0iirg/cdQKTH4MrLzffguIN+cKjXr33gatOIoS/0gg/LzaWnp1rnoCKX0ubBrsnw8Cxog3bo7oXyNLTGXMGuOPINMN4ir45jf/n2HwFo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047105258865.0006733444611; Sat, 29 Aug 2026 16:45:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Sih-0008Ah-R8; Sat, 29 Aug 2026 19:44:19 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sic-00089w-46; Sat, 29 Aug 2026 19:44:14 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0SiY-0006FR-VB; Sat, 29 Aug 2026 19:44:13 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzf0RdjzBqY4; Sun, 30 Aug 2026 01:43:42 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzd5NRdz7tLT; Sun, 30 Aug 2026 01:43:41 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047022; bh=Hi1sHMtAcIkGqN+6fEfecIGe3R/X86Z+Of2u3jYHCAQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PYHiXqhDNCpPmE4ZLBm4Dp5zNnAz03+LEu4DXT0LPoELXuVx3u7KWOG71t+6bLxTE g2SiiYRGKF9Y2r2+gWoHEAU0O13Zcs510OrcBzMASjzwISPUqfNTvuFoS+GOmY8IVy SRw38r0Z8YhtJg4wGB8OhPD+xAxfGT1GWnVjwsHmYvMU3/tr12QQDXp1kT+ihvOt7I 1QfXfDMcktsTfNdUmkQMbq1hnOI4rilZQbYLo/XJTCEx7YwP6U0lA/Xl75k3rwjzrk BRrQ1Zpi5wWquUHiSITAGo+mq/as8J+d2EEJ7XjpJZdhwxHizEzMQuAUIAWrB5fW/i LZtIGoxPXOaeg== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 10/30] hw/misc: add RP2040 reset and power state controllers Date: Sun, 30 Aug 2026 01:42:44 +0200 Message-ID: <20260829234308.33725-11-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047110721158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the RP2040 RESETS and PSM register blocks, including atomic aliases, = watchdog selection and completion state. Expose the PSM update callback use= d by the later multicore integration and add focused qtests for reset-contr= oller behavior. Signed-off-by: gilles grimaud --- 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[] =3D { - { "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); =20 + 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_SYSIN= FO); 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")); =20 + 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 =20 +config RP2040_PSM + bool + +config RP2040_RESETS + bool + config RP2040_ROSC bool =20 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_sysinf= o.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 =3D update; + s->update_opaque =3D 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 =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case PSM_FRCE_ON: + value =3D s->frce_on; + break; + case PSM_FRCE_OFF: + value =3D s->frce_off; + break; + case PSM_WDSEL: + value =3D s->wdsel; + break; + case PSM_DONE: + value =3D rp2040_psm_done(s); + break; + default: + value =3D 0; + rp2040_log_unimplemented_read("psm", size, + RP2040_PSM_BASE + addr, offset, valu= e); + break; + } + + return value; +} + +static void rp2040_psm_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + RP2040PsmState *s =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D value64; + uint32_t old_frce_off =3D s->frce_off; + + switch (offset) { + case PSM_FRCE_ON: + s->frce_on =3D rp2040_psm_apply_alias(s->frce_on, value, alias) & + RP2040_PSM_VALID_MASK; + break; + case PSM_FRCE_OFF: + s->frce_off =3D rp2040_psm_apply_alias(s->frce_off, value, alias) & + RP2040_PSM_VALID_MASK; + if (s->frce_off !=3D old_frce_off) { + rp2040_psm_update(s); + } + break; + case PSM_WDSEL: + s->wdsel =3D 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 =3D { + .read =3D rp2040_psm_read, + .write =3D rp2040_psm_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_psm_reset(DeviceState *dev) +{ + RP2040PsmState *s =3D RP2040_PSM(dev); + + s->frce_on =3D 0; + s->frce_off =3D 0; + s->wdsel =3D 0; + rp2040_psm_update(s); +} + +static int rp2040_psm_post_load(void *opaque, int version_id) +{ + RP2040PsmState *s =3D opaque; + + rp2040_psm_update(s); + return 0; +} + +static void rp2040_psm_init(Object *obj) +{ + RP2040PsmState *s =3D 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 =3D { + .name =3D TYPE_RP2040_PSM, + .version_id =3D 1, + .minimum_version_id =3D 1, + .post_load =3D rp2040_psm_post_load, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_psm_reset); + dc->vmsd =3D &rp2040_psm_vmstate; +} + +static const TypeInfo rp2040_psm_info =3D { + .name =3D TYPE_RP2040_PSM, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040PsmState), + .instance_init =3D rp2040_psm_init, + .class_init =3D 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 siz= e) +{ + RP2040ResetsState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case RESETS_RESET: + value =3D s->reset; + break; + case RESETS_WDSEL: + value =3D s->wdsel; + break; + case RESETS_RESET_DONE: + value =3D (~s->reset) & RESETS_VALID_MASK; + break; + default: + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D value64; + + switch (offset) { + case RESETS_RESET: + s->reset =3D rp2040_resets_apply_alias(s->reset, value, alias) & + RESETS_VALID_MASK; + break; + case RESETS_WDSEL: + s->wdsel =3D 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 =3D { + .read =3D rp2040_resets_read, + .write =3D rp2040_resets_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_resets_reset(DeviceState *dev) +{ + RP2040ResetsState *s =3D RP2040_RESETS(dev); + + s->reset =3D RESETS_VALID_MASK; + s->wdsel =3D 0; +} + +static void rp2040_resets_init(Object *obj) +{ + RP2040ResetsState *s =3D 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 =3D { + .name =3D TYPE_RP2040_RESETS, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_resets_reset); + dc->vmsd =3D &rp2040_resets_vmstate; +} + +static const TypeInfo rp2040_resets_info =3D { + .name =3D TYPE_RP2040_RESETS, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040ResetsState), + .instance_init =3D rp2040_resets_init, + .class_init =3D 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_reset= s.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 =3D \ (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : [])= + \ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-sysc= fg-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-t= est.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 =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET), =3D=3D, + RESETS_VALID_MASK); + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_WDSEL), =3D=3D, = 0); + + qtest_quit(qts); +} + +static void test_resets_set_clear_done(void) +{ + QTestState *qts =3D rp2040_start(); + uint32_t mask =3D 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, + =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE) & ma= sk, + =3D=3D, mask); + + qtest_writel(qts, RESETS_BASE + RESETS_RESET + ATOMIC_SET, RESET_PWM); + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET) & RESET_P= WM, + =3D=3D, RESET_PWM); + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE) & + RESET_PWM, =3D=3D, 0); + + qtest_writel(qts, RESETS_BASE + RESETS_RESET + ATOMIC_CLR, RESET_PWM); + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_RESET_DONE) & ma= sk, + =3D=3D, mask); + + qtest_quit(qts); +} + +static void test_resets_wdsel(void) +{ + QTestState *qts =3D rp2040_start(); + + qtest_writel(qts, RESETS_BASE + RESETS_WDSEL, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, RESETS_BASE + RESETS_WDSEL), =3D=3D, + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047172; cv=none; d=zohomail.com; s=zohoarc; b=cbfdMT3Q4qnv05zg/6QQsG/huG7M+ggeIODQjgoChGRP3W/TX4MW2BrirGHF6ZopsfgIXrzqKRD5nok6d06c4GjIgs7slshWa/Pk+w2Y2TvJY7fUo96oCXV8raCDpeNQd4LFqbd2ZfKjIsh0KeGVdxvAC8/ar6wuLysRz0eGHuk= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047172; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=jaGznRlhYw7viXdXk7BC2gWQcp908uo1V4fO1DssXg8=; b=FPoK29kGvsPRUhc+ltsaTvf/lfb34oh655Pk6mOH5xJ0pZqOVUZafiZwAFIMtGJCLJwU6EaNrdqzZOR8/8QUcuUQW/TDHL3INO0DBh7kMBTn1m4ddTf2MgoC25wdauIx4AyPoDqzql420CyM1Oe5hgMoIdCH/ZXYw+Uwf7PohT4= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047172342891.9050596016009; Sat, 29 Aug 2026 16:46:12 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Sis-0008ED-7G; Sat, 29 Aug 2026 19:44:30 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sip-0008Db-Kl; Sat, 29 Aug 2026 19:44:27 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sil-0006H2-Ua; Sat, 29 Aug 2026 19:44:27 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXWzf3Gm7z838H; Sun, 30 Aug 2026 01:43:42 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzf10dVz7vrV; Sun, 30 Aug 2026 01:43:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047022; bh=jaGznRlhYw7viXdXk7BC2gWQcp908uo1V4fO1DssXg8=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BoqApYQ0kdvdU9NUEr4dALvT2CPU7UxFMOP8g2Il0Tz08oBcEY29ymduPZn11WdJ2 57v7K9iOYCwmHjzSy02qCHLgXRwHfUXuYvRjQAxgkYbU1w+GMkBt5uTNBk+wMb8UhN oGz3pwZmFRdZFLnpXkqRMvvndcC2/6aHuIcl3UEQRtk++MCWFy7NxFHFHyaI/TjWUk n+aIuG3eYasXPmMZrOEqAzs2oD4NfsjEN7hEnPWHVCLx8KVmRie3GLk5IJ9Lo9/IwJ utNJpzy5K4WmXssk5ObFTZsxNh8Ii+5c8oVpGkO+dQnUSri51VdoB5g1ZU5N0+/Icl /t1Q9EIs5YU5g== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 11/30] hw/misc: add RP2040 watchdog Date: Sun, 30 Aug 2026 01:42:45 +0200 Message-ID: <20260829234308.33725-12-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047175122154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud 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 f= orced triggering. Signed-off-by: gilles grimaud --- 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 =20 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); =20 s->irq =3D 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); =20 + 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 =20 +config RP2040_WATCHDOG + bool + config RP2040_XOSC bool =20 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: fi= les('rp2040_syscfg.c')) system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: files('rp2040_sysinf= o.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_watch= dog.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 =3D (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 =3D clock_get_hz(s->clk_ref); + uint32_t cycles =3D 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 =3D 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 |=3D WATCHDOG_TICK_RUNNING; + value |=3D (s->tick & WATCHDOG_TICK_CYCLES_MASK) << + WATCHDOG_TICK_COUNT_SHIFT; + } + + return value; +} + +static void rp2040_watchdog_update(RP2040WatchdogState *s) +{ + uint32_t tick_hz =3D rp2040_watchdog_tick_freq_hz(s); + uint64_t watchdog_hz =3D (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 =3D MIN(ptimer_get_count(s->timer), WATCHDOG_CTRL_TIME_M= ASK); + + 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 =3D 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 s= ize) +{ + RP2040WatchdogState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + hwaddr word_offset =3D offset & ~3ULL; + unsigned scratch; + uint64_t value; + + switch (word_offset) { + case WATCHDOG_CTRL: + value =3D rp2040_watchdog_ctrl_read(s); + break; + case WATCHDOG_LOAD: + value =3D 0; + break; + case WATCHDOG_REASON: + value =3D s->reason; + break; + case WATCHDOG_TICK: + value =3D rp2040_watchdog_tick_read(s); + break; + default: + if (rp2040_watchdog_scratch_offset(word_offset, &scratch)) { + value =3D s->scratch[scratch]; + } else { + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + hwaddr word_offset =3D offset & ~3ULL; + unsigned scratch; + uint32_t value =3D rp2040_watchdog_expand_write(value64, size); + + switch (word_offset) { + case WATCHDOG_CTRL: { + uint32_t old =3D rp2040_watchdog_ctrl_read(s); + uint32_t newval =3D rp2040_watchdog_apply_alias(old, value, alias); + + s->ctrl =3D 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 =3D 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 =3D 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] =3D + 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 =3D { + .read =3D rp2040_watchdog_read, + .write =3D rp2040_watchdog_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + .unaligned =3D false, + }, +}; + +static void rp2040_watchdog_reset(DeviceState *dev) +{ + RP2040WatchdogState *s =3D RP2040_WATCHDOG(dev); + + s->ctrl =3D WATCHDOG_CTRL_PAUSE_MASK; + s->load =3D 0; + s->tick =3D 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 =3D RP2040_WATCHDOG(opaque); + + rp2040_watchdog_reset_request(s, WATCHDOG_REASON_TIMER, true); +} + +static void rp2040_watchdog_init(Object *obj) +{ + RP2040WatchdogState *s =3D RP2040_WATCHDOG(obj); + DeviceState *dev =3D DEVICE(obj); + + s->clk_ref =3D 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 =3D RP2040_WATCHDOG(dev); + + if (!clock_has_source(s->clk_ref)) { + error_setg(errp, "RP2040 watchdog: clk-ref clock must be connected= "); + return; + } + + s->timer =3D 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 =3D { + .name =3D TYPE_RP2040_WATCHDOG, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 *dat= a) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D rp2040_watchdog_realize; + device_class_set_legacy_reset(dc, rp2040_watchdog_reset); + dc->vmsd =3D &rp2040_watchdog_vmstate; +} + +static const TypeInfo rp2040_watchdog_info =3D { + .name =3D TYPE_RP2040_WATCHDOG, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040WatchdogState), + .instance_init =3D rp2040_watchdog_init, + .class_init =3D 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" =20 @@ -54,6 +55,7 @@ struct RP2040State { RP2040RoscState rosc; RP2040TbmanState tbman; RP2040VregState vreg; + RP2040WatchdogState watchdog; RP2040XoscState xosc; =20 MemoryRegion *board_memory; diff --git a/include/hw/misc/rp2040_watchdog.h b/include/hw/misc/rp2040_wat= chdog.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 =3D \ 'rp2040-resets-test= ', 'rp2040-rosc-test', 'rp2040-tbman-test', - 'rp2040-vreg-test']= : []) + \ + 'rp2040-vreg-test', + 'rp2040-watchdog-te= st'] : []) + \ (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-watchd= og-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 =3D qtest_qmp_eventwait_ref(qts, "WATCHDOG"); + QDict *data =3D qdict_get_qdict(event, "data"); + + g_assert_cmpstr(qdict_get_str(data, "action"), =3D=3D, "none"); + qobject_unref(event); +} + +static void test_reset_values(void) +{ + QTestState *qts =3D rp2040_watchdog_start(); + + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_CTRL), =3D= =3D, + CTRL_PAUSE_DBG1 | CTRL_PAUSE_DBG0 | CTRL_PAUSE_JTAG); + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_LOAD), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_REASON), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_TICK), =3D= =3D, + TICK_ENABLE); + + qtest_quit(qts); +} + +static void test_scratch_registers(void) +{ + QTestState *qts =3D rp2040_watchdog_start(); + + qtest_writel(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0, 0x12345678); + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), = =3D=3D, + 0x12345678); + + g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), = =3D=3D, + 0x78); + g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 1= ), + =3D=3D, 0x56); + g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 2= ), + =3D=3D, 0x34); + g_assert_cmphex(qtest_readb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 3= ), + =3D=3D, 0x12); + + qtest_writeb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0, 0xa5); + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), = =3D=3D, + 0xa5a5a5a5); + + qtest_writeb(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0 + 1, 0x3c); + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), = =3D=3D, + 0x3c3c3c3c); + + qtest_writew(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0, 0xf00d); + g_assert_cmphex(qtest_readl(qts, WATCHDOG_BASE + WATCHDOG_SCRATCH0), = =3D=3D, + 0xf00df00d); + + qtest_quit(qts); +} + +static void test_timer_expiry(void) +{ + QTestState *qts =3D rp2040_watchdog_start(); + uint32_t ctrl; + + /* + * The default clk_ref is 6 MHz. CYCLES=3D6 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), =3D=3D, + 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 =3D 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), =3D= =3D, + REASON_TIMER); + + qtest_quit(qts); +} + +static void test_force_trigger(void) +{ + QTestState *qts =3D 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), =3D= =3D, + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047120; cv=none; d=zohomail.com; s=zohoarc; b=T/sGi72QiL+dpW4VOINgVk8qqWrBlDc/6qSfVxA0Nwg4rUlZSKhE9ju3GRLTt1Xe36CHcRs6M4Z+KZ11ViagyKZAYqqSmlV+6m4zER1lpFf3dzUsU6Y5JvOVttcAzSDce3TxEIGLOO30StovZwQVtUhxE9H/QIK8PL4chELR9Ew= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047120; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=xyEggmT/8IjRA3Y//4vtDQIEsFwZnyBKaRa8yKyXVTM=; b=hyyIrl5wdkDsCgtX4Hh0QgZlOlSNdp+Z5k7/hV3B7EMHkSl8QtfsL4y/HDMGol5+OBlFveS7ZrdV264f1CvS8elrMkuKLYZxc7dRJMW49864el58YhsDpP+1EY+eAPK3xQiN2dPtdqaIA9n7iPTHEbWvp6nU7ekaXdB7Y6cpxiY= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047119791539.2190640899665; Sat, 29 Aug 2026 16:45:19 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Sil-0008CH-LY; Sat, 29 Aug 2026 19:44:23 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sig-0008Ai-Ux; Sat, 29 Aug 2026 19:44:19 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sie-0006GF-64; Sat, 29 Aug 2026 19:44:18 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzf5xJdzBqYJ; Sun, 30 Aug 2026 01:43:42 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzf3kwlz7tLT; Sun, 30 Aug 2026 01:43:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047022; bh=xyEggmT/8IjRA3Y//4vtDQIEsFwZnyBKaRa8yKyXVTM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sbKRjmmdZVQ4lJqkdZPtQG5InH3EVYsfEa6pILftx4kTDL8tnLZQifCf32PkxoEGu upD42Jtt2YM8FD+6URsgmDbCutK5KNlpTRuY4MdBbiUzM8JnTBz1Ln/xihiRG0zd/3 SoNRFoZbioPVwxMCnFZvFED+6gZML1Ov3lbkSZAcGz0HqXXXHkrTc7+rbEF8IAgqOJ 9y8Pwccv9nr7Fa6wq5OTxMDXLNaUVV1d8PuiVRd653rFYCuL7V/1cPCAdbqbFIpAuy HmV7S/Wy5DzbgYWTR2Y2KgtBos6smSgjwrQYU9PrsGNtYHNYbqDcwWUH+bpyrdqvO2 D9b07KCYaIdlg== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 12/30] hw/misc: add RP2040 pad controls Date: Sun, 30 Aug 2026 01:42:46 +0200 Message-ID: <20260829234308.33725-13-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047122047158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the RP2040 PADS_BANK0 and PADS_QSPI register banks, including documen= ted 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 --- 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[] =3D { { "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) } =20 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); =20 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); =20 + 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 =20 +config RP2040_PADS + bool + config RP2040_PLL bool =20 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 =3D (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 =3D (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 =3D opaque; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint64_t value; + + switch (offset) { + case PADS_BANK0_VOLTAGE_SELECT: + value =3D s->voltage_select; + break; + case PADS_BANK0_SWCLK: + value =3D s->swclk; + break; + case PADS_BANK0_SWD: + value =3D s->swd; + break; + default: + if (rp2040_pads_bank0_gpio_offset(offset, &index)) { + value =3D s->gpio[index]; + } else { + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint32_t value =3D value64; + + switch (offset) { + case PADS_BANK0_VOLTAGE_SELECT: + s->voltage_select =3D + rp2040_pads_apply_alias(s->voltage_select, value, alias) & + PADS_VOLTAGE_SELECT_MASK; + break; + case PADS_BANK0_SWCLK: + s->swclk =3D rp2040_pads_apply_alias(s->swclk, value, alias) & + PADS_PAD_MASK; + break; + case PADS_BANK0_SWD: + s->swd =3D rp2040_pads_apply_alias(s->swd, value, alias) & + PADS_PAD_MASK; + break; + default: + if (rp2040_pads_bank0_gpio_offset(offset, &index)) { + s->gpio[index] =3D rp2040_pads_apply_alias(s->gpio[index], val= ue, + alias) & PADS_PAD_MAS= K; + } 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 =3D opaque; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint64_t value; + + switch (offset) { + case PADS_QSPI_VOLTAGE_SELECT: + value =3D s->voltage_select; + break; + default: + if (rp2040_pads_qspi_pad_offset(offset, &index)) { + value =3D s->pad[index]; + } else { + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint32_t value =3D value64; + + switch (offset) { + case PADS_QSPI_VOLTAGE_SELECT: + s->voltage_select =3D + 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] =3D 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 =3D { + .read =3D rp2040_pads_bank0_read, + .write =3D rp2040_pads_bank0_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static const MemoryRegionOps rp2040_pads_qspi_ops =3D { + .read =3D rp2040_pads_qspi_read, + .write =3D rp2040_pads_qspi_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_pads_bank0_reset(DeviceState *dev) +{ + RP2040PadsBank0State *s =3D RP2040_PADS_BANK0(dev); + unsigned i; + + s->voltage_select =3D 0; + for (i =3D 0; i < ARRAY_SIZE(s->gpio); i++) { + s->gpio[i] =3D PADS_BANK0_GPIO_RESET; + } + s->swclk =3D PADS_BANK0_SWD_RESET; + s->swd =3D PADS_BANK0_SWD_RESET; +} + +static void rp2040_pads_qspi_reset(DeviceState *dev) +{ + RP2040PadsQspiState *s =3D RP2040_PADS_QSPI(dev); + + s->voltage_select =3D 0; + s->pad[0] =3D PADS_QSPI_SCLK_RESET; + s->pad[1] =3D PADS_QSPI_SD_RESET; + s->pad[2] =3D PADS_QSPI_SD_RESET; + s->pad[3] =3D PADS_QSPI_SD_RESET; + s->pad[4] =3D PADS_QSPI_SD_RESET; + s->pad[5] =3D PADS_QSPI_SS_RESET; +} + +static void rp2040_pads_bank0_init(Object *obj) +{ + RP2040PadsBank0State *s =3D 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 =3D 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 =3D { + .name =3D TYPE_RP2040_PADS_BANK0, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D { + .name =3D TYPE_RP2040_PADS_QSPI, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 *d= ata) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_pads_bank0_reset); + dc->vmsd =3D &rp2040_pads_bank0_vmstate; +} + +static void rp2040_pads_qspi_class_init(ObjectClass *klass, const void *da= ta) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_pads_qspi_reset); + dc->vmsd =3D &rp2040_pads_qspi_vmstate; +} + +static const TypeInfo rp2040_pads_bank0_info =3D { + .name =3D TYPE_RP2040_PADS_BANK0, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040PadsBank0State), + .instance_init =3D rp2040_pads_bank0_init, + .class_init =3D rp2040_pads_bank0_class_init, +}; + +static const TypeInfo rp2040_pads_qspi_info =3D { + .name =3D TYPE_RP2040_PADS_QSPI, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040PadsQspiState), + .instance_init =3D rp2040_pads_qspi_init, + .class_init =3D 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 =3D \ (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : [])= + \ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-sysc= fg-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 =3D rp2040_pads_start(); + + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + + PADS_BANK0_VOLTAGE), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO0), + =3D=3D, 0x56); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_GPIO29), + =3D=3D, 0x56); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_SWCLK), + =3D=3D, 0x5a); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + PADS_BANK0_SWD), + =3D=3D, 0x5a); + + qtest_writel(qts, PADS_BANK0_BASE + PADS_BANK0_VOLTAGE, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, PADS_BANK0_BASE + + PADS_BANK0_VOLTAGE), =3D=3D, 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), + =3D=3D, 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), + =3D=3D, 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), + =3D=3D, 0x81); + + qtest_quit(qts); +} + +static void test_qspi(void) +{ + QTestState *qts =3D rp2040_pads_start(); + + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_VOLTAGE), + =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SCLK), + =3D=3D, 0x56); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SD0), + =3D=3D, 0x52); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SD3), + =3D=3D, 0x52); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS), + =3D=3D, 0x5a); + + qtest_writel(qts, PADS_QSPI_BASE + PADS_QSPI_SS, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, PADS_QSPI_BASE + PADS_QSPI_SS), + =3D=3D, 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), + =3D=3D, 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), + =3D=3D, 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047146; cv=none; d=zohomail.com; s=zohoarc; b=K3I0iPtQ8U3R79wBuVGvkT7txqzp6j01s5pSYsiEsNAfH2Yu1mOjMeh/ppW/dSrT1DNFqYGeizZC1lqbntQgJ0mITYzPjZw/WMkpRd7Eul74e8aOCOfzN2aeXoZYjRNezPv5GxqD4FkYZLzJp1X+6io7aPZ/V1RU6OxxAW8KlaI= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047146; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=UA53Dbe2MvOAJunleR0nfdNUztIkzhmGrz1Uzr5I+Ik=; b=m5DQ6/X3DT5/ZZmoGWZmdGv4AYwr3neUtUZsZUKCmNt1Tx5Rf6FGnjFGEJD4r3yt/H2tlFkGU1jRZdXzz6IVGvSSgBItcZpQGdFHUnHPwrGzjv9/Ex89Vz1eZg+v3/tgJ4WTEnwQqEExjFR7QLZWL+8IsAKQtHHkGpFrXyACpx0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047146889925.8046024089355; Sat, 29 Aug 2026 16:45:46 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Sip-0008Di-Tc; Sat, 29 Aug 2026 19:44:27 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sin-0008Cl-2A; Sat, 29 Aug 2026 19:44:25 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Sij-0006Gr-1q; Sat, 29 Aug 2026 19:44:24 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXWzg1SvczBqYL; Sun, 30 Aug 2026 01:43:43 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzf6Qz0z7vrV; Sun, 30 Aug 2026 01:43:42 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047023; bh=UA53Dbe2MvOAJunleR0nfdNUztIkzhmGrz1Uzr5I+Ik=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Q96cib9e+Vc9becDCtbDZuTn71RewHeKocdfQMxxLXu4HMY8WgvSwcAhXtKpoAKm8 CWAh3kAWQpO0qDpr0RojMClqw/ch9SdfFLuQYMQGewy8bab3e5grPeKSN/bSB2rEFH jy9mFrkYMwfnppSadrB/k/NMjJqIRN2MLhcYYlqsTevBo3vUx7kp5Bx/3yf8BQ3+va CB3ToOshGZ5kBxAWIPWBUpzKGvON30zwKAeSrd6x+FDHRSMOJSRpHvTUspAXDNblRg 9iGAoffNW7+NmbL6rbge3N3674Hr4Vpp/QQ5ruGM6ibyIkGG3C5da0Iz71P9SiBl8K fWBgZhuhbjf9g== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 13/30] hw/misc: add RP2040 IO_BANK0 Date: Sun, 30 Aug 2026 01:42:47 +0200 Message-ID: <20260829234308.33725-14-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047148512154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model GPIO control registers and the PROC0, PROC1 and dormant-wake interrup= t banks, including atomic aliases and last-bank masks. Connect the PROC0 in= terrupt output to the Cortex-M0+ NVIC and add focused qtests for register b= ehavior and interrupt propagation. Signed-off-by: gilles grimaud --- 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 =20 /* * 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[] =3D { - { "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) } =20 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); =20 + 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 =20 +config RP2040_IOBANK0 + bool + config RP2040_NYI bool =20 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_iobank= 0.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 >=3D RP2040_IOBANK0_NUM_GPIOS * IOBANK0_GPIO_STRIDE) { + return false; + } + + *gpio =3D offset / IOBANK0_GPIO_STRIDE; + *reg =3D offset & (IOBANK0_GPIO_STRIDE - 1); + return *reg =3D=3D IOBANK0_GPIO_STATUS || *reg =3D=3D IOBANK0_GPIO_CTR= L; +} + +static bool rp2040_iobank0_bank_offset(hwaddr offset, hwaddr base, + unsigned *bank) +{ + if (offset < base || offset >=3D base + RP2040_IOBANK0_IRQ_BANKS * 4 || + (offset & 0x3)) { + return false; + } + + *bank =3D (offset - base) / 4; + return true; +} + +static uint32_t rp2040_iobank0_irq_mask(unsigned bank) +{ + return bank =3D=3D 3 ? IOBANK0_IRQ_LAST_MASK : IOBANK0_IRQ_MASK; +} + +static uint32_t rp2040_iobank0_intr_edge_mask(unsigned bank) +{ + return bank =3D=3D 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 gp= io) +{ + return (s->ctrl[gpio] & IOBANK0_CTRL_FUNCSEL_MASK) =3D=3D IOBANK0_FUNC= SEL_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 =3D false; + bool proc1_level =3D false; + int i; + + for (i =3D 0; i < RP2040_IOBANK0_IRQ_BANKS; i++) { + proc0_level |=3D rp2040_iobank0_ints(s->intr[i], s->proc0_inte[i], + s->proc0_intf[i]) !=3D 0; + proc1_level |=3D rp2040_iobank0_ints(s->intr[i], s->proc1_inte[i], + s->proc1_intf[i]) !=3D 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 si= ze) +{ + RP2040IoBank0State *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + unsigned gpio; + unsigned reg; + unsigned bank; + uint64_t value; + + if (rp2040_iobank0_gpio_offset(offset, &gpio, ®)) { + value =3D reg =3D=3D IOBANK0_GPIO_STATUS ? 0 : s->ctrl[gpio]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_INTR, &bank)) { + value =3D s->intr[bank]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTE, &ban= k)) { + value =3D s->proc0_inte[bank]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTF, &ban= k)) { + value =3D s->proc0_intf[bank]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTS, &ban= k)) { + value =3D rp2040_iobank0_ints(s->intr[bank], s->proc0_inte[bank], + s->proc0_intf[bank]); + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTE, &ban= k)) { + value =3D s->proc1_inte[bank]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTF, &ban= k)) { + value =3D s->proc1_intf[bank]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC1_INTS, &ban= k)) { + value =3D 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 =3D s->dormant_wake_inte[bank]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTF, + &bank)) { + value =3D s->dormant_wake_intf[bank]; + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_DORMANT_INTS, + &bank)) { + value =3D rp2040_iobank0_ints(s->intr[bank], + s->dormant_wake_inte[bank], + s->dormant_wake_intf[bank]); + } else { + value =3D 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 =3D rp2040_iobank0_apply_alias(*reg, value64, alias) & + rp2040_iobank0_irq_mask(bank); +} + +static void rp2040_iobank0_write(void *opaque, hwaddr addr, uint64_t value= 64, + unsigned size) +{ + RP2040IoBank0State *s =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + unsigned gpio; + unsigned reg; + unsigned bank; + uint32_t value =3D value64; + + if (rp2040_iobank0_gpio_offset(offset, &gpio, ®)) { + if (reg =3D=3D IOBANK0_GPIO_CTRL) { + s->ctrl[gpio] =3D + rp2040_iobank0_apply_alias(s->ctrl[gpio], value, alias) & + IOBANK0_CTRL_RW_MASK; + if (gpio <=3D 1 || gpio =3D=3D 4 || gpio =3D=3D 5) { + rp2040_iobank0_update_uart_pins(s); + } + } + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_INTR, &bank)) { + s->intr[bank] &=3D ~(value & rp2040_iobank0_intr_edge_mask(bank)); + rp2040_iobank0_update_irq(s); + } else if (rp2040_iobank0_bank_offset(offset, IOBANK0_PROC0_INTE, &ban= k)) { + 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, &ban= k)) { + 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, &ban= k)) { + 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, &ban= k)) { + 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 =3D { + .read =3D rp2040_iobank0_read, + .write =3D rp2040_iobank0_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_iobank0_reset(DeviceState *dev) +{ + RP2040IoBank0State *s =3D RP2040_IOBANK0(dev); + int i; + + for (i =3D 0; i < ARRAY_SIZE(s->ctrl); i++) { + s->ctrl[i] =3D 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 =3D 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 =3D { + .name =3D TYPE_RP2040_IOBANK0, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_iobank0_reset); + dc->vmsd =3D &rp2040_iobank0_vmstate; +} + +static const TypeInfo rp2040_iobank0_info =3D { + .name =3D TYPE_RP2040_IOBANK0, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040IoBank0State), + .instance_init =3D rp2040_iobank0_init, + .class_init =3D 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_ioba= nk0.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 =3D \ (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : [])= + \ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-sysc= fg-test', 'rp2040-clocks-test= ', + 'rp2040-iobank0-tes= t', '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 =3D rp2040_iobank0_start(); + + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_STATUS), =3D=3D,= 0); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), =3D=3D, + CTRL_RESET); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO29_CTRL), =3D=3D, + CTRL_RESET); + + qtest_writel(qts, IOBANK0_BASE + GPIO0_CTRL, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), =3D=3D, + CTRL_RW_MASK); + qtest_writel(qts, IOBANK0_BASE + ATOMIC_CLR + GPIO0_CTRL, 0x30); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), =3D=3D, + CTRL_RW_MASK & ~0x30); + qtest_writel(qts, IOBANK0_BASE + ATOMIC_XOR + GPIO0_CTRL, 0x03); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + GPIO0_CTRL), =3D=3D, + (CTRL_RW_MASK & ~0x30) ^ 0x03); + + qtest_quit(qts); +} + +static void test_proc0_irq(void) +{ + QTestState *qts =3D rp2040_iobank0_start(); + uint32_t irq =3D BIT(IO_IRQ_BANK0); + + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + INTR0), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTS0), =3D=3D, = 0); + g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & irq, =3D=3D, 0); + + qtest_writel(qts, IOBANK0_BASE + PROC0_INTF0, BIT(7)); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTS0), =3D=3D, + BIT(7)); + g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & irq, =3D=3D, irq); + + qtest_writel(qts, IOBANK0_BASE + ATOMIC_CLR + PROC0_INTF0, BIT(7)); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTS0), =3D=3D, = 0); + qtest_writel(qts, NVIC_ICPR, irq); + g_assert_cmphex(qtest_readl(qts, NVIC_ISPR) & irq, =3D=3D, 0); + + qtest_writel(qts, IOBANK0_BASE + ATOMIC_SET + PROC0_INTE0, BIT(3)); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC0_INTE0), =3D=3D, + BIT(3)); + + qtest_quit(qts); +} + +static void test_last_bank_masks(void) +{ + QTestState *qts =3D 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), =3D=3D, + IRQ_LAST_MASK); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + PROC1_INTS3), =3D=3D, + 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), =3D=3D, + IRQ_LAST_MASK); + g_assert_cmphex(qtest_readl(qts, IOBANK0_BASE + DORMANT_INTS3), =3D=3D, + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788047105; cv=none; d=zohomail.com; s=zohoarc; b=im5VBHWhzZO+g8IFNdHAeY9/m1v79Fi/E8casYm7gLdj/hH/7ViTE0hEOSAPwjNoq2YiowFL0Et19vzi5j1EMXnuHCMgsVJgDlTovpcjwLQ03hUBxlNLjT1uNimywF+/dLy85PXohX7jOYVXo5xNoaiTy4dcpf1WBVS+NeFoTj4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788047105; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=+IncyZOzLuaGeKBlStDm8lonLD3YLbuaPl/r4eKXcEE=; b=khb4qQX6TifzAQV//FhPFZfocXCtcjbRSRKGb1tRWDlIdhimnGn5qRbnWtRxg5NggCbjhg5zZFundauL7jF5VO9+vybVHRBIqbqDxzvIC2Z2CWfvIyRvg5WpSmpUkVdawiZsNYrzkSTeNh84nk6j96Afv2UV/DXaAx+Ls56W2Oc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788047105287258.0658386739507; Sat, 29 Aug 2026 16:45:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Sj1-0008LF-3G; Sat, 29 Aug 2026 19:44:39 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Siz-0008KS-C1; Sat, 29 Aug 2026 19:44:37 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Siw-0006Hy-Nz; Sat, 29 Aug 2026 19:44:37 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXWzg4CYgz838j; Sun, 30 Aug 2026 01:43:43 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXWzg21Kqz7tLT; Sun, 30 Aug 2026 01:43:43 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788047023; bh=+IncyZOzLuaGeKBlStDm8lonLD3YLbuaPl/r4eKXcEE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ck/6+XNnGunfQEx1+3rPfQH+csRPzoX2VOhSvs9ziz8e1VeLmPz76CvPbfsz4IMca Fb+WfAHIuPiqUyB1FAd5SUN/8irZltujAGiqOeHxjktrefgFd+fIWhFAkCa3cTf1bf q6p3YJZ6Bma8KOIc8VuVj3jZVTejMCy+bVuVMXn6GBYyzrE4hwvlBNnLGXd6ATxzqW yFT/xayMpGJ7jT9DsI/zjYaBBDIqPwcbHPC0uIsWXV9YAgo2kajMC9RxOa8jRTIq+y s+qqvwszYRcdf+YCpntp+SpqGvLQPz04UaMDTtei4Gv19BMCYwnoisRYNigBkD7t4h 7vyXV/XXd/lEw== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, gilles grimaud , Paolo Bonzini , Peter Maydell , Fabiano Rosas , Laurent Vivier Subject: [PATCH RFC v2 14/30] hw/misc: add RP2040 IO_QSPI Date: Sun, 30 Aug 2026 01:42:48 +0200 Message-ID: <20260829234308.33725-15-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788047110637158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the six QSPI GPIO control pairs and the PROC0, PROC1 and dormant-wake= interrupt registers, including atomic aliases and documented masks. Wire t= he register block into the RP2040 SoC and add focused qtests for reset, ato= mic control updates and forced interrupt status. The QSPI chip-select conne= ction is deferred to the XIP controller patch. Signed-off-by: gilles grimaud --- 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[] =3D { - { "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]); =20 + 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 =20 +config RP2040_IOQSPI + bool + config RP2040_NYI bool =20 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_iobank= 0.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) !=3D 0x4) { + return false; + } + + *index =3D offset / 8; + return *index < ARRAY_SIZE(((RP2040IoQspiState *)0)->ctrl); +} + +static bool rp2040_ioqspi_status_offset(hwaddr offset) +{ + return offset <=3D 0x28 && (offset & 0x7) =3D=3D 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 siz= e) +{ + RP2040IoQspiState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint64_t value; + + if (rp2040_ioqspi_status_offset(offset)) { + value =3D 0; + } else if (rp2040_ioqspi_ctrl_offset(offset, &index)) { + value =3D s->ctrl[index]; + } else { + switch (offset) { + case IOQSPI_INTR: + value =3D s->intr; + break; + case IOQSPI_PROC0_INTE: + value =3D s->proc0_inte; + break; + case IOQSPI_PROC0_INTF: + value =3D s->proc0_intf; + break; + case IOQSPI_PROC0_INTS: + value =3D rp2040_ioqspi_ints(s->intr, s->proc0_inte, + s->proc0_intf); + break; + case IOQSPI_PROC1_INTE: + value =3D s->proc1_inte; + break; + case IOQSPI_PROC1_INTF: + value =3D s->proc1_intf; + break; + case IOQSPI_PROC1_INTS: + value =3D rp2040_ioqspi_ints(s->intr, s->proc1_inte, + s->proc1_intf); + break; + case IOQSPI_DORMANT_INTE: + value =3D s->dormant_wake_inte; + break; + case IOQSPI_DORMANT_INTF: + value =3D s->dormant_wake_intf; + break; + case IOQSPI_DORMANT_INTS: + value =3D rp2040_ioqspi_ints(s->intr, s->dormant_wake_inte, + s->dormant_wake_intf); + break; + default: + value =3D 0; + rp2040_log_unimplemented_read("ioqspi", size, + RP2040_IOQSPI_BASE + addr, offse= t, + value); + break; + } + } + + return value; +} + +static void rp2040_ioqspi_write(void *opaque, hwaddr addr, + uint64_t value64, unsigned size) +{ + RP2040IoQspiState *s =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint32_t value =3D value64; + + if (rp2040_ioqspi_ctrl_offset(offset, &index)) { + s->ctrl[index] =3D + rp2040_ioqspi_apply_alias(s->ctrl[index], value, alias) & + IOQSPI_CTRL_RW_MASK; + } else { + switch (offset) { + case IOQSPI_INTR: + s->intr &=3D ~(value & IOQSPI_INTR_EDGE_MASK); + break; + case IOQSPI_PROC0_INTE: + s->proc0_inte =3D + rp2040_ioqspi_apply_alias(s->proc0_inte, value, alias) & + IOQSPI_IRQ_MASK; + break; + case IOQSPI_PROC0_INTF: + s->proc0_intf =3D + rp2040_ioqspi_apply_alias(s->proc0_intf, value, alias) & + IOQSPI_IRQ_MASK; + break; + case IOQSPI_PROC1_INTE: + s->proc1_inte =3D + rp2040_ioqspi_apply_alias(s->proc1_inte, value, alias) & + IOQSPI_IRQ_MASK; + break; + case IOQSPI_PROC1_INTF: + s->proc1_intf =3D + rp2040_ioqspi_apply_alias(s->proc1_intf, value, alias) & + IOQSPI_IRQ_MASK; + break; + case IOQSPI_DORMANT_INTE: + s->dormant_wake_inte =3D + rp2040_ioqspi_apply_alias(s->dormant_wake_inte, value, + alias) & + IOQSPI_IRQ_MASK; + break; + case IOQSPI_DORMANT_INTF: + s->dormant_wake_intf =3D + 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 =3D { + .read =3D rp2040_ioqspi_read, + .write =3D rp2040_ioqspi_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_ioqspi_reset(DeviceState *dev) +{ + RP2040IoQspiState *s =3D RP2040_IOQSPI(dev); + int i; + + for (i =3D 0; i < ARRAY_SIZE(s->ctrl); i++) { + s->ctrl[i] =3D IOQSPI_CTRL_RESET; + } + s->intr =3D 0; + s->proc0_inte =3D 0; + s->proc0_intf =3D 0; + s->proc1_inte =3D 0; + s->proc1_intf =3D 0; + s->dormant_wake_inte =3D 0; + s->dormant_wake_intf =3D 0; +} + +static void rp2040_ioqspi_init(Object *obj) +{ + RP2040IoQspiState *s =3D 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 =3D { + .name =3D TYPE_RP2040_IOQSPI, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_ioqspi_reset); + dc->vmsd =3D &rp2040_ioqspi_vmstate; +} + +static const TypeInfo rp2040_ioqspi_info =3D { + .name =3D TYPE_RP2040_IOQSPI, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040IoQspiState), + .instance_init =3D rp2040_ioqspi_init, + .class_init =3D 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_ioqsp= i.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 =3D \ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-sysc= fg-test', 'rp2040-clocks-test= ', 'rp2040-iobank0-tes= t', + '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-t= est.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 =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SCLK_STATUS), = =3D=3D, + 0); + g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SCLK_CTRL), =3D= =3D, + IOQSPI_CTRL_RESET); + g_assert_cmphex(qtest_readl(qts, IOQSPI_BASE + IOQSPI_SD1_CTRL), =3D= =3D, + IOQSPI_CTRL_RESET); + + qtest_quit(qts); +} + +static void test_ioqspi_atomic_ctrl_aliases(void) +{ + QTestState *qts =3D 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), =3D= =3D, + BIT(9)); + + qtest_quit(qts); +} + +static void test_ioqspi_interrupt_force_status(void) +{ + QTestState *qts =3D 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), =3D= =3D, + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070762; cv=none; d=zohomail.com; s=zohoarc; b=n27XeBz4MSDVR2yTtzILfzZv99mgpzRQSM/stkZjNCld9hNFmWl2eJf4Bw7SVrp5Bdpc68RKJYWOYOLGCyoEjkC6QHG1T50g/C4lN2vLxwmPa55+CyKx5ElSUl+UmBajf+5Pq38OBi0vkBnFV0wXebyHfI0bInZ+/aJRqPWlrKs= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070762; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=XvbYHMNtChB846XBwJQAYneGYp412W5DCJOPWphN4tI=; b=apsaKT55/ynPP10bCpy9zqv5f6T075XZPFrtqXL29LongDy1v95th9RzSwEiQ5SGryEXYjmYU0c9k9Uww48yRJM0iQzRCNK18w04utiFtVY5lYS+kWkPh+nJPbImVD0bx91dnv2TEtYL+oSunuYDVORK8vaGd5uOQbRpaZMLhQo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070762862131.50771682043353; Sat, 29 Aug 2026 23:19:22 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yr7-0002Po-2P; Sun, 30 Aug 2026 02:17:25 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yr5-0002Ot-FN; Sun, 30 Aug 2026 02:17:23 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yr2-0005Fg-7Z; Sun, 30 Aug 2026 02:17:23 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXhjn0ggjzBqYY; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjm6Ccmz7swM; Sun, 30 Aug 2026 08:17:16 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070637; bh=XvbYHMNtChB846XBwJQAYneGYp412W5DCJOPWphN4tI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=jAxqOzUUh9n8ATgCkroxFfM5IakpPagc/1E/k74i4oFDddNiBrZhHFpyAn4ISYV/T I2kT4kYpBxkf+onc+zfyeYaM9OxRXxGre/p4+mguLrJ//MmYVKp+ouXNI5TWhTX/j/ epkLHA8ZuLp6pBYeroxgqaP14Acf3ned/zkXkYcJNAPP3Zfwr4oU62QB4CiRuUHDdk eNH8oOjOpfMin4VA7bhqFXA0v+hjt6bhz3ZQu1/6Lza2mpPbFjMQODpunSLWdkkGf7 4+g8yiyVlcKemK0pCbUc6Db0M+mgVkLDT481dREkl7pFEe4IIi95BD/5kAHvujRTx9 s9MQDn28N2IjA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 15/30] hw/char: integrate RP2040 UART pinmux Date: Sun, 30 Aug 2026 08:15:46 +0200 Message-ID: <20260830061601.52136-1-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070765390154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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; }; =20 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); } =20 +static bool raspi_pico_get_strict_uart_pins(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s =3D 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 =3D RASPI_PICO_MACHINE(obj); + + s->strict_uart_pins =3D value; +} + +static void raspi_pico_machine_initfn(Object *obj) +{ + RaspiPicoMachineState *s =3D RASPI_PICO_MACHINE(obj); + + s->strict_uart_pins =3D true; +} + static void raspi_pico_machine_class_init(ObjectClass *oc, const void *dat= a) { MachineClass *mc =3D MACHINE_CLASS(oc); @@ -123,12 +148,20 @@ static void raspi_pico_machine_class_init(ObjectClass= *oc, const void *data) "Use a deterministic seed for th= e " "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 UAR= T1 " + "reaches its host serial backend= "); } =20 static const TypeInfo raspi_pico_machine_info =3D { .name =3D TYPE_RASPI_PICO_MACHINE, .parent =3D TYPE_MACHINE, .instance_size =3D sizeof(RaspiPicoMachineState), + .instance_init =3D raspi_pico_machine_initfn, .class_init =3D raspi_pico_machine_class_init, .instance_finalize =3D raspi_pico_machine_finalize, .interfaces =3D 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[] =3D { { "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); } =20 +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 =3D opaque; + + switch (pin) { + case 0: + s->uart0_tx_pin_enabled =3D level; + break; + case 1: + s->uart0_rx_pin_enabled =3D level; + break; + case 2: + s->uart1_tx_pin_enabled =3D level; + break; + case 3: + s->uart1_rx_pin_enabled =3D level; + break; + default: + g_assert_not_reached(); + } + + rp2040_update_uart_pins(s); +} + static void rp2040_soc_init(Object *obj) { RP2040State *s =3D RP2040(obj); int i; =20 + 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)= ); =20 if (!sysbus_realize(SYS_BUS_DEVICE(&s->ioqspi), errp)) { return; @@ -430,15 +479,19 @@ static void rp2040_soc_realize(DeviceState *dev, Erro= r **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); } =20 static const Property rp2040_soc_properties[] =3D { DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGI= ON, MemoryRegion *), DEFINE_PROP_STRING("bootrom-file", RP2040State, bootrom_file), + DEFINE_PROP_BOOL("strict-uart-pins", RP2040State, strict_uart_pins, tr= ue), }; =20 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, C= hardev *chr) /* Fractional Baud Rate Divider, UARTFBRD */ #define FBRD_MASK 0x3f =20 +/* 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] =3D { 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; static const unsigned char pl011_id_luminary[8] =3D @@ -171,6 +177,21 @@ static void pl011_update_dreq(PL011State *s) (s->dmacr & DMACR_RXDMAE) && s->read_count > 0); } =20 +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 =3D 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 =3D true; + } pl011_loopback_tx(s, data); s->int_level |=3D INT_TX; pl011_update(s); @@ -535,6 +562,10 @@ static int pl011_can_receive(void *opaque) * UART continuously enabled regardless of the enable bits. */ =20 + if (!s->rx_connected) { + return 0; + } + trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_availa= ble); 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) =20 static void pl011_event(void *opaque, QEMUChrEvent event) { - if (event =3D=3D CHR_EVENT_BREAK && !pl011_loopback_enabled(opaque)) { + PL011State *s =3D opaque; + + if (event =3D=3D CHR_EVENT_BREAK && s->rx_connected && + !pl011_loopback_enabled(opaque)) { pl011_fifo_rx_put(opaque, DR_BE); } } =20 +void pl011_set_tx_connected(PL011State *s, bool connected) +{ + s->tx_connected =3D connected; + if (connected) { + s->logged_disconnected_tx =3D false; + } +} + +void pl011_set_rx_connected(PL011State *s, bool connected) +{ + s->rx_connected =3D 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 =3D (offset + PL011_ATOMIC_XOR) & PL011_ATOMIC_ALIAS_MASK; + hwaddr reg =3D offset & 0xfff; + uint32_t old =3D pl011_read(opaque, reg, size); + uint32_t new =3D pl011_apply_atomic_alias(old, value, alias); + + pl011_write(opaque, reg, new, size); +} + static void pl011_clock_update(void *opaque, ClockEvent event) { PL011State *s =3D PL011(opaque); @@ -578,6 +646,14 @@ static const MemoryRegionOps pl011_ops =3D { .impl.max_access_size =3D 4, }; =20 +static const MemoryRegionOps pl011_atomic_alias_ops =3D { + .read =3D pl011_atomic_alias_read, + .write =3D pl011_atomic_alias_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .impl.min_access_size =3D 4, + .impl.max_access_size =3D 4, +}; + static bool pl011_clock_needed(void *opaque) { PL011State *s =3D PL011(opaque); @@ -666,8 +742,14 @@ static void pl011_init(Object *obj) PL011State *s =3D PL011(obj); int i; =20 + s->tx_connected =3D true; + s->rx_connected =3D true; memory_region_init_io(&s->iomem, OBJECT(s), &pl011_ops, s, "pl011", 0x= 1000); 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 =3D 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; =20 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; =20 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 { }; =20 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); =20 #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 =3D \ 'rp2040-resets-test= ', 'rp2040-rosc-test', 'rp2040-tbman-test', + 'rp2040-uart-test', 'rp2040-vreg-test', 'rp2040-watchdog-te= st'] : []) + \ (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 =3D NULL; + + fd =3D g_file_open_tmp("rp2040-uart-XXXXXX", &path, NULL); + g_assert_cmpint(fd, >=3D, 0); + close(fd); + return path; +} + +static void assert_file_contents(const char *path, const char *expected) +{ + g_autofree char *contents =3D NULL; + gsize length; + + g_assert_true(g_file_get_contents(path, &contents, &length, NULL)); + g_assert_cmpuint(length, =3D=3D, strlen(expected)); + g_assert_cmpmem(contents, length, expected, strlen(expected)); +} + +static void test_uart0_pinmux(void) +{ + g_autofree char *path =3D new_output_path(); + QTestState *qts =3D qtest_initf("-machine raspi-pico " + "-chardev file,id=3Duart0,path=3D%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 =3D new_output_path(); + QTestState *qts =3D qtest_initf("-machine raspi-pico -serial null " + "-chardev file,id=3Duart1,path=3D%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 =3D new_output_path(); + QTestState *qts =3D qtest_initf("-machine raspi-pico,strict-uart-pins= =3Doff " + "-chardev file,id=3Duart0,path=3D%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 =3D 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), =3D=3D, 1); + for (retries =3D 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), =3D=3D, 'R'); + + close(sock_fd); + qtest_quit(qts); +} + +static void test_atomic_aliases(void) +{ + QTestState *qts =3D qtest_init("-machine raspi-pico"); + uint32_t reset =3D 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), =3D=3D, + reset & ~0x100); + qtest_writel(qts, UART0_BASE + ATOMIC_SET + UART_CR, 0x100); + g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), =3D=3D, reset); + qtest_writel(qts, UART0_BASE + ATOMIC_XOR + UART_CR, 0x200); + g_assert_cmphex(qtest_readl(qts, UART0_BASE + UART_CR), =3D=3D, + 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/rp204= 0-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, =3DGPIO0_CTRL + movs r1, GPIO_FUNC_UART + str r1, [r0] ldr r0, =3Dstart_message bl puts =20 diff --git a/tests/tcg/arm/system/rp2040-uart.S b/tests/tcg/arm/system/rp20= 40-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 @@ =20 #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 =20 @@ -25,6 +27,9 @@ vector_table: .thumb_func .global reset_handler reset_handler: + ldr r0, =3DGPIO0_CTRL + movs r1, GPIO_FUNC_UART + str r1, [r0] ldr r0, =3DUART0_DR ldr r1, =3Dmessage =20 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070813; cv=none; d=zohomail.com; s=zohoarc; b=W+6uvgJ10yOd5zC4zOkxjzLM7LHr/W5yFT0bs2Svv5i6lOBLXnwPeovtK1IVb79CkgpGsj3bLeHY/VRH/3QWT3UwOgYrtatiPOwQGU4oQy4Tpydkd1PEwO1a+HBxPZahiT3FBtSZ+bj93Zu86/QF72PDlLwI+M3tiMrf7qGscro= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070813; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=VnNL4Vhac2Gl5CCzXIjMMSPGgp/BomdyeKTVTuTnGFk=; b=Lmbd3ayV4xSNUjjPQI+B8bp1JjUKaTktA6tbm0RHhe/zneQSiQzTGUmR2+P5X2mDxMlpiNTZOQegLcfFTKX38q3Ea6y1ZQt1Bmh43yDTGzblX+ug0RSddEZ2/tL9octtGOljxXJh+ASGe4vVKb415GhDpsJKHFjPNT0mbaQ/4hw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070813030412.612070474648; Sat, 29 Aug 2026 23:20:13 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0YrE-0002U9-Mu; Sun, 30 Aug 2026 02:17:32 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrC-0002Sk-Le; Sun, 30 Aug 2026 02:17:30 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yr8-0005GS-3g; Sun, 30 Aug 2026 02:17:30 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXhjn1zZBzBqYZ; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjn0Wxqz7xSW; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070637; bh=VnNL4Vhac2Gl5CCzXIjMMSPGgp/BomdyeKTVTuTnGFk=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=WO+pN7/hWmdhifpyuSaHbpystV0OERHLmKFw+Rg9fewkR3YGCp4vj8ybhRkGYHa7a V6C2qmEjxAXvTdUq2/R/NQPF0fQx35c3koX/0ZvPs9d3Y1anijgnKtkjbwk7yaxpQ/ A6Mw1wq/I99OB1GCLe446nKzsVfFH4Ni2++Sf7LzK8AQCFroMTactxLbJ1LBgtlYfe BtjevpT9GWn3Bd/yNDGgTA/w4Cz4chrkVbKKPMOFM0v6hma7HkDr3jHZJhVOAzXv7W dL9QeD3B6W2/KidSEgknPxw7s88jsvKJ6B4OGXpYvn4LNBoquDHYITyHCCHZQdhMxR GYioZ+WtVq5Kg== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 16/30] hw/misc: add RP2040 SIO and multicore support Date: Sun, 30 Aug 2026 08:15:47 +0200 Message-ID: <20260830061601.52136-2-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070815784154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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) =20 sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); =20 - 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); } =20 @@ -133,9 +134,9 @@ static void raspi_pico_machine_class_init(ObjectClass *= oc, const void *data) =20 mc->desc =3D "Raspberry Pi Pico (Cortex-M0+)"; mc->init =3D raspi_pico_init; - mc->default_cpus =3D 1; - mc->min_cpus =3D 1; - mc->max_cpus =3D 1; + mc->default_cpus =3D RP2040_NUM_CORES; + mc->min_cpus =3D RP2040_NUM_CORES; + mc->max_cpus =3D RP2040_NUM_CORES; mc->default_ram_size =3D 0; mc->no_parallel =3D 1; mc->no_floppy =3D 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" =20 #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 =20 /* - * 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 f= or + * the RP2040 ROM FIFO launch sequence: { 0, 0, 1, VTOR, SP, PC }. */ static const uint8_t rp2040_bootrom[] =3D { - 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, }; =20 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 }, }; =20 static MemTxResult rp2040_powered_off_read(void *opaque, hwaddr addr, @@ -117,10 +134,12 @@ static void rp2040_update_nmi(RP2040State *s) for (i =3D 0; i < RP2040_NUM_IRQS; i++) { bool route_to_nmi =3D nmi_mask & BIT(i); =20 - qemu_set_irq(s->cpu_irq[i], s->irq_level[i] && !route_to_nmi); - nmi_level |=3D s->irq_level[i] && route_to_nmi; + bool irq_level =3D s->irq_level[0][i]; + + qemu_set_irq(s->cpu_irq[0][i], irq_level && !route_to_nmi); + nmi_level |=3D irq_level && route_to_nmi; } - qemu_set_irq(s->nmi_irq, nmi_level); + qemu_set_irq(s->nmi_irq[0], nmi_level); } =20 static void rp2040_syscfg_update(void *opaque) @@ -136,10 +155,75 @@ static void rp2040_set_irq(void *opaque, int irq, int= level) RP2040State *s =3D opaque; =20 assert(irq >=3D 0 && irq < RP2040_NUM_IRQS); - s->irq_level[irq] =3D level; + s->irq_level[0][irq] =3D level; rp2040_update_nmi(s); } =20 +static void rp2040_start_core1_async_work(CPUState *cs, run_on_cpu_data da= ta) +{ + ARMCPU *cpu =3D ARM_CPU(cs); + CPUARMState *env =3D &cpu->env; + + cpu_reset(cs); + cpu->power_state =3D PSCI_ON; + env->halt_reason =3D NOT_HALTED; + arm_rebuild_hflags(env); + cs->halted =3D 0; + cpu_resume(cs); +} + +static bool rp2040_core1_powered_off(RP2040State *s) +{ + ARMCPU *cpu =3D s->armv7m[RP2040_PROC1].cpu; + + return !cpu || cpu->power_state =3D=3D 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 dat= a) +{ + ARMCPU *cpu =3D ARM_CPU(cs); + + cpu->power_state =3D PSCI_OFF; + cpu->env.halt_reason =3D HALT_PSCI; + cs->halted =3D 1; + cs->exception_index =3D 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 =3D opaque; + bool proc1_forced_off =3D 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); =20 - 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 =3D 0; i < RP2040_NUM_CORES; i++) { + g_autofree char *name =3D 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); =20 for (i =3D 0; i < ARRAY_SIZE(s->uart); i++) { g_autofree char *name =3D 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_SYSIN= FO); 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, Erro= r **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); =20 if (!sysbus_realize(SYS_BUS_DEVICE(&s->resets), errp)) { return; } sysbus_mmio_map(SYS_BUS_DEVICE(&s->resets), 0, RP2040_RESETS_BASE); =20 - 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 =3D 0; i < RP2040_NUM_IRQS; i++) { - s->cpu_irq[i] =3D 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 =3D 0; i < RP2040_NUM_CORES; i++) { + int irq; + g_autofree char *name =3D 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 =3D 0; irq < RP2040_NUM_IRQS; irq++) { + s->cpu_irq[i][irq] =3D qdev_get_gpio_in(DEVICE(&s->armv7m[i]), + irq); + } + s->nmi_irq[i] =3D qdev_get_gpio_in_named(DEVICE(&s->armv7m[i]), + "NMI", 0); } - s->nmi_irq =3D qdev_get_gpio_in_named(DEVICE(&s->armv7m), "NMI", 0); + rp2040_update_nmi(s); =20 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]); =20 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 =20 +config RP2040_SIO + bool + config RP2040_SYSCFG bool =20 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_sysinf= o.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 executi= ng + * guest vCPU, not a host CPU identifier. + */ + if (current_cpu && current_cpu->cpu_index =3D=3D 1) { + return 1; + } + + return 0; +} + +static void rp2040_sio_update_fifo_irq(RP2040SioState *s) +{ + int i; + + for (i =3D 0; i < RP2040_SIO_NUM_CORES; i++) { + qemu_set_irq(s->fifo_irq[i], s->fifo_level[i] !=3D 0); + } +} + +static uint32_t rp2040_sio_fifo_status(RP2040SioState *s, unsigned core) +{ + unsigned peer =3D core ^ 1; + uint32_t value =3D s->fifo_sticky[core] & SIO_FIFO_ST_WC_MASK; + + if (s->fifo_level[core] !=3D 0) { + value |=3D SIO_FIFO_ST_VLD; + } + if (s->fifo_level[peer] < RP2040_SIO_FIFO_DEPTH) { + value |=3D SIO_FIFO_ST_RDY; + } + + return value; +} + +static void rp2040_sio_fifo_push(RP2040SioState *s, unsigned core, + uint32_t value) +{ + unsigned peer =3D core ^ 1; + + if (s->fifo_level[peer] =3D=3D RP2040_SIO_FIFO_DEPTH) { + s->fifo_sticky[core] |=3D BIT(2); + return; + } + + s->fifo[peer][s->fifo_wptr[peer]] =3D value; + s->fifo_wptr[peer] =3D (s->fifo_wptr[peer] + 1) % RP2040_SIO_FIFO_DEPT= H; + 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] =3D=3D 0) { + s->fifo_sticky[core] |=3D BIT(3); + return 0; + } + + value =3D s->fifo[core][s->fifo_rptr[core]]; + s->fifo_rptr[core] =3D (s->fifo_rptr[core] + 1) % RP2040_SIO_FIFO_DEPT= H; + 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 =3D (offset - SIO_SPINLOCK_BASE) / sizeof(uint32_t); + return *index < 32; +} + +static int32_t rp2040_sio_sign32(uint32_t value) +{ + int32_t signed_value =3D (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 =3D s->div_dividend[core]; + uint32_t divisor =3D s->div_divisor[core]; + uint32_t quotient; + uint32_t remainder; + + if (is_signed) { + int32_t signed_dividend =3D (int32_t)dividend; + int32_t signed_divisor =3D (int32_t)divisor; + + if (signed_divisor =3D=3D 0) { + quotient =3D -rp2040_sio_sign32(dividend); + remainder =3D dividend; + } else if (signed_dividend =3D=3D INT32_MIN && signed_divisor =3D= =3D -1) { + quotient =3D dividend; + remainder =3D 0; + } else { + quotient =3D signed_dividend / signed_divisor; + remainder =3D signed_dividend % signed_divisor; + } + } else if (divisor =3D=3D 0) { + quotient =3D UINT32_MAX; + remainder =3D dividend; + } else { + quotient =3D dividend / divisor; + remainder =3D dividend % divisor; + } + + s->div_quotient[core] =3D quotient; + s->div_remainder[core] =3D remainder; + s->div_dirty[core] =3D 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 bas= e) +{ + return interp * RP2040_SIO_INTERP_NUM_BASES + base; +} + +static uint32_t rp2040_sio_interp_ctrl_mask(unsigned interp, unsigned lane) +{ + uint32_t mask =3D 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 =3D=3D 0 && lane =3D=3D 0) { + mask |=3D SIO_INTERP0_CTRL_BLEND; + } else if (interp =3D=3D 1 && lane =3D=3D 0) { + mask |=3D SIO_INTERP1_CTRL_CLAMP; + } + + return mask; +} + +static bool rp2040_sio_interp_offset(hwaddr addr, unsigned *interp, + hwaddr *reg) +{ + if (addr >=3D SIO_INTERP0_BASE && + addr < SIO_INTERP0_BASE + SIO_INTERP_BLOCK_SIZE) { + *interp =3D 0; + *reg =3D addr - SIO_INTERP0_BASE; + return true; + } + + if (addr >=3D SIO_INTERP1_BASE && + addr < SIO_INTERP1_BASE + SIO_INTERP_BLOCK_SIZE) { + *interp =3D 1; + *reg =3D 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 =3D=3D 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 =3D=3D 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 =3D s->interp_ctrl[core] + [rp2040_sio_interp_index(interp, lane)]; + uint32_t input; + uint32_t shifted; + uint32_t value; + unsigned shift =3D ctrl & SIO_INTERP_CTRL_SHIFT_MASK; + unsigned lsb =3D (ctrl & SIO_INTERP_CTRL_LSB_MASK) >> + SIO_INTERP_CTRL_MASK_LSB_SHIFT; + unsigned msb =3D (ctrl & SIO_INTERP_CTRL_MSB_MASK) >> + SIO_INTERP_CTRL_MASK_MSB_SHIFT; + + input =3D 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 =3D input >> shift; + value =3D shifted & rp2040_sio_interp_mask(lsb, msb); + if (ctrl & SIO_INTERP_CTRL_SIGNED) { + value =3D rp2040_sio_interp_sign_extend(value, msb); + } + + return value; +} + +static uint32_t rp2040_sio_interp_force_msb(RP2040SioState *s, unsigned co= re, + unsigned interp, unsigned lane, + uint32_t value) +{ + uint32_t ctrl =3D s->interp_ctrl[core] + [rp2040_sio_interp_index(interp, lane)]; + uint32_t force =3D (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 alp= ha) +{ + uint32_t ctrl1 =3D s->interp_ctrl[core][rp2040_sio_interp_index(0, 1)]; + uint32_t base0 =3D s->interp_base[core][rp2040_sio_interp_base_index(0= , 0)]; + uint32_t base1 =3D s->interp_base[core][rp2040_sio_interp_base_index(0= , 1)]; + int64_t start; + int64_t end; + + if (ctrl1 & SIO_INTERP_CTRL_SIGNED) { + start =3D (int16_t)base0; + end =3D (int16_t)base1; + } else { + start =3D (uint16_t)base0; + end =3D (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 =3D interp =3D=3D 0 && + (s->interp_ctrl[core][rp2040_sio_interp_index(0, 0)] & + SIO_INTERP0_CTRL_BLEND); + bool clamp =3D interp =3D=3D 1 && + (s->interp_ctrl[core][rp2040_sio_interp_index(1, 0)] & + SIO_INTERP1_CTRL_CLAMP); + uint32_t base0 =3D s->interp_base[core] + [rp2040_sio_interp_base_index(interp, 0= )]; + uint32_t base1 =3D s->interp_base[core] + [rp2040_sio_interp_base_index(interp, 1= )]; + uint32_t base2 =3D s->interp_base[core] + [rp2040_sio_interp_base_index(interp, 2= )]; + + result->raw[0] =3D rp2040_sio_interp_raw(s, core, interp, 0); + result->raw[1] =3D rp2040_sio_interp_raw(s, core, interp, 1); + result->lane[0] =3D base0 + result->raw[0]; + result->lane[1] =3D base1 + result->raw[1]; + result->full =3D base2 + result->raw[0] + result->raw[1]; + + if (blend) { + uint32_t alpha =3D result->raw[1] & 0xff; + + result->lane[0] =3D alpha; + result->lane[1] =3D rp2040_sio_interp_blend_result(s, core, alpha); + result->full =3D base2 + result->raw[0]; + } else if (clamp) { + uint32_t ctrl0 =3D s->interp_ctrl[core][rp2040_sio_interp_index(1,= 0)]; + + if (ctrl0 & SIO_INTERP_CTRL_SIGNED) { + int32_t value =3D result->raw[0]; + int32_t lower =3D base0; + int32_t upper =3D base1; + + result->lane[0] =3D value < lower ? lower : + value > upper ? upper : value; + } else { + result->lane[0] =3D 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 =3D s->interp_ctrl[core] + [rp2040_sio_interp_index(interp, 0)]; + uint32_t ctrl1 =3D 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 =3D (ctrl0 & SIO_INTERP_CTRL_CROSS_RESULT) ? + result.lane[1] : result.lane[0]; + next1 =3D (ctrl1 & SIO_INTERP_CTRL_CROSS_RESULT) ? + result.lane[0] : result.lane[1]; + + s->interp_accum[core][rp2040_sio_interp_index(interp, 0)] =3D next0; + s->interp_accum[core][rp2040_sio_interp_index(interp, 1)] =3D 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 =3D s->interp_ctrl[core] + [rp2040_sio_interp_index(interp, lane)]; + unsigned shift =3D ctrl & SIO_INTERP_CTRL_SHIFT_MASK; + unsigned lsb =3D (ctrl & SIO_INTERP_CTRL_LSB_MASK) >> + SIO_INTERP_CTRL_MASK_LSB_SHIFT; + unsigned msb =3D (ctrl & SIO_INTERP_CTRL_MSB_MASK) >> + SIO_INTERP_CTRL_MASK_MSB_SHIFT; + uint32_t input =3D s->interp_accum[core][rp2040_sio_interp_index( + interp, + (ctrl & + SIO_INTERP_CTRL_CROSS_INPUT) ? + (lane ^ 1) : lane)]; + uint32_t shifted =3D input >> shift; + + return (shifted & ~rp2040_sio_interp_mask(lsb, msb)) !=3D 0; +} + +static uint32_t rp2040_sio_interp_ctrl_read(RP2040SioState *s, unsigned co= re, + unsigned interp, unsigned lane) +{ + uint32_t ctrl =3D s->interp_ctrl[core] + [rp2040_sio_interp_index(interp, lane)]; + bool overf0 =3D rp2040_sio_interp_overf(s, core, interp, 0); + bool overf1 =3D rp2040_sio_interp_overf(s, core, interp, 1); + + ctrl &=3D ~SIO_INTERP_CTRL_OVERF_MASK; + if (overf0) { + ctrl |=3D SIO_INTERP_CTRL_OVERF0; + } + if (overf1) { + ctrl |=3D SIO_INTERP_CTRL_OVERF1; + } + if (overf0 || overf1) { + ctrl |=3D 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 =3D=3D 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 =3D=3D SIO_INTERP_PEEK_LANE0) { + return rp2040_sio_interp_force_msb(s, core, interp, 0, + result.lane[0]); + } + if (reg =3D=3D 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 =3D=3D SIO_INTERP_CTRL_LANE= 1); + case SIO_INTERP_ACCUM0_ADD: + case SIO_INTERP_ACCUM1_ADD: + return rp2040_sio_interp_raw(s, core, interp, + reg =3D=3D 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 =3D reg =3D=3D SIO_INTERP_ACCUM1; + s->interp_accum[core][rp2040_sio_interp_index(interp, lane)] =3D v= alue; + break; + case SIO_INTERP_BASE0: + case SIO_INTERP_BASE1: + case SIO_INTERP_BASE2: + base =3D (reg - SIO_INTERP_BASE0) / sizeof(uint32_t); + s->interp_base[core][rp2040_sio_interp_base_index(interp, base)] = =3D + value; + break; + case SIO_INTERP_CTRL_LANE0: + case SIO_INTERP_CTRL_LANE1: + lane =3D reg =3D=3D SIO_INTERP_CTRL_LANE1; + s->interp_ctrl[core][rp2040_sio_interp_index(interp, lane)] =3D + value & rp2040_sio_interp_ctrl_mask(interp, lane); + break; + case SIO_INTERP_ACCUM0_ADD: + case SIO_INTERP_ACCUM1_ADD: + lane =3D reg =3D=3D SIO_INTERP_ACCUM1_ADD; + s->interp_accum[core][rp2040_sio_interp_index(interp, lane)] +=3D + value & 0x00ffffff; + break; + case SIO_INTERP_BASE_1AND0: + base01_signed =3D interp =3D=3D 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 =3D 0; base < 2; base++) { + uint16_t half =3D value >> (base * 16); + bool sign =3D 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= )] =3D + sign ? (uint32_t)(int16_t)half : half; + } + break; + default: + break; + } +} + +static uint64_t rp2040_sio_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040SioState *s =3D opaque; + unsigned core =3D rp2040_sio_current_core(); + unsigned interp; + unsigned index; + unsigned shift =3D (addr & 3) * 8; + hwaddr interp_reg; + hwaddr reg =3D addr & ~3ULL; + uint64_t value; + + if (rp2040_sio_interp_offset(reg, &interp, &interp_reg)) { + value =3D rp2040_sio_interp_read(s, core, interp, interp_reg); + return extract64(value, shift, size * 8); + } + + switch (reg) { + case SIO_CPUID: + value =3D core; + break; + case SIO_GPIO_IN: + value =3D s->gpio_in; + break; + case SIO_GPIO_HI_IN: + value =3D s->gpio_hi_in; + break; + case SIO_GPIO_OUT: + value =3D s->gpio_out; + break; + case SIO_GPIO_OE: + value =3D s->gpio_oe; + break; + case SIO_GPIO_HI_OUT: + value =3D s->gpio_hi_out; + break; + case SIO_GPIO_HI_OE: + value =3D s->gpio_hi_oe; + break; + case SIO_FIFO_ST: + value =3D rp2040_sio_fifo_status(s, core); + break; + case SIO_FIFO_RD: + value =3D rp2040_sio_fifo_pop(s, core); + break; + case SIO_SPINLOCK_ST: + value =3D s->spinlock_st; + break; + case SIO_DIV_UDIVIDEND: + case SIO_DIV_SDIVIDEND: + value =3D s->div_dividend[core]; + break; + case SIO_DIV_UDIVISOR: + case SIO_DIV_SDIVISOR: + value =3D s->div_divisor[core]; + break; + case SIO_DIV_QUOTIENT: + value =3D s->div_quotient[core]; + s->div_dirty[core] =3D false; + break; + case SIO_DIV_REMAINDER: + value =3D s->div_remainder[core]; + break; + case SIO_DIV_CSR: + value =3D rp2040_sio_div_csr(s, core); + break; + default: + if (rp2040_sio_spinlock_offset(reg, &index)) { + value =3D (s->spinlock_st & BIT(index)) ? 0 : BIT(index); + s->spinlock_st |=3D BIT(index); + } else { + value =3D 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 =3D opaque; + unsigned core =3D rp2040_sio_current_core(); + unsigned interp; + unsigned index; + unsigned shift =3D (addr & 3) * 8; + hwaddr interp_reg; + hwaddr reg =3D addr & ~3ULL; + uint32_t value =3D (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 =3D value & SIO_GPIO_MASK; + break; + case SIO_GPIO_OUT_SET: + s->gpio_out |=3D value & SIO_GPIO_MASK; + break; + case SIO_GPIO_OUT_CLR: + s->gpio_out &=3D ~(value & SIO_GPIO_MASK); + break; + case SIO_GPIO_OUT_XOR: + s->gpio_out ^=3D value & SIO_GPIO_MASK; + break; + case SIO_GPIO_OE: + s->gpio_oe =3D value & SIO_GPIO_MASK; + break; + case SIO_GPIO_OE_SET: + s->gpio_oe |=3D value & SIO_GPIO_MASK; + break; + case SIO_GPIO_OE_CLR: + s->gpio_oe &=3D ~(value & SIO_GPIO_MASK); + break; + case SIO_GPIO_OE_XOR: + s->gpio_oe ^=3D value & SIO_GPIO_MASK; + break; + case SIO_GPIO_HI_OUT: + s->gpio_hi_out =3D value & SIO_GPIO_HI_MASK; + break; + case SIO_GPIO_HI_OUT_SET: + s->gpio_hi_out |=3D value & SIO_GPIO_HI_MASK; + break; + case SIO_GPIO_HI_OUT_CLR: + s->gpio_hi_out &=3D ~(value & SIO_GPIO_HI_MASK); + break; + case SIO_GPIO_HI_OUT_XOR: + s->gpio_hi_out ^=3D value & SIO_GPIO_HI_MASK; + break; + case SIO_GPIO_HI_OE: + s->gpio_hi_oe =3D value & SIO_GPIO_HI_MASK; + break; + case SIO_GPIO_HI_OE_SET: + s->gpio_hi_oe |=3D value & SIO_GPIO_HI_MASK; + break; + case SIO_GPIO_HI_OE_CLR: + s->gpio_hi_oe &=3D ~(value & SIO_GPIO_HI_MASK); + break; + case SIO_GPIO_HI_OE_XOR: + s->gpio_hi_oe ^=3D value & SIO_GPIO_HI_MASK; + break; + case SIO_FIFO_ST: + s->fifo_sticky[core] &=3D ~(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] =3D value; + rp2040_sio_divide(s, core, false); + break; + case SIO_DIV_UDIVISOR: + s->div_divisor[core] =3D value; + rp2040_sio_divide(s, core, false); + break; + case SIO_DIV_SDIVIDEND: + s->div_dividend[core] =3D value; + rp2040_sio_divide(s, core, true); + break; + case SIO_DIV_SDIVISOR: + s->div_divisor[core] =3D value; + rp2040_sio_divide(s, core, true); + break; + case SIO_DIV_QUOTIENT: + s->div_quotient[core] =3D value; + s->div_dirty[core] =3D true; + break; + case SIO_DIV_REMAINDER: + s->div_remainder[core] =3D value; + s->div_dirty[core] =3D true; + break; + default: + if (rp2040_sio_spinlock_offset(reg, &index)) { + s->spinlock_st &=3D ~BIT(index); + } else { + rp2040_log_unimplemented_write("sio", size, + RP2040_SIO_BASE + addr, addr, + value64); + } + break; + } +} + +static const MemoryRegionOps rp2040_sio_ops =3D { + .read =3D rp2040_sio_read, + .write =3D rp2040_sio_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + .unaligned =3D false, + }, +}; + +static void rp2040_sio_reset(DeviceState *dev) +{ + RP2040SioState *s =3D RP2040_SIO(dev); + + s->gpio_in &=3D SIO_GPIO_MASK; + s->gpio_hi_in &=3D SIO_GPIO_HI_MASK; + s->gpio_out =3D 0; + s->gpio_oe =3D 0; + s->gpio_hi_out =3D 0; + s->gpio_hi_oe =3D 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 =3D 0; + rp2040_sio_update_fifo_irq(s); +} + +static void rp2040_sio_init(Object *obj) +{ + RP2040SioState *s =3D 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 =3D { + .name =3D TYPE_RP2040_SIO, + .version_id =3D 3, + .minimum_version_id =3D 2, + .fields =3D (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[] =3D { + 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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_sio_reset); + device_class_set_props(dc, rp2040_sio_properties); + dc->vmsd =3D &vmstate_rp2040_sio; +} + +static const TypeInfo rp2040_sio_info =3D { + .name =3D TYPE_RP2040_SIO, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040SioState), + .instance_init =3D rp2040_sio_init, + .class_init =3D 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) =20 +#define RP2040_NUM_CORES 2 #define RP2040_NUM_IRQS 32 =20 struct RP2040State { SysBusDevice parent_obj; =20 - 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; =20 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; =20 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 =3D \ 'rp2040-iobank0-tes= t', '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 =3D rp2040_psm_start(); + + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_ON), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_WDSEL), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE), =3D=3D, + PSM_VALID_MASK); + + qtest_quit(qts); +} + +static void test_proc1_force_off(void) +{ + QTestState *qts =3D 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), =3D=3D, + PSM_PROC1); + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE) & PSM_PROC1, =3D= =3D, + 0); + + qtest_writel(qts, PSM_BASE + ATOMIC_CLR + PSM_FRCE_OFF, PSM_PROC1); + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_FRCE_OFF), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_DONE) & PSM_PROC1, =3D= =3D, + PSM_PROC1); + + qtest_quit(qts); +} + +static void test_register_masks(void) +{ + QTestState *qts =3D 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), =3D=3D, + PSM_VALID_MASK); + g_assert_cmphex(qtest_readl(qts, PSM_BASE + PSM_WDSEL), =3D=3D, + 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 =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_CPUID), =3D=3D, 0); + g_assert_cmphex(qtest_readb(qts, SIO_BASE + SIO_CPUID), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_IN), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_IN), =3D=3D, B= IT(1)); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_OUT), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_GPIO_HI_OUT), =3D=3D, = 0); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), =3D=3D, BIT(= 1)); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), =3D=3D, = 0); + + qtest_quit(qts); +} + +static void test_sio_gpio_alias_registers(void) +{ + QTestState *qts =3D 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), =3D=3D, + 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), =3D=3D, + BIT(2) | BIT(5)); + + qtest_quit(qts); +} + +static void test_sio_fifo_empty_read_sets_roe(void) +{ + QTestState *qts =3D rp2040_start(); + + qtest_readl(qts, SIO_BASE + SIO_FIFO_RD); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_FIFO_ST), =3D=3D, + 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), =3D=3D, BIT(= 1)); + + qtest_quit(qts); +} + +static void test_sio_spinlock_claim_release(void) +{ + QTestState *qts =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK0), =3D=3D, BI= T(0)); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK0), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), =3D=3D, = BIT(0)); + + qtest_writel(qts, SIO_BASE + SIO_SPINLOCK0, 0); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_SPINLOCK_ST), =3D=3D, = 0); + + qtest_quit(qts); +} + +static void test_sio_divider(void) +{ + QTestState *qts =3D 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), =3D=3D, + DIV_CSR_READY | DIV_CSR_DIRTY); + g_assert_cmpuint(qtest_readl(qts, SIO_BASE + SIO_DIV_REMAINDER), =3D= =3D, 2); + g_assert_cmpuint(qtest_readl(qts, SIO_BASE + SIO_DIV_QUOTIENT), =3D=3D= , 14); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_DIV_CSR), =3D=3D, + 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), + =3D=3D, -14); + g_assert_cmpint((int32_t)qtest_readl(qts, + SIO_BASE + SIO_DIV_REMAINDER), + =3D=3D, -2); + + qtest_quit(qts); +} + +static void test_sio_interp_mask_and_sign(void) +{ + QTestState *qts =3D 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), + =3D=3D, 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), + =3D=3D, 0xffffffc0); + + qtest_quit(qts); +} + +static void test_sio_interp_pop_cross_result(void) +{ + QTestState *qts =3D rp2040_start(); + uint32_t full_mask =3D 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), + =3D=3D, 124); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_POP_LANE1), + =3D=3D, 456); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE0), + =3D=3D, 457); + + qtest_quit(qts); +} + +static void test_sio_interp_blend(void) +{ + QTestState *qts =3D rp2040_start(); + uint32_t full_mask =3D 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), + =3D=3D, 0x00004000); + + qtest_writel(qts, SIO_BASE + SIO_INTERP0_BASE_1AND0, 0xe000f000); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP0_PEEK_LANE1), + =3D=3D, 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), + =3D=3D, 0xffffe800); + + qtest_quit(qts); +} + +static void test_sio_interp_clamp(void) +{ + QTestState *qts =3D 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), + =3D=3D, 0); + + qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, 512); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0), + =3D=3D, 128); + + qtest_writel(qts, SIO_BASE + SIO_INTERP1_ACCUM0, 1024); + g_assert_cmphex(qtest_readl(qts, SIO_BASE + SIO_INTERP1_PEEK_LANE0), + =3D=3D, 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070685; cv=none; d=zohomail.com; s=zohoarc; b=JyyDv9ZwT1g0od5VFicxSHIxbOAC0o3ze7BlJauQIXCShEtTugXnZfmzELarrJKUFbmt95KrzFimLUi5d0lNEFXkOPsMbwqatL5H+DpeomqD8oll3E0OhdV8v8EJuxc4QJu6oqVRikoCUTURnNIcXYuZHaKj5qD9yNRF+P1Fwz4= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070685; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=eS13Ssqri4iRX6kVa315iV+PIAJEgv3FuAsoOCa5nzQ=; b=DciLC+zXKoTTDOWk+v/Q4Cn0oAN8uq7DKougkl/le9FTZeWaQS62gV8q70DLbW5ZRwB0LFNa6/XW2E9JfFv/Z84B3yh8LuWKl95OZYAFKsxYVAb1d2k0qvfhPDyezP84qTe4C0kYCLsMNiLyMgBQAjSuFEDVTNP7I1a6Sm8uRVc= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070685017688.4082764017085; Sat, 29 Aug 2026 23:18:05 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yr7-0002QI-LU; Sun, 30 Aug 2026 02:17:25 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yr5-0002Op-ER; Sun, 30 Aug 2026 02:17:23 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yr2-0005Fh-50; Sun, 30 Aug 2026 02:17:23 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjn3FXbz8338; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjn1nSMz7swM; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070637; bh=eS13Ssqri4iRX6kVa315iV+PIAJEgv3FuAsoOCa5nzQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=U/o36QRHS8BGBD6XGgY2OqGI6Jyz8hRNF45w4tTW7jaIJjpa77U2eEcF/tCnZliul lAoH7+KCRgd5V0cik1BS012vqVgLzyGKBfH8newld0aoj8iO+ebZ5f/xPtdDHjVcS1 rIFMnQy+PWG98UJbbRo9vIU71mAOvxBwNt1oJtIWVO9abaCIYlC2jLedES6qUoKNOJ rXpT3TreKFwtv9Ch9UV3nJfE24jOpmX3rErOXRK+v/aDr++rDeCaG0OOJiLOZ4x47Q 0/trlGj8nhXdccvEMllNq7tOdscnot0K+Xw1W+0S5o+zqLRRiYBTlPsCkZF8O8h4L4 qt2BpQguruh9g== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 17/30] tests/tcg/arm: test RP2040 SIO and multicore Date: Sun, 30 Aug 2026 08:15:48 +0200 Message-ID: <20260830061601.52136-3-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070692397158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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=3D-semihosting-config en= able=3Don,target=3Dnative,cha =20 ARM_TESTS+=3Dtest-armv6m-undef =20 -RP2040_TESTS=3Drp2040-boot rp2040-uart rp2040-mpu +RP2040_TESTS=3Drp2040-boot rp2040-uart rp2040-mpu \ + rp2040-sio-fifo rp2040-sio-divider rp2040-psm-proc1 \ + rp2040-core1-launch =20 $(RP2040_TESTS): %: %.S rp2040-minimal.ld $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ @@ -31,11 +33,16 @@ $(RP2040_TESTS): %: %.S rp2040-minimal.ld run-rp2040-boot: QEMU_OPTS=3D-M raspi-pico \ -semihosting-config enable=3Don,target=3Dnative -kernel =20 -run-rp2040-uart run-rp2040-mpu: QEMU_OPTS=3D-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=3D-M raspi-pico,strict-uart-pins=3Doff \ -serial chardev:output \ -semihosting-config enable=3Don,target=3Dnative -kernel =20 -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=3D$<.out$(COMMA)id=3Doutput \ diff --git a/tests/tcg/arm/system/rp2040-core1-launch.S b/tests/tcg/arm/sys= tem/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, =3Dstart_message + bl puts + + ldr r4, =3DSIO_BASE + ldr r0, [r4, SIO_CPUID] + cmp r0, 0 + bne fail_core0_id + + ldr r6, =3Dlaunch_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, =3Decho_message + bl puts + + bl fifo_pop + ldr r1, =3DCORE1_ACK + cmp r0, r1 + bne fail_ack + + ldr r0, =3Dack_message + bl puts + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +.thumb_func +core1_entry: + ldr r4, =3DSIO_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, =3DCORE1_ACK + bl fifo_push + pop {pc} + +core1_failed: + ldr r0, =3D0xbad10001 + 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, =3Dfail_core0_id_message + b test_failed + +fail_echo: + ldr r0, =3Dfail_echo_message + b test_failed + +fail_ack: + ldr r0, =3Dfail_ack_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +8: + b 8b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/s= ystem/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, =3Dstart_message + bl puts + + ldr r4, =3D(PSM_BASE + PSM_FRCE_OFF) + ldr r5, =3DPSM_PROC1 + ldr r6, =3D(PSM_BASE + PSM_DONE) + + ldr r0, [r4] + tst r0, r5 + bne fail_initial + ldr r0, [r6] + tst r0, r5 + beq fail_initial + + ldr r0, =3Dinitial_message + bl puts + + ldr r0, =3D(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, =3Doff_message + bl puts + + ldr r0, =3D(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, =3Don_message + bl puts + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_initial: + ldr r0, =3Dfail_initial_message + b test_failed + +fail_force_off: + ldr r0, =3Dfail_force_off_message + b test_failed + +fail_restart: + ldr r0, =3Dfail_restart_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/syst= em/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/syst= em/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, =3D\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, =3Dstart_message + bl puts + ldr r4, =3DSIO_BASE + + /* Unsigned: 100 / 7 =3D 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 =3D -14 remainder -2. */ + ldr r0, =3D-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, =3D0x1234 + 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, =3D0xaabbccdd + str r0, [r4, SIO_DIV_REMAINDER] + ldr r0, =3D0x11223344 + 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, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_unsigned: + ldr r0, =3Dfail_unsigned_message + b test_failed + +fail_signed: + ldr r0, =3Dfail_signed_message + b test_failed + +fail_divzero: + ldr r0, =3Dfail_divzero_message + b test_failed + +fail_restore: + ldr r0, =3Dfail_restore_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/sy= stem/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, =3Dstart_message + bl puts + + ldr r4, =3DSIO_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, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_cpuid: + ldr r0, =3Dfail_cpuid_message + b test_failed + +fail_rdy: + ldr r0, =3Dfail_rdy_message + b test_failed + +fail_vld: + ldr r0, =3Dfail_vld_message + b test_failed + +fail_ready_word: + ldr r0, =3Dfail_ready_word_message + b test_failed + +fail_roe: + ldr r0, =3Dfail_roe_message + b test_failed + +fail_roe_clear: + ldr r0, =3Dfail_roe_clear_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/syste= m/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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070713; cv=none; d=zohomail.com; s=zohoarc; b=eLTyDKdc9Oa33fu5xqhG2jU/bGY2eGYnelwr08rRnrWmGha0LA18e5bKJ5zOahhR8RBnaydRZ7cmWP5SUEY9lMkuvx8uUADHpH3ba++F6zBBM6tFOiE9rFHnMPRpYk5eX9ZEPFxbWzSjXZbLRwvfhnOcTWG8t9JDJKvY8OxTxh8= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070713; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=51MSPAX34QLjScUHTSmMcl8ETlx++ork8pmFINwZGBw=; b=NC2SkGP8wB6w/HvR8zW/HcCGHcAr/KpJ+qwRs2/cIUBvvwTM4GA3J/aKL1y1uQALaBdEmDy5eBwQLBxxRPLqkt9UOqWfXYxDaBgW2ISC3geexleQQzumu+xvAwKSRmVhFmMh+J4pC8tClrJkQR+48U9+V5oFpUKfyhgH7AoGGo8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070712674403.1125824130146; Sat, 29 Aug 2026 23:18:32 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0YrJ-0002Wq-Th; Sun, 30 Aug 2026 02:17:37 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrH-0002Vq-U9; Sun, 30 Aug 2026 02:17:35 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrE-0005HD-SE; Sun, 30 Aug 2026 02:17:35 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXhjn4TptzBqYb; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjn33vGz7xSW; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070637; bh=51MSPAX34QLjScUHTSmMcl8ETlx++ork8pmFINwZGBw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=KAsHty5L6WD7SVXiUS5STQGp+0OptaocwDqe3KDZNwWt0TO8bI/tFsi4W0IN0fL0R ds9E8xKlXjwolf8hWR93Obhi7/2/UUYherDnnwsCmchz4P8ISIs80fBQKB9a78cfrD Bamqrp3QAio3EaFnI+6C7xOmvgAKF5mNPusv2iCMKxZ8y4eltYy1lsDk0f9n4MrmAF ZOmlowqQKIKT9KyoaPFhHCTZqccrXhNAk6bZ+Dj+AtaWl+T5a5Sp485cFcLSr74Lg8 xrVqHClh6zLmJtaQFv7xZulHEvMi5bH7HL6wSNro3WA3jzX4Aq8RGsUP8U7a9sA+2q rkkFLSYVsa3ag== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 18/30] hw/timer: add RP2040 timer and alarms Date: Sun, 30 Aug 2026 08:15:49 +0200 Message-ID: <20260830061601.52136-4-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070718150154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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); =20 + if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, RP2040_TIMER_BASE); + for (i =3D 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 =20 +config RP2040_TIMER + bool + config RP2040_VREG bool =20 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_sysinf= o.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_watch= dog.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_u= s; +} + +static uint32_t rp2040_timer_ints(RP2040TimerState *s) +{ + uint32_t ints =3D (s->intr & s->inte) | s->intf; + + return ints & TIMER_ALARM_MASK; +} + +static void rp2040_timer_update_irq(RP2040TimerState *s) +{ + uint32_t ints =3D rp2040_timer_ints(s); + int i; + + for (i =3D 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 =3D rp2040_timer_now_us(s); + delta =3D s->alarm[alarm] - (uint32_t)now_us; + if (delta =3D=3D 0 || delta > INT32_MAX) { + timer_del(s->alarm_timer[alarm]); + s->armed &=3D ~BIT(alarm); + s->intr |=3D 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 =3D 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 =3D (offset - TIMER_ALARM0) / sizeof(uint32_t); + return true; +} + +static uint64_t rp2040_timer_read(void *opaque, hwaddr addr, unsigned size) +{ + RP2040TimerState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t now_us =3D rp2040_timer_now_us(s); + unsigned alarm; + uint64_t value; + + switch (offset) { + case TIMER_TIMEHR: + case TIMER_TIMERAWH: + value =3D now_us >> 32; + break; + case TIMER_TIMELR: + case TIMER_TIMERAWL: + value =3D (uint32_t)now_us; + break; + case TIMER_ARMED: + value =3D s->armed; + break; + case TIMER_DBGPAUSE: + value =3D s->dbgpause; + break; + case TIMER_PAUSE: + value =3D s->pause; + break; + case TIMER_INTR: + value =3D s->intr; + break; + case TIMER_INTE: + value =3D s->inte; + break; + case TIMER_INTF: + value =3D s->intf; + break; + case TIMER_INTS: + value =3D rp2040_timer_ints(s); + break; + default: + if (rp2040_timer_alarm_offset(offset, &alarm)) { + value =3D s->alarm[alarm]; + } else { + value =3D 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 =3D value; + } else { + s->time_offset_us =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D value64; + uint32_t old; + uint64_t now_us; + unsigned alarm; + + switch (offset) { + case TIMER_TIMEHW: + now_us =3D rp2040_timer_now_us(s); + rp2040_timer_set_time(s, ((uint64_t)value << 32) | + (uint32_t)now_us); + break; + case TIMER_TIMELW: + now_us =3D rp2040_timer_now_us(s); + rp2040_timer_set_time(s, (now_us & UINT64_C(0xffffffff00000000)) | + value); + break; + case TIMER_ARMED: + s->armed &=3D ~(value & TIMER_ALARM_MASK); + rp2040_timer_update_alarms(s); + break; + case TIMER_DBGPAUSE: + s->dbgpause =3D rp2040_timer_apply_alias(s->dbgpause, value, alias= ) & + TIMER_DBGPAUSE_MASK; + break; + case TIMER_PAUSE: + old =3D s->pause; + now_us =3D rp2040_timer_now_us(s); + s->pause =3D 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 =3D now_us; + } else if ((old & TIMER_PAUSE_MASK) && + !(s->pause & TIMER_PAUSE_MASK)) { + s->time_offset_us =3D s->paused_time_us - + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 10= 00; + rp2040_timer_update_alarms(s); + } + break; + case TIMER_INTR: + s->intr &=3D ~(value & TIMER_ALARM_MASK); + rp2040_timer_update_irq(s); + break; + case TIMER_INTE: + s->inte =3D rp2040_timer_apply_alias(s->inte, value, alias) & + TIMER_ALARM_MASK; + rp2040_timer_update_irq(s); + break; + case TIMER_INTF: + s->intf =3D 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] =3D value; + s->armed |=3D BIT(alarm); + rp2040_timer_update_alarm(s, alarm); + } else { + rp2040_log_unimplemented_write("timer", size, + RP2040_TIMER_BASE + addr, offse= t, + value64); + } + break; + } +} + +static const MemoryRegionOps rp2040_timer_ops =3D { + .read =3D rp2040_timer_read, + .write =3D rp2040_timer_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_timer_alarm_tick(void *opaque) +{ + uintptr_t packed =3D (uintptr_t)opaque; + RP2040TimerState *s =3D (RP2040TimerState *)(packed & ~(uintptr_t)0x3); + unsigned alarm =3D packed & 0x3; + + s->armed &=3D ~BIT(alarm); + s->intr |=3D BIT(alarm); + rp2040_timer_update_irq(s); +} + +static void rp2040_timer_reset(DeviceState *dev) +{ + RP2040TimerState *s =3D RP2040_TIMER(dev); + int i; + + s->time_offset_us =3D -qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) / 1000; + s->paused_time_us =3D 0; + memset(s->alarm, 0, sizeof(s->alarm)); + s->armed =3D 0; + s->dbgpause =3D TIMER_DBGPAUSE_MASK; + s->pause =3D 0; + s->intr =3D 0; + s->inte =3D 0; + s->intf =3D 0; + + for (i =3D 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 =3D 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 =3D RP2040_TIMER(dev); + int i; + + for (i =3D 0; i < RP2040_TIMER_NUM_ALARMS; i++) { + s->alarm_timer[i] =3D + timer_new_ns(QEMU_CLOCK_VIRTUAL, rp2040_timer_alarm_tick, + (void *)((uintptr_t)s | i)); + } +} + +static const VMStateDescription rp2040_timer_vmstate =3D { + .name =3D TYPE_RP2040_TIMER, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + dc->realize =3D rp2040_timer_realize; + device_class_set_legacy_reset(dc, rp2040_timer_reset); + dc->vmsd =3D &rp2040_timer_vmstate; +} + +static const TypeInfo rp2040_timer_info =3D { + .name =3D TYPE_RP2040_TIMER, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040TimerState), + .instance_init =3D rp2040_timer_init, + .class_init =3D 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 =3D \ 'rp2040-rosc-test', 'rp2040-sio-test', 'rp2040-tbman-test', + 'rp2040-timer-test', 'rp2040-uart-test', 'rp2040-vreg-test', 'rp2040-watchdog-te= st'] : []) + \ diff --git a/tests/qtest/rp2040-timer-test.c b/tests/qtest/rp2040-timer-tes= t.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 =3D rp2040_timer_start(); + + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_DBGPAUSE), =3D=3D, + TIMER_DBGPAUSE_MASK); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_PAUSE), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTE), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMEHR), =3D=3D, 0= ); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_TIMERAWH), =3D=3D,= 0); + + qtest_quit(qts); +} + +static void test_counter_and_pause(void) +{ + QTestState *qts =3D rp2040_timer_start(); + uint32_t before =3D 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, + =3D=3D, 123); + + qtest_writel(qts, TIMER_BASE + TIMER_PAUSE, TIMER_PAUSE_MASK); + paused =3D 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), =3D=3D, + 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), =3D=3D, + 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), =3D=3D, + 0x12345682); + + qtest_quit(qts); +} + +static void test_alarm(void) +{ + QTestState *qts =3D rp2040_timer_start(); + uint32_t now =3D 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), =3D=3D, BI= T(0)); + + qtest_clock_step(qts, 99 * NS_PER_US); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), =3D=3D, 0); + qtest_clock_step(qts, NS_PER_US); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_ARMED), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), =3D=3D, BIT= (0)); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), =3D=3D, BIT= (0)); + + qtest_writel(qts, TIMER_BASE + TIMER_INTR, BIT(0)); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTR), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), =3D=3D, 0); + + qtest_quit(qts); +} + +static void test_interrupt_force_and_masks(void) +{ + QTestState *qts =3D 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), =3D=3D, BIT= (2)); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), =3D=3D, BIT= (2)); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), =3D=3D, BIT= (2)); + + qtest_writel(qts, TIMER_BASE + TIMER_CLR_ALIAS + TIMER_INTF, BIT(2)); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTF), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_INTS), =3D=3D, 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), =3D=3D, + TIMER_DBGPAUSE_MASK); + g_assert_cmphex(qtest_readl(qts, TIMER_BASE + TIMER_PAUSE), =3D=3D, + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070783; cv=none; d=zohomail.com; s=zohoarc; b=RV7+rdRAp9L/0fWSItjoQdZQ3QAPalphmUBgA1LgezAbkiJ4ucjaWx1p+rDMad7hRYhZIlUSe9WlT89mDXFk3WCZ9uJ0L04K3Zq5Hw9Qu2vd1LnxvWmIMjjvNj6KwDJdnkwyK57bSG0ysfayRmzS8gE8qKlDbg2AoFYUgT0So5Q= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070783; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=c4A9mjdzKitmoqARJ2fI5K6z4YrhdjXxJE1wNl1lxGA=; b=dqkWBtYmEclVbQs8VPcCWG7yGyZgSENt+bxHaiaqqw2gzHEUHCGJaiIyhZHPah6s3aXn4UHtlSnFdUDdXq9aL8yoD0RxbxamKmtYA6QACuRCdXo1ax5TGCCkiPZiOMEogfFINg4I1HvBYDd8dzlotenj5p6FAu9fj8JTVE/BdIs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070782862748.4094448890424; Sat, 29 Aug 2026 23:19:42 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0YrG-0002Ul-4H; Sun, 30 Aug 2026 02:17:34 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrF-0002UN-BA; Sun, 30 Aug 2026 02:17:33 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrD-0005Gq-F0; Sun, 30 Aug 2026 02:17:33 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjn5g20z833L; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjn4JhDz7swM; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070637; bh=c4A9mjdzKitmoqARJ2fI5K6z4YrhdjXxJE1wNl1lxGA=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sst9B0mvFpBFUSyVkllqdsyaZplaBiIxa+Fx79GaTiS4l8E9gxOal1AVqEz5aFO9s Yrx0hTdg2Gk2YoU0dzzjsFycijqDt6jrxrc24ztUNgrPzzm/yQkwzd8ypIeH/FhEmu /ztDEHlYOgJyw77YiDspN9NQx2lUbns5wMFVG2CVxKQn+Sfnut1Cx72LAEiWt3L8jj vSxrfV4myiYeZtAS0It9IU8NL6K9oOk104AGOVJejx0bv06uQA38jWsllPjk8+E1vE bAFyzaWwdG2OUdDOGl9IhlQEPW+vqIMgBDCUAG5eYDCSlmncXkIjr7Y29V6geHvXEo w3B42FU4In0Sw== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 19/30] tests/tcg/arm: test RP2040 timer alarm interrupt Date: Sun, 30 Aug 2026 08:15:50 +0200 Message-ID: <20260830061601.52136-5-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070786297158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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+=3Dtest-armv6m-undef =20 RP2040_TESTS=3Drp2040-boot rp2040-uart rp2040-mpu \ rp2040-sio-fifo rp2040-sio-divider rp2040-psm-proc1 \ - rp2040-core1-launch + rp2040-core1-launch rp2040-timer =20 $(RP2040_TESTS): %: %.S rp2040-minimal.ld $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ @@ -35,14 +35,15 @@ run-rp2040-boot: QEMU_OPTS=3D-M raspi-pico \ =20 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=3D-M raspi-pico,strict-uart-pins=3Doff \ -serial chardev:output \ -semihosting-config enable=3Don,target=3Dnative -kernel =20 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=3D$<.out$(COMMA)id=3Doutput \ diff --git a/tests/tcg/arm/system/rp2040-timer.S b/tests/tcg/arm/system/rp2= 040-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, =3Dstart_message + bl puts + + ldr r0, =3DTEST_FLAG + movs r1, 0 + str r1, [r0] + + ldr r0, =3DNVIC_ISER + movs r1, 1 + str r1, [r0] + + ldr r4, =3DTIMER_BASE + str r1, [r4, TIMER_INTR] + str r1, [r4, TIMER_INTE] + ldr r2, [r4, TIMER_RAWL] + ldr r3, =3D1000 + adds r2, r2, r3 + str r2, [r4, TIMER_ALARM0] + +1: + ldr r0, =3DTEST_FLAG + ldr r1, [r0] + cmp r1, 0 + beq 1b + + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +2: + b 2b + +.thumb_func +timer_irq: + ldr r0, =3DTIMER_BASE + movs r1, 1 + str r1, [r0, TIMER_INTR] + ldr r0, =3DTEST_FLAG + str r1, [r0] + bx lr + +.thumb_func +default_handler: + ldr r0, =3Dfail_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +3: + b 3b + +.thumb_func +puts: + ldr r2, =3DUART0_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/r= p2040-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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070791; cv=none; d=zohomail.com; s=zohoarc; b=F3qcZALMBic/PllGEQaMEXctWv6G2Va4dPZZNu0SWId0vNAKtkj9ffDAO0SVf/AGudDThqWoF5RqftnxKV0/Li3giuWNMxq9ZXb6MI3QB+zdOxXls+0YSLIt3QkJKt3ArICdHcwNE0woNaeKv/vW59OTrlVhhpW5ylF6feC4sfE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070791; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=4xhwuyr+n+QcNUv2R7Y25aXIh4SEgz71s++aYNYTmtQ=; b=jYucYiuA7phQ5YhMMI6n2CoC7G7vu1Rr2KDW4hN0OIn2cgpSQzulqn7kTtGLTqUwqsZyI0sJGP0zcpjSFVOsABbLFyJwVus0B4xBGjIYAJzF/EuhcDDLKtvl4H/vJ2pjpek/YaSfvu03Xp3XjmZXELpDh2+QsAUYRfV+ANG1FU8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070791568678.7310700608862; Sat, 29 Aug 2026 23:19:51 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0YrU-0002bd-7U; Sun, 30 Aug 2026 02:17:48 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrO-0002Yy-Rh; Sun, 30 Aug 2026 02:17:43 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrK-0005Ht-1V; Sun, 30 Aug 2026 02:17:42 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXhjn72s6zBqYc; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjn5YCWz7xSW; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070638; bh=4xhwuyr+n+QcNUv2R7Y25aXIh4SEgz71s++aYNYTmtQ=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=RsDeLa/U81SaWNUi57FEDeBFwgrcSznNBvaR63L37m83CBs5ktzk+9f4SK4dZyUct WCGb2+6EaVD6iitlZPVgT4dE8yFc6PH1MSYwXYcGvErhOr4GFnTXUFECdUZmVbc6CC cXhR1tmKSYh4NKu6qp37a25RLew210msdfwkXT0QVJmk+sbL9fvMtp1WTl0LE/41Is 4ra2SpnUmgGHCB8jDSqVf3VsMgw0aRnvdVzFn0fFe3+dTUK6QwJlrWJwqHlbhdVqkH 3bSzE2Nu8dr+eLWkiJzj+pkR4tvH+9IqzWMQGA5I84ztWF86Zgk71ybykA/pCu/BJF 902W9BDvinuTw== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 20/30] hw/ssi: add RP2040 XIP flash controller Date: Sun, 30 Aug 2026 08:15:51 +0200 Message-ID: <20260830061601.52136-6-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070794684158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the XIP windows, cache control registers, SSI interface and external = NOR flash commands. Add raw-file persistence, configurable flash UID and fo= cused qtests, and connect IO_QSPI chip-select overrides. Update the synthetic boot path and existing TCG guests to execute a compile= d second-stage boot block. The boot2 source is derived from the Raspberry P= i Pico SDK and therefore retains its BSD-3-Clause license. Signed-off-by: gilles grimaud --- 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 =3D> 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 =3D> 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 =20 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) =20 -#define PICO_FLASH_SIZE (2 * MiB) - struct RaspiPicoMachineState { MachineState parent_obj; =20 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; }; =20 +static char *raspi_pico_get_flash_file(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s =3D 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 =3D RASPI_PICO_MACHINE(obj); + + g_free(s->flash_file); + s->flash_file =3D g_strdup(value); +} + +static char *raspi_pico_get_flash_uid(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s =3D 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 =3D RASPI_PICO_MACHINE(obj); + const char *p; + uint64_t uid; + int i; + + if (!value || !*value) { + g_free(s->flash_uid); + s->flash_uid =3D NULL; + s->flash_uid_value =3D 0; + s->flash_uid_set =3D false; + return; + } + + p =3D g_str_has_prefix(value, "0x") || g_str_has_prefix(value, "0X") ? + value + 2 : value; + if (strlen(p) !=3D 16) { + error_setg(errp, "flash-uid must be exactly 16 hexadecimal digits"= ); + return; + } + for (i =3D 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 =3D g_strdup(value); + s->flash_uid_value =3D uid; + s->flash_uid_set =3D true; +} + static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp) { RaspiPicoMachineState *s =3D 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); =20 - 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); =20 - 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); } =20 static void raspi_pico_machine_finalize(Object *obj) { RaspiPicoMachineState *s =3D RASPI_PICO_MACHINE(obj); =20 + g_free(s->flash_file); + g_free(s->flash_uid); g_free(s->rosc_random_seed); } =20 @@ -142,6 +214,19 @@ static void raspi_pico_machine_class_init(ObjectClass = *oc, const void *data) mc->no_floppy =3D 1; mc->no_cdrom =3D 1; =20 + 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 digi= ts"); + 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" =20 @@ -29,37 +30,109 @@ #define RP2040_PROC1 1 =20 /* - * Minimal synthetic ROM. Core 0 enters the XIP image while core 1 waits f= or - * 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 RP2= 040 + * mask ROM performs more checks, but boot2 expects to run from SRAM while= it + * configures XIP. */ static const uint8_t rp2040_bootrom[] =3D { - 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 */ }; =20 static const struct { @@ -293,6 +366,8 @@ static void rp2040_soc_init(Object *obj) "chardev"); } =20 + 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; } =20 + 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_BA= SE, + &s->xip.xip_noalloc); + memory_region_add_subregion(get_system_memory(), RP2040_XIP_NOCACHE_BA= SE, + &s->xip.xip_nocache); + memory_region_add_subregion(get_system_memory(), + RP2040_XIP_NOCACHE_NOALLOC_BASE, + &s->xip.xip_nocache_noalloc); + for (i =3D 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]); =20 + 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 @@ */ =20 #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 @@ =20 #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 =20 @@ -68,6 +74,22 @@ static uint32_t rp2040_ioqspi_ints(uint32_t intr, uint32= _t inte, return (intr & inte) | intf; } =20 +static void rp2040_ioqspi_update_ss(RP2040IoQspiState *s) +{ + uint32_t outover; + + if (!s->xip) { + return; + } + + outover =3D s->ctrl[IOQSPI_SS_INDEX] & IOQSPI_CTRL_OUTOVER_MASK; + if (outover =3D=3D IOQSPI_CTRL_OUTOVER_LOW) { + rp2040_xip_qspi_cs(s->xip, false); + } else if (outover =3D=3D IOQSPI_CTRL_OUTOVER_HIGH) { + rp2040_xip_qspi_cs(s->xip, true); + } +} + static uint64_t rp2040_ioqspi_read(void *opaque, hwaddr addr, unsigned siz= e) { RP2040IoQspiState *s =3D opaque; @@ -139,6 +161,9 @@ static void rp2040_ioqspi_write(void *opaque, hwaddr ad= dr, s->ctrl[index] =3D rp2040_ioqspi_apply_alias(s->ctrl[index], value, alias) & IOQSPI_CTRL_RW_MASK; + if (index =3D=3D 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 a= ddr, } } =20 +static const Property rp2040_ioqspi_properties[] =3D { + DEFINE_PROP_LINK("xip", RP2040IoQspiState, xip, TYPE_RP2040_XIP, + RP2040XipState *), +}; + static const MemoryRegionOps rp2040_ioqspi_ops =3D { .read =3D rp2040_ioqspi_read, .write =3D rp2040_ioqspi_write, @@ -245,6 +275,7 @@ static void rp2040_ioqspi_class_init(ObjectClass *klass= , const void *data) DeviceClass *dc =3D DEVICE_CLASS(klass); =20 device_class_set_legacy_reset(dc, rp2040_ioqspi_reset); + device_class_set_props(dc, rp2040_ioqspi_properties); dc->vmsd =3D &rp2040_ioqspi_vmstate; } =20 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 =20 +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 =3D 0; + s->rx_pos =3D 0; +} + +static void rp2040_xip_rx_push(RP2040XipState *s, uint8_t value) +{ + if (s->rx_len =3D=3D 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 -=3D s->rx_pos; + s->rx_pos =3D 0; + } + + if (s->rx_len < ARRAY_SIZE(s->rx)) { + s->rx[s->rx_len++] =3D value; + qemu_irq_pulse(s->dreq_rx); + } +} + +static bool rp2040_xip_rx_compact(RP2040XipState *s) +{ + if (s->rx_len =3D=3D 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 -=3D s->rx_pos; + s->rx_pos =3D 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 =3D 0xffffffff; + return false; + } + + *value =3D 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 en= able + * DMA BSWAP when copying words from SSI_DR0. + */ + s->rx[s->rx_len++] =3D extract32(value, 24, 8); + s->rx[s->rx_len++] =3D extract32(value, 16, 8); + s->rx[s->rx_len++] =3D extract32(value, 8, 8); + s->rx[s->rx_len++] =3D 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) >=3D + 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 +=3D 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 =3D addr; + s->ssi_bulk_remaining =3D 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 =3D 0; + s->stream_fifo_pos =3D 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 =3D 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 =3D=3D s->stream_fifo_len) { + s->stream_fifo_pos =3D 0; + s->stream_fifo_len =3D 0; + } + + while (s->stream_ctr > 0 && + s->stream_fifo_len < ARRAY_SIZE(s->stream_fifo)) { + s->stream_fifo[s->stream_fifo_len++] =3D rp2040_xip_stream_word(s); + s->stream_addr +=3D 4; + s->stream_ctr--; + } + + rp2040_xip_update_stream_dreq(s); +} + +static uint32_t rp2040_xip_stream_pop(RP2040XipState *s) +{ + uint32_t value =3D 0; + + rp2040_xip_stream_fill(s); + if (s->stream_fifo_pos < s->stream_fifo_len) { + value =3D 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 =3D RP2040_XIP_STAT_FLUSH_READY; + + rp2040_xip_stream_fill(s); + if (s->stream_fifo_pos =3D=3D s->stream_fifo_len) { + stat |=3D RP2040_XIP_STAT_FIFO_EMPTY; + } + if (s->stream_fifo_len - s->stream_fifo_pos =3D=3D + ARRAY_SIZE(s->stream_fifo)) { + stat |=3D RP2040_XIP_STAT_FIFO_FULL; + } + + return stat; +} + +static uint8_t rp2040_xip_status(RP2040XipState *s) +{ + uint8_t status =3D 0; + + if (s->busy) { + status |=3D FLASH_STATUS_WIP; + } + if (s->write_enable) { + status |=3D 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 =3D false; +} + +static void rp2040_xip_reset_tx(RP2040XipState *s) +{ + s->tx_len =3D 0; + s->tx_unsupported_logged =3D false; + s->ssi_bulk_remaining =3D 0; +} + +static bool rp2040_xip_writeback(RP2040XipState *s, Error **errp) +{ + g_autoptr(GError) gerr =3D 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 =3D 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 =3D RP2040_BOOT2_CRC_INIT; + int i; + int bit; + + for (i =3D 0; i < RP2040_BOOT2_CRC_SIZE; i++) { + crc ^=3D (uint32_t)data[i] << 24; + for (bit =3D 0; bit < 8; bit++) { + if (crc & BIT(31)) { + crc =3D (crc << 1) ^ RP2040_BOOT2_CRC_POLY; + } else { + crc <<=3D 1; + } + } + } + + return crc; +} + +static bool rp2040_xip_boot2_empty(const uint8_t *data) +{ + int i; + + for (i =3D 0; i < RP2040_BOOT2_SIZE; i++) { + if (data[i] !=3D 0xff) { + return false; + } + } + + return true; +} + +static bool rp2040_xip_boot2_looks_like_vector_table(const uint8_t *data) +{ + uint32_t initial_sp =3D ldl_le_p(data); + uint32_t reset =3D ldl_le_p(data + 4); + + return initial_sp >=3D RP2040_SRAM_BASE && + initial_sp <=3D RP2040_SRAM_END && + reset >=3D 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 tab= le, " + "not RP2040 boot2; use a Pico SDK UF2 or an ELF with " + "boot2 linked at 0x%08x", filename, RP2040_XIP_FLASH_BA= SE); + return false; + } + + crc =3D 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 >=3D 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 =3D false; + + if (s->tx_len <=3D 4) { + return; + } + + addr =3D rp2040_xip_tx_addr(s); + if (addr >=3D s->flash_size) { + return; + } + + page_end =3D ROUND_UP(addr + 1, FLASH_PAGE_SIZE); + data_len =3D MIN(s->tx_len - 4, page_end - addr); + data_len =3D MIN(data_len, s->flash_size - addr); + + for (i =3D 0; i < data_len; i++) { + s->storage[addr + i] &=3D s->tx[4 + i]; + } + + rp2040_xip_writeback_or_warn(s); + s->busy =3D true; +} + +static void rp2040_xip_erase(RP2040XipState *s) +{ + uint32_t addr; + uint32_t base; + + trace_rp2040_xip_erase(s->tx_len >=3D 4 ? rp2040_xip_tx_addr(s) : 0, + s->write_enable); + + if (!s->write_enable) { + return; + } + + s->write_enable =3D false; + + if (s->tx_len < 4) { + return; + } + + addr =3D rp2040_xip_tx_addr(s); + base =3D QEMU_ALIGN_DOWN(addr, FLASH_SECTOR_SIZE); + if (base >=3D 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 =3D true; +} + +static void rp2040_xip_finish_command(RP2040XipState *s) +{ + if (s->tx_len =3D=3D 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 =3D 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++] =3D value; + } + + switch (s->tx[0]) { + case FLASH_CMD_WRITE_STATUS: + s->write_enable =3D 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 =3D 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 <=3D 1 + FLASH_UNIQUE_ID_DUMMY_BYTES) { + rp2040_xip_rx_push(s, 0); + } else { + unsigned index =3D 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 <=3D 4) { + rp2040_xip_rx_push(s, 0); + } else { + addr =3D 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 befo= re + * consuming data. We do not model bus width or wait-cycle timing = here. + */ + if (s->tx_len <=3D 5) { + rp2040_xip_rx_push(s, 0); + } else { + addr =3D 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 =3D g_strdup_printf("opcode 0x%02x", + s->tx[0]); + + rp2040_log_nyi("xip.ssi", "flash command", detail); + s->tx_unsupported_logged =3D 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 =3D 0; + unsigned i; + + if (s->busy || addr + size > s->flash_size) { + return MEMTX_ERROR; + } + + for (i =3D 0; i < size; i++) { + hwaddr cur =3D addr + i; + uint8_t byte =3D s->storage[cur]; + + value |=3D (uint64_t)byte << (i * 8); + } + *data =3D value; + return MEMTX_OK; +} + +static MemTxResult rp2040_xip_read(void *opaque, hwaddr addr, uint64_t *da= ta, + 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 da= ta, + unsigned size, MemTxAttrs attrs) +{ + RP2040XipState *s =3D opaque; + unsigned i; + + if (!s->xip_writable) { + return MEMTX_ERROR; + } + if (addr + size > s->flash_size) { + return MEMTX_ERROR; + } + + for (i =3D 0; i < size; i++) { + s->storage[addr + i] =3D extract64(data, i * 8, 8); + } + return MEMTX_OK; +} + +static uint64_t rp2040_xip_ctrl_read(void *opaque, hwaddr addr, unsigned s= ize) +{ + RP2040XipState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case RP2040_XIP_CTRL: + value =3D s->xip_ctrl; + break; + case RP2040_XIP_FLUSH: + case RP2040_XIP_CTR_HIT: + case RP2040_XIP_CTR_ACC: + value =3D 0; + break; + case RP2040_XIP_STAT: + value =3D rp2040_xip_stat(s); + break; + case RP2040_XIP_STREAM_ADDR: + value =3D s->stream_addr; + break; + case RP2040_XIP_STREAM_CTR: + value =3D s->stream_ctr; + break; + case RP2040_XIP_STREAM_FIFO: + value =3D rp2040_xip_stream_pop(s); + break; + default: + value =3D 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 valu= e, + unsigned size) +{ + RP2040XipState *s =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t new_value; + + switch (offset) { + case RP2040_XIP_CTRL: + new_value =3D rp2040_xip_apply_alias(s->xip_ctrl, value, alias); + s->xip_ctrl =3D 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 =3D value & ~3u; + break; + case RP2040_XIP_STREAM_CTR: + s->stream_ctr =3D value & RP2040_XIP_STREAM_CTR_MASK; + if (s->stream_ctr =3D=3D 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 si= ze) +{ + 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 si= ze) +{ + RP2040XipState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint8_t value; + uint32_t risr =3D s->rx_len > s->rx_pos ? 0 : 1; + uint64_t ret; + + if (offset >=3D RP2040_SSI_DR0 && offset <=3D RP2040_SSI_DR_END) { + unsigned i; + + if (s->tx_len > 0 && s->ssi_bulk_remaining =3D=3D 0) { + if (s->rx_pos < s->rx_len) { + value =3D s->rx[s->rx_pos++]; + } else { + value =3D 0; + } + if (s->rx_pos =3D=3D s->rx_len) { + rp2040_xip_rx_clear(s); + } + return value; + } + + ret =3D 0; + for (i =3D 0; i < size; i++) { + if (s->rx_pos =3D=3D s->rx_len) { + rp2040_xip_rx_clear(s); + rp2040_xip_ssi_bulk_fill(s); + } + if (s->rx_pos < s->rx_len) { + value =3D s->rx[s->rx_pos++]; + } else { + value =3D 0; + } + ret |=3D (uint64_t)value << (i * 8); + } + if (s->rx_pos =3D=3D s->rx_len) { + rp2040_xip_rx_clear(s); + rp2040_xip_ssi_bulk_fill(s); + } + return ret; + } + + switch (offset) { + case RP2040_SSI_CTRLR0: + ret =3D s->ctrlr0; + break; + case RP2040_SSI_CTRLR1: + ret =3D s->ctrlr1; + break; + case RP2040_SSI_SSIENR: + ret =3D s->ssienr; + break; + case RP2040_SSI_SER: + ret =3D s->ser; + break; + case RP2040_SSI_BAUDR: + ret =3D s->baudr; + break; + case RP2040_SSI_TXFTLR: + ret =3D s->txftlr; + break; + case RP2040_SSI_RXFTLR: + ret =3D s->rxftlr; + break; + case RP2040_SSI_TXFLR: + ret =3D 0; + break; + case RP2040_SSI_RXFLR: + ret =3D s->rx_len - s->rx_pos; + break; + case RP2040_SSI_SR: + ret =3D 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 =3D=3D ARRAY_SIZE(s->rx) ? + RP2040_SSI_SR_RFF : 0); + break; + case RP2040_SSI_IMR: + ret =3D s->imr; + break; + case RP2040_SSI_ISR: + case RP2040_SSI_RISR: + ret =3D risr; + break; + case RP2040_SSI_TXOICR: + case RP2040_SSI_RXOICR: + case RP2040_SSI_RXUICR: + case RP2040_SSI_MSTICR: + case RP2040_SSI_ICR: + ret =3D 0; + break; + case RP2040_SSI_DMACR: + ret =3D s->dmacr; + break; + case RP2040_SSI_DMATDLR: + ret =3D s->dmatdlr; + break; + case RP2040_SSI_DMARDLR: + ret =3D s->dmardlr; + break; + case RP2040_SSI_IDR: + ret =3D 0; + break; + case RP2040_SSI_VERSION_ID: + ret =3D 0x3430312a; + break; + case RP2040_SSI_RX_SAMPLE_DLY: + ret =3D s->rx_sample_dly; + break; + case RP2040_SSI_SPI_CTRLR0: + ret =3D s->spi_ctrlr0; + break; + default: + ret =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t old_ssienr =3D s->ssienr; + uint32_t old_ser =3D s->ser; + uint32_t new_value; + + if (offset >=3D RP2040_SSI_DR0 && offset <=3D RP2040_SSI_DR_END) { + if (s->tx_len < 8 || (s->tx_len & 0x3f) =3D=3D 0) { + trace_rp2040_xip_dr_write(value, size, s->tx_len, s->ctrlr0, + s->ctrlr1, s->spi_ctrlr0); + } + if (size =3D=3D 4 && (value & 0xff) =3D=3D 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 =3D rp2040_xip_apply_alias(s->ctrlr0, value, alias); + trace_rp2040_xip_ctrlr0(s->ctrlr0); + break; + case RP2040_SSI_CTRLR1: + s->ctrlr1 =3D rp2040_xip_apply_alias(s->ctrlr1, value, alias); + trace_rp2040_xip_ctrlr1(s->ctrlr1); + break; + case RP2040_SSI_SSIENR: + new_value =3D rp2040_xip_apply_alias(s->ssienr, value, alias); + s->ssienr =3D 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 =3D rp2040_xip_apply_alias(s->ser, value, alias); + s->ser =3D 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 =3D rp2040_xip_apply_alias(s->baudr, value, alias); + s->baudr =3D new_value & 0xffff; + break; + case RP2040_SSI_TXFTLR: + new_value =3D rp2040_xip_apply_alias(s->txftlr, value, alias); + s->txftlr =3D new_value & 0xff; + break; + case RP2040_SSI_RXFTLR: + new_value =3D rp2040_xip_apply_alias(s->rxftlr, value, alias); + s->rxftlr =3D new_value & 0xff; + break; + case RP2040_SSI_IMR: + new_value =3D rp2040_xip_apply_alias(s->imr, value, alias); + s->imr =3D new_value & 0x3f; + break; + case RP2040_SSI_DMACR: + new_value =3D rp2040_xip_apply_alias(s->dmacr, value, alias); + s->dmacr =3D new_value & (RP2040_SSI_DMACR_TDMAE | + RP2040_SSI_DMACR_RDMAE); + break; + case RP2040_SSI_DMATDLR: + s->dmatdlr =3D rp2040_xip_apply_alias(s->dmatdlr, value, alias) & = 0xff; + break; + case RP2040_SSI_DMARDLR: + s->dmardlr =3D rp2040_xip_apply_alias(s->dmardlr, value, alias) & = 0xff; + break; + case RP2040_SSI_RX_SAMPLE_DLY: + s->rx_sample_dly =3D rp2040_xip_apply_alias(s->rx_sample_dly, valu= e, + alias) & 0xff; + break; + case RP2040_SSI_SPI_CTRLR0: + s->spi_ctrlr0 =3D rp2040_xip_apply_alias(s->spi_ctrlr0, value, ali= as); + 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 =3D { + .read_with_attrs =3D rp2040_xip_read, + .write_with_attrs =3D rp2040_xip_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + .unaligned =3D true, + }, +}; + +static const MemoryRegionOps rp2040_xip_alias_ops =3D { + .read_with_attrs =3D rp2040_xip_alias_read, + .write_with_attrs =3D rp2040_xip_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + .unaligned =3D true, + }, +}; + +static const MemoryRegionOps rp2040_xip_ctrl_ops =3D { + .read =3D rp2040_xip_ctrl_read, + .write =3D rp2040_xip_ctrl_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static const MemoryRegionOps rp2040_xip_ssi_ops =3D { + .read =3D rp2040_xip_ssi_read, + .write =3D rp2040_xip_ssi_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + .unaligned =3D true, + }, +}; + +static const MemoryRegionOps rp2040_xip_aux_ops =3D { + .read =3D rp2040_xip_aux_read, + .write =3D rp2040_xip_aux_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +void rp2040_xip_set_writable(RP2040XipState *s, bool writable) +{ + s->xip_writable =3D writable; +} + +void rp2040_xip_qspi_cs(RP2040XipState *s, bool high) +{ + if (s->qspi_cs_high =3D=3D high) { + return; + } + + trace_rp2040_xip_qspi_cs(high, s->tx_len); + s->qspi_cs_high =3D 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 =3D 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 =3D (const Elf32_Ehdr *)contents; + if (memcmp(ehdr->e_ident, ELFMAG, SELFMAG) !=3D 0) { + return false; + } + if (ehdr->e_ident[EI_CLASS] !=3D ELFCLASS32 || + ehdr->e_ident[EI_DATA] !=3D ELFDATA2LSB || + le16_to_cpu(ehdr->e_machine) !=3D 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) !=3D 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 =3D (const Elf32_Phdr *)(contents + le32_to_cpu(ehdr->e_phoff)); + for (i =3D 0; i < le16_to_cpu(ehdr->e_phnum); i++) { + uint32_t paddr =3D le32_to_cpu(phdr[i].p_paddr); + uint32_t filesz =3D le32_to_cpu(phdr[i].p_filesz); + uint32_t memsz =3D le32_to_cpu(phdr[i].p_memsz); + uint32_t off =3D le32_to_cpu(phdr[i].p_offset); + uint32_t xip_off; + + if (le32_to_cpu(phdr[i].p_type) !=3D PT_LOAD) { + continue; + } + + if (filesz =3D=3D 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 =3D 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 =3D 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 =3D 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 !=3D 64 * KiB) { + g_autofree char *detail =3D g_strdup_printf("block size %" PRIu32, + block_size); + + rp2040_log_nyi("bootrom", "flash_range_erase block size", detail); + } + if (block_cmd !=3D 0x20 && block_cmd !=3D 0xd8) { + g_autofree char *detail =3D 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 =3D 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 =3D g_malloc(count); + if (address_space_read(&address_space_memory, data_addr, + MEMTXATTRS_UNSPECIFIED, buf, count) !=3D MEMTX_= OK) { + error_setg(errp, "could not read flash program buffer at 0x%08" PR= Ix32, + data_addr); + return false; + } + + for (i =3D 0; i < count; i++) { + s->storage[flash_offs + i] &=3D buf[i]; + } + + return rp2040_xip_writeback(s, errp); +} + +static void rp2040_xip_realize(DeviceState *dev, Error **errp) +{ + RP2040XipState *s =3D RP2040_XIP(dev); + g_autofree gchar *contents =3D NULL; + gsize contents_len =3D 0; + + if (s->flash_size =3D=3D 0) { + error_setg(errp, "flash-size must be non-zero"); + return; + } + + s->xip_writable =3D true; + s->storage =3D 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 =3D RP2040_XIP(dev); + + s->xip_ctrl =3D RP2040_XIP_CTRL_EN | RP2040_XIP_CTRL_ERR_BADWRITE; + s->ctrlr0 =3D 0; + s->ctrlr1 =3D 0; + s->ssienr =3D 0; + s->ser =3D 0; + s->baudr =3D 0; + s->txftlr =3D 0; + s->rxftlr =3D 0; + s->imr =3D 0; + s->dmacr =3D 0; + s->dmatdlr =3D 0; + s->dmardlr =3D 4; + s->rx_sample_dly =3D 0; + s->spi_ctrlr0 =3D 0; + s->write_enable =3D false; + s->busy =3D false; + s->qspi_cs_high =3D true; + s->stream_addr =3D 0; + s->stream_ctr =3D 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 =3D RP2040_XIP(obj); + + g_free(s->flash_file); + g_free(s->storage); +} + +static void rp2040_xip_init(Object *obj) +{ + RP2040XipState *s =3D 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[] =3D { + 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 =3D DEVICE_CLASS(klass); + + dc->realize =3D 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 =3D { + .name =3D TYPE_RP2040_XIP, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040XipState), + .instance_init =3D rp2040_xip_init, + .instance_finalize =3D rp2040_xip_finalize, + .class_init =3D 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_dat= a) "tx_data: 0x%" PRIx32 ibex_spi_host_write(uint64_t addr, uint32_t size, uint64_t data) "@0x%" PR= Ix64 " size %u: 0x%" PRIx64 ibex_spi_host_read(uint64_t addr, uint32_t size) "@0x%" PRIx64 " size %u:" =20 +# 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 0= x%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) "o= ld 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%" PRI= x64 pnv_spi_write(uint64_t addr, uint64_t val) "addr 0x%" PRIx64 " val 0x%" PR= Ix64 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" =20 #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; =20 MemoryRegion *board_memory; MemoryRegion cpu_memory[RP2040_NUM_CORES]; diff --git a/include/hw/misc/rp2040_ioqspi.h b/include/hw/misc/rp2040_ioqsp= i.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) =20 +typedef struct RP2040XipState RP2040XipState; + #define RP2040_IOQSPI_BASE 0x40018000 #define RP2040_IOQSPI_SIZE 0x4000 =20 @@ -28,6 +30,7 @@ struct RP2040IoQspiState { uint32_t proc1_intf; uint32_t dormant_wake_inte; uint32_t dormant_wake_intf; + RP2040XipState *xip; }; =20 #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 =3D \ 'rp2040-timer-test', 'rp2040-uart-test', 'rp2040-vreg-test', - 'rp2040-watchdog-te= st'] : []) + \ + 'rp2040-watchdog-te= st', + '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 =3D 0; i < 4; i++) { + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0); + qtest_readl(qts, XIP_SSI_BASE + SSI_DR0); + } + for (i =3D 0; i < 8; i++) { + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0); + uid[i] =3D 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 =3D 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 =3D 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[] =3D { + 0x3e, 0xb8, 0xa7, 0x49, 0x3f, 0xcc, 0x06, 0x08, + }; + QTestState *qts =3D 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[] =3D { + 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + }; + QTestState *qts =3D rp2040_start("flash-uid=3D0011223344556677"); + 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[] =3D { 0x0f, 0x55, 0xaa, 0xf0 }; + static const uint8_t second[] =3D { 0xf0, 0xaa, 0x55, 0x0f }; + static const uint8_t combined[] =3D { 0x00, 0x00, 0x00, 0x00 }; + static const uint8_t erased[] =3D { 0xff, 0xff, 0xff, 0xff }; + uint8_t actual[sizeof(first)]; + QTestState *qts =3D 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[] =3D { 0x12, 0x34, 0x56, 0x78 }; + g_autoptr(GError) error =3D NULL; + g_autofree char *path =3D NULL; + g_autofree char *machine_args =3D NULL; + uint8_t initial[4096]; + uint8_t actual[sizeof(expected)]; + QTestState *qts; + int fd; + + memset(initial, 0xff, sizeof(initial)); + fd =3D g_file_open_tmp("qemu-rp2040-flash-XXXXXX", &path, &error); + g_assert_no_error(error); + g_assert_cmpint(fd, >=3D, 0); + close(fd); + g_assert_true(g_file_set_contents(path, (const char *)initial, + sizeof(initial), &error)); + g_assert_no_error(error); + + machine_args =3D g_strdup_printf("flash-file=3D%s", path); + qts =3D rp2040_start(machine_args); + program_flash_bytes(qts, 0x800, expected, sizeof(expected)); + qtest_quit(qts); + + qts =3D 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[] =3D { 0x5a, 0xa5 }; + uint8_t actual[sizeof(expected)]; + QTestState *qts =3D 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 =3D 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[] =3D { + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, + }; + QTestState *qts =3D 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), =3D=3D, + XIP_STAT_FLUSH_READY | XIP_STAT_FIFO_FULL); + for (i =3D 0; i < ARRAY_SIZE(expected); i++) { + g_assert_cmphex(qtest_readl(qts, + XIP_CTRL_BASE + XIP_STREAM_FIFO), =3D= =3D, + expected[i]); + } + g_assert_cmphex(qtest_readl(qts, XIP_CTRL_BASE + XIP_STREAM_CTR), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, XIP_CTRL_BASE + XIP_STAT) & + XIP_STAT_FIFO_EMPTY, =3D=3D, XIP_STAT_FIFO_EMPTY); + + qtest_quit(qts); +} + +static void test_ssi_bulk_read(void) +{ + static const uint32_t expected[] =3D { + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, + }; + QTestState *qts =3D 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), =3D=3D, + 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 =3D 0; i < ARRAY_SIZE(expected); i++) { + g_assert_cmphex(bswap32(qtest_readl(qts, + XIP_SSI_BASE + SSI_DR0)), =3D= =3D, + 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=3Drp2040-boot rp2040-uart rp2040-mpu \ rp2040-sio-fifo rp2040-sio-divider rp2040-psm-proc1 \ rp2040-core1-launch rp2040-timer =20 -$(RP2040_TESTS): %: %.S rp2040-minimal.ld +$(RP2040_TESTS): %: %.S rp2040-boot2-w25q080.S rp2040.ld $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ -Wl,--build-id=3Dnone -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 =20 run-rp2040-boot: QEMU_OPTS=3D-M raspi-pico \ -semihosting-config enable=3Don,target=3Dnative -kernel diff --git a/tests/tcg/arm/system/rp2040-boot2-w25q080.S b/tests/tcg/arm/sy= stem/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, =3DPADS_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, =3DXIP_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, =3DCTRL0_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, =3DCTRLR0_ENTER_XIP + str r1, [r3, SSI_CTRLR0] + movs r1, 0 + str r1, [r3, SSI_CTRLR1] + ldr r1, =3DSPI_CTRLR0_ENTER_XIP + ldr r0, =3D(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, =3DSPI_CTRLR0_XIP + ldr r0, =3D(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, =3D(XIP_BASE + 0x100) + ldr r1, =3DVTOR + 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) =20 MEMORY @@ -10,7 +8,14 @@ MEMORY =20 SECTIONS { - .vectors ORIGIN(FLASH) : + .boot2 ORIGIN(FLASH) : + { + KEEP(*(.boot2)) + } > FLASH + + ASSERT(SIZEOF(.boot2) =3D=3D 256, "RP2040 boot2 must be exactly 256 by= tes") + + .vectors ORIGIN(FLASH) + 0x100 : { KEEP(*(.vectors)) } > FLASH --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070787; cv=none; d=zohomail.com; s=zohoarc; b=i4Jpb3cOO8dTH300wiuAtjI21WU/dYWS1FWPw0GnOo2yXGvIIWXPYMjt1dOUSdQs0bfbgStqvkwTZj66zUrCQWzQanXO/c22WJkiGM/1BJRBIQzFF/Newgn3LkKhGgqyozLzgEIw3Kmw+bV3TLHTN08Hwj6+2K+9kJkqcjD8u8Q= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070787; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=XXc8xY6jbSJdHuZ5c/p5UvxqIcENO+prlnwXQ9kmufs=; b=EYIaC0aw+inz6/TAbBfTAQjpNoyQPjEG+UyVP8487L3jfTPI539M69/0+2uPgNFGY52XPC2HYOy2AwbwDc1eXdaQT3brUa8yk+s3IY2O1p2jHFvUjCSsXHBiyRVGYqIVey6BM0ah4xyc03wCic339uLSH6aLuyKanoosaYrBfnw= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070787877683.9725530128779; Sat, 29 Aug 2026 23:19:47 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0YrW-0002cL-AH; Sun, 30 Aug 2026 02:17:50 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrS-0002aC-3F; Sun, 30 Aug 2026 02:17:46 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrM-0005I6-Vw; Sun, 30 Aug 2026 02:17:45 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjp1FlNz833X; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjn6tkMz7swM; Sun, 30 Aug 2026 08:17:17 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070638; bh=XXc8xY6jbSJdHuZ5c/p5UvxqIcENO+prlnwXQ9kmufs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=oyQEiDXUoN+SHhrPccs9XAy/n+gxoVn3l6TG4nlFjuixl+OZASfL8H/HtwXU8f8QJ VgccXIx4rcIh8Dv0ERR/s0MLB/Q/NfC5yeQgsAq8MCR8QABH8GILgf/jmMxVrRboXn MusNJSk+z48fzbtA4tYy8Z06CN7rsty8I2vVkGvrZmEBEuUcZRoe6MLuADzDRECrgB eBG0vFf1Po7Gbq5ohuJRgYF1Nq9IyrXLgU7Nl4ge+4ZmYTNcZ4uzk7PJzCvWMb1bkB UJVZbOzFiPQpCBbMjrlR9RihBGQVbmnZiW1ZK7ccI9qSofevdCkJsVBs4SU1Qq5Tbb r2iB+6qBuWqsQ== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 21/30] tests/tcg/arm: test RP2040 flash and XIP behavior Date: Sun, 30 Aug 2026 08:15:52 +0200 Message-ID: <20260830061601.52136-7-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070788201158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Exercise write-enable, page programming, sector erase and NOR bit semantics= through the RP2040 SSI command path. Verify raw flash persistence across Q= EMU invocations and IO_QSPI chip-select behavior.\n\nCover XIP accesses whi= le the flash is busy, including direct HardFault delivery, multicore lockou= t, and faults observed independently by core 0 and core 1. Signed-off-by: gilles grimaud --- 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=3Drp2040-boot rp2040-uart rp2040-mpu \ rp2040-sio-fifo rp2040-sio-divider rp2040-psm-proc1 \ rp2040-core1-launch rp2040-timer =20 -$(RP2040_TESTS): %: %.S rp2040-boot2-w25q080.S rp2040.ld +RP2040_FLASH_TESTS=3Drp2040-flash-commands rp2040-flash-ioqspi \ + rp2040-flash-busy-hardfault rp2040-flash-busy-lockout \ + rp2040-core0-xip-fault rp2040-core1-xip-fault + +RP2040_GUESTS=3D$(RP2040_TESTS) $(RP2040_FLASH_TESTS) \ + rp2040-flash-persist-reader + +$(RP2040_GUESTS): %: %.S rp2040-boot2-w25q080.S rp2040.ld $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ -Wl,--build-id=3Dnone -x assembler-with-cpp \ $(ARM_SRC)/rp2040-boot2-w25q080.S $< \ @@ -36,7 +43,10 @@ run-rp2040-boot: QEMU_OPTS=3D-M raspi-pico \ =20 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=3D-M raspi-pico,strict-uart-pins=3Doff \ -serial chardev:output \ -semihosting-config enable=3Don,target=3Dnative -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=3D$<.out$(COMMA)id=3Doutput \ $(QEMU_OPTS) $<) $(call diff-out,$<,$(ARM_SRC)/$<.ref) - -ARM_TESTS+=3D$(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=3D$<.out$(COMMA)id=3Doutput \ + $(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=3Doff$(COMMA)flash-file=3D$<.flash= \ + -serial null -semihosting-config enable=3Don$(COMMA)target=3Dnative \ + -kernel $< + $(QEMU) -monitor none -display none \ + -chardev file$(COMMA)path=3D$<.persist.out$(COMMA)id=3Doutput \ + -M raspi-pico$(COMMA)strict-uart-pins=3Doff$(COMMA)flash-file=3D$<.flash= \ + -serial chardev:output \ + -semihosting-config enable=3Don$(COMMA)target=3Dnative \ + -kernel rp2040-flash-persist-reader + $(call diff-out,$<.persist,$(ARM_SRC)/rp2040-flash-persist-reader.ref) + rm -f $<.flash $<.persist.out + +ARM_TESTS+=3D$(RP2040_TESTS) $(RP2040_FLASH_TESTS) =20 # These objects provide the basic boot code and helper functions for all t= ests CRT_OBJS=3Dboot.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, =3Dstart_message + bl xip_puts + ldr r0, =3DSRAM_BASE + ldr r1, =3Dsram_start + ldr r2, =3Dsram_end + +1: + cmp r1, r2 + bhs 2f + ldr r3, [r1] + str r3, [r0] + adds r1, 4 + adds r0, 4 + b 1b + +2: + ldr r0, =3DSRAM_BASE + adds r0, 1 + bx r0 + +.thumb_func +default_handler: + b default_handler + +.thumb_func +xip_probe: + ldr r0, =3Dxip_no_fault_message + bl xip_puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +3: + b 3b + +.thumb_func +xip_puts: + ldr r2, =3DUART0_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, =3DSRAM_BASE + ldr r1, =3DSRAM_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, =3DSCB_VTOR + str r0, [r2] + +.align 2 +.thumb_func +sram_entry: + ldr r4, =3DSSI_BASE + movs r0, 0 + str r0, [r4, SSI_SSIENR] + ldr r0, =3DSSI_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, =3Dxip_probe + adds r0, 1 + bx r0 + +.thumb_func +sram_ssi_txrx: + ldr r2, =3D(SSI_BASE + SSI_DR0) + str r0, [r2] + ldr r3, =3D(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, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +7: + b 7b + +.thumb_func +sram_puts: + ldr r2, =3DUART0_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/ar= m/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, =3Dstart_message + bl xip_puts + ldr r0, =3DSRAM_BASE + ldr r1, =3Dsram_start + ldr r2, =3Dsram_end + +1: + cmp r1, r2 + bhs 2f + ldr r3, [r1] + str r3, [r0] + adds r1, 4 + adds r0, 4 + b 1b + +2: + ldr r0, =3DSRAM_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, =3DCORE1_READY + bl xip_fifo_push + +3: + b 3b + +.thumb_func +xip_fifo_push: + ldr r1, =3D(SIO_BASE + SIO_FIFO_ST) + +4: + ldr r2, [r1] + movs r3, SIO_FIFO_ST_RDY + tst r2, r3 + beq 4b + ldr r1, =3D(SIO_BASE + SIO_FIFO_WR) + str r0, [r1] + bx lr + +.thumb_func +xip_puts: + ldr r2, =3DUART0_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, =3DSCB_VTOR + ldr r1, =3DSRAM_BASE + str r1, [r0] + bl sram_launch_core1 + bl sram_fifo_pop + ldr r1, =3DCORE1_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, =3D(SIO_BASE + SIO_FIFO_ST) + +10: + ldr r2, [r1] + movs r3, SIO_FIFO_ST_RDY + tst r2, r3 + beq 10b + ldr r1, =3D(SIO_BASE + SIO_FIFO_WR) + str r0, [r1] + bx lr + +.thumb_func +sram_fifo_pop: + ldr r1, =3D(SIO_BASE + SIO_FIFO_ST) + +11: + ldr r2, [r1] + movs r3, SIO_FIFO_ST_VLD + tst r2, r3 + beq 11b + ldr r1, =3D(SIO_BASE + SIO_FIFO_RD) + ldr r0, [r1] + bx lr + +.thumb_func +sram_fifo_drain: + push {r0, r1, r2, r3, lr} + ldr r1, =3D(SIO_BASE + SIO_FIFO_ST) + +12: + ldr r2, [r1] + movs r3, SIO_FIFO_ST_VLD + tst r2, r3 + beq 13f + ldr r0, =3D(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, =3DSSI_BASE + movs r0, 0 + str r0, [r4, SSI_SSIENR] + ldr r0, =3DSSI_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, =3D(SSI_BASE + SSI_DR0) + str r0, [r2] + ldr r3, =3D(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, =3DSIO_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, =3DADP_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, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +16: + b 16b + +.thumb_func +sram_puts: + ldr r2, =3DUART0_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/ar= m/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, =3DSRAM_BASE + ldr r1, =3Dram_code_start + ldr r2, =3Dram_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, =3DSRAM_BASE + 1 + bx r0 + +.thumb_func +default_handler: + b default_handler + +.align 2 +ram_code_start: +.thumb_func +ram_main: + ldr r0, =3DSRAM_VTOR + ldr r1, =3DSRAM_END + str r1, [r0] + ldr r1, =3DSRAM_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, =3DVTOR + 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, =3DXIP_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, =3DSSI_SER + movs r0, 1 + str r0, [r1] + bx lr + +.thumb_func +deselect_flash: + ldr r1, =3DSSI_SER + movs r0, 0 + str r0, [r1] + bx lr + +.thumb_func +ssi_byte: + ldr r2, =3DSSI_DR0 + str r0, [r2] + ldr r3, =3DSSI_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, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +2: + b 2b + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +3: + b 3b + +.thumb_func +puts: + ldr r2, =3DUART0_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/t= cg/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/a= rm/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, =3Dstart_message + bl puts + bl copy_core1_sram + bl launch_core1 + + ldr r0, =3Dlaunch_message + bl puts + bl claim_spinlock + ldr r0, =3DLOCK_REQUEST + bl fifo_push + sev + ldr r0, =3DLOCK_ACK + bl fifo_pop_expect + + ldr r0, =3Dlocked_message + bl puts + bl copy_and_run_core0_sram + ldr r0, =3Dfail_return_message + b test_failed + +.thumb_func +launch_core1: + push {r4, r5, lr} + ldr r4, =3Dlaunch_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, =3Dcore1_sram_start + ldr r1, =3DCORE1_SRAM_VTOR + ldr r2, =3Dcore1_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, =3D(SIO_BASE + SIO_SPINLOCK0) + ldr r1, [r0] + cmp r1, 0 + beq fail_spinlock + bx lr + +.thumb_func +copy_and_run_core0_sram: + ldr r0, =3Dcore0_sram_start + ldr r1, =3DCORE0_SRAM_CODE + ldr r2, =3Dcore0_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, =3DCORE0_SRAM_CODE + adds r0, 1 + bx r0 + +.thumb_func +fifo_push: + push {r1, r2, r3, lr} + ldr r1, =3DSIO_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, =3DSIO_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, =3DSIO_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, =3Dfail_launch_message + b test_failed + +fail_spinlock: + ldr r0, =3Dfail_spinlock_message + b test_failed + +fail_fifo: + ldr r0, =3Dfail_fifo_message + b test_failed + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +11: + b 11b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + push {r1, r2, lr} + ldr r1, =3DUART0_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, =3DCORE0_SRAM_VTOR + ldr r1, =3DSRAM_END + str r1, [r0] + movs r1, 0 + str r1, [r0, 4] + ldr r1, =3Dcore0_sram_hardfault + ldr r2, =3Dcore0_sram_start + subs r1, r2 + ldr r2, =3DCORE0_SRAM_CODE + adds r1, r1, r2 + str r1, [r0, 12] + ldr r0, =3DSCB_VTOR + ldr r1, =3DCORE0_SRAM_VTOR + str r1, [r0] + + ldr r0, =3DPAGE_BUFFER + movs r1, 0x5a + str r1, [r0] + + ldr r4, =3DSSI_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, =3DFLASH_ADDRESS + ldrb r0, [r0] + ldr r0, =3Dcore0_sram_no_fault_message + ldr r1, =3Dcore0_sram_start + subs r0, r1 + ldr r1, =3DCORE0_SRAM_CODE + adds r0, r1 + bl core0_sram_puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +14: + b 14b + +.thumb_func +core0_sram_hardfault: + ldr r0, =3Dcore0_sram_ok_message + ldr r1, =3Dcore0_sram_start + subs r0, r1 + ldr r1, =3DCORE0_SRAM_CODE + adds r0, r1 + bl core0_sram_puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +15: + b 15b + +.thumb_func +core0_sram_puts: + ldr r1, =3DUART0_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, =3DSCB_VTOR + ldr r1, =3DCORE1_SRAM_VTOR + str r1, [r0] + ldr r0, =3DNVIC_ISER + ldr r1, =3DSIO_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, =3DLOCK_REQUEST + cmp r0, r1 + bne core1_sram_bad_request + ldr r0, =3DLOCK_ACK + bl core1_sram_fifo_push + +19: + wfe + b 19b + +core1_sram_bad_request: + ldr r0, =3DBAD_ACK + bl core1_sram_fifo_push + +20: + b 20b + +.thumb_func +core1_sram_fifo_push: + ldr r1, =3DSIO_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, =3DSIO_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/s= ystem/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, =3DSRAM_BASE + ldr r1, =3Dram_code_start + ldr r2, =3Dram_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, =3DSRAM_BASE + 1 + bx r0 + +.thumb_func +default_handler: + b default_handler + +.align 2 +ram_code_start: +.thumb_func +ram_main: + ldr r0, =3Dstart_message + bl puts + + bl write_enable + bl expect_wel + ldr r0, =3DFLASH_TARGET_OFFSET + bl erase_sector + bl expect_busy_wel_clear + bl expect_target_erased + + bl write_enable + bl expect_wel + ldr r0, =3DFLASH_TARGET_OFFSET + ldr r1, =3Dflash_message + movs r2, 5 + bl program_data + bl expect_busy_wel_clear + bl expect_flash_message + ldr r0, =3Dprogram_ok_message + bl puts + + bl write_enable + ldr r0, =3DFLASH_TARGET_OFFSET + bl erase_sector + bl expect_busy_wel_clear + bl expect_target_erased + ldr r0, =3Derase_ok_message + bl puts + + bl write_enable + ldr r0, =3DFLASH_TARGET_OFFSET + ldr r1, =3Dflash_message + movs r2, 5 + bl program_data + bl expect_busy_wel_clear + bl expect_flash_message + + ldr r0, =3DFLASH_TARGET_OFFSET + 8 + ldr r1, =3Dno_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, =3DXIP_TARGET + 8 + ldrb r0, [r1] + cmp r0, 0xff + beq 9f + b fail_no_wel +9: + ldr r0, =3Dno_wel_ok_message + bl puts + + bl write_enable + ldr r0, =3DFLASH_TARGET_OFFSET + 16 + ldr r1, =3Dzero_value + movs r2, 1 + bl program_data + bl expect_busy_wel_clear + bl write_enable + ldr r0, =3DFLASH_TARGET_OFFSET + 16 + ldr r1, =3Derased_value + movs r2, 1 + bl program_data + bl expect_busy_wel_clear + ldr r1, =3DXIP_TARGET + 16 + ldrb r0, [r1] + cmp r0, 0 + beq 10f + b fail_old_and_new +10: + ldr r0, =3Dold_and_new_ok_message + bl puts + + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_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, =3DSSI_SER + movs r0, 1 + str r0, [r1] + bx lr + +.thumb_func +deselect_flash: + ldr r1, =3DSSI_SER + movs r0, 0 + str r0, [r1] + bx lr + +.thumb_func +ssi_byte: + ldr r2, =3DSSI_DR0 + str r0, [r2] + ldr r3, =3DSSI_RXFLR + +2: + ldr r0, [r3] + cmp r0, 0 + beq 2b + ldr r0, [r2] + bx lr + +.thumb_func +expect_target_erased: + ldr r1, =3DXIP_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, =3DXIP_TARGET + ldr r1, =3Dflash_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, =3Dfail_wel_message + b test_failed + +fail_status: + ldr r0, =3Dfail_status_message + b test_failed + +fail_program: + ldr r0, =3Dfail_program_message + b test_failed + +fail_erase: + ldr r0, =3Dfail_erase_message + b test_failed + +fail_no_wel: + ldr r0, =3Dfail_no_wel_message + b test_failed + +fail_old_and_new: + ldr r0, =3Dfail_old_and_new_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +5: + b 5b + +.thumb_func +puts: + ldr r2, =3DUART0_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/sys= tem/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, =3DSRAM_BASE + ldr r1, =3Dram_code_start + ldr r2, =3Dram_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, =3DSRAM_BASE + 1 + bx r0 + +.thumb_func +default_handler: + b default_handler + +.align 2 +ram_code_start: +.thumb_func +ram_main: + ldr r0, =3Dstart_message + bl puts + ldr r1, =3DSSI_SSIENR + movs r0, 0 + str r0, [r1] + ldr r1, =3DSSI_CTRLR0 + ldr r0, =3D0x00070000 + str r0, [r1] + ldr r1, =3DSSI_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, =3DXIP_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, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_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, =3DSSI_DR0 + str r0, [r2] + ldr r3, =3DSSI_RXFLR + +2: + ldr r0, [r3] + cmp r0, 0 + beq 2b + ldr r0, [r2] + bx lr + +.thumb_func +cs_low: + ldr r1, =3DIO_QSPI_SS_CTRL + movs r0, 2 + lsls r0, r0, 8 + str r0, [r1] + bx lr + +.thumb_func +cs_high: + ldr r1, =3DIO_QSPI_SS_CTRL + movs r0, 3 + lsls r0, r0, 8 + str r0, [r1] + bx lr + +test_failed: + ldr r0, =3Dfail_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +3: + b 3b + +.thumb_func +puts: + ldr r2, =3DUART0_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/s= ystem/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, =3DXIP_TARGET + ldr r1, =3Dexpected_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, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +test_failed: + ldr r0, =3Dfail_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +default_handler: + b default_handler + +.thumb_func +puts: + ldr r2, =3DUART0_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/t= cg/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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070775; cv=none; d=zohomail.com; s=zohoarc; b=k8h90G65AyAXfbFH1fBOvGjNZPUQMuXs6RshHtLkJ3qHoYbzZasWjIZwIxa6CsJzsiglFRhSYA84xuRwWLeSTir7cfP1Y/srEGh/CTt6UMEc5vxaQx3oduHYgFcpoXcC4uvDWlGcsg9AjVYn+5Rcl9C667scd3YFKyYaONaNGeE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070775; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=NU1Br5qRqu26HVA6jGQYVK1RBdnhudyZMVInVkl7YtE=; b=FELiVPQngwqmcomEqt5Gqt2yBJerEREM9390vFwATCoc7Y5nGrV7lycKE6qxes/42k4PG9gxxKJRkhH1oR2/LZU8Ce4U56JlMGOp6OfGh1eAvK0v4Xvru/Nae7U9IF6qvYpL6pQhsydA+z0SxXDbKsF5ONn0eK9t9NrZ7vRJCC0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070775620351.13101779774547; Sat, 29 Aug 2026 23:19:35 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yra-0002eZ-9t; Sun, 30 Aug 2026 02:17:54 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrW-0002cQ-Ar; Sun, 30 Aug 2026 02:17:51 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrR-0005IU-63; Sun, 30 Aug 2026 02:17:50 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXhjp2g3PzBqYg; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjp18ZYz7xSW; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070638; bh=NU1Br5qRqu26HVA6jGQYVK1RBdnhudyZMVInVkl7YtE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=f5nri38HYAadpyLvymtsOpMomteOUJRb4deXpwbjaaf8jkQnOgqOfhIllbEihgHcR QF5sCYXAv1mI3cq9UEjTmJCb4ZkeaqGBsyq7i7VETprN4yPy9F1n5O2Ik03Q22Biiw yRomuH45IWphxpNaJRL0GzyxDAOnKJVCoWQWyloLIl0GDj6+Li7hY/QIfi+10TylnC 5UeMeE4uzYfllbwUxljB3ql+yMdbyW7uCPf/7FTr5TOgtSZqwnuPS8BBrTSV2NfEYs /KLp8Yw0nwGmQ14w1RvxTxcMhhA69oVplbSn0MqyMNiQkwibgYUQSiw09/sOeTiAyr Z0Uw2mQnZm2lg== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 22/30] hw/dma: add RP2040 DMA controller Date: Sun, 30 Aug 2026 08:15:53 +0200 Message-ID: <20260830061601.52136-8-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070779534154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Model the twelve RP2040 DMA channels, register aliases, interrupts, chainin= g, ring wrapping, aborts, sniff checksums and AHB error status. Pace transf= ers from DREQ inputs and the four virtual DMA timers.\n\nWire the controlle= r 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 b= ehavior rather than bus-cycle accurate timing. Signed-off-by: gilles grimaud --- 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); =20 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); =20 + 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_SSI= RX)); + qdev_connect_gpio_out_named(DEVICE(&s->xip), "dreq-stream", 0, + qdev_get_gpio_in_named(DEVICE(&s->dma), + "dreq", + RP2040_DREQ_XIP_STR= EAM)); + for (i =3D 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 =20 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: fi= les('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 =3D 0; i < RP2040_DMA_NUM_IRQS; i++) { + qemu_set_irq(s->irq[i], rp2040_dma_ints(s, i) !=3D 0); + } +} + +static unsigned rp2040_dma_transfer_size(RP2040DmaChannel *ch) +{ + switch ((ch->ctrl & DMA_CTRL_DATA_SIZE_MASK) >> DMA_CTRL_DATA_SIZE_SHI= FT) { + 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 d= req, + 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 =3D=3D RP2040_DREQ_XIP_SSITX; +} + +static bool rp2040_dma_treq_is_connected_level(uint32_t treq) +{ + return treq =3D=3D RP2040_DREQ_UART0_TX || treq =3D=3D RP2040_DREQ_UAR= T0_RX || + treq =3D=3D RP2040_DREQ_UART1_TX || treq =3D=3D RP2040_DREQ_UAR= T1_RX || + treq =3D=3D RP2040_DREQ_XIP_STREAM; +} + +static bool rp2040_dma_treq_is_timer(uint32_t treq) +{ + return treq >=3D RP2040_DREQ_DMA_TIMER0 && + treq <=3D RP2040_DREQ_DMA_TIMER3; +} + +static uint64_t rp2040_dma_timer_period_ns(uint32_t value) +{ + uint32_t x =3D value >> 16; + uint32_t y =3D value & 0xffff; + uint64_t period; + + if (x =3D=3D 0 || y =3D=3D 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 >=3D y) { + return DMA_PACING_SYSCLK_NS; + } + + period =3D 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 =3D &s->pacing_timer[index]; + + pt->period_ns =3D rp2040_dma_timer_period_ns(s->timer[index]); + if (pt->period_ns =3D=3D 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 =3D opaque; + RP2040DmaState *s =3D pt->dma; + uint32_t dreq =3D 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 !=3D 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 =3D (ch->ctrl & DMA_CTRL_RING_SIZE_MASK) >> + DMA_CTRL_RING_SIZE_SHIFT; + ring_write =3D ch->ctrl & DMA_CTRL_RING_SEL; + if (ring_size =3D=3D 0 || ring_write !=3D write) { + return addr + width; + } + + mask =3D (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 =3D buf[0]; + + if (width >=3D 2) { + value |=3D (uint32_t)buf[1] << 8; + } + if (width >=3D 4) { + value |=3D (uint32_t)buf[2] << 16; + value |=3D (uint32_t)buf[3] << 24; + } + return value; +} + +static void rp2040_dma_sniff_bswap(uint8_t *buf, unsigned width) +{ + if (width =3D=3D 2) { + uint8_t t =3D buf[0]; + buf[0] =3D buf[1]; + buf[1] =3D t; + } else if (width =3D=3D 4) { + uint8_t t =3D buf[0]; + buf[0] =3D buf[3]; + buf[3] =3D t; + t =3D buf[1]; + buf[1] =3D buf[2]; + buf[2] =3D 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 =3D 0; i < width; i++) { + uint8_t byte =3D reverse ? revbit8(buf[i]) : buf[i]; + + crc ^=3D (uint32_t)byte << 24; + for (bit =3D 0; bit < 8; bit++) { + crc =3D (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 =3D crc; + int i; + int bit; + + for (i =3D 0; i < width; i++) { + uint8_t byte =3D reverse ? revbit8(buf[i]) : buf[i]; + + crc16 ^=3D (uint16_t)byte << 8; + for (bit =3D 0; bit < 8; bit++) { + crc16 =3D (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 =3D &s->chan[index]; + uint8_t sniff_buf[4] =3D { 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 =3D (s->sniff_ctrl & DMA_SNIFF_CTRL_DMACH_MASK) >> + DMA_SNIFF_CTRL_DMACH_SHIFT; + if (channel !=3D index) { + return; + } + + memcpy(sniff_buf, buf, width); + if (s->sniff_ctrl & DMA_SNIFF_CTRL_BSWAP) { + rp2040_dma_sniff_bswap(sniff_buf, width); + } + + calc =3D (s->sniff_ctrl & DMA_SNIFF_CTRL_CALC_MASK) >> + DMA_SNIFF_CTRL_CALC_SHIFT; + switch (calc) { + case DMA_SNIFF_CALC_CRC32: + s->sniff_data =3D rp2040_dma_sniff_crc32(s->sniff_data, sniff_buf, + width, false); + break; + case DMA_SNIFF_CALC_CRC32R: + s->sniff_data =3D rp2040_dma_sniff_crc32(s->sniff_data, sniff_buf, + width, true); + break; + case DMA_SNIFF_CALC_CRC16: + s->sniff_data =3D rp2040_dma_sniff_crc16(s->sniff_data, sniff_buf, + width, false); + break; + case DMA_SNIFF_CALC_CRC16R: + s->sniff_data =3D rp2040_dma_sniff_crc16(s->sniff_data, sniff_buf, + width, true); + break; + case DMA_SNIFF_CALC_EVEN: + value =3D rp2040_dma_sniff_word(sniff_buf, width); + s->sniff_data =3D (s->sniff_data ^ ctpop32(value)) & 1; + break; + case DMA_SNIFF_CALC_SUM: + s->sniff_data +=3D rp2040_dma_sniff_word(sniff_buf, width); + break; + default: + break; + } +} + +static uint32_t rp2040_dma_sniff_read_data(RP2040DmaState *s) +{ + uint32_t value =3D s->sniff_data; + + if (s->sniff_ctrl & DMA_SNIFF_CTRL_OUT_REV) { + value =3D revbit32(value); + } + if (s->sniff_ctrl & DMA_SNIFF_CTRL_OUT_INV) { + value =3D ~value; + } + return value; +} + +static void rp2040_dma_finish_channel(RP2040DmaState *s, unsigned index) +{ + RP2040DmaChannel *ch =3D &s->chan[index]; + uint32_t chain_to; + + ch->ctrl &=3D ~DMA_CTRL_BUSY; + if (!(ch->ctrl & DMA_CTRL_IRQ_QUIET)) { + s->intr |=3D BIT(index); + rp2040_dma_update_irq(s); + } + + chain_to =3D (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 !=3D index) { + rp2040_dma_request_start(s, chain_to); + } +} + +static void rp2040_dma_run_beats(RP2040DmaState *s, unsigned index, + uint32_t beats) +{ + RP2040DmaChannel *ch =3D &s->chan[index]; + uint32_t count; + unsigned width; + + if (!(ch->ctrl & DMA_CTRL_EN) || ch->trans_count =3D=3D 0) { + return; + } + + width =3D rp2040_dma_transfer_size(ch); + count =3D MIN(ch->trans_count, beats); + + while (count--) { + uint8_t buf[4] =3D { 0 }; + MemTxResult result; + + result =3D address_space_rw(&s->dma_as, ch->read_addr, + MEMTXATTRS_UNSPECIFIED, buf, width, fals= e); + if (result !=3D MEMTX_OK) { + ch->ctrl |=3D DMA_CTRL_READ_ERROR | DMA_CTRL_AHB_ERROR; + break; + } + + if (ch->ctrl & DMA_CTRL_BSWAP) { + if (width =3D=3D 2) { + uint8_t t =3D buf[0]; + buf[0] =3D buf[1]; + buf[1] =3D t; + } else if (width =3D=3D 4) { + uint8_t t =3D buf[0]; + buf[0] =3D buf[3]; + buf[3] =3D t; + t =3D buf[1]; + buf[1] =3D buf[2]; + buf[2] =3D t; + } + } + rp2040_dma_sniff_update(s, index, buf, width); + + if (ch->write_addr >=3D RP2040_DMA_BASE && + ch->write_addr < RP2040_DMA_BASE + RP2040_DMA_SIZE) { + if (width =3D=3D 4) { + rp2040_dma_write_reg(s, ch->write_addr - RP2040_DMA_BASE, + ldl_le_p(buf), width); + result =3D MEMTX_OK; + } else { + result =3D MEMTX_ERROR; + } + } else { + result =3D address_space_rw(&s->dma_as, ch->write_addr, + MEMTXATTRS_UNSPECIFIED, buf, width, + true); + } + if (result !=3D MEMTX_OK) { + ch->ctrl |=3D DMA_CTRL_WRITE_ERROR | DMA_CTRL_AHB_ERROR; + break; + } + + ch->trans_count--; + ch->read_addr =3D rp2040_dma_next_addr(ch, ch->read_addr, width, f= alse); + ch->write_addr =3D rp2040_dma_next_addr(ch, ch->write_addr, width,= true); + } + + if ((ch->ctrl & DMA_CTRL_ERROR_MASK) || ch->trans_count =3D=3D 0) { + rp2040_dma_finish_channel(s, index); + } +} + +static void rp2040_dma_start_channel(RP2040DmaState *s, unsigned index) +{ + RP2040DmaChannel *ch =3D &s->chan[index]; + uint32_t treq; + + if (!(ch->ctrl & DMA_CTRL_EN)) { + return; + } + + if (ch->trans_count =3D=3D 0 && ch->reload_count !=3D 0) { + ch->trans_count =3D ch->reload_count; + } + + if (ch->trans_count =3D=3D 0) { + if (ch->ctrl & DMA_CTRL_IRQ_QUIET) { + s->intr |=3D BIT(index); + rp2040_dma_update_irq(s); + } + return; + } + + ch->ctrl |=3D DMA_CTRL_BUSY; + ch->ctrl &=3D ~DMA_CTRL_ERROR_MASK; + ch->paced_nyi_logged =3D false; + + treq =3D rp2040_dma_treq(ch); + if (treq =3D=3D RP2040_DREQ_FORCE || rp2040_dma_treq_is_ready_sink(tre= q)) { + rp2040_dma_run_beats(s, index, UINT32_MAX); + } else if ((rp2040_dma_treq_is_connected_level(treq) || + treq =3D=3D RP2040_DREQ_XIP_SSIRX) && s->dreq_level[treq])= { + rp2040_dma_dreq_pulse(s, treq); + } else if (treq !=3D 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 =3D true; + } +} + +static void rp2040_dma_drain_starts(RP2040DmaState *s) +{ + if (s->engine_active) { + return; + } + + s->engine_active =3D true; + while (s->pending_start) { + unsigned index =3D ctz32(s->pending_start); + + s->pending_start &=3D ~BIT(index); + rp2040_dma_start_channel(s, index); + } + s->engine_active =3D false; +} + +static void rp2040_dma_request_start(RP2040DmaState *s, unsigned index) +{ + if (index >=3D RP2040_DMA_NUM_CHANNELS) { + return; + } + + s->pending_start |=3D BIT(index); + rp2040_dma_drain_starts(s); +} + +static void rp2040_dma_dreq_pulse(RP2040DmaState *s, uint32_t dreq) +{ + if (dreq >=3D RP2040_DMA_NUM_DREQS) { + return; + } + + if (s->pending_dreq[dreq] !=3D 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 =3D opaque; + + if (n < 0 || n >=3D RP2040_DMA_NUM_DREQS) { + return; + } + + s->dreq_level[n] =3D 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 =3D opaque; + int i; + int dreq; + + if (s->dreq_servicing) { + return; + } + + s->dreq_servicing =3D true; + for (dreq =3D 0; dreq < RP2040_DMA_NUM_DREQS; dreq++) { + while (s->pending_dreq[dreq] > 0) { + s->pending_dreq[dreq]--; + for (i =3D 0; i < RP2040_DMA_NUM_CHANNELS; i++) { + RP2040DmaChannel *ch =3D &s->chan[i]; + + if ((ch->ctrl & DMA_CTRL_BUSY) && rp2040_dma_treq(ch) =3D= =3D dreq) { + s->engine_active =3D true; + rp2040_dma_run_beats(s, i, 1); + s->engine_active =3D 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 =3D false; +} + +static bool rp2040_dma_dreq_has_busy_channel(RP2040DmaState *s, uint32_t d= req, + unsigned except) +{ + int i; + + for (i =3D 0; i < RP2040_DMA_NUM_CHANNELS; i++) { + RP2040DmaChannel *ch =3D &s->chan[i]; + + if (i !=3D except && (ch->ctrl & DMA_CTRL_BUSY) && + rp2040_dma_treq(ch) =3D=3D dreq) { + return true; + } + } + return false; +} + +static void rp2040_dma_abort_channel(RP2040DmaState *s, unsigned index) +{ + RP2040DmaChannel *ch =3D &s->chan[index]; + uint32_t treq =3D 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 &=3D ~DMA_CTRL_BUSY; + ch->trans_count =3D 0; + if (treq < RP2040_DMA_NUM_DREQS && + !rp2040_dma_dreq_has_busy_channel(s, treq, index)) { + s->pending_dreq[treq] =3D 0; + } +} + +static uint32_t rp2040_dma_read_channel(RP2040DmaState *s, unsigned index, + hwaddr offset) +{ + RP2040DmaChannel *ch =3D &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 =3D &s->chan[index]; + uint32_t errors =3D ch->ctrl & DMA_CTRL_ERROR_MASK; + + errors &=3D ~(value & (DMA_CTRL_READ_ERROR | DMA_CTRL_WRITE_ERROR)); + if (errors & (DMA_CTRL_READ_ERROR | DMA_CTRL_WRITE_ERROR)) { + errors |=3D DMA_CTRL_AHB_ERROR; + } else { + errors &=3D ~DMA_CTRL_AHB_ERROR; + } + ch->ctrl =3D (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 =3D &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 =3D value; + break; + case DMA_CH_AL3_READ_ADDR: + ch->read_addr =3D 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 =3D value; + break; + case DMA_CH_AL2_WRITE_ADDR: + ch->write_addr =3D 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 =3D value; + ch->reload_count =3D value; + break; + case DMA_CH_AL1_TRANS_COUNT: + ch->trans_count =3D value; + ch->reload_count =3D 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 =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint32_t value; + + if (offset < RP2040_DMA_NUM_CHANNELS * DMA_CH_SIZE) { + value =3D rp2040_dma_read_channel(s, offset / DMA_CH_SIZE, + offset % DMA_CH_SIZE); + } else { + switch (offset) { + case DMA_INTR: + value =3D s->intr; + break; + case DMA_INTE0: + value =3D s->inte[0]; + break; + case DMA_INTF0: + value =3D s->intf[0]; + break; + case DMA_INTS0: + value =3D rp2040_dma_ints(s, 0); + break; + case DMA_INTE1: + value =3D s->inte[1]; + break; + case DMA_INTF1: + value =3D s->intf[1]; + break; + case DMA_INTS1: + value =3D rp2040_dma_ints(s, 1); + break; + case DMA_TIMER0 ... DMA_TIMER0 + 3 * sizeof(uint32_t): + value =3D s->timer[(offset - DMA_TIMER0) / sizeof(uint32_t)]; + break; + case DMA_SNIFF_CTRL: + value =3D s->sniff_ctrl; + break; + case DMA_SNIFF_DATA: + value =3D rp2040_dma_sniff_read_data(s); + break; + case DMA_FIFO_LEVELS: + value =3D 0; + break; + case DMA_CHAN_ABORT: + value =3D 0; + break; + default: + value =3D 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 =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D 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 &=3D ~(value & DMA_CHANNEL_MASK); + rp2040_dma_update_irq(s); + break; + case DMA_INTE0: + s->inte[0] =3D rp2040_dma_apply_alias(s->inte[0], value, alias= ) & + DMA_CHANNEL_MASK; + rp2040_dma_update_irq(s); + break; + case DMA_INTF0: + s->intf[0] =3D rp2040_dma_apply_alias(s->intf[0], value, alias= ) & + DMA_CHANNEL_MASK; + rp2040_dma_update_irq(s); + break; + case DMA_INTS0: + s->intr &=3D ~(value & DMA_CHANNEL_MASK); + rp2040_dma_update_irq(s); + break; + case DMA_INTE1: + s->inte[1] =3D rp2040_dma_apply_alias(s->inte[1], value, alias= ) & + DMA_CHANNEL_MASK; + rp2040_dma_update_irq(s); + break; + case DMA_INTF1: + s->intf[1] =3D rp2040_dma_apply_alias(s->intf[1], value, alias= ) & + DMA_CHANNEL_MASK; + rp2040_dma_update_irq(s); + break; + case DMA_INTS1: + s->intr &=3D ~(value & DMA_CHANNEL_MASK); + rp2040_dma_update_irq(s); + break; + case DMA_TIMER0 ... DMA_TIMER0 + 3 * sizeof(uint32_t): + i =3D (offset - DMA_TIMER0) / sizeof(uint32_t); + s->timer[i] =3D rp2040_dma_apply_alias(s->timer[i], value, ali= as); + rp2040_dma_timer_update(s, i); + break; + case DMA_MULTI_CHAN_TRIGGER: + value &=3D DMA_CHANNEL_MASK; + for (i =3D 0; i < RP2040_DMA_NUM_CHANNELS; i++) { + if (value & BIT(i)) { + rp2040_dma_request_start(s, i); + } + } + break; + case DMA_SNIFF_CTRL: + old =3D s->sniff_ctrl; + s->sniff_ctrl =3D rp2040_dma_apply_alias(old, value, alias) & + DMA_SNIFF_CTRL_MASK; + break; + case DMA_SNIFF_DATA: + s->sniff_data =3D rp2040_dma_apply_alias(s->sniff_data, value, + alias); + break; + case DMA_CHAN_ABORT: + value &=3D DMA_CHANNEL_MASK; + for (i =3D 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 =3D { + .read =3D rp2040_dma_read, + .write =3D rp2040_dma_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_dma_reset(DeviceState *dev) +{ + RP2040DmaState *s =3D RP2040_DMA(dev); + int i; + + for (i =3D 0; i < RP2040_DMA_NUM_CHANNELS; i++) { + s->chan[i].read_addr =3D 0; + s->chan[i].write_addr =3D 0; + s->chan[i].trans_count =3D 0; + s->chan[i].reload_count =3D 0; + s->chan[i].ctrl =3D i << DMA_CTRL_CHAIN_TO_SHIFT; + s->chan[i].paced_nyi_logged =3D false; + } + s->intr =3D 0; + s->inte[0] =3D 0; + s->inte[1] =3D 0; + s->intf[0] =3D 0; + s->intf[1] =3D 0; + memset(s->timer, 0, sizeof(s->timer)); + for (i =3D 0; i < RP2040_DMA_NUM_TIMERS; i++) { + s->pacing_timer[i].period_ns =3D 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 =3D false; + s->pending_start =3D 0; + s->sniff_ctrl =3D 0; + s->sniff_data =3D 0; + rp2040_dma_update_irq(s); +} + +static void rp2040_dma_init(Object *obj) +{ + RP2040DmaState *s =3D RP2040_DMA(obj); + SysBusDevice *sbd =3D 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 =3D 0; i < RP2040_DMA_NUM_IRQS; i++) { + sysbus_init_irq(sbd, &s->irq[i]); + } + s->dreq_bh =3D 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 =3D 0; i < RP2040_DMA_NUM_TIMERS; i++) { + s->pacing_timer[i].dma =3D s; + s->pacing_timer[i].index =3D i; + s->pacing_timer[i].timer =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, + rp2040_dma_timer_cb, + &s->pacing_timer[i]); + } +} + +static void rp2040_dma_finalize(Object *obj) +{ + RP2040DmaState *s =3D RP2040_DMA(obj); + int i; + + for (i =3D 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 =3D 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 =3D { + .name =3D TYPE_RP2040_DMA, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (const VMStateField[]) { + VMSTATE_END_OF_LIST() + } +}; + +static const Property rp2040_dma_properties[] =3D { + DEFINE_PROP_LINK("memory", RP2040DmaState, dma_mr, TYPE_MEMORY_REGION, + MemoryRegion *), +}; + +static void rp2040_dma_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->realize =3D rp2040_dma_realize; + device_class_set_legacy_reset(dc, rp2040_dma_reset); + dc->vmsd =3D &rp2040_dma_vmstate; + device_class_set_props(dc, rp2040_dma_properties); +} + +static const TypeInfo rp2040_dma_info =3D { + .name =3D TYPE_RP2040_DMA, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040DmaState), + .instance_init =3D rp2040_dma_init, + .instance_finalize =3D rp2040_dma_finalize, + .class_init =3D 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 =3D \ (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : [])= + \ (config_all_devices.has_key('CONFIG_RASPI_PICO') ? ['rp2040-sysinfo-sysc= fg-test', 'rp2040-clocks-test= ', + 'rp2040-dma-test', 'rp2040-iobank0-tes= t', '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 =3D rp2040_start(); + uint32_t ctrl =3D DMA_CTRL_EN | DMA_CTRL_INCR_WRITE | DMA_CTRL_DATA_SI= ZE_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, =3D=3D, DMA_CTRL_BUSY); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 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), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) & + DMA_CTRL_BUSY, =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, SRAM_BASE), =3D=3D, 0); + + qtest_quit(qts); +} + +static void test_dma_write_ring_wrap(void) +{ + QTestState *qts =3D rp2040_start(); + const uint8_t source[] =3D { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15 }; + const uint8_t initial[] =3D { 0xaa, 0xaa, 0xaa, 0xaa }; + uint8_t dest[sizeof(initial)]; + uint32_t read_addr =3D SRAM_BASE + 0x80; + uint32_t write_addr =3D SRAM_BASE + 0x100; + uint32_t ctrl =3D DMA_CTRL_EN | DMA_CTRL_INCR_READ | DMA_CTRL_INCR_WRI= TE | + 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], =3D=3D, 0x14); + g_assert_cmphex(dest[1], =3D=3D, 0x15); + g_assert_cmphex(dest[2], =3D=3D, 0x12); + g_assert_cmphex(dest[3], =3D=3D, 0x13); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_READ_ADDR), =3D=3D, + read_addr + sizeof(source)); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_WRITE_ADDR), =3D=3D, + write_addr + 2); + + qtest_quit(qts); +} + +static void test_dma_channel_abort(void) +{ + QTestState *qts =3D rp2040_start(); + uint32_t ctrl =3D DMA_CTRL_EN | DMA_CTRL_INCR_WRITE | DMA_CTRL_DATA_SI= ZE_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, =3D=3D, DMA_CTRL_BUSY); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 4); + + qtest_writel(qts, DMA_BASE + DMA_CHAN_ABORT, BIT(0)); + + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CHAN_ABORT), =3D=3D, 0= ); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) & + DMA_CTRL_BUSY, =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 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), =3D=3D, 0xffffffff); + + qtest_quit(qts); +} + +static void test_dma_sniff_sum(void) +{ + QTestState *qts =3D rp2040_start(); + const uint8_t source[] =3D { 1, 2, 3, 4 }; + uint32_t read_addr =3D SRAM_BASE + 0x180; + uint32_t write_addr =3D SRAM_BASE + 0x190; + uint32_t ctrl =3D DMA_CTRL_EN | DMA_CTRL_INCR_READ | DMA_CTRL_INCR_WRI= TE | + 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), =3D=3D, 0= x10a); + + 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), =3D=3D, + 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), =3D=3D, + 0xfffffef5); + + qtest_quit(qts); +} + +static void test_dma_error_status(void) +{ + QTestState *qts =3D rp2040_start(); + uint32_t ctrl =3D 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), =3D=3D, + DMA_CTRL_AHB_ERROR | DMA_CTRL_READ_ERROR); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 1); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR), =3D=3D, BIT(0)); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTS0), =3D=3D, BIT(0)= ); + + qtest_writel(qts, DMA_BASE + DMA_INTS0, BIT(0)); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR), =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTS0), =3D=3D, 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), =3D=3D, 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), =3D=3D, + DMA_CTRL_AHB_ERROR | DMA_CTRL_WRITE_ERROR); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 1); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR), =3D=3D, BIT(0)); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTS0), =3D=3D, BIT(0)= ); + + qtest_quit(qts); +} + +static void test_dma_timer_dreq(void) +{ + QTestState *qts =3D rp2040_start(); + const uint8_t source[] =3D { 0xa1, 0xb2, 0xc3 }; + uint8_t dest[sizeof(source)]; + uint32_t read_addr =3D SRAM_BASE + 0x280; + uint32_t write_addr =3D SRAM_BASE + 0x290; + uint32_t ctrl =3D DMA_CTRL_EN | DMA_CTRL_INCR_READ | DMA_CTRL_INCR_WRI= TE | + 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, =3D=3D, DMA_CTRL_BUSY); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, + sizeof(source)); + + /* + * X/Y =3D 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), =3D= =3D, + sizeof(source)); + + qtest_clock_step(qts, 1); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 2); + g_assert_cmphex(qtest_readb(qts, write_addr), =3D=3D, source[0]); + + qtest_clock_step(qts, 8000); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 1); + g_assert_cmphex(qtest_readb(qts, write_addr + 1), =3D=3D, source[1]); + + qtest_clock_step(qts, 8000); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_TRANS_COUNT), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) & + DMA_CTRL_BUSY, =3D=3D, 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 =3D rp2040_start(); + const uint8_t source[] =3D { 0x44, 0x4d, 0x41 }; + uint8_t dest[sizeof(source)]; + uint32_t blocks[] =3D { + sizeof(source), SRAM_BASE + 0x380, + 0, 0, + }; + uint32_t block_addr =3D SRAM_BASE + 0x300; + uint32_t read_addr =3D SRAM_BASE + 0x380; + uint32_t write_addr =3D SRAM_BASE + 0x390; + uint32_t chan1 =3D 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 =3D 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 =3D 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), =3D=3D, = 0); + g_assert_cmphex(qtest_readl(qts, chan1 + DMA_CH_CTRL_TRIG) & + DMA_CTRL_BUSY, =3D=3D, 0); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_INTR) & BIT(1), =3D=3D, + 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070764; cv=none; d=zohomail.com; s=zohoarc; b=ZEWhvxRgbJeuTXq0mQQoY+M4Hbn9D9mjDHcKBX8gFEIflhW8tIzWi1pv90yLfcV1T2FgcBQ+xxD41r30Kwps5j4fVDsxXuP/+hZyLVg6zT+Vo8wJbF5111p4by+61gdI/72N2S/tp+3Oq71mpOVxvgJxbJjk+yyak37hBf4395s= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070764; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=SmE0oazU4gxkDkJwfNizh86WOIHEMkky/buRlU3j15A=; b=iD+CX3D1HEfTsWyzTDQsDHyWHvWttAbKP1bNtP/fNVq9eoj/BSIok8wabstpZiyCvukPl1V7vbDlABSsP213iuqM6ATOAynd19iAao7U+MBAajSnnelOFAmVGy5A2/vyXjWIwl5mSAZWEqPckfiKGMGm0PvIJbso2EA0RYTgqyo= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070764070599.392500959838; Sat, 29 Aug 2026 23:19:24 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yrc-0002gO-TL; Sun, 30 Aug 2026 02:17:56 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yrb-0002er-2H; Sun, 30 Aug 2026 02:17:55 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrY-0005J0-D2; Sun, 30 Aug 2026 02:17:54 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXhjp3s7qzBqYk; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjp2ThDz7swM; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070638; bh=SmE0oazU4gxkDkJwfNizh86WOIHEMkky/buRlU3j15A=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=Hf1yfjEnz0g7DHv04/kHTNcyqbUu1xXW2sVT+BdNzTwvlHLykz8I6MDa6l+jkw1Yk XsO88iV0d4MqXLeYlkhOYoEtid78u8NmavpbsrJY6dpA4cO8m7ktVu1X5okw7WsL5r sy+wdPKoMSdUH+iKaoSBvUJWE5WuyK71+tmH+OBqAGnxlEqMCxtPZ6Yq6CamArAX8+ 0UKgAnG1ZGtRfCh8cpk8+hYucf10F3izvYpL1+RbxcM8Zh4MScpIH9Tsn6GveLzjtq v8X8BCxRgMJ5iu81kksMXxQps6hmfqrqwjux+Q4JePBFMDkoZqfM4hqAqvGLyCs9Hc 1//70F650xq4w== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 23/30] tests/tcg/arm: test RP2040 DMA transfers Date: Sun, 30 Aug 2026 08:15:54 +0200 Message-ID: <20260830061601.52136-9-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070769317154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Add source-built bare-metal tests for memory copy and fill transfers, compl= etion status and interrupt acknowledgement.\n\nExercise UART0 and UART1 tra= nsmit and receive pacing through their DMA request lines. Each UART test wa= its for DMA IRQ completion and validates looped-back input before reporting= success. Signed-off-by: gilles grimaud --- 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=3Drp2040-flash-commands rp2040-flash-= ioqspi \ rp2040-flash-busy-hardfault rp2040-flash-busy-lockout \ rp2040-core0-xip-fault rp2040-core1-xip-fault =20 +RP2040_DMA_TESTS=3Drp2040-dma-memory rp2040-dma-uart0 rp2040-dma-uart1 + RP2040_GUESTS=3D$(RP2040_TESTS) $(RP2040_FLASH_TESTS) \ - rp2040-flash-persist-reader + rp2040-dma-memory rp2040-flash-persist-reader =20 $(RP2040_GUESTS): %: %.S rp2040-boot2-w25q080.S rp2040.ld $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ @@ -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=3D-M raspi-pico,strict-uart-pins=3Doff \ -serial chardev:output \ -semihosting-config enable=3Don,target=3Dnative -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=3D$<.out$(COMMA)id=3Doutput \ @@ -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 =20 -ARM_TESTS+=3D$(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=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ + -DUART_INDEX=3D$* -Wl,--build-id=3Dnone -x assembler-with-cpp \ + $(ARM_SRC)/rp2040-boot2-w25q080.S $< \ + -o $@ -nostdlib -static \ + -T $(ARM_SRC)/rp2040.ld + +run-rp2040-dma-uart0: QEMU_OPTS=3D-M raspi-pico,strict-uart-pins=3Doff \ + -serial chardev:output \ + -semihosting-config enable=3Don,target=3Dnative -kernel +run-rp2040-dma-uart1: QEMU_OPTS=3D-M raspi-pico,strict-uart-pins=3Doff \ + -serial null -serial chardev:output \ + -semihosting-config enable=3Don,target=3Dnative -kernel + +run-rp2040-dma-uart0 run-rp2040-dma-uart1: run-%: % + $(call run-test, $<, \ + $(QEMU) -monitor none -display none \ + -chardev file$(COMMA)path=3D$<.out$(COMMA)id=3Doutput \ + $(QEMU_OPTS) $<) + $(call diff-out,$<,$(ARM_SRC)/$<.ref) + +ARM_TESTS+=3D$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS) =20 # These objects provide the basic boot code and helper functions for all t= ests CRT_OBJS=3Dboot.o diff --git a/tests/tcg/arm/system/rp2040-dma-memory.S b/tests/tcg/arm/syste= m/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, =3D\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, =3Dstart_message + bl puts + + ldr r5, =3Dcopy_source + ldr r0, =3D0x11223344 + str r0, [r5, #0] + ldr r0, =3D0xaabbccdd + str r0, [r5, #4] + ldr r0, =3D0x01020304 + str r0, [r5, #8] + ldr r0, =3D0xfeedc0de + str r0, [r5, #12] + ldr r5, =3Dfill_source + ldr r0, =3D0x55aa55aa + str r0, [r5] + + ldr r4, =3DDMA_BASE + + ldr r0, =3Dcopy_source + str r0, [r4, DMA_READ_ADDR] + ldr r0, =3Dcopy_destination + str r0, [r4, DMA_WRITE_ADDR] + movs r0, 4 + str r0, [r4, DMA_TRANS_COUNT] + ldr r0, =3DDMA_CTRL_WORD_COPY + str r0, [r4, DMA_CTRL_TRIG] + + ldr r5, =3Dcopy_source + ldr r6, =3Dcopy_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, =3DDMA_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, =3Dcopy_ok_message + bl puts + + ldr r0, =3Dfill_source + str r0, [r4, DMA_READ_ADDR] + ldr r0, =3Dfill_destination + str r0, [r4, DMA_WRITE_ADDR] + movs r0, 4 + str r0, [r4, DMA_TRANS_COUNT] + ldr r0, =3DDMA_CTRL_WORD_FILL + str r0, [r4, DMA_CTRL_TRIG] + + ldr r5, =3Dfill_destination + ldr r1, =3D0x55aa55aa + 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, =3Dfill_ok_message + bl puts + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_copy: + ldr r0, =3Dfail_copy_message + b test_failed + +fail_fill: + ldr r0, =3Dfail_fill_message + b test_failed + +fail_irq: + ldr r0, =3Dfail_irq_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/sys= tem/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 =3D=3D 0 +#define UART_BASE 0x40034000 +#define DREQ_UART_TX 20 +#define DREQ_UART_RX 21 +#define UART_NAME "UART0" +#elif UART_INDEX =3D=3D 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, =3DUART_BASE + movs r1, 0x10 + str r1, [r0, UART_LCR_H] + ldr r1, =3DUART_CR_ENABLED + str r1, [r0, UART_CR] + movs r1, 3 + str r1, [r0, UART_DMACR] + + ldr r0, =3DNVIC_ICER + movs r2, 0 + mvns r2, r2 + str r2, [r0] + ldr r0, =3DNVIC_ICPR + str r2, [r0] + ldr r0, =3DNVIC_ISER + ldr r1, =3D(1 << DMA_IRQ_0) + str r1, [r0] + cpsie i + + ldr r0, =3Dstart_message + movs r1, START_MESSAGE_LEN + bl dma_tx + + ldr r0, =3DUART_BASE + ldr r1, =3DUART_CR_LOOPBACK + str r1, [r0, UART_CR] + ldr r0, =3Dhello_message + movs r1, 5 + bl dma_tx + ldr r0, =3DUART_BASE + ldr r1, =3DUART_CR_ENABLED + str r1, [r0, UART_CR] + ldr r0, =3Dnewline_message + bl puts + + ldr r0, =3Drx_buffer + movs r1, 5 + bl dma_rx + + ldr r0, =3Drx_buffer + ldr r1, =3Dhello_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, =3Dok_message + movs r1, OK_MESSAGE_LEN + bl dma_tx + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_data: + ldr r0, =3Dfail_data_message + b test_failed + +fail_irq: + ldr r0, =3Dfail_irq_message + b test_failed + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +dma_tx: + push {r4, lr} + mov r4, r1 + bl clear_dma_irq + ldr r2, =3DDMA_BASE + str r0, [r2, DMA_READ_ADDR] + ldr r3, =3DUART_BASE + str r3, [r2, DMA_WRITE_ADDR] + str r4, [r2, DMA_TRANS_COUNT] + ldr r3, =3DDMA_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, =3DDMA_BASE + ldr r3, =3DUART_BASE + str r3, [r2, DMA_READ_ADDR] + str r0, [r2, DMA_WRITE_ADDR] + str r4, [r2, DMA_TRANS_COUNT] + ldr r3, =3DDMA_CTRL_RX + str r3, [r2, DMA_CTRL_TRIG] + bl wait_dma_irq + pop {r4, pc} + +.thumb_func +clear_dma_irq: + ldr r2, =3Dirq_flag + movs r3, 0 + str r3, [r2] + ldr r2, =3DDMA_BASE + DMA_INTR + movs r3, 1 + str r3, [r2] + ldr r2, =3DDMA_BASE + DMA_INTE0 + str r3, [r2] + bx lr + +.thumb_func +wait_dma_irq: + ldr r2, =3Dirq_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, =3DDMA_BASE + DMA_INTR + ldr r1, [r0] + movs r2, 1 + tst r1, r2 + beq fail_irq + str r1, [r0] + ldr r0, =3Dirq_flag + str r1, [r0] + bx lr + +.thumb_func +puts: + ldr r2, =3DUART_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/syst= em/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/syst= em/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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070789; cv=none; d=zohomail.com; s=zohoarc; b=Juc2CX8lGxk45oUzb6Zat8Tm929yhA2bZ2bx1Jtfq7wJoQUelr+KkxxqDiCK9R71QEvGwXjuDWw09Ud0mS7e/celQt3JlZy7ERa8aUk8LIpapK02koQMiBzqvtK76ludXKukuYTGkt0Xq3QaHgH3O9RMA2b6WuCkzAIz8v+h+/g= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070789; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=JqUNpB1XViYFvwRpXI59jqC3eqxIurwOlbp6EzM/cWg=; b=Kne6+ZXmWu68sa1vv/G/W+KQh26fihZupyL0zpnvMuNN9Rq4m0w0/ZnA2vwtCxkYkOsykXPyN2dqTjefrRkGvc3TTkH7VIYVTgUOdLhE7LmGTloImLj/YlOANlm8RcHFuMg7rCGCMRmByuTYUQ7/AOLtk8r2ze4MAU6Ew4cQaa8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070789502108.23933918691807; Sat, 29 Aug 2026 23:19:49 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yre-0002m7-Qw; Sun, 30 Aug 2026 02:17:58 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yrc-0002ft-FL; Sun, 30 Aug 2026 02:17:56 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YrZ-0005J6-C1; Sun, 30 Aug 2026 02:17:56 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjp58S6z833f; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjp3ggtz7xSW; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070638; bh=JqUNpB1XViYFvwRpXI59jqC3eqxIurwOlbp6EzM/cWg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=owkIxYK5rVXnLQ7eaVapKj7U1W/OBYd6Ps1/MeGvnTklD8tAj1bZaBvcshsHjs1a7 LJuBMHSgiIej5SlyIN0tGPB7SMZxdFnbsv4cfgHY61CUNNJ6eIIPaJncRSDM/L3SPC U17E5j23+Lin+NhVd9J36GfoSFUgDuCQe87ah2oZIMdSs4e+d/s5W55ciDVbcyoxI4 +Lw4rl+FGuFJD2YxAY55E8K7CFa7kjyFii1PswF7uKG7kUTYDuvyalThcVVKoy5PFD Eo9b0aCVufX354F/ugckafkWIz9h+3TlWnUo1Ri6Vd3/aJ3s5u4iEwFcnIwOHZT+bt uJD6Aan4Z2Y7g== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 24/30] hw/ssi: load RP2040 UF2 flash images Date: Sun, 30 Aug 2026 08:15:55 +0200 Message-ID: <20260830061601.52136-10-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070790158158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Accept RP2040-family UF2 files through the Raspberry Pi Pico -kernel path. = Validate block framing, family IDs, payload sizes and XIP flash bounds befo= re overlaying payloads onto flash storage.\n\nAdd a focused qtest for multi= -block UF2 loading and provide a host-side converter for producing raw flas= h images from Pico UF2 files. Signed-off-by: gilles grimaud --- 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* =20 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 =20 +#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, c= onst char *filename, return true; } =20 +static bool rp2040_xip_load_uf2(RP2040XipState *s, const char *filename, + Error **errp) +{ + g_autofree gchar *contents =3D NULL; + gsize len; + unsigned blocks; + unsigned i; + bool copied =3D 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) !=3D UF2_MAGIC_START0 || + ldl_le_p(contents + 4) !=3D UF2_MAGIC_START1) { + return false; + } + + if (len % UF2_BLOCK_SIZE !=3D 0) { + error_setg(errp, "invalid UF2 image '%s': size is not a multiple " + "of %u bytes", filename, UF2_BLOCK_SIZE); + return true; + } + + blocks =3D len / UF2_BLOCK_SIZE; + for (i =3D 0; i < blocks; i++) { + const uint8_t *block =3D (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) !=3D UF2_MAGIC_START0 || + ldl_le_p(block + 4) !=3D UF2_MAGIC_START1 || + ldl_le_p(block + UF2_BLOCK_SIZE - 4) !=3D UF2_MAGIC_END) { + error_setg(errp, "invalid UF2 image '%s': bad magic in block %= u", + filename, i); + return true; + } + + flags =3D 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 =3D ldl_le_p(block + 28); + if (family_id !=3D UF2_RP2040_FAMILY_ID) { + error_setg(errp, "unsupported UF2 family ID 0x%08" PRIx32 + " in '%s'", family_id, filename); + return true; + } + + target =3D ldl_le_p(block + 12); + payload_size =3D 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 =3D target - RP2040_XIP_FLASH_BASE; + memcpy(s->storage + offset, block + UF2_HEADER_SIZE, payload_size); + copied =3D 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; } =20 + 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 =3D 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 =3D 0x0A324655 +UF2_MAGIC_START1 =3D 0x9E5D5157 +UF2_MAGIC_END =3D 0x0AB16F30 + +UF2_BLOCK_SIZE =3D 512 +UF2_HEADER_SIZE =3D 32 + +FLAG_NOT_MAIN_FLASH =3D 0x00000001 +FLAG_FAMILY_ID_PRESENT =3D 0x00002000 + +RP2040_FAMILY_ID =3D 0xE48BFF56 + +# Usual RP2350 / Pico 2 family IDs. +# They are detected for diagnostics. This script does not implement +# RP2350 partition table remapping. +RP2350_FAMILY_IDS =3D { + 0xE48BFF58: "rp2350-arm-secure", + 0xE48BFF59: "rp2350-arm-nonsecure", + 0xE48BFF5A: "rp2350-riscv", + 0xE48BFF5B: "data-or-absolute", +} + +XIP_BASE =3D 0x10000000 + +# RP2040 UF2 flash window: 0x10000000 .. 0x11000000 +PICO1_FLASH_WINDOW_SIZE =3D 16 * 1024 * 1024 + +# RP2350 UF2 flash window: 0x10000000 .. 0x12000000 +PICO2_FLASH_WINDOW_SIZE =3D 32 * 1024 * 1024 + + +def print_help() -> None: + """Print a minimal usage message.""" + + program_name =3D 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 t= hem + has the .uf2 extension. + """ + + if len(arguments) !=3D 2: + print_help() + sys.exit(2) + + first_path, second_path =3D arguments + first_ext =3D get_extension(first_path) + second_ext =3D get_extension(second_path) + + if first_ext =3D=3D ".uf2" and second_ext !=3D ".uf2": + return first_path, second_path + + if second_ext =3D=3D ".uf2" and first_ext !=3D ".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 =3D os.path.getsize(path) + + if file_size =3D=3D 0: + raise ValueError("empty file") + + if file_size % UF2_BLOCK_SIZE !=3D 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 =3D 0 + + while True: + block =3D input_file.read(UF2_BLOCK_SIZE) + + if not block: + break + + physical_block_index +=3D 1 + + if len(block) !=3D UF2_BLOCK_SIZE: + raise ValueError( + f"incomplete block at physical index {physical_block_i= ndex}" + ) + + 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] =3D {} + warnings: list[str] =3D [] + errors: list[str] =3D [] + + family_ids: set[int] =3D set() + valid_block_count =3D 0 + non_uf2_block_count =3D 0 + ignored_not_main_flash_count =3D 0 + + min_offset =3D None + max_end_offset =3D 0 + + announced_num_blocks =3D None + seen_block_numbers: set[int] =3D 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, + ) =3D struct.unpack_from(" PICO2_FLASH_WINDOW_SIZE: + errors.append( + f"block {physical_index}: page exceeds Pico 2 flash window= : " + f"offset=3D0x{flash_offset:x}, size=3D{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 =3D block[UF2_HEADER_SIZE:UF2_HEADER_SIZE + payload_size] + pages[flash_offset] =3D payload + + if min_offset is None or flash_offset < min_offset: + min_offset =3D flash_offset + + if flash_offset + payload_size > max_end_offset: + max_end_offset =3D flash_offset + payload_size + + if valid_block_count =3D=3D 0: + errors.append("no valid UF2 block found") + + if (announced_num_blocks is not None + and len(seen_block_numbers) !=3D 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 =3D ", ".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 =3D=3D 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) -> byte= array: + """ + Build a raw flash image initialized with erased flash bytes, 0xff. + + Only the occupied part is emitted: + output size =3D highest written offset + payload size + """ + + image =3D bytearray([0xFF] * occupied_size) + + for flash_offset, payload in pages.items(): + image[flash_offset:flash_offset + len(payload)] =3D 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 =3D detect_input_output(sys.argv[1:]) + + try: + analysis =3D 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 =3D analysis["pages"] + family_ids =3D analysis["family_ids"] + warnings =3D analysis["warnings"] + errors =3D 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 =3D analysis["max_end_offset"] + image =3D 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 =3D 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 =3D ", ".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__ =3D=3D "__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) =20 +#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"); } =20 +static void build_uf2_block(uint8_t *block, uint32_t target, uint32_t inde= x, + 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 =3D NULL; + g_autofree char *path =3D 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 =3D 0; i < ARRAY_SIZE(payload); i++) { + payload[i] =3D i ^ 0xa5; + } + build_uf2_block(uf2, XIP_BASE, 0, boot2); + build_uf2_block(uf2 + UF2_BLOCK_SIZE, XIP_BASE + 0x2000, 1, payload); + + fd =3D g_file_open_tmp("qemu-rp2040-uf2-XXXXXX", &path, &error); + g_assert_no_error(error); + g_assert_cmpint(fd, >=3D, 0); + close(fd); + g_assert_true(g_file_set_contents(path, (const char *)uf2, sizeof(uf2), + &error)); + g_assert_no_error(error); + + qts =3D 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", --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070751; cv=none; d=zohomail.com; s=zohoarc; b=RFliMiXKvFWt8BzCNgzvnuJNg+UjqDnKnjHsX3PFI+Lf52Cdut5Iq32QM+tSeczAEhSQzadXkC+NDX+s4c68q1zpybTyL/iYB1zYJOYPMg2gEOJPtvzQpAJmP8KeUy6wLog2DVTvKdBV/0OVpBqog8huAOKBbnKZgFmdpvwzga0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070751; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=cFM2jfrPyndQJ8QXbCLiQuVny0z7mWKjr5OK4mBueLU=; b=AEBF5ZJK1zUzadPw5usEpiyPHuDFeWtbx+/Nw1iSyaYnnTbIK6e+lKkkJueDy6aepD3vHPNORnZhZ/gXVa726NM+Oa6EjSjExyq1/6HumEdvX1/tuU/QDNbiSTNtWQbaj0WGHnGddqC7XRQp8Fg6aEl/OTUHAsZNtUbKLMggJjs= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070751929438.39410807845275; Sat, 29 Aug 2026 23:19:11 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yro-0002vm-2p; Sun, 30 Aug 2026 02:18:08 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yrm-0002sB-Lp; Sun, 30 Aug 2026 02:18:06 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yrk-0005KE-1B; Sun, 30 Aug 2026 02:18:06 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjp6N3Nz8340; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjp4yKCz7swM; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070638; bh=cFM2jfrPyndQJ8QXbCLiQuVny0z7mWKjr5OK4mBueLU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=dCIJKpd8itCacyAw/R+SbIBrjKo73Bj7W145RvhxTZSBoKzOanXqyHZ6iOy3C8PzS bs5224WdcY48RGmbA2EKjpuDIELSolZpx4WMrKGo2MJJfaiDs7OqKTVVmGpvhyh/iA mISVKgtOa+6SrxoKh+EcsN5dDzRsbKVqYUtT/yvuaOL3xnu9HAds1L1zWXzrIugVxn Z2nLF+yfqi18FMvNMpg3ZrRB8M5w8kiKFjdXgsJ0ND2L+OMGkLdRHtkNkXT9/3rnQe g0/ECvVrpfgx9ug+V0ivB2rRxWIjglzYbUP5k3Potz582FkJjlIVAWLUiye3zQNIYQ LbxuRHLIa43RQ== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 25/30] hw/misc: add RP2040 bus controller Date: Sun, 30 Aug 2026 08:15:56 +0200 Message-ID: <20260830061601.52136-11-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070756160154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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[] =3D { - { "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) } =20 object_initialize_child(obj, "xip", &s->xip, TYPE_RP2040_XIP); + object_initialize_child(obj, "busctrl", &s->busctrl, + TYPE_RP2040_BUSCTRL); =20 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); =20 + 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 =20 +config RP2040_BUSCTRL + bool + config RP2040_CLOCKS bool =20 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_busctr= l.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_iobank= 0.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] !=3D BUSCTRL_PERFSEL_RESET && + s->perfctr[index] !=3D BUSCTRL_PERFCTR_MASK) { + s->perfctr[index]++; + } + + return s->perfctr[index]; +} + +static uint64_t rp2040_busctrl_read(void *opaque, hwaddr addr, unsigned si= ze) +{ + RP2040BusCtrlState *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint64_t value; + + if (offset >=3D BUSCTRL_PERFCTR_BASE && + offset < BUSCTRL_PERFCTR_BASE + BUSCTRL_NUM_COUNTERS * + BUSCTRL_PERF_STRIDE && + (offset - BUSCTRL_PERFCTR_BASE) % BUSCTRL_PERF_STRIDE =3D=3D 0) { + index =3D (offset - BUSCTRL_PERFCTR_BASE) / BUSCTRL_PERF_STRIDE; + return rp2040_busctrl_read_counter(s, index); + } + + if (offset >=3D BUSCTRL_PERFSEL_BASE && + offset < BUSCTRL_PERFSEL_BASE + BUSCTRL_NUM_COUNTERS * + BUSCTRL_PERF_STRIDE && + (offset - BUSCTRL_PERFSEL_BASE) % BUSCTRL_PERF_STRIDE =3D=3D 0) { + index =3D (offset - BUSCTRL_PERFSEL_BASE) / BUSCTRL_PERF_STRIDE; + return s->perfsel[index]; + } + + switch (offset) { + case BUSCTRL_PRIORITY: + value =3D s->priority; + break; + case BUSCTRL_PRIORITY_ACK: + value =3D BUSCTRL_PRIORITY_ACKED; + break; + default: + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + unsigned index; + uint32_t value =3D value64; + + if (offset >=3D BUSCTRL_PERFCTR_BASE && + offset < BUSCTRL_PERFCTR_BASE + BUSCTRL_NUM_COUNTERS * + BUSCTRL_PERF_STRIDE && + (offset - BUSCTRL_PERFCTR_BASE) % BUSCTRL_PERF_STRIDE =3D=3D 0) { + index =3D (offset - BUSCTRL_PERFCTR_BASE) / BUSCTRL_PERF_STRIDE; + s->perfctr[index] =3D + rp2040_busctrl_apply_alias(s->perfctr[index], value, alias) & + BUSCTRL_PERFCTR_MASK; + return; + } + + if (offset >=3D BUSCTRL_PERFSEL_BASE && + offset < BUSCTRL_PERFSEL_BASE + BUSCTRL_NUM_COUNTERS * + BUSCTRL_PERF_STRIDE && + (offset - BUSCTRL_PERFSEL_BASE) % BUSCTRL_PERF_STRIDE =3D=3D 0) { + index =3D (offset - BUSCTRL_PERFSEL_BASE) / BUSCTRL_PERF_STRIDE; + s->perfsel[index] =3D + rp2040_busctrl_apply_alias(s->perfsel[index], value, alias) & + BUSCTRL_PERFSEL_MASK; + return; + } + + switch (offset) { + case BUSCTRL_PRIORITY: + s->priority =3D rp2040_busctrl_apply_alias(s->priority, value, ali= as) & + 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 =3D { + .read =3D rp2040_busctrl_read, + .write =3D rp2040_busctrl_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + +static void rp2040_busctrl_reset(DeviceState *dev) +{ + RP2040BusCtrlState *s =3D RP2040_BUSCTRL(dev); + int i; + + s->priority =3D 0; + for (i =3D 0; i < BUSCTRL_NUM_COUNTERS; i++) { + s->perfctr[i] =3D 0; + s->perfsel[i] =3D BUSCTRL_PERFSEL_RESET; + } +} + +static void rp2040_busctrl_init(Object *obj) +{ + RP2040BusCtrlState *s =3D 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 =3D { + .name =3D TYPE_RP2040_BUSCTRL, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (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 =3D DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, rp2040_busctrl_reset); + dc->vmsd =3D &rp2040_busctrl_vmstate; +} + +static const TypeInfo rp2040_busctrl_info =3D { + .name =3D TYPE_RP2040_BUSCTRL, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RP2040BusCtrlState), + .instance_init =3D rp2040_busctrl_init, + .class_init =3D 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 { =20 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_busc= trl.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 =3D \ (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-sysc= fg-test', + 'rp2040-busctrl-tes= t', 'rp2040-clocks-test= ', 'rp2040-dma-test', 'rp2040-iobank0-tes= t', 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 =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY), =3D= =3D, 0); + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY_ACK), + =3D=3D, 1); + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL0), + =3D=3D, BUSCTRL_PERFSEL_RESET); + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL1), + =3D=3D, BUSCTRL_PERFSEL_RESET); + + qtest_quit(qts); +} + +static void test_busctrl_priority(void) +{ + QTestState *qts =3D rp2040_start(); + + qtest_writel(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY, 0xffffffff); + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY), + =3D=3D, BUSCTRL_PRIORITY_MASK); + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PRIORITY_ACK), + =3D=3D, 1); + + qtest_quit(qts); +} + +static void test_busctrl_perf_counter(void) +{ + QTestState *qts =3D rp2040_start(); + uint32_t first; + uint32_t second; + + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0), + =3D=3D, 0); + qtest_writel(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL0, + BUSCTRL_PERFSEL_SRAM5); + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFSEL0), + =3D=3D, BUSCTRL_PERFSEL_SRAM5); + + first =3D qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0); + second =3D qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0); + g_assert_cmphex(first, =3D=3D, 1); + g_assert_cmphex(second, =3D=3D, 2); + + qtest_writel(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0, + BUSCTRL_PERFCTR_MASK); + g_assert_cmphex(qtest_readl(qts, BUSCTRL_BASE + BUSCTRL_PERFCTR0), + =3D=3D, 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070802; cv=none; d=zohomail.com; s=zohoarc; b=fYi6CwiXfpw6Fl6cCevb9uGbN1s4X1prKccywwKZRfNtejc5U8tG5vFwngKWpjgFkCqNkzmwcI4L34J5ZVvZRGriEfKLylviSST5GyuO32QTq8Kp7XChjXcPW7hPaJwY4xqRcuZO8DckaH9B54+CN4efFj+YNxzdtpAvUZ4qPgA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070802; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=c9z8dU4O2YK2suuIBTSBJbTwyv+59modIwsJ1H91r84=; b=T/ZjbaAVLpb4mVCqyR5FL1GSmOKnY6U6X+mgignsEIulKMcFuTQrKlQXO2/OowIevdJlLdJ8u716Nu231zn6+Azl9/78f/dl6iBIXbzC1TkI+5XWiVwT6MBuvdUMJmscOW8fdK7S5U8WAUfGxvlAv8FTYLSTjzU4eOHA81k2RHM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070802180785.5311291055162; Sat, 29 Aug 2026 23:20:02 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yry-0003Et-1Q; Sun, 30 Aug 2026 02:18:18 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yrw-0003Cz-Di; Sun, 30 Aug 2026 02:18:16 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yru-0005LX-99; Sun, 30 Aug 2026 02:18:16 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjq0TrFz834L; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjp6BsRz7xSW; Sun, 30 Aug 2026 08:17:18 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070639; bh=c9z8dU4O2YK2suuIBTSBJbTwyv+59modIwsJ1H91r84=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ae1YfOacUqQ25hsuFmxMxWodj4hgFN/qFv8M+yuCy9ISPhb6RyEsK/pDLXVVk9yDs NgloSAPicFwz1h+MEBK7AorVyu1ZZen7siIHtFnTlb7MpGxj1kLMXY12tx69ICffIA FedJ4RZB3yqnTtq8lah4NRe5ozrCeFPJ/l4aEC0vKZs1RLscvGoTFqV/zPu4mp1XfW oyzzdmBmUgzh4F2CPH8K2ppUjLO4HKVDmEHv5V/2J83RPvHqMTxYs+J3+RxUqXViF5 BDQtUtMngfNdb+LEhk9q+/obawCHuN08mlnNOP0gwMzxS7x5hA3y/ZYdE65Tf4W0G4 cAjaIRb+z1gLQ== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 26/30] hw/arm: add shallow RP2040 USB controller Date: Sun, 30 Aug 2026 08:15:57 +0200 Message-ID: <20260830061601.52136-12-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070805805154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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 =20 /* @@ -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 = =3D { }, }; =20 +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)); } =20 +static uint64_t rp2040_usbctrl_regs_read(void *opaque, hwaddr addr, + unsigned size) +{ + RP2040State *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint64_t value; + + switch (offset) { + case USBCTRL_SIE_STATUS: + value =3D 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 =3D s->usbctrl_reg[offset / sizeof(uint32_t)]; + break; + case USBCTRL_INTS: + value =3D (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 =3D s->usbctrl_reg[offset / sizeof(uint32_t)]; + } else { + value =3D 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 =3D opaque; + hwaddr alias =3D addr & ATOMIC_ALIAS_MASK; + hwaddr offset =3D addr & 0xfff; + uint32_t value =3D value64; + uint32_t old; + + if (offset < sizeof(s->usbctrl_reg)) { + old =3D s->usbctrl_reg[offset / sizeof(uint32_t)]; + s->usbctrl_reg[offset / sizeof(uint32_t)] =3D + 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 =3D { + .read =3D rp2040_usbctrl_regs_read, + .write =3D rp2040_usbctrl_regs_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + static void rp2040_update_nmi(RP2040State *s) { uint32_t nmi_mask =3D 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); =20 + 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 =3D true; =20 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 =20 #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; =20 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 =3D \ 'rp2040-sio-test', 'rp2040-tbman-test', 'rp2040-timer-test', + 'rp2040-usb-test', 'rp2040-uart-test', 'rp2040-vreg-test', 'rp2040-watchdog-te= st', 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 =3D rp2040_start(); + + qtest_writel(qts, USBCTRL_DPRAM_BASE + 0x20, 0x12345678); + g_assert_cmphex(qtest_readl(qts, USBCTRL_DPRAM_BASE + 0x20), + =3D=3D, 0x12345678); + + qtest_quit(qts); +} + +static void test_usb_registers(void) +{ + QTestState *qts =3D rp2040_start(); + + g_assert_cmphex(qtest_readl(qts, USBCTRL_REGS_BASE + + USBCTRL_SIE_STATUS) & + USBCTRL_VBUS_DETECTED, =3D=3D, 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), =3D=3D, 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), =3D=3D, 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), =3D=3D, 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), + =3D=3D, 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(); +} --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070786; cv=none; d=zohomail.com; s=zohoarc; b=Vxtj0DY0JT2I71ChiiF9jivV8IZ2RV6l9wBtI3EZd5FWxZXCRM0WqPnnV/U4i1Q85K8ywYXWKFvvgOn0YsG7O45M2dYOEEwtV9SJVYEPL3K2LCIX/IMmi0IOPlF4OSVGJfmB5gLe/97U/aFrwGmzScQfxRGUTNSA5AMTY3xxhpM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070786; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=kT9kRZ6+0R4ZalvTYMSLD2k5njnxa6qC9PP6/R5tuKs=; b=iXDtAEFY3vk/yreXDN3KDqSGo+1bk+nGbrMxO1xIFjOK8BmO6gGdquSbZLekD/eg0a5cf4f06zy8aLi0xuFrzBg2TN3EFEgGOO6Lmv0fHIUL+kNXcmVdn3HgOfM4+ZpXOLp+KVwtkS2+cf9vWibVipUExr8rUFj7SXcP79LiecE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070785700429.0568124759793; Sat, 29 Aug 2026 23:19:45 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Ys8-0003RB-Mk; Sun, 30 Aug 2026 02:18:28 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Ys6-0003Po-MA; Sun, 30 Aug 2026 02:18:26 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Ys3-0005MY-NU; Sun, 30 Aug 2026 02:18:26 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjq1rqgz834S; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjq0Qflz7swM; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070639; bh=kT9kRZ6+0R4ZalvTYMSLD2k5njnxa6qC9PP6/R5tuKs=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=HG2/5cxhcqivzhHVoUPPAcx5StvvqjZXqn9RUvltREFKZHxS+HZEzebGCNiTDFuHr ECz5++ZOYFezCVsvd8NSTMVZjWlXMCT+IGz0WIWdmjiV17k+ghZUY9zPXEXx02vxQn ej4nbUeTvs12GDca4ExBVIx5xVT7+wYsjTmAazWQY34fSL8z9vDJ4waplv+F7G7wo5 wPQo01ehkO/7ZzWicU7Aaa3rIXjNyWGxpRyX5u7QcqPMsbt2oQqaOdTdiVj+CcjsGY mA8SiNLjaP0vom7H2QF7xTGp1VqygISWMgga08Na7GkzB92iCYqyCfxuZIbkjMmscG kx2ev5kDwGJoA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 27/30] hw/arm: add RP2040 synthetic boot ROM service tables Date: Sun, 30 Aug 2026 08:15:58 +0200 Message-ID: <20260830061601.52136-13-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070788213158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Signed-off-by: gilles grimaud --- 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 =20 +#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 }, }; =20 +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[] =3D { + 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[] =3D { + 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[] =3D { + 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[] =3D { + 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[] =3D { + 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[] =3D { + 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[] =3D { + 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[] =3D { + 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[] =3D { + 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[] =3D { + { 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[] =3D { + 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[] =3D { + 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 val= ue) +{ + rom[offset] =3D value; + rom[offset + 1] =3D value >> 8; +} + +static void rp2040_store_word(uint8_t *rom, uint32_t offset, uint32_t valu= e) +{ + rom[offset] =3D value; + rom[offset + 1] =3D value >> 8; + rom[offset + 2] =3D value >> 16; + rom[offset + 3] =3D value >> 24; +} + +static void rp2040_install_synthetic_bootrom(void) +{ + g_autofree uint8_t *rom =3D g_malloc0(RP2040_ROM_SIZE); + uint32_t func_base =3D RP2040_BOOTROM_STUBS_OFFSET; + uint32_t func_table =3D RP2040_BOOTROM_FUNC_TABLE_OFFSET; + uint32_t data_table =3D RP2040_BOOTROM_DATA_TABLE_OFFSET; + uint32_t float_nyi =3D RP2040_BOOTROM_FLOAT_NYI_STUB_OFFSET | 1; + uint32_t double_nyi =3D RP2040_BOOTROM_DOUBLE_NYI_STUB_OFFSET | 1; + int i; + + memcpy(rom, rp2040_bootrom, sizeof(rp2040_bootrom)); + rom[RP2040_BOOTROM_ROM_VERSION_OFFSET] =3D + 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 =3D 0; i < ARRAY_SIZE(rp2040_bootrom_functions); i++) { + const RP2040BootromFunction *func =3D &rp2040_bootrom_functions[i]; + uint32_t entry =3D func_base | 1; + + if (func->impl) { + memcpy(rom + func_base, func->impl, func->impl_size); + func_base +=3D 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 +=3D 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] =3D RP2040_BOOTROM_FLOAT_TABLE_= WORDS; + rom[RP2040_BOOTROM_DOUBLE_TABLE_OFFSET] =3D RP2040_BOOTROM_FLOAT_TABLE= _WORDS; + for (i =3D 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 =3D 0; i < ARRAY_SIZE(rp2040_bootrom_data); i++) { + const RP2040BootromData *data =3D &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 = =3D { }, }; =20 +static const char *rp2040_bootrom_function_name(uint16_t code) +{ + int i; + + for (i =3D 0; i < ARRAY_SIZE(rp2040_bootrom_functions); i++) { + if (rp2040_bootrom_functions[i].code =3D=3D 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 =3D value; + const char *name =3D 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 =3D { + .read =3D rp2040_synthetic_rom_dbg_read, + .write =3D rp2040_synthetic_rom_dbg_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 4, + .max_access_size =3D 4, + }, +}; + static void rp2040_update_nmi(RP2040State *s) { uint32_t nmi_mask =3D 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); =20 + 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 =3D true; =20 return true; @@ -606,8 +894,7 @@ static bool rp2040_load_bootrom(RP2040State *s, Error *= *errp) g_autofree char *filename =3D NULL; =20 if (!s->bootrom_file) { - rom_add_blob_fixed("rp2040.bootrom", rp2040_bootrom, - sizeof(rp2040_bootrom), RP2040_ROM_BASE); + rp2040_install_synthetic_bootrom(); return true; } =20 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 =20 #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; =20 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) =20 +rp2040-bootrom-helpers: rp2040-bootrom-helpers.S \ + rp2040-boot2-w25q080.S rp2040.ld + $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ + -Wl,--build-id=3Dnone -x assembler-with-cpp \ + $(ARM_SRC)/rp2040-boot2-w25q080.S $< \ + -o $@ -nostdlib -static \ + -T $(ARM_SRC)/rp2040.ld + +run-rp2040-bootrom-helpers: QEMU_OPTS=3D\ + -M raspi-pico,strict-uart-pins=3Doff \ + -serial chardev:output \ + -semihosting-config enable=3Don,target=3Dnative -kernel +run-rp2040-bootrom-helpers: rp2040-bootrom-helpers + $(call run-test, $<, \ + $(QEMU) -monitor none -display none \ + -chardev file$(COMMA)path=3D$<.out$(COMMA)id=3Doutput \ + $(QEMU_OPTS) $<) + $(call diff-out,$<,$(ARM_SRC)/rp2040-bootrom-helpers.ref) + +RP2040_TESTS+=3Drp2040-bootrom-helpers + ARM_TESTS+=3D$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS) =20 # These objects provide the basic boot code and helper functions for all t= ests 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, =3D\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, =3D\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, =3Dstart_message + bl puts + + lookup ROM_FUNC_POPCOUNT32 + ldr r0, =3D0xf0f00f0f + blx r4 + fail_if_ne r0, 16, fail_bits + + lookup ROM_FUNC_CLZ32 + ldr r0, =3D0x00f00000 + blx r4 + fail_if_ne r0, 8, fail_bits + + lookup ROM_FUNC_CTZ32 + ldr r0, =3D0x00008000 + blx r4 + fail_if_ne r0, 15, fail_bits + + lookup ROM_FUNC_REVERSE32 + ldr r0, =3D0x80000001 + blx r4 + fail_if_ne r0, 0x80000001, fail_bits + + ldr r0, =3Dbits_ok_message + bl puts + + lookup ROM_FUNC_MEMSET + ldr r0, =3DTEST_BUFFER + movs r1, 0x5a + movs r2, 4 + blx r4 + ldr r5, =3DTEST_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, =3DTEST_BUFFER + 0x20 + ldr r0, =3D0x11223344 + str r0, [r5] + lookup ROM_FUNC_MEMCPY + ldr r0, =3DTEST_BUFFER + 0x10 + ldr r1, =3DTEST_BUFFER + 0x20 + movs r2, 4 + blx r4 + ldr r5, =3DTEST_BUFFER + 0x10 + ldr r0, [r5] + fail_if_ne r0, 0x11223344, fail_byte_memory + + ldr r0, =3Dbyte_memory_ok_message + bl puts + + lookup ROM_FUNC_MEMSET4 + ldr r5, =3DTEST_BUFFER + 0x30 + ldr r0, =3D0xdeadbeef + str r0, [r5, 8] + ldr r0, =3DTEST_BUFFER + 0x30 + movs r1, 0x5a + movs r2, 8 + blx r4 + ldr r5, =3DTEST_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, =3DTEST_BUFFER + 0x40 + ldr r0, =3D0x55667788 + str r0, [r5] + ldr r0, =3D0x99aabbcc + str r0, [r5, 4] + ldr r5, =3DTEST_BUFFER + 0x50 + ldr r0, =3D0xdeadbeef + str r0, [r5, 8] + lookup ROM_FUNC_MEMCPY44 + ldr r0, =3DTEST_BUFFER + 0x50 + ldr r1, =3DTEST_BUFFER + 0x40 + movs r2, 8 + blx r4 + ldr r5, =3DTEST_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, =3Dword_memory_ok_message + bl puts + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_lookup: + ldr r0, =3Dfail_lookup_message + b test_failed + +fail_bits: + ldr r0, =3Dfail_bits_message + b test_failed + +fail_byte_memory: + ldr r0, =3Dfail_byte_memory_message + b test_failed + +fail_word_memory: + ldr r0, =3Dfail_word_memory_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +rom_func_lookup: + push {r4, lr} + mov r1, r0 + ldr r0, =3DROM_FUNC_TABLE_PTR + ldrh r0, [r0] + ldr r4, =3DROM_TABLE_LOOKUP_PTR + ldrh r4, [r4] + blx r4 + pop {r4, pc} + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/ar= m/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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070752; cv=none; d=zohomail.com; s=zohoarc; b=SZ9n88C6uzITcTLa3yZKdhS44BildaM4VPagC3PN89PfyxrxyAn5aSPxh7AMWgLXjJQ9ukhuO3Y1zQLO5+I9noxDrCJKuSqu6XvkdjE0D87UvBJxcZSvHnXdbyHAgntW/xbOIRk9SrbQzrIgr5ivtWh1gxqzvI+O8Ru9TKUG82M= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070752; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=9/cVQ+OzEfcAFmc0Zd/6Yh/NC+vg6zGNe5AE5sJhFKo=; b=e1f66PbjjYOwJixjTB6QtjPYPenkCeHp78WeTPKiJuVZMU5bD1rggRI2zpyzoMB1hQHRB4HalI95MZWPfJ0Gca2+/krPkGjcoULbTawIzMzsA7DmkBdv1Xo1xQVnZyKwpIXy23UL41FA8aO0Jl5E1oBgyk86A0BOGmMrlfrqMw0= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070752457679.1194139594054; Sat, 29 Aug 2026 23:19:12 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0Yrj-0002pl-7s; Sun, 30 Aug 2026 02:18:03 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yrh-0002nO-RP; Sun, 30 Aug 2026 02:18:01 -0400 Received: from smtp-out-1.univ-lille.fr ([194.254.129.82]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0Yrd-0005Ja-At; Sun, 30 Aug 2026 02:18:01 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4hXhjq3DfbzBqYr; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjq1hVQz7xSW; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070639; bh=9/cVQ+OzEfcAFmc0Zd/6Yh/NC+vg6zGNe5AE5sJhFKo=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=PvpOmCVgbWL+APptr/9WDFNzW7Vbbt9wr+hmxkgtePvJr2EtO1WiloKnL5F6+wSqx 0VM1YgElt4VMaLFL48qE/mT2+71hsyXZhT7GqHTzYu0+TgH8IPKgk92VOtzAYzhmPo 1+AngIEETtvkaSHpoUB4hmSHX2oubwKJ+0AtVVzUOXeTUlL2+YYqkIwTjHNS4UVFKv QvJS0NyMyuozn64wBLu9HhWDyMc3FuhrB4BfLpeWbPhbtkFcTyV0Ql09HVtEiP4DGc xh9/R/yd8nCuFABFL5TqcBs7J49DuVZNQ3CA5FWVLMLW2Yve5ETCzQxIqOmqjSmLzP 2uls0K5WtPU4w== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 28/30] hw/arm: add RP2040 synthetic boot ROM accelerated helpers Date: Sun, 30 Aug 2026 08:15:59 +0200 Message-ID: <20260830061601.52136-14-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.82; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-1.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070757158154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Provide float and double boot ROM helpers through the synthetic QEMU helper= aperture, and accelerate the boot ROM flash entry points by calling the ex= isting XIP flash model directly. These shortcuts are installed only when the built-in synthetic ROM is selec= ted. They are not RP2040 hardware interfaces; an external mask ROM continue= s to exercise the modeled SSI/XIP path. Add check-tcg coverage for floating-point tables and multicore flash lockou= t. Signed-off-by: gilles grimaud --- 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 =20 #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 =20 /* * 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; =20 typedef struct RP2040BootromData { @@ -254,23 +324,98 @@ static const uint8_t rp2040_bootrom_memset4[] =3D { 0x10, 0xbc, 0x70, 0x47, }; =20 +static const uint8_t rp2040_bootrom_flash_noarg[] =3D { + /* + * 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[] =3D { + 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[] =3D { + 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[] =3D { + 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 } =20 static const RP2040BootromFunction rp2040_bootrom_functions[] =3D { - 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_OFFS= ET), + RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('E', 'X'), + "flash_exit_xip", + rp2040_bootrom_flash_noarg, + RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFS= ET), + RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('F', 'C'), + "flash_flush_cache", + rp2040_bootrom_flash_noarg, + RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFS= ET), + RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('I', 'F'), + "connect_internal_flash", + rp2040_bootrom_flash_noarg, + RP2040_BOOTROM_HELPER_NOARG_CODE_LITERAL_OFFS= ET), + RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('R', 'E'), + "flash_range_erase", + rp2040_bootrom_flash_args4, + RP2040_BOOTROM_HELPER_ARGS4_CODE_LITERAL_OFFS= ET), + RP2040_BOOTROM_IMPL_CODE(RP2040_ROM_TABLE_CODE('R', 'P'), + "flash_range_program", + rp2040_bootrom_flash_args3, + RP2040_BOOTROM_HELPER_ARGS3_CODE_LITERAL_OFFS= ET), 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[] = =3D { }; =20 static const uint8_t rp2040_bootrom_lookup[] =3D { - 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 */ }; =20 static const uint8_t rp2040_bootrom_nyi_stub[] =3D { - 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 */ }; =20 static void rp2040_store_hword(uint8_t *rom, uint32_t offset, uint16_t val= ue) @@ -323,6 +489,56 @@ static void rp2040_store_word(uint8_t *rom, uint32_t o= ffset, uint32_t value) rom[offset + 3] =3D value >> 24; } =20 +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_OF= FSET, + command); +} + static void rp2040_install_synthetic_bootrom(void) { g_autofree uint8_t *rom =3D 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) =20 if (func->impl) { memcpy(rom + func_base, func->impl, func->impl_size); + if (func->code_literal_offset !=3D UINT32_MAX) { + rp2040_store_word(rom, func_base + func->code_literal_offs= et, + func->code); + } func_base +=3D 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 +=3D 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] =3D RP2040_BOOTROM_FLOAT_TABLE_= WORDS; rom[RP2040_BOOTROM_DOUBLE_TABLE_OFFSET] =3D RP2040_BOOTROM_FLOAT_TABLE= _WORDS; for (i =3D 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 =3D i * sizeof(uint32_t); + + if (rp2040_bootrom_fp_supported(table_offset)) { + uint32_t float_stub =3D RP2040_BOOTROM_FLOAT_STUBS_OFFSET + + i * RP2040_BOOTROM_FP_STUB_SIZE; + uint32_t double_stub =3D 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); + } } =20 for (i =3D 0; i < ARRAY_SIZE(rp2040_bootrom_data); i++) { @@ -403,6 +646,21 @@ static void rp2040_install_synthetic_bootrom(void) RP2040_ROM_BASE); } =20 +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 = =3D { }, }; =20 -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; - } -} =20 static void rp2040_update_mempowerdown(RP2040State *s) { @@ -538,30 +782,632 @@ static const char *rp2040_bootrom_function_name(uint= 16_t code) return rp2040_bootrom_functions[i].name; } } + return NULL; } =20 +static void rp2040_init_float_status(float_status *status) +{ + *status =3D (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 =3D g_strdup_printf("%s table offset 0x%02" PR= Ix32, + is_double ? "double" : "floa= t", + 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 =3D { .u =3D value }; + + return v.f; +} + +static uint32_t rp2040_host_float_to_u32(float value) +{ + union { + float f; + uint32_t u; + } v =3D { .f =3D value }; + + return v.u; +} + +static double rp2040_u64_to_host_double(uint64_t value) +{ + union { + uint64_t u; + double d; + } v =3D { .u =3D value }; + + return v.d; +} + +static uint64_t rp2040_host_double_to_u64(double value) +{ + union { + double d; + uint64_t u; + } v =3D { .d =3D 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 >=3D INT32_MAX) { + return INT32_MAX; + } + if (value <=3D INT32_MIN) { + return INT32_MIN; + } + return value; +} + +static uint32_t rp2040_synthetic_double_to_u32(double value) +{ + if (isnan(value) || value <=3D 0) { + return 0; + } + if (value >=3D UINT32_MAX) { + return UINT32_MAX; + } + return value; +} + +static int64_t rp2040_synthetic_double_to_i64(double value) +{ + if (isnan(value)) { + return 0; + } + if (value >=3D 0x1p63) { + return INT64_MAX; + } + if (value <=3D -0x1p63) { + return INT64_MIN; + } + return value; +} + +static uint64_t rp2040_synthetic_double_to_u64(double value) +{ + if (isnan(value) || value <=3D 0) { + return 0; + } + if (value >=3D 0x1p64) { + return UINT64_MAX; + } + return value; +} + +static void rp2040_synthetic_float_op(RP2040State *s, uint32_t offset) +{ + float_status status; + float32 a =3D make_float32(s->synthetic_rom_dbg_arg[0]); + float32 b =3D make_float32(s->synthetic_rom_dbg_arg[1]); + float host_a =3D rp2040_u32_to_host_float(s->synthetic_rom_dbg_arg[0]); + float host_b =3D 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_res= ult)); + s->synthetic_rom_dbg_result[1] =3D 0; + + switch (offset) { + case RP2040_SF_TABLE_FADD: + s->synthetic_rom_dbg_result[0] =3D + float32_val(float32_add(a, b, &status)); + break; + case RP2040_SF_TABLE_FSUB: + s->synthetic_rom_dbg_result[0] =3D + float32_val(float32_sub(a, b, &status)); + break; + case RP2040_SF_TABLE_FMUL: + s->synthetic_rom_dbg_result[0] =3D + float32_val(float32_mul(a, b, &status)); + break; + case RP2040_SF_TABLE_FDIV: + s->synthetic_rom_dbg_result[0] =3D + 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] =3D + rp2040_synthetic_fp_compare(host_a, host_b); + break; + case RP2040_SF_TABLE_FSQRT: + s->synthetic_rom_dbg_result[0] =3D + float32_val(float32_sqrt(a, &status)); + break; + case RP2040_SF_TABLE_FLOAT2INT: + s->synthetic_rom_dbg_result[0] =3D + float32_to_int32_round_to_zero(a, &status); + break; + case RP2040_SF_TABLE_FLOAT2FIX: + scaled =3D ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xf= f); + s->synthetic_rom_dbg_result[0] =3D + rp2040_synthetic_double_to_i32(scaled); + break; + case RP2040_SF_TABLE_FLOAT2UINT: + s->synthetic_rom_dbg_result[0] =3D + float32_to_uint32_round_to_zero(a, &status); + break; + case RP2040_SF_TABLE_FLOAT2UFIX: + scaled =3D ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xf= f); + s->synthetic_rom_dbg_result[0] =3D + rp2040_synthetic_double_to_u32(scaled); + break; + case RP2040_SF_TABLE_INT2FLOAT: + s->synthetic_rom_dbg_result[0] =3D + float32_val(int32_to_float32(s->synthetic_rom_dbg_arg[0], + &status)); + break; + case RP2040_SF_TABLE_FIX2FLOAT: + r32 =3D 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] =3D float32_val(r32); + break; + case RP2040_SF_TABLE_UINT2FLOAT: + s->synthetic_rom_dbg_result[0] =3D + float32_val(uint32_to_float32(s->synthetic_rom_dbg_arg[0], + &status)); + break; + case RP2040_SF_TABLE_UFIX2FLOAT: + r32 =3D 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] =3D float32_val(r32); + break; + case RP2040_SF_TABLE_FCOS: + s->synthetic_rom_dbg_result[0] =3D + rp2040_host_float_to_u32(cosf(host_a)); + break; + case RP2040_SF_TABLE_FSIN: + s->synthetic_rom_dbg_result[0] =3D + rp2040_host_float_to_u32(sinf(host_a)); + s->synthetic_rom_dbg_result[1] =3D + rp2040_host_float_to_u32(cosf(host_a)); + break; + case RP2040_SF_TABLE_FTAN: + s->synthetic_rom_dbg_result[0] =3D + rp2040_host_float_to_u32(tanf(host_a)); + break; + case RP2040_SF_TABLE_V3_FSINCOS: + s->synthetic_rom_dbg_result[0] =3D + rp2040_host_float_to_u32(sinf(host_a)); + s->synthetic_rom_dbg_result[1] =3D + rp2040_host_float_to_u32(cosf(host_a)); + break; + case RP2040_SF_TABLE_FEXP: + s->synthetic_rom_dbg_result[0] =3D + rp2040_host_float_to_u32(expf(host_a)); + break; + case RP2040_SF_TABLE_FLN: + s->synthetic_rom_dbg_result[0] =3D + rp2040_host_float_to_u32(logf(host_a)); + break; + case RP2040_SF_TABLE_FATAN2: + s->synthetic_rom_dbg_result[0] =3D + rp2040_host_float_to_u32(atan2f(host_a, host_b)); + break; + case RP2040_SF_TABLE_INT642FLOAT: + r =3D deposit64(s->synthetic_rom_dbg_arg[0], 32, 32, + s->synthetic_rom_dbg_arg[1]); + r32 =3D int64_to_float32((int64_t)r, &status); + s->synthetic_rom_dbg_result[0] =3D float32_val(r32); + break; + case RP2040_SF_TABLE_FIX642FLOAT: + r =3D deposit64(s->synthetic_rom_dbg_arg[0], 32, 32, + s->synthetic_rom_dbg_arg[1]); + r32 =3D 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] =3D float32_val(r32); + break; + case RP2040_SF_TABLE_UINT642FLOAT: + r =3D deposit64(s->synthetic_rom_dbg_arg[0], 32, 32, + s->synthetic_rom_dbg_arg[1]); + r32 =3D uint64_to_float32(r, &status); + s->synthetic_rom_dbg_result[0] =3D float32_val(r32); + break; + case RP2040_SF_TABLE_UFIX642FLOAT: + r =3D deposit64(s->synthetic_rom_dbg_arg[0], 32, 32, + s->synthetic_rom_dbg_arg[1]); + r32 =3D 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] =3D float32_val(r32); + break; + case RP2040_SF_TABLE_FLOAT2INT64: + r =3D float32_to_int64_round_to_zero(a, &status); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2FIX64: + scaled =3D ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xf= f); + r =3D rp2040_synthetic_double_to_i64(scaled); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2UINT64: + r =3D float32_to_uint64_round_to_zero(a, &status); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2UFIX64: + scaled =3D ldexp((double)host_a, s->synthetic_rom_dbg_arg[1] & 0xf= f); + r =3D rp2040_synthetic_double_to_u64(scaled); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2DOUBLE: + r64 =3D float32_to_float64(a, &status); + r =3D float64_val(r64); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + default: + s->synthetic_rom_dbg_result[0] =3D 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 =3D deposit64(s->synthetic_rom_dbg_arg[0], 32, 32, + s->synthetic_rom_dbg_arg[1]); + uint64_t bv =3D deposit64(s->synthetic_rom_dbg_arg[2], 32, 32, + s->synthetic_rom_dbg_arg[3]); + float64 a =3D make_float64(av); + float64 b =3D make_float64(bv); + double host_a =3D rp2040_u64_to_host_double(av); + double host_b =3D 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_res= ult)); + + switch (offset) { + case RP2040_SF_TABLE_FADD: + r64 =3D float64_add(a, b, &status); + goto return_double; + case RP2040_SF_TABLE_FSUB: + r64 =3D float64_sub(a, b, &status); + goto return_double; + case RP2040_SF_TABLE_FMUL: + r64 =3D float64_mul(a, b, &status); + goto return_double; + case RP2040_SF_TABLE_FDIV: + r64 =3D 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] =3D + rp2040_synthetic_fp_compare(host_a, host_b); + s->synthetic_rom_dbg_result[1] =3D 0; + break; + case RP2040_SF_TABLE_FSQRT: + r64 =3D float64_sqrt(a, &status); + goto return_double; + case RP2040_SF_TABLE_FLOAT2INT: + s->synthetic_rom_dbg_result[0] =3D + float64_to_int32_round_to_zero(a, &status); + s->synthetic_rom_dbg_result[1] =3D 0; + break; + case RP2040_SF_TABLE_FLOAT2FIX: + scaled =3D ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff); + s->synthetic_rom_dbg_result[0] =3D + rp2040_synthetic_double_to_i32(scaled); + s->synthetic_rom_dbg_result[1] =3D 0; + break; + case RP2040_SF_TABLE_FLOAT2UINT: + s->synthetic_rom_dbg_result[0] =3D + float64_to_uint32_round_to_zero(a, &status); + s->synthetic_rom_dbg_result[1] =3D 0; + break; + case RP2040_SF_TABLE_FLOAT2UFIX: + scaled =3D ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff); + s->synthetic_rom_dbg_result[0] =3D + rp2040_synthetic_double_to_u32(scaled); + s->synthetic_rom_dbg_result[1] =3D 0; + break; + case RP2040_SF_TABLE_INT2FLOAT: + r64 =3D int32_to_float64(s->synthetic_rom_dbg_arg[0], &status); + goto return_double; + case RP2040_SF_TABLE_FIX2FLOAT: + r64 =3D 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 =3D uint32_to_float64(s->synthetic_rom_dbg_arg[0], &status); + goto return_double; + case RP2040_SF_TABLE_UFIX2FLOAT: + r64 =3D 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 =3D make_float64(rp2040_host_double_to_u64(cos(host_a))); + goto return_double; + case RP2040_SF_TABLE_FSIN: + r64 =3D make_float64(rp2040_host_double_to_u64(sin(host_a))); + goto return_double; + case RP2040_SF_TABLE_FTAN: + r64 =3D make_float64(rp2040_host_double_to_u64(tan(host_a))); + goto return_double; + case RP2040_SF_TABLE_V3_FSINCOS: + r =3D rp2040_host_double_to_u64(sin(host_a)); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + r =3D rp2040_host_double_to_u64(cos(host_a)); + s->synthetic_rom_dbg_result[2] =3D r; + s->synthetic_rom_dbg_result[3] =3D r >> 32; + break; + case RP2040_SF_TABLE_FEXP: + r64 =3D make_float64(rp2040_host_double_to_u64(exp(host_a))); + goto return_double; + case RP2040_SF_TABLE_FLN: + r64 =3D make_float64(rp2040_host_double_to_u64(log(host_a))); + goto return_double; + case RP2040_SF_TABLE_FATAN2: + r64 =3D make_float64(rp2040_host_double_to_u64(atan2(host_a, host_= b))); + goto return_double; + case RP2040_SF_TABLE_INT642FLOAT: + r64 =3D int64_to_float64((int64_t)av, &status); + goto return_double; + case RP2040_SF_TABLE_FIX642FLOAT: + r64 =3D 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 =3D uint64_to_float64(av, &status); + goto return_double; + case RP2040_SF_TABLE_UFIX642FLOAT: + r64 =3D 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 =3D float64_to_int64_round_to_zero(a, &status); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2FIX64: + scaled =3D ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff); + r =3D rp2040_synthetic_double_to_i64(scaled); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2UINT64: + r =3D float64_to_uint64_round_to_zero(a, &status); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2UFIX64: + scaled =3D ldexp(host_a, s->synthetic_rom_dbg_arg[2] & 0xff); + r =3D rp2040_synthetic_double_to_u64(scaled); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; + break; + case RP2040_SF_TABLE_FLOAT2DOUBLE: + r32 =3D float64_to_float32(a, &status); + s->synthetic_rom_dbg_result[0] =3D float32_val(r32); + s->synthetic_rom_dbg_result[1] =3D 0; + break; + default: + s->synthetic_rom_dbg_result[0] =3D 0; + s->synthetic_rom_dbg_result[1] =3D 0; + rp2040_synthetic_fp_nyi(true, offset); + break; + } + return; + +return_double: + r =3D float64_val(r64); + s->synthetic_rom_dbg_result[0] =3D r; + s->synthetic_rom_dbg_result[1] =3D r >> 32; +} + +static bool rp2040_synthetic_fp_op(RP2040State *s, uint32_t command) +{ + uint32_t offset =3D 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 =3D ++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 =3D opaque; + hwaddr offset =3D addr & 0xfff; + uint32_t command =3D value; uint16_t code =3D value; const char *name =3D rp2040_bootrom_function_name(code); char feature[64]; char detail[64]; + Error *local_err =3D 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)] =3D 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 comman= ds"); + 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 connect= ed"); + 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; + } =20 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); } =20 static uint64_t rp2040_synthetic_rom_dbg_read(void *opaque, hwaddr addr, unsigned size) { + RP2040State *s =3D opaque; + hwaddr offset =3D addr & 0xfff; + + if (offset >=3D RP2040_SYNTHETIC_ROM_DBG_ARG0 && + offset <=3D 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 >=3D RP2040_SYNTHETIC_ROM_DBG_RESULT0 && + offset <=3D RP2040_SYNTHETIC_ROM_DBG_RESULT3 && + QEMU_IS_ALIGNED(offset, sizeof(uint32_t))) { + return s->synthetic_rom_dbg_result[(offset - + RP2040_SYNTHETIC_ROM_DBG_RESUL= T0) / + sizeof(uint32_t)]; + } + if (offset >=3D 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; } =20 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. =20 +# 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 =20 #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]; =20 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 =20 RP2040_TESTS+=3Drp2040-bootrom-helpers =20 +rp2040-bootrom-float rp2040-flash-lockout: %: %.S \ + rp2040-boot2-w25q080.S rp2040.ld + $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ + -Wl,--build-id=3Dnone -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=3D\ + -M raspi-pico,strict-uart-pins=3Doff \ + -serial chardev:output \ + -semihosting-config enable=3Don,target=3Dnative -kernel +run-rp2040-bootrom-float run-rp2040-flash-lockout: run-%: % + $(call run-test, $<, \ + $(QEMU) -monitor none -display none \ + -chardev file$(COMMA)path=3D$<.out$(COMMA)id=3Doutput \ + $(QEMU_OPTS) $<) + $(call diff-out,$<,$(ARM_SRC)/$<.ref) + +RP2040_TESTS+=3Drp2040-bootrom-float rp2040-flash-lockout + ARM_TESTS+=3D$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS) =20 # These objects provide the basic boot code and helper functions for all t= ests diff --git a/tests/tcg/arm/system/rp2040-bootrom-float.S b/tests/tcg/arm/sy= stem/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, =3D\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, =3D\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, =3Dstart_message + bl puts + + lookup_data ROM_DATA_FLOAT_TABLE, r6 + + /* 1.25f + 2.5f =3D 3.75f. */ + load_table_func r6, FP_TABLE_FADD + ldr r0, =3D0x3fa00000 + ldr r1, =3D0x40200000 + blx r4 + fail_if_ne r0, 0x40700000, fail_single + + /* 3.75f * 2.0f =3D 7.5f. */ + load_table_func r6, FP_TABLE_FMUL + ldr r0, =3D0x40700000 + ldr r1, =3D0x40000000 + blx r4 + fail_if_ne r0, 0x40f00000, fail_single + + /* 7.5f / 3.0f =3D 2.5f. */ + load_table_func r6, FP_TABLE_FDIV + ldr r0, =3D0x40f00000 + ldr r1, =3D0x40400000 + blx r4 + fail_if_ne r0, 0x40200000, fail_single + + /* Convert 1.25f to 0x3ff4000000000000. */ + load_table_func r6, FP_TABLE_FLOAT2DOUBLE + ldr r0, =3D0x3fa00000 + blx r4 + fail_if_ne r0, 0x00000000, fail_single + fail_if_ne r1, 0x3ff40000, fail_single + + ldr r0, =3Dsingle_ok_message + bl puts + + lookup_data ROM_DATA_DOUBLE_TABLE, r7 + + /* 1.5 + 4.0 =3D 5.5. */ + load_table_func r7, FP_TABLE_FADD + movs r0, 0 + ldr r1, =3D0x3ff80000 + movs r2, 0 + ldr r3, =3D0x40100000 + 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, =3D0x3ff80000 + blx r4 + fail_if_ne r0, 0x3fc00000, fail_double + + ldr r0, =3Ddouble_ok_message + bl puts + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_lookup: + ldr r0, =3Dfail_lookup_message + b test_failed + +fail_single: + ldr r0, =3Dfail_single_message + b test_failed + +fail_double: + ldr r0, =3Dfail_double_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +rom_data_lookup: + push {r4, lr} + mov r1, r0 + ldr r0, =3DROM_DATA_TABLE_PTR + ldrh r0, [r0] + ldr r4, =3DROM_TABLE_LOOKUP_PTR + ldrh r4, [r4] + blx r4 + pop {r4, pc} + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/sy= stem/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, =3Dstart_message + bl puts + bl launch_core1 + + ldr r0, =3Dlaunch_message + bl puts + bl claim_spinlock + ldr r0, =3DLOCK_REQUEST + bl fifo_push + sev + ldr r0, =3DLOCK_ACK + bl fifo_pop_expect + + ldr r0, =3Dlocked_message + bl puts + bl flash_erase_program_verify + + ldr r0, =3Dflash_message + bl puts + ldr r0, =3DRELEASE_REQUEST + bl fifo_push + sev + ldr r0, =3DRELEASE_ACK + bl fifo_pop_expect + bl release_spinlock + + ldr r0, =3Dreleased_message + bl puts + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +.thumb_func +core1_entry: + ldr r0, =3DNVIC_ISER + ldr r1, =3DSIO_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, =3DLOCK_REQUEST + cmp r0, r1 + bne core1_bad_request + ldr r0, =3DLOCK_ACK + bl fifo_push + +3: + wfe + ldr r0, =3DSIO_BASE + ldr r1, [r0, SIO_FIFO_ST] + movs r0, SIO_FIFO_ST_VLD + tst r1, r0 + beq 3b + bl fifo_pop + ldr r1, =3DRELEASE_REQUEST + cmp r0, r1 + bne core1_bad_request + ldr r0, =3DRELEASE_ACK + bl fifo_push + pop {r0, r1, r2, r3, r4, pc} + +core1_bad_request: + ldr r0, =3DBAD_ACK + bl fifo_push + +4: + b 4b + +.thumb_func +launch_core1: + push {r4, r5, lr} + ldr r4, =3Dlaunch_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, =3D(SIO_BASE + SIO_SPINLOCK0) + ldr r1, [r0] + cmp r1, 0 + beq fail_spinlock + bx lr + +.thumb_func +release_spinlock: + ldr r0, =3D(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, =3DPAGE_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, =3DDEBUG_BASE + ldr r0, =3DFLASH_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, =3DDEBUG_FLASH_ERASE + str r0, [r4, DEBUG_CMD] + + ldr r0, =3DFLASH_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, =3DFLASH_OFFSET + str r0, [r4, DEBUG_ARG0] + ldr r0, =3DPAGE_BUFFER + str r0, [r4, DEBUG_ARG1] + movs r0, 1 + lsls r0, r0, 8 + str r0, [r4, DEBUG_ARG2] + ldr r0, =3DDEBUG_FLASH_PROGRAM + str r0, [r4, DEBUG_CMD] + + ldr r0, =3DFLASH_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, =3DSIO_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, =3DSIO_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, =3DSIO_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, =3Dfail_launch_message + b test_failed + +fail_spinlock: + ldr r0, =3Dfail_spinlock_message + b test_failed + +fail_fifo: + ldr r0, =3Dfail_fifo_message + b test_failed + +fail_flash: + ldr r0, =3Dfail_flash_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +14: + b 14b + +.thumb_func +default_handler: + ldr r0, =3Dfail_exception_message + b test_failed + +.thumb_func +puts: + push {r1, r2, lr} + ldr r1, =3DUART0_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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070845; cv=none; d=zohomail.com; s=zohoarc; b=IdVEIwGdotAlTiDz+SuAic3LGIgeJ1OX4N9GuTZDrx+qFJa491iLMIixFoB2tP4nb2EmvmYCipKPm7M/3/DeN8Sd6syf2S53wy4MKZ+ttsSyro2fmhzoqw64esMYbjAmHBKemKfdZ/UbOo3g/v9+wvS2BeF96zFSxITgWbEkftg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070845; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=2jNIm2DZsw+/80O3hSGisl0rPQJu2OjNuzFE/l70qI0=; b=U2BCMrn78Z3/phEqlcQQ6pOimLuUBTswMAGbOTIcQHODz7kSuEXW8/6dIdDSdWdp+VMKlMTsH9qnu6EtKueV8keeaNrWtggpQFPG+soi3p6x7Q3mlrhzybVctpFKiaEe3SG1O39muA1HpNKftSeYFMeEtaDEP10rcqc+NTxAb4Y= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070845349665.6485434436867; Sat, 29 Aug 2026 23:20:45 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0YsI-0003e1-Ab; Sun, 30 Aug 2026 02:18:38 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YsG-0003Z5-4G; Sun, 30 Aug 2026 02:18:36 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YsD-0005Na-GV; Sun, 30 Aug 2026 02:18:35 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjq4Zpzz835H; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjq33M6z7swM; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070639; bh=2jNIm2DZsw+/80O3hSGisl0rPQJu2OjNuzFE/l70qI0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=qPySLHTE+K2+T1yxxY1YF8HaskOVJa/wY8nTUm/B//BnQbZcdtOty8c9Kxb4WQN5V 4MyssybP/fVRg9MQi1LFbtmyuFnxgseP15C/a80CCXM7t0NXBKOQKePK3wRv4QGwQh 1UuNdOzjeb84nJ3aipslapQEbbvOOzQ7tiwMZg/gK105y+MKKeXxZLLYIrA+H/opO6 vLJpvygvNbPD+TuorBzRMSUalE8XAYi6ChYiqVjJFlmD+V/qTky6DUHO1ccLcrM/Oq pbPcDAA2NHn/m/NlMiv4V8DALWEzhzngKBDxb07l0SiN3tCscRLmCEyaEJpIJ+DgKw dKJ7rjH6GHnjg== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 29/30] hw/arm: add optional Pico SDK exit handling Date: Sun, 30 Aug 2026 08:16:00 +0200 Message-ID: <20260830061601.52136-15-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070846382158500 Content-Type: text/plain; charset="utf-8" From: gilles grimaud 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 architectu= ral BKPT-to-HardFault behavior unchanged and chain non-BKPT faults to the a= pplication handler. Cover the default HardFault path, explicit semihosting exit, and the option= al Pico SDK exit path with compiled check-tcg guests. Signed-off-by: gilles grimaud --- 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; }; =20 static char *raspi_pico_get_flash_file(Object *obj, Error **errp) @@ -164,6 +165,7 @@ static void raspi_pico_init(MachineState *machine) =20 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 *ob= j, bool value, s->strict_uart_pins =3D value; } =20 +static bool raspi_pico_get_pico_sdk_exit(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s =3D 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 =3D RASPI_PICO_MACHINE(obj); + + s->pico_sdk_exit =3D value; +} + static void raspi_pico_machine_initfn(Object *obj) { RaspiPicoMachineState *s =3D 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 UAR= T1 " "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"); } =20 static const TypeInfo raspi_pico_machine_info =3D { 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 =20 #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" */ =20 #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[] =3D { 0x00, 0x00, 0x00, 0x00, /* function code literal */ }; =20 +static const uint8_t rp2040_pico_sdk_exit_trampoline[] =3D { + 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 val= ue) { rom[offset] =3D value; @@ -1316,6 +1331,12 @@ static void rp2040_synthetic_rom_dbg_write(void *opa= que, hwaddr addr, return; } =20 + if (command =3D=3D RP2040_SYNTHETIC_ROM_DBG_CMD_EXIT) { + qemu_system_shutdown_request_with_code(SHUTDOWN_CAUSE_GUEST_SHUTDO= WN, + 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_db= g_ops =3D { }, }; =20 +static MemTxResult rp2040_pico_sdk_exit_page_read(void *opaque, hwaddr add= r, + uint64_t *data, + unsigned size, + MemTxAttrs attrs) +{ + RP2040State *s =3D opaque; + MemTxResult result; + unsigned int i; + + result =3D rp2040_xip_read_data(&s->xip, addr, data, size); + if (result !=3D MEMTX_OK) { + return result; + } + + for (i =3D 0; i < size; i++) { + hwaddr cur =3D addr + i; + + if (cur >=3D RP2040_APP_HARDFAULT_VECTOR_OFFSET && + cur < RP2040_APP_HARDFAULT_VECTOR_OFFSET + sizeof(uint32_t)) { + uint8_t byte =3D extract32( + RP2040_PICO_SDK_EXIT_TRAMPOLINE_BASE | 1, + (cur - RP2040_APP_HARDFAULT_VECTOR_OFFSET) * 8, 8); + + *data =3D 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 =3D { + .read_with_attrs =3D rp2040_pico_sdk_exit_page_read, + .write_with_attrs =3D rp2040_pico_sdk_exit_page_write, + .endianness =3D DEVICE_LITTLE_ENDIAN, + .valid =3D { + .min_access_size =3D 1, + .max_access_size =3D 4, + .unaligned =3D true, + }, +}; + static void rp2040_update_nmi(RP2040State *s) { uint32_t nmi_mask =3D 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); =20 s->mempowerdown_ready =3D true; =20 @@ -1778,6 +1857,13 @@ static void rp2040_soc_realize(DeviceState *dev, Err= or **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_BA= SE, &s->xip.xip_noalloc); memory_region_add_subregion(get_system_memory(), RP2040_XIP_NOCACHE_BA= SE, @@ -2001,6 +2087,37 @@ static void rp2040_soc_realize(DeviceState *dev, Err= or **errp) rp2040_update_uart_pins(s); } =20 +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 =3D ldl_le_p( + s->xip.storage + RP2040_APP_HARDFAULT_VECTOR_OFFSET); + if (!(original_handler & 1) || original_handler =3D=3D 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 =3D 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 =3D 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[] =3D { DEFINE_PROP_LINK("memory", RP2040State, board_memory, TYPE_MEMORY_REGI= ON, 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 =20 #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; =20 @@ -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; =20 Clock *sysclk; }; =20 +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= -%: % =20 RP2040_TESTS+=3Drp2040-bootrom-float rp2040-flash-lockout =20 +rp2040-bkpt-hardfault: rp2040-bkpt-hardfault.S \ + rp2040-boot2-w25q080.S rp2040.ld + $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ + -Wl,--build-id=3Dnone -x assembler-with-cpp \ + $(ARM_SRC)/rp2040-boot2-w25q080.S $< \ + -o $@ -nostdlib -static \ + -T $(ARM_SRC)/rp2040.ld + +run-rp2040-bkpt-hardfault: QEMU_OPTS=3D-M raspi-pico,strict-uart-pins=3Dof= f \ + -serial chardev:output \ + -semihosting-config enable=3Don,target=3Dnative -kernel +run-rp2040-bkpt-hardfault: rp2040-bkpt-hardfault + $(call run-test, $<, \ + $(QEMU) -monitor none -display none \ + -chardev file$(COMMA)path=3D$<.out$(COMMA)id=3Doutput \ + $(QEMU_OPTS) $<) + $(call diff-out,$<,$(ARM_SRC)/rp2040-bkpt-hardfault.ref) + +RP2040_TESTS+=3Drp2040-bkpt-hardfault + +rp2040-semihost-exit: rp2040-exit.S \ + rp2040-boot2-w25q080.S rp2040.ld + $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ + -DEXIT_MODE=3D0 -Wl,--build-id=3Dnone -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=3Don$(COMMA)target=3Dnative \ + -kernel $<; rc=3D$$?; test $$rc -eq 23 + +RP2040_TESTS+=3Drp2040-semihost-exit + +rp2040-pico-sdk-exit: rp2040-exit.S \ + rp2040-boot2-w25q080.S rp2040.ld + $(CC) -mcpu=3Dcortex-m0plus -mthumb -mfloat-abi=3Dsoft \ + -DEXIT_MODE=3D1 -Wl,--build-id=3Dnone -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=3Don \ + -monitor none -display none -serial none -kernel $<; \ + rc=3D$$?; test $$rc -eq 23 + +RP2040_TESTS+=3Drp2040-pico-sdk-exit + ARM_TESTS+=3D$(RP2040_TESTS) $(RP2040_FLASH_TESTS) $(RP2040_DMA_TESTS) =20 # These objects provide the basic boot code and helper functions for all t= ests diff --git a/tests/tcg/arm/system/rp2040-bkpt-hardfault.S b/tests/tcg/arm/s= ystem/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, =3Dstart_message + bl puts + bkpt 0 + + ldr r0, =3Dfail_return_message + b test_failed + +.thumb_func +hardfault_handler: + mrs r0, ipsr + cmp r0, HARDFAULT_EXCEPTION + bne fail_exception + + ldr r0, =3Dok_message + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_APPLICATION_EXIT + bkpt 0xab + +1: + b 1b + +fail_exception: + ldr r0, =3Dfail_exception_message + +test_failed: + bl puts + movs r0, SYS_EXIT + ldr r1, =3DADP_STOPPED_RUNTIME_ERROR + bkpt 0xab + +2: + b 2b + +.thumb_func +default_handler: + ldr r0, =3Dfail_vector_message + b test_failed + +.thumb_func +puts: + ldr r2, =3DUART0_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/rp20= 40-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 =3D=3D EXIT_MODE_SEMIHOST + movs r0, SYS_EXIT_EXTENDED + ldr r1, =3Dsemihost_exit_block + bkpt 0xab +#elif EXIT_MODE =3D=3D 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 --=20 2.55.0 From nobody Sun Sep 20 22:30:57 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=univ-lille.fr ARC-Seal: i=1; a=rsa-sha256; t=1788070811; cv=none; d=zohomail.com; s=zohoarc; b=Z53Y+YRfiNsDwRJ/EkauzsBqr/11QOdqzIzK3sFzRyA2g41YtU3IOQiiLFhpMh6hIY8E2r5TR6cN2QigEnz+kwUbWBkl2qeLBX1s6mw/lPiDwiHqFy+KMwqcraiYq/H2O2BBMlzCGqSY+A2og2ciOsNhJwIgFAqsioVp24aCHJA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1788070811; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=bjT3ypkIXI8EKZ95beLD27r7ihmVEE/GnSnULJx7NsU=; b=IOgpR8SvjC3CH/d+tQax+JoVQJhcSleR1un4TsLxc4qJYDxWKVEzPGiv4xxEDJg8eOc56l9a4KPLIIz40M2jtZUY7pNdEeQgfBcoZ9nbQMudgBozaYvBtyhSLOtggjqBCcs/KjnbYm+5cUgr4MolGXNrighsCN3RdCwQd04t3k8= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1788070811758973.7960269761744; Sat, 29 Aug 2026 23:20:11 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1x0YsV-000434-3R; Sun, 30 Aug 2026 02:18:51 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YsR-0003z2-5N; Sun, 30 Aug 2026 02:18:48 -0400 Received: from smtp-out-2.univ-lille.fr ([194.254.129.106]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1x0YsM-0005OO-F0; Sun, 30 Aug 2026 02:18:46 -0400 Received: from smtp01.univ-lille.fr (smtp01.univ-lille.fr [194.254.129.95]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4hXhjq5sQHz835m; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) Received: from localhost.localdomain (unknown [10.134.14.34]) (Authenticated sender: gilles.grimaud) by smtp01.univ-lille.fr (Postfix) with ESMTPSA id 4hXhjq4PVXz7xSW; Sun, 30 Aug 2026 08:17:19 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1788070639; bh=bjT3ypkIXI8EKZ95beLD27r7ihmVEE/GnSnULJx7NsU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=BbDmBLuRqTI4hhZbJERLFwfLXNz5JihkrSYGiwOXcM1mSJ5AhUKtG5vQ7r4I7cDYG 6i3PSNOJ0OGU0tv4O1YA0TGPUVnCF0g7XnFGmtAAR6RojgO4KUuCkc5TSsWJ+4II2m d321smdG3gn2/WoHo/562n1s/2f+AmhL8UjdybSTm/sgYUmEq4GWW+WeU+lY29L86O Tr5F1vn0oPimKg+O9JC9mK+C6PApiTiL0ymxypgq1IWTv562ZNG4TQrj+uLGCZdHlx yC4ew7e7Y9u6f0I+ZTGS2ug7NlJYNt12zVl75McBijLdxZqFg+FRygpk/98PJT5SA+ Z0JyPzDPqu9eA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: alex.bennee@linaro.org, qemu-arm@nongnu.org, peter.maydell@linaro.org Subject: [PATCH RFC v2 30/30] docs/system/arm: document Raspberry Pi Pico Date: Sun, 30 Aug 2026 08:16:01 +0200 Message-ID: <20260830061601.52136-16-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> References: <20260829234308.33725-1-gilles.grimaud@univ-lille.fr> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=194.254.129.106; envelope-from=gilles.grimaud@univ-lille.fr; helo=smtp-out-2.univ-lille.fr X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @univ-lille.fr) X-ZM-MESSAGEID: 1788070813896154100 Content-Type: text/plain; charset="utf-8" From: gilles grimaud Document the RP2040 machine lineage, supported and shallow devices, boot RO= M profiles, ELF/UF2/raw loading, persistent flash, serial backends, machine= properties, flash policy, Pico SDK exit compatibility, and known limitatio= ns. Add the board page to the Arm system manual and its ownership entry to MAIN= TAINERS. Signed-off-by: gilles grimaud --- 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* =20 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``) +=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +The ``raspi-pico`` machine models a minimal Raspberry Pi Pico 1 board based +on the RP2040 microcontroller. The current model is intended for bare-met= al +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 firmw= are +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 a= nd + 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 ali= as + 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_U= SB + 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 sup= port + * 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 a= re + 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 b= us + 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 behavio= ur + are not timing-accurate. + * QEMU's normal gdbstub can debug the emulated Cortex-M0+ CPUs. The RP20= 40 + SWD/debug fabric itself is not implemented: debug pause inputs and + ``DBGFORCE``-style registers are stored where useful, but they do not m= odel + an external SWD probe connected to the chip. + * DMA is implemented for the paths listed above, but DREQ sources belongi= ng + to unimplemented peripherals do not transfer data. + * The synthetic ROM provides a useful subset of the RP2040 function and d= ata + 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 wi= th +``-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=3Dflash.bin -serial std= io + +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 b= ack +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 over= ride +it with: + +.. code-block:: bash + + $ qemu-system-arm -machine raspi-pico,flash-uid=3D0011223344556677 \ + -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=3D0x1234 \ + -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()`` pa= th +is delivered through the normal DebugMonitor/HardFault rules; merely runni= ng +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=3Don \ + -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`` reque= sts +guest shutdown using the stacked ``r0`` value as the exit status. Other +HardFaults are forwarded to the application's original handler. This opti= on +is disabled by default, requires a valid Thumb HardFault vector in the loa= ded +XIP image, and does not modify ``flash-file``. It is a Pico SDK test +convenience rather than an RP2040 hardware feature. Standard Arm semihost= ing +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 mechan= ism +used by other machines for firmware blobs. QEMU does not ship an RP2040 b= oot +ROM image for this machine; use ``-bios`` with a user-provided ROM image w= hen +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 lo= g: + +.. code-block:: bash + + $ qemu-system-arm -machine raspi-pico,flash-file=3Dflash.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 t= he +absolute address, register offset, access size and write value. The XIP/S= SI +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 synthe= tic +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 sho= uld +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 softw= are +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 enab= le +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 mo= del: +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 f= or +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 f= lash +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 mod= el +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`` registe= rs. +``CHIP_RESET`` records chip-level reset sources: power-on/brown-out, RUN p= in, +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 hi= gh +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 duplic= ated +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 pa= ges +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 inver= se +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 th= en +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 QE= MU, +``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 P= LL +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 translat= ed +by the XIP hardware into external serial flash transfers. The XIP block a= lso +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`` aliase= s 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 descri= bes +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 ru= ns +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 da= ta +tables. It implements ``memcpy``, ``memset``, their word-oriented variant= s, +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. P= age +program and sector erase still enforce the modeled NOR rules. These helpe= rs +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 ser= ial +flash command state and for raising the documented QEMU HardFault policy w= hen +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.ro= m`` +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 t= he +initial emulation are ``CTRLR0``, ``CTRLR1``, ``SSIENR``, ``SER``, ``BAUDR= ``, +``SR`` and the data register window beginning at ``DR0``. The data regist= er +window feeds the transmit FIFO on writes and pops the receive FIFO on read= s. +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 progr= am +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 ch= osen +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 PrimeC= ell +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. S= ee +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`` dri= ves +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. UAR= T0 +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=3DUART`` before UART0 = host +serial transmit/receive is connected, and GPIO4/GPIO5 do the same for UART= 1. +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=3Doff``. ``PADS_BANK= 0`` +stores the documented pad-control registers separately from this UART path. + +For the initial console use case, the documented stable status behaviour i= s: + + * ``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 a= dded +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``/sta= ck, + 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 regressio= n. + * 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 regist= ers, + 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 output= s to + external pins, and does not connect arbitrary peripheral functions thro= ugh + the GPIO matrix. Edge detection and full interrupt source modelling re= main + future work. + * SIO divider and interpolator results are computed immediately when their + registers are accessed. QEMU does not model the RP2040 single-cycle ti= ming, + 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 dir= ect + reads and DMA from ``XIP_AUX_BASE``, but without flash idle-cycle timin= g. + 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 expo= sed + 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 for= ced + ``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 F= IFO + launch sequence, a subset of the ROM function/data tables, and accelera= ted + floating-point and flash helpers. These helpers are functionally tested + but intentionally bypass ROM instruction timing and, for flash operatio= ns, + the low-level SSI command sequence. The external ``-bios`` path can ru= n 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 BOOTS= EL + 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 mod= e. + 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 t= imer + registers and the Pico's nominal 125 MHz system clock as the virtual so= urce. + 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 addr= ess + 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 timin= g. + * ``SYSINFO`` and ``SYSCFG`` expose the documented register layout used by + early firmware. ``PROC0_NMI_MASK`` is wired for interrupt sources rout= ed + through the RP2040 IRQ shim, currently including UART0, and + ``MEMPOWERDOWN`` makes powered-off ROM and SRAM bank windows return mem= ory + 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 dete= ctor + 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 a= re + 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 --=20 2.55.0