From nobody Sun Jul 26 11:08:36 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=1783290602; cv=none; d=zohomail.com; s=zohoarc; b=GI3Yt25zFneOlmfXrW40kiSUmka3fAAq6QUsIRR25fOBVgffE7kwfAnJQ61a4epI8+L4/mOElXE69BtsJvmrXh65b4x54LX2/uQyib6RP0HdoMf04TQhw8jc+3c3j5BQjESX/nLx/smOSj8OhMr87qOE0Ou1Pcrk3pxhIswESqA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290602; 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=cRESgzp/SZTyo+3gbVtIVtdguX4/QAKAUfTh8NG2E7c=; b=BvcvkOp+2/HBgSo6tnTesrTO/6sjgRP+CGuyKk7kWfYFgpqAGlAoHF2waiUuOWhBz9OJdER2eE0r/Hd2pd8xOQbmJiO9g6ZrL22y0AFpltfIzxeqPUyTZW5GA2Pfe0Mmbfu4hDxOAka+TQkYYVZH66bRRKbzQkZM8xfQSkxoKJE= 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 1783290602646528.9390564276813; Sun, 5 Jul 2026 15:30:02 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJx-0006mk-Iy; Sun, 05 Jul 2026 18:28:17 -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 1wgUvz-0002AZ-JD; Sun, 05 Jul 2026 18:03:31 -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 1wgUvt-0001fG-Rt; Sun, 05 Jul 2026 18:03:31 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4gthMF74vQz85XT; Mon, 6 Jul 2026 00:03:21 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMF5cY2z6tBv; Mon, 6 Jul 2026 00:03:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289002; bh=cRESgzp/SZTyo+3gbVtIVtdguX4/QAKAUfTh8NG2E7c=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=SEbEMEWhPLFfq2HppafCscUKzWgV1ois/9SYWu+o62FD7YVSGC8K0ed++230r/sjR oqCsOxqQhvmEbqhfklo1N9hGMTwKKudieyJYNIWBONbtqamnvu6ba1xdoQhf386odz 5VeUa3inmiNXzNY1Q51BL067UWuqy3XcSCHtg1QGOoM7JzYRL7HULhJGrp3l3slV8Q zrE6EDWfnzU7KGsUGXKDXChoxPMSEi+cKs65zXkfqjmzkZBaCydvGmHCoMuTFIQ+vz HaGNqHZU+bZhrC//6HgzjQcPTUUNkR/ymvnayqKEzw5y6zNrOyrDaeSCA4KdkpVKOh 4lPVxchzudF0w== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 1/9] hw/misc: add RP2040 peripheral models Date: Mon, 6 Jul 2026 00:03:12 +0200 Message-ID: <20260705220320.90395-2-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290604348158500 Add shallow RP2040 models for clocks, resets, watchdog, SIO, GPIO bank/pads= , ROSC, PLL, timer, syscfg/sysinfo, TBMAN, PSM, VREG and related helpers. These devices provide enough behaviour for the Raspberry Pi Pico machine, S= DK examples and the RP2040 mask ROM path to make forward progress while kee= ping unsupported register behaviour explicit. This continues the RP2040/Pico enablement started by Alex Benn=C3=A9e's 202= 2 RFC series, adapted to current QEMU APIs. Signed-off-by: Gilles Grimaud --- hw/misc/Kconfig | 54 ++ hw/misc/meson.build | 18 + hw/misc/rp2040_busctrl.c | 208 +++++++ hw/misc/rp2040_clocks.c | 480 ++++++++++++++++ hw/misc/rp2040_iobank0.c | 353 ++++++++++++ hw/misc/rp2040_ioqspi.c | 294 ++++++++++ hw/misc/rp2040_nyi.c | 47 ++ hw/misc/rp2040_pads.c | 323 +++++++++++ hw/misc/rp2040_pll.c | 234 ++++++++ hw/misc/rp2040_psm.c | 198 +++++++ hw/misc/rp2040_resets.c | 152 +++++ hw/misc/rp2040_rosc.c | 430 ++++++++++++++ hw/misc/rp2040_sio.c | 905 ++++++++++++++++++++++++++++++ hw/misc/rp2040_syscfg.c | 246 ++++++++ hw/misc/rp2040_sysinfo.c | 111 ++++ hw/misc/rp2040_tbman.c | 94 ++++ hw/misc/rp2040_timer.c | 372 ++++++++++++ hw/misc/rp2040_vreg.c | 179 ++++++ hw/misc/rp2040_watchdog.c | 361 ++++++++++++ hw/misc/rp2040_xosc.c | 237 ++++++++ hw/misc/unimp.c | 13 +- include/hw/misc/rp2040_busctrl.h | 29 + include/hw/misc/rp2040_clocks.h | 36 ++ include/hw/misc/rp2040_iobank0.h | 45 ++ include/hw/misc/rp2040_ioqspi.h | 36 ++ include/hw/misc/rp2040_nyi.h | 19 + include/hw/misc/rp2040_pads.h | 42 ++ include/hw/misc/rp2040_pll.h | 37 ++ include/hw/misc/rp2040_psm.h | 40 ++ include/hw/misc/rp2040_resets.h | 28 + include/hw/misc/rp2040_rosc.h | 42 ++ include/hw/misc/rp2040_sio.h | 57 ++ include/hw/misc/rp2040_syscfg.h | 42 ++ include/hw/misc/rp2040_sysinfo.h | 25 + include/hw/misc/rp2040_tbman.h | 25 + include/hw/misc/rp2040_timer.h | 39 ++ include/hw/misc/rp2040_vreg.h | 28 + include/hw/misc/rp2040_watchdog.h | 35 ++ include/hw/misc/rp2040_xosc.h | 33 ++ include/hw/misc/unimp.h | 2 + 40 files changed, 5945 insertions(+), 4 deletions(-) create mode 100644 hw/misc/rp2040_busctrl.c create mode 100644 hw/misc/rp2040_clocks.c create mode 100644 hw/misc/rp2040_iobank0.c create mode 100644 hw/misc/rp2040_ioqspi.c create mode 100644 hw/misc/rp2040_nyi.c create mode 100644 hw/misc/rp2040_pads.c create mode 100644 hw/misc/rp2040_pll.c create mode 100644 hw/misc/rp2040_psm.c create mode 100644 hw/misc/rp2040_resets.c create mode 100644 hw/misc/rp2040_rosc.c create mode 100644 hw/misc/rp2040_sio.c create mode 100644 hw/misc/rp2040_syscfg.c create mode 100644 hw/misc/rp2040_sysinfo.c create mode 100644 hw/misc/rp2040_tbman.c create mode 100644 hw/misc/rp2040_timer.c create mode 100644 hw/misc/rp2040_vreg.c create mode 100644 hw/misc/rp2040_watchdog.c create mode 100644 hw/misc/rp2040_xosc.c create mode 100644 include/hw/misc/rp2040_busctrl.h create mode 100644 include/hw/misc/rp2040_clocks.h create mode 100644 include/hw/misc/rp2040_iobank0.h create mode 100644 include/hw/misc/rp2040_ioqspi.h create mode 100644 include/hw/misc/rp2040_nyi.h create mode 100644 include/hw/misc/rp2040_pads.h create mode 100644 include/hw/misc/rp2040_pll.h create mode 100644 include/hw/misc/rp2040_psm.h create mode 100644 include/hw/misc/rp2040_resets.h create mode 100644 include/hw/misc/rp2040_rosc.h create mode 100644 include/hw/misc/rp2040_sio.h create mode 100644 include/hw/misc/rp2040_syscfg.h create mode 100644 include/hw/misc/rp2040_sysinfo.h create mode 100644 include/hw/misc/rp2040_tbman.h create mode 100644 include/hw/misc/rp2040_timer.h create mode 100644 include/hw/misc/rp2040_vreg.h create mode 100644 include/hw/misc/rp2040_watchdog.h create mode 100644 include/hw/misc/rp2040_xosc.h diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 1543ee6653..1480a6d60d 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -101,6 +101,60 @@ config FSL_IMX8MP_ANALOG config FSL_IMX8MP_CCM bool =20 +config RP2040_BUSCTRL + bool + +config RP2040_CLOCKS + bool + +config RP2040_IOBANK0 + bool + +config RP2040_IOQSPI + bool + +config RP2040_NYI + bool + +config RP2040_PADS + bool + +config RP2040_PLL + bool + +config RP2040_PSM + bool + +config RP2040_RESETS + bool + +config RP2040_ROSC + bool + +config RP2040_SIO + bool + +config RP2040_SYSCFG + bool + +config RP2040_SYSINFO + bool + +config RP2040_TBMAN + bool + +config RP2040_TIMER + bool + +config RP2040_VREG + bool + +config RP2040_WATCHDOG + bool + +config RP2040_XOSC + bool + config STM32_RCC bool =20 diff --git a/hw/misc/meson.build b/hw/misc/meson.build index 23265f6035..d89305ae14 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -95,6 +95,24 @@ 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')) +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')) +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'= )) +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')) 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_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/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_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/hw/misc/rp2040_ioqspi.c b/hw/misc/rp2040_ioqspi.c new file mode 100644 index 0000000000..8356b7aea0 --- /dev/null +++ b/hw/misc/rp2040_ioqspi.c @@ -0,0 +1,294 @@ +/* + * RP2040 QSPI IO bank emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#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" + +#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_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 + +#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 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; + 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; + if (index =3D=3D IOQSPI_SS_INDEX) { + rp2040_ioqspi_update_ss(s); + } + } 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 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, + .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); + device_class_set_props(dc, rp2040_ioqspi_properties); + 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/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/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/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/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/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_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/hw/misc/rp2040_syscfg.c b/hw/misc/rp2040_syscfg.c new file mode 100644 index 0000000000..0e0dd712c6 --- /dev/null +++ b/hw/misc/rp2040_syscfg.c @@ -0,0 +1,246 @@ +/* + * 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/log.h" +#include "qemu/module.h" + +#define SYSCFG_PROC0_NMI_MASK 0x00 +#define SYSCFG_PROC1_NMI_MASK 0x04 +#define SYSCFG_PROC_CONFIG 0x08 +#define SYSCFG_PROC_IN_SYNC_BYPASS 0x0c +#define SYSCFG_PROC_IN_SYNC_BYPASS_HI 0x10 +#define SYSCFG_DBGFORCE 0x14 +#define SYSCFG_MEMPOWERDOWN 0x18 + +#define PROC_CONFIG_RW_MASK 0xff000000 +#define PROC_CONFIG_RESET 0x10000000 +#define PROC_IN_SYNC_BYPASS_MASK 0x3fffffff +#define PROC_IN_SYNC_BYPASS_HI_MASK 0x0000003f +#define DBGFORCE_RW_MASK 0x000000ee +#define DBGFORCE_RESET 0x00000066 +#define MEMPOWERDOWN_MASK 0x000000ff + +#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); + rp2040_syscfg_update(s); + 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..36268a1b22 --- /dev/null +++ b/hw/misc/rp2040_sysinfo.c @@ -0,0 +1,111 @@ +/* + * 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) + +/* + * The datasheet documents this as the chip source git hash, but does not + * require software-visible semantics beyond a stable chip-version value. + */ +#define SYSINFO_GITREF_QEMU 0x00000000 + +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: + value =3D 0; + break; + case SYSINFO_PLATFORM: + value =3D SYSINFO_PLATFORM_ASIC; + break; + case SYSINFO_GITREF_RP2040: + value =3D SYSINFO_GITREF_QEMU; + 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/hw/misc/rp2040_tbman.c b/hw/misc/rp2040_tbman.c new file mode 100644 index 0000000000..72460e9c95 --- /dev/null +++ b/hw/misc/rp2040_tbman.c @@ -0,0 +1,94 @@ +/* + * 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/log.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_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/hw/misc/rp2040_vreg.c b/hw/misc/rp2040_vreg.c new file mode 100644 index 0000000000..812a64fce4 --- /dev/null +++ b/hw/misc/rp2040_vreg.c @@ -0,0 +1,179 @@ +/* + * 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/log.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/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/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/hw/misc/unimp.c b/hw/misc/unimp.c index 521b84bf20..91ef789640 100644 --- a/hw/misc/unimp.c +++ b/hw/misc/unimp.c @@ -23,8 +23,10 @@ static uint64_t unimp_read(void *opaque, hwaddr offset, = unsigned size) UnimplementedDeviceState *s =3D UNIMPLEMENTED_DEVICE(opaque); =20 qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read " - "(size %d, offset 0x%0*" HWADDR_PRIx ")\n", - s->name, size, s->offset_fmt_width, offset); + "(size %d, addr 0x%08" PRIx64 + ", offset 0x%0*" HWADDR_PRIx ")\n", + s->name, size, s->base + offset, + s->offset_fmt_width, offset); return 0; } =20 @@ -34,9 +36,11 @@ static void unimp_write(void *opaque, hwaddr offset, UnimplementedDeviceState *s =3D UNIMPLEMENTED_DEVICE(opaque); =20 qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write " - "(size %d, offset 0x%0*" HWADDR_PRIx + "(size %d, addr 0x%08" PRIx64 + ", offset 0x%0*" HWADDR_PRIx ", value 0x%0*" PRIx64 ")\n", - s->name, size, s->offset_fmt_width, offset, size << 1, v= alue); + s->name, size, s->base + offset, + s->offset_fmt_width, offset, size << 1, value); } =20 static const MemoryRegionOps unimp_ops =3D { @@ -71,6 +75,7 @@ static void unimp_realize(DeviceState *dev, Error **errp) } =20 static const Property unimp_properties[] =3D { + DEFINE_PROP_UINT64("base", UnimplementedDeviceState, base, 0), DEFINE_PROP_UINT64("size", UnimplementedDeviceState, size, 0), DEFINE_PROP_STRING("name", UnimplementedDeviceState, name), }; 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/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_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/include/hw/misc/rp2040_ioqspi.h b/include/hw/misc/rp2040_ioqsp= i.h new file mode 100644 index 0000000000..eae63588e6 --- /dev/null +++ b/include/hw/misc/rp2040_ioqspi.h @@ -0,0 +1,36 @@ +/* + * 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) + +typedef struct RP2040XipState RP2040XipState; + +#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; + RP2040XipState *xip; +}; + +#endif 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 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/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/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/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_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/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/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_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/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/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/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/include/hw/misc/unimp.h b/include/hw/misc/unimp.h index 77a5077511..d06ca425ec 100644 --- a/include/hw/misc/unimp.h +++ b/include/hw/misc/unimp.h @@ -22,6 +22,7 @@ struct UnimplementedDeviceState { MemoryRegion iomem; unsigned offset_fmt_width; char *name; + uint64_t base; uint64_t size; }; =20 @@ -45,6 +46,7 @@ static inline void create_unimplemented_device(const char= *name, DeviceState *dev =3D qdev_new(TYPE_UNIMPLEMENTED_DEVICE); =20 qdev_prop_set_string(dev, "name", name); + qdev_prop_set_uint64(dev, "base", base); qdev_prop_set_uint64(dev, "size", size); sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal); =20 --=20 2.53.0 From nobody Sun Jul 26 11:08:36 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=1783290545; cv=none; d=zohomail.com; s=zohoarc; b=Uy5s+YPbFZ0/XRKS2iEHLvLikmX/IgSqvgxziJajVeKCDZcy0Kh1OePryOlrKB4QPgo221Wfpu/hKcTzKH1MdkO5Q6KmaxItk9q0qFxE3k5KAtZLj6sg24Mne0uqzbPp8wVckMjy0WtUHX9Jf3rPD/lFu+SNcD2V01ilmzeUqU0= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290545; 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=xCfOCgqDLPldVkUKoAMQH8J8GPFKCaDBa0QZtcWZzXI=; b=OdOpzqkiLFoeel2cnuwpVz8Em5ENi/vaQsgVwysDEX6QtdQZVVSQF4/68qUJBdHV+apwtcSKr4/ht1a/khYgcxw3+yuJo0Zwia6Yvrp7h0lNd3UG3JW9UmAHeWtFAT9jcdQhfmT7KWZQROyjfDnImlgTLUO3oXKtSwVReADAN4k= 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 1783290544542792.8266239659118; Sun, 5 Jul 2026 15:29:04 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJx-0006me-B4; Sun, 05 Jul 2026 18:28:17 -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 1wgUwB-0002FI-Ou; Sun, 05 Jul 2026 18:03:44 -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 1wgUw6-0001k6-Sq; Sun, 05 Jul 2026 18:03:43 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4gthMG1Jdsz85Xb; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMF6ykYz6sT6; Mon, 6 Jul 2026 00:03:21 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289002; bh=xCfOCgqDLPldVkUKoAMQH8J8GPFKCaDBa0QZtcWZzXI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=IS/1s7ByUCgRP93YpN3nHWq/Je9P857ut0SL5MNgXtF30fYZjpUd1pT1GtxQZjsX+ 9bY5KSIzB36v6Mq1MdKPqZ0fRNpOTz5TnRHPvakRffDiLwYPQvxtpyOS66SKqIsF+f reswJgY9A7n1dVRWDtWYIAvFxJFkJBgFcjGPk6oshB1sNu99yrPQWAEgWBG7GmpTv8 WjF6YK4HRAURJjfxiEIs1gbgJMMNadl9HpeK7hYMyBH79lMw7pSCJJ9xkLg404xYyq o2seMBel+OHnD4dKzCKv6DGUhDG5FtZfw110FbuI0e4cjgToRnMOzjwQPxfZ5pMXsE eMmAA0GK8U0Pw== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 2/9] hw/ssi: add RP2040 XIP flash model Date: Mon, 6 Jul 2026 00:03:13 +0200 Message-ID: <20260705220320.90395-3-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290549403158500 Content-Type: text/plain; charset="utf-8" Add the RP2040 XIP/SSI flash model used by the Pico board. The model suppor= ts direct XIP reads, UF2/ELF/raw overlays from -kernel, raw flash backing f= iles, basic NOR program/erase semantics, XIP stream reads and DMA-visible a= ccesses. The model intentionally stays functional rather than cycle accurate; unsupp= orted flash commands are reported through the shared RP2040 not-yet-impleme= nted diagnostics. Signed-off-by: Gilles Grimaud --- hw/ssi/Kconfig | 3 + hw/ssi/meson.build | 1 + hw/ssi/rp2040_xip.c | 1465 +++++++++++++++++++++++++++++++++++ hw/ssi/trace-events | 12 + include/hw/ssi/rp2040_xip.h | 94 +++ 5 files changed, 1575 insertions(+) create mode 100644 hw/ssi/rp2040_xip.c create mode 100644 include/hw/ssi/rp2040_xip.h diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig index 1bd56463c1..e31077862b 100644 --- a/hw/ssi/Kconfig +++ b/hw/ssi/Kconfig @@ -29,6 +29,9 @@ config PNV_SPI bool select SSI =20 +config RP2040_XIP + bool + config ALLWINNER_A10_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..5cc5ce8093 --- /dev/null +++ b/hw/ssi/rp2040_xip.c @@ -0,0 +1,1465 @@ +/* + * 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 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 +#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; + } +} + +static MemTxResult rp2040_xip_read_common(RP2040XipState *s, hwaddr addr, + uint64_t *data, unsigned size, + bool synthetic_vector_overlay) +{ + 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]; + + /* + * Synthetic ROM CI exit support: let firmware copy a HardFault ve= ctor + * that points back into ROM without mutating persistent flash sto= rage. + */ + if (synthetic_vector_overlay && + s->synthetic_hardfault_vector_enabled && + cur >=3D RP2040_BOOT2_SIZE + 0x0c && + cur < RP2040_BOOT2_SIZE + 0x10) { + byte =3D extract32(s->synthetic_hardfault_vector, + (cur - (RP2040_BOOT2_SIZE + 0x0c)) * 8, 8); + } + 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_common(opaque, addr, data, size, true); +} + +static MemTxResult rp2040_xip_alias_read(void *opaque, hwaddr addr, + uint64_t *data, unsigned size, + MemTxAttrs attrs) +{ + return rp2040_xip_read_common(opaque, addr, data, size, false); +} + +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; + + 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_set_synthetic_hardfault_vector(RP2040XipState *s, + uint32_t handler) +{ + s->synthetic_hardfault_vector =3D handler; + s->synthetic_hardfault_vector_enabled =3D true; +} + +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; +} + +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) +{ + 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; + } + + 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); + 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 2f36cf96b8..e1f8cd75ec 100644 --- a/hw/ssi/trace-events +++ b/hw/ssi/trace-events @@ -33,6 +33,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/ssi/rp2040_xip.h b/include/hw/ssi/rp2040_xip.h new file mode 100644 index 0000000000..9081f19d51 --- /dev/null +++ b/include/hw/ssi/rp2040_xip.h @@ -0,0 +1,94 @@ +/* + * RP2040 XIP/SSI flash controller emulation + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_SSI_RP2040_XIP_H +#define HW_SSI_RP2040_XIP_H + +#include "hw/core/sysbus.h" +#include "hw/core/irq.h" +#include "qom/object.h" + +#define TYPE_RP2040_XIP "rp2040-xip" +OBJECT_DECLARE_SIMPLE_TYPE(RP2040XipState, RP2040_XIP) + +#define RP2040_XIP_CTRL_BASE 0x14000000 +#define RP2040_XIP_SSI_BASE 0x18000000 +#define RP2040_XIP_AUX_BASE 0x50400000 +#define RP2040_XIP_CTRL_SIZE 0x4000 +#define RP2040_XIP_SSI_SIZE 0x4000 +#define RP2040_XIP_AUX_SIZE 0x4000 +#define RP2040_XIP_STREAM_FIFO_DEPTH 4 + +struct RP2040XipState { + SysBusDevice parent_obj; + + MemoryRegion xip; + MemoryRegion xip_noalloc; + MemoryRegion xip_nocache; + MemoryRegion xip_nocache_noalloc; + MemoryRegion ctrl; + MemoryRegion ssi; + MemoryRegion aux; + qemu_irq dreq_rx; + qemu_irq dreq_stream; + + uint32_t flash_size; + char *flash_file; + uint64_t flash_uid; + uint8_t *storage; + bool xip_writable; + + uint32_t xip_ctrl; + + uint32_t ctrlr0; + uint32_t ctrlr1; + uint32_t ssienr; + uint32_t ser; + uint32_t baudr; + uint32_t txftlr; + uint32_t rxftlr; + uint32_t imr; + uint32_t dmacr; + uint32_t dmatdlr; + uint32_t dmardlr; + uint32_t rx_sample_dly; + uint32_t spi_ctrlr0; + + bool write_enable; + bool busy; + bool synthetic_hardfault_vector_enabled; + uint32_t synthetic_hardfault_vector; + 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); +void rp2040_xip_set_synthetic_hardfault_vector(RP2040XipState *s, + uint32_t handler); +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 --=20 2.53.0 From nobody Sun Jul 26 11:08:36 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=1783290591; cv=none; d=zohomail.com; s=zohoarc; b=g/uoiSQOKOPwjZm+qywugjJjAEWcjW48BuCzpgjFSbgCo3RvJ2X77UIacr851UPuqnlEBQODnGPRkVAg6qBdMECM2BjNElUELEAVe/NJgBwvn4uxFCkHyJzhkkqtABusJoLid0g/jLXybWKmaexGS9f5/UJ28cnHMDLhhaLANts= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290591; 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=Gm90oyUNlaNPGf2wnyQvIDxJ66fFR4rAlPCuC1y0eMY=; b=cfgQraU3FPfg0YHm+Y1wpXTVVApkTADePrR970CqC0VPAqfrHVlL56uUlXpenGOUomN02/ow8iDfFfAh7yGnEwvEnyZG7hBrpPiRkwLcKWtWAXsTGuQEOERXeH+4ZU0eznW6TeQi3e7enliqMIRjK9B6QKFG38rFKBZG0mKvowE= 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 1783290591927340.32005744096455; Sun, 5 Jul 2026 15:29:51 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJy-0006nL-5z; Sun, 05 Jul 2026 18:28: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 1wgUwM-0002HN-Vy; Sun, 05 Jul 2026 18:03: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 1wgUwJ-0001w0-4e; Sun, 05 Jul 2026 18:03:54 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4gthMG2NnGz85Xk; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMG1BJnz6tBv; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289002; bh=Gm90oyUNlaNPGf2wnyQvIDxJ66fFR4rAlPCuC1y0eMY=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=UIH6JcBWkw9ThGAqy9QbQ6K1H+IGMTm1EPtWgCW0L5iYLE+4rv1059F2mqFSF8PRw xi3jMdiXAzmm81PuOs/qQBXTt8dstny5C7Aq/MxcLBQaFSymtZBLqUxNRGIWhO0z78 wY3lWuNokr/VhSW3c7PiBsRCFaiQz2hdTkVxIZPFQFgi5UouZIz9vHHCzt0gdZGoTu GWg1QBR5ZIPB3pKjKUpaOyYq5Nq5rBecARlAooL/w/WbGZkag+Md/f1tDCHi10qMND Ubj9L6234iT+yF5+DlrOrDLEfGRhqgPQCIbIE1xR8vg+SMaQLx3vWivesl0Usi3uud SKn2wYq9oao+g== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 3/9] hw/dma: add RP2040 DMA controller Date: Mon, 6 Jul 2026 00:03:14 +0200 Message-ID: <20260705220320.90395-4-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290594438158500 Content-Type: text/plain; charset="utf-8" Add a functional RP2040 DMA controller with channel registers, interrupts, = chaining, ring wrapping, sniff checksums, abort status, DREQ plumbing and A= HB error reporting suitable for Pico SDK workloads. The model favours deterministic functional behaviour over detailed bus-cycl= e timing. Signed-off-by: Gilles Grimaud --- hw/dma/Kconfig | 3 + hw/dma/meson.build | 1 + hw/dma/rp2040_dma.c | 1032 +++++++++++++++++++++++++++++++++++ include/hw/dma/rp2040_dma.h | 77 +++ 4 files changed, 1113 insertions(+) create mode 100644 hw/dma/rp2040_dma.c create mode 100644 include/hw/dma/rp2040_dma.h diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig index 98fbb1bb04..d91fb53f71 100644 --- a/hw/dma/Kconfig +++ b/hw/dma/Kconfig @@ -30,3 +30,6 @@ config SIFIVE_PDMA config XLNX_CSU_DMA bool select REGISTER + +config RP2040_DMA + bool diff --git a/hw/dma/meson.build b/hw/dma/meson.build index cc7810beb8..11437b15f9 100644 --- a/hw/dma/meson.build +++ b/hw/dma/meson.build @@ -10,5 +10,6 @@ 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'= )) 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/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 --=20 2.53.0 From nobody Sun Jul 26 11:08:36 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=1783290609; cv=none; d=zohomail.com; s=zohoarc; b=d39FVW/JVvcv19sxa3W1dyTKbWXVN1p83Sm8N8TX8ggs2plfxIVkwwggbUQvbE0BcKmxgxrtf9KLvoQfQ+OAChT1Ypo0NKghRkRNjTrD3ib5NtJduLG64evQe0XRYgI3+6NnNo+eenyvL1s54q1aLhtJZjV/AyiNxIHYiqagLIw= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290609; 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=qIHCJs8odbg5/PUzASEvth7UI0FqeJM5X0+4+tlDNmU=; b=j3B6OIvX7idJWRzlHroPjLrr/VsvAp5CUYGWqpI9+n6bigcCPoVOemV2/0j2Swi9ClUaKI+WsgXRLzx/3NOD3+5EA04Cvl+gPYKdnFL2PIqHXiDzUcxYCuqbuw/v0mD9MP7PEbIp/ZEeME+/01ugEuQG3mp12hryNLfPNcPzIDU= 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 1783290609230707.2360347933246; Sun, 5 Jul 2026 15:30:09 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJx-0006md-3w; Sun, 05 Jul 2026 18:28:17 -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 1wgUw0-0002BE-Ud; Sun, 05 Jul 2026 18:03:32 -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 1wgUvy-0001gF-Tq; Sun, 05 Jul 2026 18:03:32 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4gthMG3K0XzBsqx; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMG2F7sz6sT6; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289002; bh=qIHCJs8odbg5/PUzASEvth7UI0FqeJM5X0+4+tlDNmU=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=bLWqsffcaEf6F0cN1O2A35Gg/0ze1t+k+e2icv4kqcPvj+nxeeEKnscGfK4KGAbEc /Mvj/c+Zb8qnA4M4j24RQq6/d6MXZgFJgGqZSjy0Mmpds9Q+3SYpFowXDky2cy0cIj HSpO5l4yC/ThmEyuN/befviiBViy7nKPYEt+k6z4YVk90nYu0qqeOA+JcQXtXbXHtn hXT8BWmN/9WS+vonVl3JMkSfC+uDd4Lif8sB5xlLGFaPkNtteK0MF0jNS69uCzT444 YvLsTIe1wO3ZSJZekkZhYLV0X+fYqCWVHuqY1041LDnx4FuLhHFnQH5YzeamOO4Wao m5+W1m4qZM4yw== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 4/9] hw/char/pl011: expose DMA request notifications Date: Mon, 6 Jul 2026 00:03:15 +0200 Message-ID: <20260705220320.90395-5-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290612071158500 Content-Type: text/plain; charset="utf-8" Allow board code to observe PL011 DMA request line changes. The RP2040 Pico= machine uses this to connect UART0 and UART1 TX/RX activity to the RP2040 = DMA DREQ model without forking the PL011 device. Signed-off-by: Gilles Grimaud --- hw/char/pl011.c | 116 +++++++++++++++++++++++++++++++++++++--- include/hw/char/pl011.h | 8 +++ 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/hw/char/pl011.c b/hw/char/pl011.c index cb12c3e224..40876b92df 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) @@ -96,6 +101,12 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Ch= ardev *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 @@ -157,6 +168,34 @@ 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) +{ + if (s->dreq_tx) { + qemu_set_irq(s->dreq_tx, + (s->dmacr & DMACR_TXDMAE) && + !(s->flags & PL011_FLAG_TXFF)); + } + if (s->dreq_rx) { + qemu_set_irq(s->dreq_rx, + (s->dmacr & DMACR_RXDMAE) && s->read_count > 0); + } +} + +static uint32_t pl011_apply_atomic_alias(uint32_t old, uint32_t value, + hwaddr alias) +{ + switch (alias) { + case PL011_ATOMIC_XOR: + return old ^ value; + case PL011_ATOMIC_SET: + return old | value; + case PL011_ATOMIC_CLR: + return old & ~value; + default: + return value; + } +} + static inline void pl011_reset_rx_fifo(PL011State *s) { s->read_count =3D 0; @@ -190,6 +229,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); @@ -254,10 +294,17 @@ 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); + pl011_update_dreq(s); } =20 static uint32_t pl011_read_rxdata(PL011State *s) @@ -280,6 +327,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 +517,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 +541,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, @@ -503,6 +550,23 @@ static void pl011_write(void *opaque, hwaddr offset, } } =20 +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 int pl011_can_receive(void *opaque) { PL011State *s =3D (PL011State *)opaque; @@ -518,6 +582,9 @@ static int pl011_can_receive(void *opaque) * historically never enforced that. So we effectively keep the * UART continuously enabled regardless of the enable bits. */ + if (!s->rx_connected) { + return 0; + } =20 trace_pl011_can_receive(s->lcr, s->read_count, fifo_depth, fifo_availa= ble); return fifo_available; @@ -526,6 +593,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, @@ -542,11 +612,28 @@ 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 void pl011_clock_update(void *opaque, ClockEvent event) { PL011State *s =3D PL011(opaque); @@ -562,6 +649,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); @@ -648,11 +743,19 @@ 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]); } + 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 +790,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..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; @@ -49,9 +50,14 @@ 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 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 @@ -61,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 --=20 2.53.0 From nobody Sun Jul 26 11:08:36 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=1783290614; cv=none; d=zohomail.com; s=zohoarc; b=MN0FO5DIHM9csNqIhfC225VE2JUDcstm0ImpGf6NwrtORPYfyHiQy6f5MuUDB/rzAh4orRYF20+4vFzubE8MBjSLLOvZ9vsLzpofJAMf3LAsk0W4C/swi3JySiDZ+x7N6Q3BTL6tfAMLsZH1GzAwSwKZfKF451T+3EwOa9VJpxE= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290614; 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=3xM0zJ+GpozPi5npm+blxQXFgeoVFlkVVTWOOD7uzhE=; b=iTdM0bp+d+uCmkD7NyHgY+P+Sprb6uE2tt2Z/wgva4oeG/1fVI9BpbuQQljhvanvTfpnfdvRqBAf+mOoTYTTxIl1p4DkPgo7T8+YzrTC1KKukFxTys/qATVtmnsjxCg0Zoz5+Dl0d65z+jmvIHFevkwFXlXDy23SaN7KYRe5AGc= 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 178329061411512.588907083283061; Sun, 5 Jul 2026 15:30:14 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJx-0006mt-OG; Sun, 05 Jul 2026 18:28:17 -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 1wgUwZ-0002Jx-6D; Sun, 05 Jul 2026 18:04:07 -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 1wgUwU-0001xZ-4p; Sun, 05 Jul 2026 18:04:06 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4gthMG4RKMz85Xr; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMG3F2pz6tBv; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289002; bh=3xM0zJ+GpozPi5npm+blxQXFgeoVFlkVVTWOOD7uzhE=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=F79uyxxIsvFTfjM3zUeMluxSlCTs5Pg/SEPnBjBy11N0eTVEEfwiZDetp/R7mHxS1 QYUtj8NFjsa/sSvz8aztotzCeIHR18CGbuRx5IeDh+hDZkT213e5YiFBBrywIQvtpu REBTsNQHul5W3hxjmDnVdZ2Qi0CG4CJgH7ugvIvm4so7OBMK8W/ofKPsrucbluqzfO +BMxxfuxpSgztQEx+4RNtiYJC0XE5kMuwZLTmDhtk72DpTjlyMJPoaWjLYDyXmygEb jbPP07y8IT02NUqxdgBjD+rNIWY1huBaihTaOc740aJO3PHiq7oBIUDpECjDyO5tZc Nb08qpnkauEEg== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 5/9] hw/arm: add Raspberry Pi Pico RP2040 machine Date: Mon, 6 Jul 2026 00:03:16 +0200 Message-ID: <20260705220320.90395-6-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290616347158500 Add an RP2040 SoC model and a Raspberry Pi Pico board machine. The machine = provides Cortex-M0+ cores, SRAM, XIP flash, UART0/UART1, DMA, a synthetic R= OM boot path, optional external mask ROM loading through -bios, flash-file = persistence, flash UID configuration, and -kernel loading for ELF, UF2 and = raw images. The implementation follows the direction of Alex Benn=C3=A9e's 2022 RP2040/= Pico RFC series while adapting the code to current QEMU APIs and the functi= onal model developed here. Signed-off-by: Gilles Grimaud --- MAINTAINERS | 20 + configs/devices/arm-softmmu/default.mak | 1 + hw/arm/Kconfig | 32 + hw/arm/meson.build | 2 + hw/arm/raspi_pico.c | 265 +++ hw/arm/rp2040.c | 2052 +++++++++++++++++++++++ hw/arm/trace-events | 3 + include/hw/arm/rp2040.h | 122 ++ 8 files changed, 2497 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 97dcc78ded..104c02ff76 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1023,6 +1023,26 @@ 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 +R: Alex Benn=C3=A9e +L: qemu-arm@nongnu.org +S: Maintained +F: configs/devices/arm-softmmu/default.mak +F: docs/system/arm/raspi-pico.rst +F: hw/arm/raspi_pico.c +F: hw/arm/rp2040.c +F: hw/dma/rp2040_dma.c +F: hw/misc/rp2040_*.c +F: hw/ssi/rp2040_xip.c +F: include/hw/arm/rp2040.h +F: include/hw/dma/rp2040_dma.h +F: include/hw/misc/rp2040_*.h +F: include/hw/ssi/rp2040_xip.h +F: scripts/uf2-to-flash.py +F: tests/functional/arm/test_raspi_pico.py +F: tests/qtest/rp2040-*.c + Real View M: Peter Maydell L: qemu-arm@nongnu.org diff --git a/configs/devices/arm-softmmu/default.mak b/configs/devices/arm-= softmmu/default.mak index 71cf164511..824d6ebfd9 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=3Dn # CONFIG_DIGIC=3Dn # CONFIG_SABRELITE=3Dn # CONFIG_EMCRAFT_SF2=3Dn diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig index 5f5c4899ad..125c36cc99 100644 --- a/hw/arm/Kconfig +++ b/hw/arm/Kconfig @@ -367,6 +367,38 @@ 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_BUSCTRL + select RP2040_CLOCKS + select RP2040_DMA + select RP2040_IOBANK0 + select RP2040_IOQSPI + select RP2040_NYI + select RP2040_PADS + select RP2040_PLL + select RP2040_PSM + select RP2040_RESETS + select RP2040_ROSC + select RP2040_SIO + select RP2040_SYSCFG + select RP2040_SYSINFO + select RP2040_TBMAN + select RP2040_TIMER + select RP2040_VREG + select RP2040_WATCHDOG + select RP2040_XIP + select RP2040_XOSC + select UNIMP + config STM32F100_SOC bool select ARM_V7M diff --git a/hw/arm/meson.build b/hw/arm/meson.build index 4233a800be..0b79b9414a 100644 --- a/hw/arm/meson.build +++ b/hw/arm/meson.build @@ -11,6 +11,7 @@ arm_common_ss.add(when: 'CONFIG_NETDUINOPLUS2', if_true: = files('netduinoplus2.c' arm_common_ss.add(when: 'CONFIG_OLIMEX_STM32_H405', if_true: files('olimex= -stm32-h405.c')) arm_common_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx.c', 'npc= m7xx_boards.c')) arm_common_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm8xx.c', 'npc= m8xx_boards.c')) +arm_common_ss.add(when: 'CONFIG_RASPI_PICO', if_true: files('raspi_pico.c'= )) arm_common_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c')) arm_common_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c')) arm_common_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c')) @@ -93,6 +94,7 @@ arm_common_ss.add(when: 'CONFIG_TEGRA241_CMDQV', if_true:= files('tegra241-cmdqv. stub_ss.add(files('tegra241-cmdqv-stubs.c')) arm_common_ss.add(when: 'CONFIG_FSL_IMX6UL', if_true: files('fsl-imx6ul.c'= , 'mcimx6ul-evk.c')) arm_common_ss.add(when: 'CONFIG_NRF51_SOC', if_true: files('nrf51_soc.c')) +arm_common_ss.add(when: 'CONFIG_RP2040', if_true: files('rp2040.c')) arm_common_ss.add(when: 'CONFIG_XEN', if_true: files( 'xen-stubs.c', 'xen-pvh.c', diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c new file mode 100644 index 0000000000..ea229214b0 --- /dev/null +++ b/hw/arm/raspi_pico.c @@ -0,0 +1,265 @@ +/* + * 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 "qemu/cutils.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) + +struct RaspiPicoMachineState { + MachineState parent_obj; + + RP2040State soc; + char *flash_file; + char *flash_uid; + uint64_t flash_uid_value; + bool flash_uid_set; + char *rosc_random_seed; + uint64_t rosc_random_seed_value; + bool rosc_random_seed_set; + bool strict_uart_pins; +}; + +static char *raspi_pico_get_flash_file(Object *obj, Error **errp) +{ + RaspiPicoMachineState *s =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); + + 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); + 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)); + 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", + s->rosc_random_seed_set); + /* + * BOOTSEL is not pressed by default on a Pico board, so the mask ROM = sees + * the QSPI SS input deasserted and tries to boot from external flash. + */ + 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); + } + object_property_set_link(OBJECT(&s->soc), "memory", + OBJECT(system_memory), &error_fatal); + + sysbus_realize(SYS_BUS_DEVICE(&s->soc), &error_fatal); + + /* + * For now, -kernel images are loaded directly into the XIP window. + * rp2040_xip_load_image() accepts ELF images first, then raw images. + */ + 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 * Mi= B); + rp2040_xip_set_writable(&s->soc.xip, false); +} + +static void raspi_pico_machine_finalize(Object *obj) +{ + RaspiPicoMachineState *s =3D RASPI_PICO_MACHINE(obj); + + g_free(s->flash_file); + g_free(s->flash_uid); + g_free(s->rosc_random_seed); +} + +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); + + mc->desc =3D "Raspberry Pi Pico (Cortex-M0+)"; + mc->init =3D raspi_pico_init; + mc->default_cpus =3D RP2040_NUM_CORES; + mc->min_cpus =3D RP2040_NUM_CORES; + mc->max_cpus =3D RP2040_NUM_CORES; + mc->no_parallel =3D 1; + mc->no_floppy =3D 1; + mc->no_cdrom =3D 1; + + object_class_property_add_str(oc, "flash-file", + raspi_pico_get_flash_file, + raspi_pico_set_flash_file); + object_class_property_set_description(oc, "flash-file", + "Load initial XIP flash contents= " + "from a raw host file"); + object_class_property_add_str(oc, "flash-uid", + raspi_pico_get_flash_uid, + raspi_pico_set_flash_uid); + object_class_property_set_description(oc, "flash-uid", + "Set the emulated external flash= " + "64-bit unique ID as 16 hex digi= ts"); + 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"); + 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 GPIO0/GPIO1 IO_BANK0 " + "FUNCSEL=3DUART before UART0 rea= ches " + "the host serial backend"); +} + +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, +}; + +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..aecfb9284b --- /dev/null +++ b/hw/arm/rp2040.c @@ -0,0 +1,2052 @@ +/* + * 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/qdev-clock.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/loader.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/runstate.h" +#include "target/arm/cpu.h" +#include "target/arm/cpu-qom.h" +#include "trace.h" + +#include + +#define RP2040_UART0_BASE 0x40034000 +#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 +#define RP2040_PROC1 1 + +#define RP2040_BOOTROM_FUNC_TABLE_PTR_OFFSET 0x14 +#define RP2040_BOOTROM_DATA_TABLE_PTR_OFFSET 0x16 +#define RP2040_BOOTROM_TABLE_LOOKUP_PTR_OFFSET 0x18 +#define RP2040_BOOTROM_LOOKUP_OFFSET 0x0100 +#define RP2040_BOOTROM_STUBS_OFFSET 0x0120 +#define RP2040_BOOTROM_FUNC_TABLE_OFFSET 0x0300 +#define RP2040_BOOTROM_DATA_TABLE_OFFSET 0x0340 +#define RP2040_BOOTROM_FLOAT_NYI_STUB_OFFSET 0x0360 +#define RP2040_BOOTROM_DOUBLE_NYI_STUB_OFFSET 0x0380 +#define RP2040_BOOTROM_FLOAT_TABLE_OFFSET 0x03c0 +#define RP2040_BOOTROM_DOUBLE_TABLE_OFFSET 0x0460 +#define RP2040_BOOTROM_FLOAT_STUBS_OFFSET 0x0600 +#define RP2040_BOOTROM_DOUBLE_STUBS_OFFSET 0x0b00 +#define RP2040_BOOTROM_FP_STUB_SIZE 36 +#define RP2040_BOOTROM_HARDFAULT_EXIT_OFFSET 0x0f80 +#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_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_ROM_VERSION_OFFSET 0x13 +#define RP2040_BOOTROM_SYNTHETIC_ROM_VERSION 2 +#define RP2040_BOOTROM_FLOAT_TABLE_WORDS 32 + +#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_ROM_DBG_CMD_EXIT 0x54495845 /* "EXIT" */ + +#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 + +#define USBCTRL_ADDR_ENDP 0x00 +#define USBCTRL_SIE_CTRL 0x4c +#define USBCTRL_SIE_STATUS 0x50 +#define USBCTRL_INT_EP_CTRL 0x54 +#define USBCTRL_BUFF_STATUS 0x58 +#define USBCTRL_BUFF_CPU_HANDLE 0x5c +#define USBCTRL_EP_ABORT 0x60 +#define USBCTRL_EP_ABORT_DONE 0x64 +#define USBCTRL_EP_STALL_ARM 0x68 +#define USBCTRL_NAK_POLL 0x6c +#define USBCTRL_EP_STATUS 0x70 +#define USBCTRL_USB_MUXING 0x74 +#define USBCTRL_USB_PWR 0x78 +#define USBCTRL_USBPHY_DIRECT 0x7c +#define USBCTRL_USBPHY_TRIM 0x80 +#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 + +/* + * 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, /* initial SP: 0x20042000 */ + 0x41, 0x00, 0x00, 0x00, /* reset handler: 0x00000041 */ + 0xb3, 0x00, 0x00, 0x00, /* NMI handler: 0x000000b3 */ + 0xb3, 0x00, 0x00, 0x00, /* HardFault handler: 0x000000b3 */ + 0xb3, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* SVC handler: 0x000000b3 */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0x00, 0x00, 0x00, 0x00, /* reserved */ + 0xb3, 0x00, 0x00, 0x00, /* PendSV handler: 0x000000b3 */ + 0xb3, 0x00, 0x00, 0x00, /* SysTick handler: 0x000000b3 */ + 0x26, 0x4c, /* ldr r4, [pc, #152] ; SIO_BASE */ + 0x20, 0x68, /* ldr r0, [r4] ; SIO_CPUID */ + 0x00, 0x28, /* cmp r0, #0 */ + 0x13, 0xd1, /* bne core1 path */ + 0x25, 0x48, /* ldr r0, [pc, #148] ; 0x10000000 */ + 0x26, 0x49, /* ldr r1, [pc, #152] ; 0x20041f00 */ + 0x40, 0x22, /* movs r2, #64 */ + 0x03, 0x68, /* ldr r3, [r0] */ + 0x0b, 0x60, /* str r3, [r1] */ + 0x04, 0x30, /* adds r0, #4 */ + 0x04, 0x31, /* adds r1, #4 */ + 0x01, 0x3a, /* subs r2, #1 */ + 0xf9, 0xd1, /* bne copy loop */ + 0x23, 0x4b, /* ldr r3, [pc, #140] ; launch entry */ + 0x9e, 0x46, /* mov lr, r3 */ + 0x23, 0x48, /* ldr r0, [pc, #140] ; 0x20041f01 */ + 0x00, 0x47, /* bx r0 */ + 0x23, 0x48, /* ldr r0, [pc, #140] ; 0x10000100 */ + 0x23, 0x49, /* ldr r1, [pc, #140] ; VTOR */ + 0x08, 0x60, /* str r0, [r1] */ + 0x06, 0xc8, /* ldm r0!, {r1, r2} */ + 0x81, 0xf3, 0x08, 0x88, /* msr msp, r1 */ + 0x10, 0x47, /* bx r2 */ + 0x21, 0x4d, /* ldr r5, [pc, #132] ; sequence */ + 0x00, 0x26, /* movs r6, #0 */ + 0x00, 0xf0, 0x1e, 0xf8, /* bl fifo_pop */ + 0x03, 0x2e, /* cmp r6, #3 */ + 0x07, 0xd2, /* bhs echo */ + 0xb1, 0x00, /* lsls r1, r6, #2 */ + 0x6a, 0x58, /* ldr r2, [r5, r1] */ + 0x90, 0x42, /* cmp r0, r2 */ + 0x0c, 0xd0, /* beq echo */ + 0x00, 0x26, /* movs r6, #0 */ + 0x00, 0xf0, 0x1c, 0xf8, /* bl fifo_push */ + 0xf3, 0xe7, /* b core1 loop */ + 0x03, 0x2e, /* cmp r6, #3 */ + 0x01, 0xd1, /* bne maybe SP */ + 0x07, 0x46, /* mov r7, r0 */ + 0x04, 0xe0, /* b echo */ + 0x04, 0x2e, /* cmp r6, #4 */ + 0x01, 0xd1, /* bne save PC */ + 0x03, 0x46, /* mov r3, r0 */ + 0x00, 0xe0, /* b echo */ + 0x05, 0x46, /* mov r5, r0 */ + 0x00, 0xf0, 0x10, 0xf8, /* bl fifo_push */ + 0x01, 0x36, /* adds r6, #1 */ + 0x06, 0x2e, /* cmp r6, #6 */ + 0xe5, 0xd1, /* bne core1 loop */ + 0x12, 0x49, /* ldr r1, [pc, #72] ; VTOR */ + 0x0f, 0x60, /* str r7, [r1] */ + 0x83, 0xf3, 0x08, 0x88, /* msr msp, r3 */ + 0x28, 0x47, /* bx r5 */ + 0xfe, 0xe7, /* hang */ + 0x09, 0x4c, /* ldr r4, [pc, #36] ; SIO_BASE */ + 0x20, 0x6d, /* ldr r0, [r4, #0x50] */ + 0x01, 0x21, /* movs r1, #1 */ + 0x08, 0x42, /* tst r0, r1 */ + 0xfb, 0xd0, /* beq fifo_pop */ + 0xa0, 0x6d, /* ldr r0, [r4, #0x58] */ + 0x70, 0x47, /* bx lr */ + 0x06, 0x4c, /* ldr r4, [pc, #24] ; SIO_BASE */ + 0x21, 0x6d, /* ldr r1, [r4, #0x50] */ + 0x02, 0x22, /* movs r2, #2 */ + 0x11, 0x42, /* tst r1, r2 */ + 0xfb, 0xd0, /* beq fifo_push */ + 0x60, 0x65, /* str r0, [r4, #0x54] */ + 0x70, 0x47, /* bx lr */ + 0x00, 0x00, 0x00, 0x00, /* core1 sequence[0] */ + 0x00, 0x00, 0x00, 0x00, /* core1 sequence[1] */ + 0x01, 0x00, 0x00, 0x00, /* core1 sequence[2] */ + 0x00, 0x00, 0x00, 0xd0, /* SIO_BASE */ + 0x00, 0x00, 0x00, 0x10, /* boot2 source: 0x10000000 */ + 0x00, 0x1f, 0x04, 0x20, /* boot2 SRAM copy: 0x20041f00 */ + 0x63, 0x00, 0x00, 0x00, /* post-boot2 launch entry: 0x00000063 */ + 0x01, 0x1f, 0x04, 0x20, /* boot2 SRAM entry: 0x20041f01 */ + 0x00, 0x01, 0x00, 0x10, /* application vectors: 0x10000100 */ + 0x08, 0xed, 0x00, 0xe0, /* VTOR: 0xe000ed08 */ + 0xd0, 0x00, 0x00, 0x00, /* core1 sequence table */ +}; + +static const struct { + const char *name; + hwaddr base; + hwaddr size; +} rp2040_unimplemented[] =3D { + { "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.rtc", 0x4005c000, 0x4000 }, + { "rp2040.pio0", 0x50200000, 0x10000 }, + { "rp2040.pio1", 0x50300000, 0x10000 }, +}; + +typedef struct RP2040BootromFunction { + uint16_t code; + const char *name; + const uint8_t *impl; + size_t impl_size; + uint32_t code_literal_offset; +} 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, + 0x01, 0x3a, 0xf7, 0xe7, 0x18, 0x46, 0x10, 0xbc, + 0x70, 0x47, +}; + +static const uint8_t rp2040_bootrom_memset4[] =3D { + 0x03, 0x46, 0x00, 0x2a, 0x03, 0xd0, 0x01, 0x60, + 0x04, 0x30, 0x01, 0x3a, 0xf9, 0xe7, 0x18, 0x46, + 0x70, 0x47, +}; + +static const uint8_t rp2040_bootrom_flash_noarg[] =3D { + 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), 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, UINT32_MAX } + +static const RP2040BootromFunction rp2040_bootrom_functions[] =3D { + 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", + 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, /* ldrh r2, [r0] */ + 0x00, 0x2a, /* cmp r2, #0 */ + 0x05, 0xd0, /* beq not_found */ + 0x91, 0x42, /* cmp r1, r2 */ + 0x01, 0xd0, /* beq found */ + 0x04, 0x30, /* adds r0, #4 */ + 0xf8, 0xe7, /* b loop */ + 0x40, 0x88, /* found: ldrh r0, [r0, #2] */ + 0x70, 0x47, /* bx lr */ + 0x5f, 0x22, /* not_found: movs r2, #0x5f */ + 0x12, 0x06, /* lsls r2, r2, #24 */ + 0xff, 0x23, /* movs r3, #0xff */ + 0x1b, 0x04, /* lsls r3, r3, #16 */ + 0x1a, 0x43, /* orrs r2, r3 ; 0x5fff0000 */ + 0x11, 0x60, /* str r1, [r2] */ + 0x00, 0xbe, /* bkpt #0 */ + 0x00, 0x20, /* movs r0, #0 */ + 0x70, 0x47, /* bx lr */ +}; + +static const uint8_t rp2040_bootrom_nyi_stub[] =3D { + 0x04, 0x49, /* ldr r1, [pc, #16] ; function code */ + 0x5f, 0x20, /* movs r0, #0x5f */ + 0x00, 0x06, /* lsls r0, r0, #24 */ + 0xff, 0x22, /* movs r2, #0xff */ + 0x12, 0x04, /* lsls r2, r2, #16 */ + 0x10, 0x43, /* orrs r0, r2 ; 0x5fff0000 */ + 0x01, 0x60, /* str r1, [r0] */ + 0x00, 0xbe, /* bkpt #0 */ + 0xfe, 0xe7, /* b . */ + 0xc0, 0x46, /* nop; align literal */ + 0x00, 0x00, 0x00, 0x00, /* function code literal */ +}; + +static const uint8_t rp2040_bootrom_hardfault_exit[] =3D { + 0xef, 0xf3, 0x08, 0x81, /* mrs r1, msp */ + 0x8a, 0x69, /* ldr r2, [r1, #24] ; stacked PC */ + 0x13, 0x88, /* ldrh r3, [r2] */ + 0x04, 0x4c, /* ldr r4, [pc, #16] ; 0xbe00 */ + 0xa3, 0x42, /* cmp r3, r4 */ + 0x04, 0xd1, /* bne hang */ + 0x08, 0x68, /* ldr r0, [r1] ; stacked R0/status */ + 0x03, 0x4c, /* ldr r4, [pc, #12] ; debug base */ + 0x60, 0x60, /* str r0, [r4, #4] */ + 0x03, 0x48, /* ldr r0, [pc, #12] ; EXIT command */ + 0x20, 0x60, /* str r0, [r4] */ + 0xfe, 0xe7, /* hang: b hang */ + 0x00, 0x00, /* align literal pool */ + 0x00, 0xbe, 0x00, 0x00, /* 0xbe00 */ + 0x00, 0x00, 0xff, 0x5f, /* 0x5fff0000 */ + 0x45, 0x58, 0x49, 0x54, /* "EXIT" */ +}; + +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 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); + 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)); + memcpy(rom + RP2040_BOOTROM_HARDFAULT_EXIT_OFFSET, + rp2040_bootrom_hardfault_exit, + sizeof(rp2040_bootrom_hardfault_exit)); + rp2040_store_word(rom, 0x0c, RP2040_BOOTROM_HARDFAULT_EXIT_OFFSET | 1); + 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); + 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, + 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++) { + 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); + } + } + + 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 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) +{ + *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->usbctrl_dpram_poweroff, + mempowerdown & BIT(6)); + 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 irq_level =3D s->irq_level[0][i]; + bool route_to_nmi =3D nmi_mask & BIT(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[0], nmi_level); +} + +static void rp2040_syscfg_update(void *opaque) +{ + RP2040State *s =3D opaque; + + rp2040_update_mempowerdown(s); + rp2040_update_nmi(s); +} + +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 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_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; + } + + 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( + 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; + } + + snprintf(feature, sizeof(feature), "boot ROM function '%c%c'", + code & 0xff, (code >> 8) & 0xff); + snprintf(detail, sizeof(detail), "%s%s%s", + name ? "SDK name: " : "", + name ? name : "unknown lookup code", + name ? "" : ""); + rp2040_log_nyi("bootrom", feature, detail); +} + +static uint64_t rp2040_synthetic_rom_dbg_read(void *opaque, hwaddr addr, + unsigned size) +{ + RP2040State *s =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", + "unsupported register"); + 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_set_irq(void *opaque, int irq, int level) +{ + RP2040State *s =3D opaque; + + assert(irq >=3D 0 && irq < RP2040_NUM_IRQS); + s->irq_level[0][irq] =3D level; + rp2040_update_nmi(s); +} + +static void rp2040_update_uart_pins(RP2040State *s) +{ + pl011_set_tx_connected(&s->uart0, + !s->strict_uart_pins || + s->uart0_tx_pin_enabled); + pl011_set_rx_connected(&s->uart0, + !s->strict_uart_pins || + s->uart0_rx_pin_enabled); + pl011_set_tx_connected(&s->uart1, + !s->strict_uart_pins || + s->uart1_tx_pin_enabled); + pl011_set_rx_connected(&s->uart1, + !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 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_soc_init(Object *obj) +{ + RP2040State *s =3D RP2040(obj); + int i; + + 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", 32); + } + object_initialize_child(obj, "uart0", &s->uart0, TYPE_PL011); + object_property_add_alias(obj, "serial0", OBJECT(&s->uart0), "chardev"= ); + object_initialize_child(obj, "uart1", &s->uart1, TYPE_PL011); + object_property_add_alias(obj, "serial1", OBJECT(&s->uart1), "chardev"= ); + + object_initialize_child(obj, "xip", &s->xip, TYPE_RP2040_XIP); + object_initialize_child(obj, "busctrl", &s->busctrl, TYPE_RP2040_BUSCT= RL); + 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, 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, + TYPE_RP2040_PADS_QSPI); + object_initialize_child(obj, "pll-sys", &s->pll_sys, TYPE_RP2040_PLL); + qdev_prop_set_string(DEVICE(&s->pll_sys), "trace-name", + "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, "psm", &s->psm, TYPE_RP2040_PSM); + object_initialize_child(obj, "resets", &s->resets, TYPE_RP2040_RESETS); + object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC); + object_initialize_child(obj, "sio", &s->sio, TYPE_RP2040_SIO); + 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, "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); + object_initialize_child(obj, "xosc", &s->xosc, TYPE_RP2040_XOSC); + + s->irq =3D qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS); + s->sysclk =3D clock_new(obj, "sysclk"); +} + +static void rp2040_soc_realize(DeviceState *dev, Error **errp) +{ + RP2040State *s =3D RP2040(dev); + Error *err =3D NULL; + g_autofree char *filename =3D NULL; + ssize_t image_size; + int i; + + if (!s->board_memory) { + error_setg(errp, "memory property was not set"); + return; + } + qdev_prop_set_bit(DEVICE(&s->armv7m[RP2040_PROC1]), "start-powered-off= ", + s->bootrom_file !=3D NULL); + + if (!memory_region_init_rom(&s->rom, OBJECT(dev), "rp2040.rom", + RP2040_ROM_SIZE, errp)) { + return; + } + memory_region_add_subregion(s->board_memory, RP2040_ROM_BASE, &s->rom); + memory_region_init_io(&s->rom_poweroff, OBJECT(dev), + &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); + memory_region_init_io(&s->synthetic_rom_dbg, OBJECT(dev), + &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); + + if (s->bootrom_file) { + 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; + } + + image_size =3D load_image_targphys(filename, RP2040_ROM_BASE, + RP2040_ROM_SIZE, errp); + if (image_size < 0) { + return; + } + } else { + rp2040_install_synthetic_bootrom(); + } + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->xip), errp)) { + return; + } + if (!s->bootrom_file) { + rp2040_xip_set_synthetic_hardfault_vector( + &s->xip, RP2040_BOOTROM_HARDFAULT_EXIT_OFFSET | 1); + } + 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); + + 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 !=3D NULL) { + 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)); + + 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]); + qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 0, + qemu_allocate_irq(rp2040_set_uart_pin, s, = 0)); + qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart0-pin", 1, + qemu_allocate_irq(rp2040_set_uart_pin, s, = 1)); + qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 0, + qemu_allocate_irq(rp2040_set_uart_pin, s, = 2)); + qdev_connect_gpio_out_named(DEVICE(&s->iobank0), "uart1-pin", 1, + qemu_allocate_irq(rp2040_set_uart_pin, s, = 3)); + + object_property_set_link(OBJECT(&s->ioqspi), "xip", OBJECT(&s->xip), + &error_abort); + 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->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); + + 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")); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->psm), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->psm), 0, RP2040_PSM_BASE); + rp2040_psm_set_update_callback(&s->psm, rp2040_psm_update, s); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->resets), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->resets), 0, RP2040_RESETS_BASE); + + 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->sio), errp)) { + return; + } + 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]); + + 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); + + 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->timer), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer), 0, RP2040_TIMER_BASE); + for (i =3D 0; i < 4; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer), i, s->irq[i]); + } + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->vreg), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE); + + qdev_connect_clock_in(DEVICE(&s->watchdog), "clk-ref", + qdev_get_clock_out(DEVICE(&s->clocks), "clk-ref"= )); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->watchdog), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->watchdog), 0, RP2040_WATCHDOG_BASE); + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->xosc), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->xosc), 0, RP2040_XOSC_BASE); + + 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); + + if (!memory_region_init_ram(&s->sram[i], OBJECT(dev), name, + RP2040_SRAM_BANK_SIZE, errp)) { + return; + } + memory_region_add_subregion(s->board_memory, + RP2040_SRAM_BASE + + i * RP2040_SRAM_BANK_SIZE, + &s->sram[i]); + memory_region_init_io(&s->sram_poweroff[i], OBJECT(dev), + &rp2040_powered_off_ops, s, poweroff_name, + RP2040_SRAM_BANK_SIZE); + memory_region_add_subregion_overlap(s->board_memory, + RP2040_SRAM_BASE + + i * RP2040_SRAM_BANK_SIZE, + &s->sram_poweroff[i], 1); + memory_region_set_enabled(&s->sram_poweroff[i], false); + } + + if (!memory_region_init_ram(&s->sram[4], OBJECT(dev), "rp2040.sram4", + RP2040_SRAM_HI_SIZE, errp)) { + return; + } + memory_region_add_subregion(s->board_memory, RP2040_SRAM4_BASE, + &s->sram[4]); + memory_region_init_io(&s->sram_poweroff[4], OBJECT(dev), + &rp2040_powered_off_ops, s, + "rp2040.sram4.poweroff", RP2040_SRAM_HI_SIZE); + memory_region_add_subregion_overlap(s->board_memory, RP2040_SRAM4_BASE, + &s->sram_poweroff[4], 1); + memory_region_set_enabled(&s->sram_poweroff[4], false); + + if (!memory_region_init_ram(&s->sram[5], OBJECT(dev), "rp2040.sram5", + RP2040_SRAM_HI_SIZE, errp)) { + return; + } + memory_region_add_subregion(s->board_memory, RP2040_SRAM5_BASE, + &s->sram[5]); + memory_region_init_io(&s->sram_poweroff[5], OBJECT(dev), + &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); + + if (!memory_region_init_ram(&s->usbctrl_dpram, OBJECT(dev), + "rp2040.usbctrl_dpram", + RP2040_USBCTRL_DPRAM_SIZE, errp)) { + return; + } + memory_region_add_subregion(s->board_memory, RP2040_USBCTRL_DPRAM_BASE, + &s->usbctrl_dpram); + memory_region_init_io(&s->usbctrl_dpram_poweroff, OBJECT(dev), + &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); + + s->mempowerdown_ready =3D true; + rp2040_update_mempowerdown(s); + + memory_region_init_io(&s->usbctrl_regs, OBJECT(dev), + &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); + + 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); + } + + for (i =3D 0; i < RP2040_NUM_CORES; i++) { + int irq; + + qdev_connect_clock_in(DEVICE(&s->armv7m[i]), "cpuclk", s->sysclk); + g_autofree char *name =3D g_strdup_printf("rp2040.proc%d-memory", = i); + + 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 !=3D NULL) { + 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); + } + rp2040_update_nmi(s); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->iobank0), 1, + s->cpu_irq[RP2040_PROC1][RP2040_IO_IRQ_BANK0]); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->sio), 1, + s->cpu_irq[RP2040_PROC1][RP2040_SIO_IRQ_PROC1]); + + qdev_connect_clock_in(DEVICE(&s->uart0), "clk", + qdev_get_clock_out(DEVICE(&s->clocks), "clk-peri= ")); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart0), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart0), 0, RP2040_UART0_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart0), 1, RP2040_UART0_BASE + 0x10= 00); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart0), 0, s->irq[RP2040_UART0_I= RQ]); + qdev_connect_gpio_out_named(DEVICE(&s->uart0), "dreq-tx", 0, + qdev_get_gpio_in_named(DEVICE(&s->dma), + "dreq", + RP2040_DREQ_UART0_T= X)); + qdev_connect_gpio_out_named(DEVICE(&s->uart0), "dreq-rx", 0, + qdev_get_gpio_in_named(DEVICE(&s->dma), + "dreq", + RP2040_DREQ_UART0_R= X)); + + qdev_connect_clock_in(DEVICE(&s->uart1), "clk", + qdev_get_clock_out(DEVICE(&s->clocks), "clk-peri= ")); + if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart1), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart1), 0, RP2040_UART1_BASE); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->uart1), 1, RP2040_UART1_BASE + 0x10= 00); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->uart1), 0, s->irq[RP2040_UART1_I= RQ]); + qdev_connect_gpio_out_named(DEVICE(&s->uart1), "dreq-tx", 0, + qdev_get_gpio_in_named(DEVICE(&s->dma), + "dreq", + RP2040_DREQ_UART1_T= X)); + qdev_connect_gpio_out_named(DEVICE(&s->uart1), "dreq-rx", 0, + qdev_get_gpio_in_named(DEVICE(&s->dma), + "dreq", + RP2040_DREQ_UART1_R= X)); + rp2040_update_uart_pins(s); +} + +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), +}; + +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/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 new file mode 100644 index 0000000000..90504264c7 --- /dev/null +++ b/include/hw/arm/rp2040.h @@ -0,0 +1,122 @@ +/* + * 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/irq.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" +#include "hw/misc/rp2040_pads.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_sio.h" +#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" +#include "hw/ssi/rp2040_xip.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_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 +#define RP2040_SRAM5_BASE 0x20041000 +#define RP2040_SRAM_LO_SIZE (4 * RP2040_SRAM_BANK_SIZE) +#define RP2040_SRAM_HI_SIZE (4 * KiB) +#define RP2040_USBCTRL_DPRAM_BASE 0x50100000 +#define RP2040_USBCTRL_DPRAM_SIZE (4 * KiB) +#define RP2040_USBCTRL_REGS_BASE 0x50110000 +#define RP2040_USBCTRL_REGS_SIZE 0x4000 +#define RP2040_SYNTHETIC_ROM_DBG_BASE 0x5fff0000 +#define RP2040_SYNTHETIC_ROM_DBG_SIZE 0x1000 +#define RP2040_SYNTHETIC_ROM_FLASH_HELPER_COUNT 6 +#define RP2040_NUM_CORES 2 +#define RP2040_NUM_IRQS 32 + +struct RP2040State { + SysBusDevice parent_obj; + + ARMv7MState armv7m[RP2040_NUM_CORES]; + PL011State uart0; + PL011State uart1; + RP2040BusCtrlState busctrl; + RP2040ClocksState clocks; + RP2040DmaState dma; + RP2040IoBank0State iobank0; + RP2040IoQspiState ioqspi; + RP2040PadsBank0State pads_bank0; + RP2040PadsQspiState pads_qspi; + RP2040PllState pll_sys; + RP2040PllState pll_usb; + RP2040PsmState psm; + RP2040ResetsState resets; + RP2040RoscState rosc; + RP2040SioState sio; + RP2040SysCfgState syscfg; + RP2040SysInfoState sysinfo; + RP2040TbmanState tbman; + RP2040TimerState timer; + RP2040VregState vreg; + RP2040WatchdogState watchdog; + RP2040XoscState xosc; + RP2040XipState xip; + + MemoryRegion *board_memory; + MemoryRegion cpu_memory[RP2040_NUM_CORES]; + MemoryRegion rom; + MemoryRegion rom_poweroff; + MemoryRegion sram[6]; + MemoryRegion sram_poweroff[6]; + 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; + + qemu_irq *irq; + qemu_irq cpu_irq[RP2040_NUM_CORES][RP2040_NUM_IRQS]; + qemu_irq nmi_irq[RP2040_NUM_CORES]; + bool irq_level[RP2040_NUM_CORES][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; + uint32_t synthetic_rom_dbg_arg[4]; + uint32_t synthetic_rom_dbg_result[4]; + uint32_t synthetic_rom_flash_helper_count[ + RP2040_SYNTHETIC_ROM_FLASH_HELPER_COUNT]; + + Clock *sysclk; +}; + +#endif --=20 2.53.0 From nobody Sun Jul 26 11:08:36 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=1783290598; cv=none; d=zohomail.com; s=zohoarc; b=oLsYnFPbyCIB8u1/qkkJwPDZ9lFk9b7h21M2cZf6MWGjIid+FAbIfdC4Ud5UsbNavY3ROqwNZpnu8ti3+cISB2T2gLrTtAEY2Fs+QJwxkGFB4eYWXJD6LgMK+owKin9VwpgZAEHuyfxLCDy4Ewv7txkDMQWQYlevTR6coePqm1Q= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290598; 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=I276WTA6G0GTG7SVq12SCXUkN5WyJYKylq0Yh4aKdhw=; b=jpiIPZBNmHCkJiXKnl8yCOMIwU45i6+W3wI+a9rLcXjkJIZVGUVJ4/yqS3v4StcDnnPiH8F0FbQ4hHJHlLaw0s9u+83xpF6DDINt5IhdC+zg66Cdb2hsgLT05KhlM/7Ryt7P3STgQA5GA0XOw912SJQaWSMmj8XXOJKpzHaKTv8= 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 1783290598955141.99220817525372; Sun, 5 Jul 2026 15:29:58 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJy-0006nO-IR; Sun, 05 Jul 2026 18:28: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 1wgUwh-0002Kb-OT; Sun, 05 Jul 2026 18:04: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 1wgUwf-00023z-B9; Sun, 05 Jul 2026 18:04:15 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4gthMG5SXNz85Y8; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMG4LP5z6sT6; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289002; bh=I276WTA6G0GTG7SVq12SCXUkN5WyJYKylq0Yh4aKdhw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=sdTftEEMyRhEhJyXXrNcxskUcjxuB4VetDQMOCXr57LKWiHklkhq2SOdFf7lAJhjL OIJ4gJfgCU2I40INosvrO3mzDSaePtHaOXbMbhTQSfYHIV6aAn7rFXtz1E7tndewJ8 7P4hSq2eB4vfaaEAXJYrdY4sU0ywUiFu3KttZbGaKp1OaPLEzuvxScIsCVbeCpL+Sc PPr8IpG4aaZJx+m6aXSh6s3J0WawRlAV2BuhJbxkJIbcnzc/n0P5/4QDnhSQIL395w ZiPG7PVbkfSxY8rlujml2Jo+bPILVDLjlPEjQHQUfti5GjTcp3nakuodW/94v8Mxr/ VrFksDg3VePdw== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 6/9] scripts: add UF2 to RP2040 flash image converter Date: Mon, 6 Jul 2026 00:03:17 +0200 Message-ID: <20260705220320.90395-7-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290600289158500 Content-Type: text/plain; charset="utf-8" Add a helper script that converts RP2040 UF2 payloads into raw flash images= . This mirrors the machine's UF2 loading path and is useful when preparing = persistent flash-file inputs outside QEMU. Signed-off-by: Gilles Grimaud --- scripts/uf2-to-flash.py | 462 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 462 insertions(+) create mode 100755 scripts/uf2-to-flash.py 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() --=20 2.53.0 From nobody Sun Jul 26 11:08:36 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=1783290591; cv=none; d=zohomail.com; s=zohoarc; b=kspYKQ9PyXH4iZmuF0/zkWSUgmRdvqf322c+WdOiC4hzXlgJuWa5g0Rhr/Cswav5u2G9GX2yIBNyNh2PnL+uUHh1qCfBQlz/duHTmfWnzPE32XlbYteqzwehkcjcP7Z05XMPhifiLjOZGhwZGb23XCQrA6ZJ0UrCZsLemP0REyA= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290591; 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=Nb/7wEHJmJ7o7Wa887+QjUm87puhwT6uWKi1riPD/2Y=; b=kdEaFYK7KFr/gJoLE4rDj/RwOucXxOX4cAmp80Mv6NPq/6u8U/2ihiPaSJYTJPUoWj/yIqCTxxwPNiYI27XUQXsPFV3jZavbG/MhDJ4HbyKwZLMqAGA43TNiKd71a1HWs/VUEaRJ44LtmqpSAQ7GD76lwzoG75DblA/Nu7nvoDA= 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 1783290591789826.6007357714582; Sun, 5 Jul 2026 15:29:51 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJy-0006nJ-3V; Sun, 05 Jul 2026 18:28: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 1wgUw7-0002EO-Ry; Sun, 05 Jul 2026 18:03:39 -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 1wgUw2-0001h2-VP; Sun, 05 Jul 2026 18:03:39 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-1.univ-lille.fr (Postfix) with ESMTPS id 4gthMG6YMCzBt0f; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMG5Mdbz6tBv; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289002; bh=Nb/7wEHJmJ7o7Wa887+QjUm87puhwT6uWKi1riPD/2Y=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZaetCRI56RQnOsbEsc+7eKSGwa4VtrglNb3qlirgNuIvrEVumRgV0gHjZh7x04Rxw EJ+Jfdyo5oTL7dLRWqY17H9yPxN0OmysjTRXuTCuljURCa+u6UbL+roqpfBIFkrTZn 3Xyh3Zm0VNUHoLNLhp6Mk3IFTQ5rEoINlUAZWaZVxBKk84Q3dCi9b+B9loNkqi0Pe3 aTWVAr5G+18TuMC8GRA9jmDu3RGCtO9A9xG/cwem1ReLeAmtsk2ox3blCBoCubm+a7 e6Y+w6aN+L7+8K8QoBEYX1+FfI6Lymf68lQ7qLc79qPwqqMKDXhC2ev5vgccc6LYw5 GkTWS5QpsxzFA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 7/9] tests/qtest: add RP2040 peripheral tests Date: Mon, 6 Jul 2026 00:03:18 +0200 Message-ID: <20260705220320.90395-8-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290594321158500 Content-Type: text/plain; charset="utf-8" Add qtests for RP2040 SIO, DMA, XIP/SSI, clocks, resets, watchdog, ROSC, SY= SINFO/SYSCFG, VREG, IO_QSPI, TBMAN and BUSCTRL behaviour. The tests cover the functional contracts used by the Pico machine and the P= ico SDK examples rather than cycle-level timing. Signed-off-by: Gilles Grimaud --- tests/qtest/meson.build | 12 + tests/qtest/rp2040-busctrl-test.c | 92 ++++++ tests/qtest/rp2040-clocks-test.c | 107 +++++++ tests/qtest/rp2040-dma-test.c | 374 +++++++++++++++++++++++ tests/qtest/rp2040-ioqspi-test.c | 81 +++++ tests/qtest/rp2040-resets-test.c | 84 +++++ tests/qtest/rp2040-rosc-test.c | 146 +++++++++ tests/qtest/rp2040-sio-test.c | 234 ++++++++++++++ tests/qtest/rp2040-sysinfo-syscfg-test.c | 177 +++++++++++ tests/qtest/rp2040-tbman-test.c | 32 ++ tests/qtest/rp2040-vreg-test.c | 79 +++++ tests/qtest/rp2040-watchdog-test.c | 148 +++++++++ tests/qtest/rp2040-xip-test.c | 212 +++++++++++++ 13 files changed, 1778 insertions(+) create mode 100644 tests/qtest/rp2040-busctrl-test.c create mode 100644 tests/qtest/rp2040-clocks-test.c create mode 100644 tests/qtest/rp2040-dma-test.c create mode 100644 tests/qtest/rp2040-ioqspi-test.c create mode 100644 tests/qtest/rp2040-resets-test.c create mode 100644 tests/qtest/rp2040-rosc-test.c create mode 100644 tests/qtest/rp2040-sio-test.c create mode 100644 tests/qtest/rp2040-sysinfo-syscfg-test.c create mode 100644 tests/qtest/rp2040-tbman-test.c create mode 100644 tests/qtest/rp2040-vreg-test.c create mode 100644 tests/qtest/rp2040-watchdog-test.c create mode 100644 tests/qtest/rp2040-xip-test.c diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 5e18b947c7..12cc8a500d 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -250,6 +250,18 @@ 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', + 'rp2040-busctrl-tes= t', + 'rp2040-clocks-test= ', + 'rp2040-dma-test', + 'rp2040-ioqspi-test= ', + 'rp2040-resets-test= ', + 'rp2040-rosc-test', + 'rp2040-sio-test', + 'rp2040-tbman-test', + 'rp2040-vreg-test', + '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-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(); +} 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(); +} 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(); +} 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(); +} 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(); +} 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(); +} diff --git a/tests/qtest/rp2040-sio-test.c b/tests/qtest/rp2040-sio-test.c new file mode 100644 index 0000000000..8212497e28 --- /dev/null +++ b/tests/qtest/rp2040-sio-test.c @@ -0,0 +1,234 @@ +/* + * 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_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) + +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_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/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(); +} diff --git a/tests/qtest/rp2040-sysinfo-syscfg-test.c b/tests/qtest/rp2040-= sysinfo-syscfg-test.c new file mode 100644 index 0000000000..7440c3570b --- /dev/null +++ b/tests/qtest/rp2040-sysinfo-syscfg-test.c @@ -0,0 +1,177 @@ +/* + * 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(); +} 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(); +} 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(); +} diff --git a/tests/qtest/rp2040-xip-test.c b/tests/qtest/rp2040-xip-test.c new file mode 100644 index 0000000000..9de4d645b8 --- /dev/null +++ b/tests/qtest/rp2040-xip-test.c @@ -0,0 +1,212 @@ +/* + * 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" + +#define XIP_BASE 0x10000000 +#define XIP_NOCACHE_NOALLOC_BASE 0x13000000 +#define XIP_CTRL_BASE 0x14000000 +#define XIP_AUX_BASE 0x50400000 +#define XIP_STAT 0x08 +#define XIP_STREAM_ADDR 0x14 +#define XIP_STREAM_CTR 0x18 +#define XIP_STREAM_FIFO 0x1c +#define XIP_SSI_BASE 0x18000000 +#define SSI_CTRLR1 0x04 +#define SSI_SSIENR 0x08 +#define SSI_DR0 0x60 +#define SSI_SER 0x10 +#define SSI_DMACR 0x4c + +#define XIP_STAT_FIFO_FULL BIT(2) +#define XIP_STAT_FIFO_EMPTY BIT(1) +#define XIP_STAT_FLUSH_READY BIT(0) + +#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_CTRL_BUSY BIT(24) +#define DMA_CTRL_BSWAP BIT(22) +#define DMA_CTRL_TREQ_SEL_SHIFT 15 +#define DMA_CTRL_INCR_WRITE BIT(5) +#define DMA_CTRL_DATA_SIZE_32 (2 << 2) +#define DMA_CTRL_EN BIT(0) +#define DREQ_XIP_STREAM 37 +#define DREQ_XIP_SSIRX 39 +#define SRAM_BASE 0x20000000 + +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 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_stream_fifo_and_dma(void) +{ + static const uint32_t expected[] =3D { + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, + }; + QTestState *qts =3D rp2040_start(NULL); + uint32_t ctrl; + uint32_t buf[ARRAY_SIZE(expected)]; + + 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); + g_assert_cmphex(qtest_readl(qts, XIP_CTRL_BASE + XIP_STREAM_FIFO), =3D= =3D, + expected[0]); + g_assert_cmphex(qtest_readl(qts, XIP_CTRL_BASE + XIP_STREAM_FIFO), =3D= =3D, + expected[1]); + + ctrl =3D DMA_CTRL_EN | DMA_CTRL_INCR_WRITE | DMA_CTRL_DATA_SIZE_32 | + (DREQ_XIP_STREAM << DMA_CTRL_TREQ_SEL_SHIFT); + qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, XIP_AUX_BASE); + qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, SRAM_BASE); + qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, ARRAY_SIZE(expected) = - 2); + qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl); + + qtest_memread(qts, SRAM_BASE, buf, + sizeof(buf[0]) * (ARRAY_SIZE(expected) - 2)); + g_assert_cmpmem(buf, sizeof(buf[0]) * (ARRAY_SIZE(expected) - 2), + expected + 2, + sizeof(expected[0]) * (ARRAY_SIZE(expected) - 2)); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) & + DMA_CTRL_BUSY, =3D=3D, 0); + 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_rx_dma_bulk_read(void) +{ + static const uint32_t expected[] =3D { + 0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c, + 0x13121110, 0x17161514, + }; + QTestState *qts =3D rp2040_start(NULL); + uint32_t ctrl; + uint32_t buf[ARRAY_SIZE(expected)]; + + 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_DMACR, 3); + qtest_writel(qts, XIP_SSI_BASE + SSI_SSIENR, 1); + + ctrl =3D DMA_CTRL_EN | DMA_CTRL_INCR_WRITE | DMA_CTRL_DATA_SIZE_32 | + DMA_CTRL_BSWAP | (DREQ_XIP_SSIRX << DMA_CTRL_TREQ_SEL_SHIFT); + qtest_writel(qts, DMA_BASE + DMA_CH_READ_ADDR, XIP_SSI_BASE + SSI_DR0); + qtest_writel(qts, DMA_BASE + DMA_CH_WRITE_ADDR, SRAM_BASE); + qtest_writel(qts, DMA_BASE + DMA_CH_TRANS_COUNT, ARRAY_SIZE(expected)); + qtest_writel(qts, DMA_BASE + DMA_CH_CTRL_TRIG, ctrl); + + qtest_writel(qts, XIP_SSI_BASE + SSI_DR0, 0xa0); + + qtest_memread(qts, SRAM_BASE, buf, sizeof(buf)); + g_assert_cmpmem(buf, sizeof(buf), expected, sizeof(expected)); + g_assert_cmphex(qtest_readl(qts, DMA_BASE + DMA_CH_CTRL_TRIG) & + DMA_CTRL_BUSY, =3D=3D, 0); + + 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/stream-fifo-and-dma", + test_stream_fifo_and_dma); + qtest_add_func("/rp2040-xip/ssi-rx-dma-bulk-read", + test_ssi_rx_dma_bulk_read); + + return g_test_run(); +} --=20 2.53.0 From nobody Sun Jul 26 11:08:36 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=1783290599; cv=none; d=zohomail.com; s=zohoarc; b=Wmh4EP1JAZS/mX1Q1TFKZ536wl6LQsRpnABJcp9jWECtXn3OS4QGJDSP1j/ZdM4y4Gloq5SoCtixwybVmSgLihsbQx7NKIR4rAKAWEEqTVp5YKhM4b4uxY2LtviQ8XDxjgyEcbAntloQUOb/Ov4JUiX8rZS3qMccbMS2RGB1YvQ= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1783290599; 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=OSguJ2MZHcJvX+sOIaneNSiE1GwVJO2D8ODtTYZVbr0=; b=kTuJ8kgpd3uekSKoZcjlOZgiFoNNj/DBTmrdnkHsDojyYvj2ZSS9J/Yi1HLIaOIelWUidxxGBuSvuuZT5lbqQAJ2EM+WQJOCpSxufrIpcPSI1lA77gRg7fjF2zcZqsWyUO7vAoRyBXGaJjzqm0pqLejwDOyH6I2T9zJbN35VKaQ= 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 1783290599003319.1669988011532; Sun, 5 Jul 2026 15:29:59 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJy-0006nT-Uw; Sun, 05 Jul 2026 18:28: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 1wgUwu-0002M5-5z; Sun, 05 Jul 2026 18:04:28 -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 1wgUwp-00024x-8K; Sun, 05 Jul 2026 18:04:27 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4gthMH0W69z85Y9; Mon, 6 Jul 2026 00:03:23 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMG6SgHz6sT6; Mon, 6 Jul 2026 00:03:22 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289003; bh=OSguJ2MZHcJvX+sOIaneNSiE1GwVJO2D8ODtTYZVbr0=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=r8nChRzdDMLqf3UdyCd1r8pdkOYXTJEI5Wk4gEQdWxFcztEBiQ5OX2sNVq7haFCfP x1RSLy1T852rx//G42eKSIm+6HmuFi2s7hOFOUIUP75aVnDlM9wqq9IkhUCTM+56FE kX2kgbcAN/AaEATd9fQgsXjqEXCwZjCYBSfuVlcGhyyMbIpv9rSHsyUUHwBb1guZpp 4Gq3Iv3hE9i0bGiAbdqTp8DxOP4aeT5RP/ji8JAe5P6dJLgYAP4QpP6lpeeDrCVDVn AWM1I2vXzASIXdjSdLFepJ3PdiljolZhsbka2/hBKkYMdTjNZgh5cG7kuTPasxmbIl zT0xHuzYAVrDg== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 8/9] tests/functional: add Raspberry Pi Pico tests Date: Mon, 6 Jul 2026 00:03:19 +0200 Message-ID: <20260705220320.90395-9-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290600204158500 Content-Type: text/plain; charset="utf-8" Add functional Raspberry Pi Pico tests covering synthetic ROM startup, UART= output, flash persistence, UF2 loading, DMA/UART paths, dual-core startup,= flash helper shortcuts, random source configuration and selected Pico SDK-= style workloads. Signed-off-by: Gilles Grimaud --- tests/functional/arm/meson.build | 2 + tests/functional/arm/test_raspi_pico.py | 1289 +++++++++++++++++++++++ 2 files changed, 1291 insertions(+) create mode 100644 tests/functional/arm/test_raspi_pico.py diff --git a/tests/functional/arm/meson.build b/tests/functional/arm/meson.= build index 61b00932db..0e3bebd5dc 100644 --- a/tests/functional/arm/meson.build +++ b/tests/functional/arm/meson.build @@ -22,6 +22,7 @@ test_arm_timeouts =3D { 'cubieboard' : 360, 'orangepi' : 540, 'quanta_gsj' : 240, + 'raspi_pico' : 30, 'raspi2' : 120, 'replay' : 240, 'tuxrun' : 240, @@ -30,6 +31,7 @@ test_arm_timeouts =3D { =20 tests_arm_system_quick =3D [ 'migration', + 'raspi_pico', ] =20 tests_arm_system_thorough =3D [ diff --git a/tests/functional/arm/test_raspi_pico.py b/tests/functional/arm= /test_raspi_pico.py new file mode 100644 index 0000000000..c0ddc018b8 --- /dev/null +++ b/tests/functional/arm/test_raspi_pico.py @@ -0,0 +1,1289 @@ +#!/usr/bin/env python3 +# +# Functional test that checks the Raspberry Pi Pico machine. +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import os +import socket +import struct +import subprocess +import time + +from qemu_test import QemuSystemTest, wait_for_console_pattern + + +class RaspiPicoMachine(QemuSystemTest): + + UF2_MAGIC_START0 =3D 0x0A324655 + UF2_MAGIC_START1 =3D 0x9E5D5157 + UF2_MAGIC_END =3D 0x0AB16F30 + UF2_FLAG_FAMILY_ID_PRESENT =3D 0x00002000 + RP2040_FAMILY_ID =3D 0xE48BFF56 + + def set_direct_uart_machine(self): + self.set_machine('raspi-pico') + self.vm.set_machine('raspi-pico,strict-uart-pins=3Doff') + + def pipico_rom(self): + rom =3D os.getenv('QEMU_TEST_PIPICO_ROM') + if rom: + if os.path.exists(rom): + return rom + self.skipTest(f'QEMU_TEST_PIPICO_ROM does not exist: {rom}') + + rom =3D os.path.join(os.getcwd(), 'pc-bios', 'pipico.rom') + if os.path.exists(rom): + return rom + + self.skipTest('pipico.rom not available; set QEMU_TEST_PIPICO_ROM') + + # Minimal Cortex-M0+ raw image linked for XIP at 0x10000000. It contai= ns a + # vector table and writes "PICO UART OK\n" to UART0 at 0x40034000. + UART_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x09, 0x00, 0x00, 0x10, + 0x07, 0x48, 0x08, 0x4a, 0x11, 0x78, 0x00, 0x29, + 0x02, 0xd0, 0x01, 0x60, 0x01, 0x32, 0xf9, 0xe7, + 0xfe, 0xe7, 0x50, 0x49, 0x43, 0x4f, 0x20, 0x55, + 0x41, 0x52, 0x54, 0x20, 0x4f, 0x4b, 0x0a, 0x00, + 0x00, 0x40, 0x03, 0x40, 0x1a, 0x00, 0x00, 0x10, + ]) + + # Arms TIMER alarm 0, waits for the NVIC IRQ0 handler to run, then wri= tes + # "TIMER OK\n" to UART0. + TIMER_ALARM_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x45, 0x00, 0x00, 0x10, + 0x6d, 0x00, 0x00, 0x10, 0x6d, 0x00, 0x00, 0x10, + 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, + 0x6f, 0x00, 0x00, 0x10, + 0x14, 0x48, 0x00, 0x21, 0x01, 0x60, 0x14, 0x48, + 0x01, 0x21, 0x01, 0x60, 0x13, 0x4c, 0x61, 0x63, + 0xa1, 0x63, 0xa2, 0x6a, 0x64, 0x23, 0xd2, 0x18, + 0x22, 0x61, 0x0e, 0x48, 0x01, 0x68, 0x00, 0x29, + 0xfb, 0xd0, 0x0f, 0x48, 0x00, 0xf0, 0x07, 0xf8, + 0xfe, 0xe7, 0x0c, 0x48, 0x01, 0x21, 0x41, 0x63, + 0x08, 0x48, 0x01, 0x60, 0x70, 0x47, 0x0b, 0x4a, + 0x01, 0x78, 0x00, 0x29, 0x02, 0xd0, 0x11, 0x60, + 0x01, 0x30, 0xf9, 0xe7, 0x70, 0x47, 0xc0, 0x46, + 0x54, 0x49, 0x4d, 0x45, 0x52, 0x20, 0x4f, 0x4b, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, + 0x00, 0xe1, + 0x00, 0xe0, 0x00, 0x40, 0x05, 0x40, 0x8c, 0x00, + 0x00, 0x10, 0x00, 0x40, 0x03, 0x40, + ]) + + # Verifies the RP2040 SIO view seen from core 0: CPUID, FIFO ready bit, + # read-on-empty sticky flag and write-one-to-clear behavior. + SIO_FIFO_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x11, 0x00, 0x00, 0x10, + 0x3b, 0x00, 0x00, 0x10, 0x3b, 0x00, 0x00, 0x10, + 0x18, 0x4c, 0x20, 0x68, 0x00, 0x28, 0x10, 0xd1, + 0x20, 0x6d, 0x02, 0x21, 0x08, 0x42, 0x0c, 0xd0, + 0xa0, 0x6d, 0x20, 0x6d, 0x08, 0x21, 0x08, 0x42, + 0x07, 0xd0, 0x21, 0x65, 0x20, 0x6d, 0x08, 0x42, + 0x03, 0xd1, 0x11, 0x48, 0x00, 0xf0, 0x05, 0xf8, + 0xfe, 0xe7, 0x10, 0x48, 0x00, 0xf0, 0x01, 0xf8, + 0xfe, 0xe7, 0x0f, 0x4a, 0x01, 0x78, 0x00, 0x29, + 0x02, 0xd0, 0x11, 0x60, 0x01, 0x30, 0xf9, 0xe7, + 0x70, 0x47, 0xc0, 0x46, 0x53, 0x49, 0x4f, 0x20, + 0x43, 0x4f, 0x52, 0x45, 0x30, 0x20, 0x4f, 0x4b, + 0x0a, 0x00, 0x53, 0x49, 0x4f, 0x20, 0x43, 0x4f, + 0x52, 0x45, 0x30, 0x20, 0x46, 0x41, 0x49, 0x4c, + 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, + 0x54, 0x00, 0x00, 0x10, 0x62, 0x00, 0x00, 0x10, + 0x00, 0x40, 0x03, 0x40, + ]) + + # Verifies the RP2040 SIO hardware divider: unsigned and signed divisi= on, + # division by zero, result save/restore writes, and CSR READY/DIRTY. + SIO_DIVIDER_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x09, 0x00, 0x00, 0x10, + 0x37, 0x4c, 0x64, 0x20, 0x20, 0x66, 0x07, 0x20, + 0x60, 0x66, 0xa0, 0x6f, 0x35, 0x4a, 0x90, 0x42, + 0x00, 0xd0, 0x4b, 0xe0, 0x60, 0x6f, 0x34, 0x4a, + 0x90, 0x42, 0x00, 0xd0, 0x46, 0xe0, 0xa0, 0x6f, + 0x30, 0x4a, 0x90, 0x42, 0x00, 0xd0, 0x41, 0xe0, + 0x20, 0x6f, 0x30, 0x4a, 0x90, 0x42, 0x00, 0xd0, + 0x3c, 0xe0, 0xa0, 0x6f, 0x2e, 0x4a, 0x90, 0x42, + 0x00, 0xd0, 0x37, 0xe0, 0x2d, 0x48, 0xa0, 0x66, + 0x07, 0x20, 0xe0, 0x66, 0x60, 0x6f, 0x2c, 0x4a, + 0x90, 0x42, 0x00, 0xd0, 0x2e, 0xe0, 0x20, 0x6f, + 0x2a, 0x4a, 0x90, 0x42, 0x00, 0xd0, 0x29, 0xe0, + 0x29, 0x48, 0x20, 0x66, 0x00, 0x20, 0x60, 0x66, + 0x60, 0x6f, 0x27, 0x4a, 0x90, 0x42, 0x00, 0xd0, + 0x20, 0xe0, 0x20, 0x6f, 0x25, 0x4a, 0x90, 0x42, + 0x00, 0xd0, 0x1b, 0xe0, 0x24, 0x48, 0x60, 0x67, + 0x24, 0x48, 0x20, 0x67, 0xa0, 0x6f, 0x19, 0x4a, + 0x90, 0x42, 0x00, 0xd0, 0x12, 0xe0, 0x60, 0x6f, + 0x1f, 0x4a, 0x90, 0x42, 0x00, 0xd0, 0x0d, 0xe0, + 0x20, 0x6f, 0x1e, 0x4a, 0x90, 0x42, 0x00, 0xd0, + 0x08, 0xe0, 0xa0, 0x6f, 0x14, 0x4a, 0x90, 0x42, + 0x00, 0xd0, 0x03, 0xe0, 0x1a, 0x48, 0x00, 0xf0, + 0x05, 0xf8, 0xfe, 0xe7, 0x19, 0x48, 0x00, 0xf0, + 0x01, 0xf8, 0xfe, 0xe7, 0x18, 0x4a, 0x01, 0x78, + 0x00, 0x29, 0x02, 0xd0, 0x11, 0x60, 0x01, 0x30, + 0xf9, 0xe7, 0x70, 0x47, 0x53, 0x49, 0x4f, 0x20, + 0x44, 0x49, 0x56, 0x20, 0x4f, 0x4b, 0x0a, 0x00, + 0x53, 0x49, 0x4f, 0x20, 0x44, 0x49, 0x56, 0x20, + 0x46, 0x41, 0x49, 0x4c, 0x0a, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xd0, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x9c, 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0xff, 0xf2, 0xff, 0xff, 0xff, + 0x34, 0x12, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, + 0xdd, 0xcc, 0xbb, 0xaa, 0x44, 0x33, 0x22, 0x11, + 0xcc, 0x00, 0x00, 0x10, 0xd8, 0x00, 0x00, 0x10, + 0x00, 0x40, 0x03, 0x40, + ]) + + # Verifies the initial PSM PROC1 force-off state, the derived DONE bit, + # and the RP2040 atomic SET/CLR aliases used by the Pico SDK. + PSM_PROC1_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x11, 0x00, 0x00, 0x10, + 0x4b, 0x00, 0x00, 0x10, 0x4b, 0x00, 0x00, 0x10, + 0x19, 0x4c, 0x1a, 0x4d, 0x1a, 0x4e, 0x20, 0x68, + 0x28, 0x42, 0x16, 0xd1, 0x30, 0x68, 0x28, 0x42, + 0x13, 0xd0, 0x18, 0x49, 0x0d, 0x60, 0x20, 0x68, + 0x28, 0x42, 0x0e, 0xd0, 0x30, 0x68, 0x28, 0x42, + 0x0b, 0xd1, 0x15, 0x49, 0x0d, 0x60, 0x20, 0x68, + 0x28, 0x42, 0x06, 0xd1, 0x30, 0x68, 0x28, 0x42, + 0x03, 0xd0, 0x12, 0x48, 0x00, 0xf0, 0x05, 0xf8, + 0xfe, 0xe7, 0x11, 0x48, 0x00, 0xf0, 0x01, 0xf8, + 0xfe, 0xe7, 0x10, 0x4a, 0x01, 0x78, 0x00, 0x29, + 0x02, 0xd0, 0x11, 0x60, 0x01, 0x30, 0xf9, 0xe7, + 0x70, 0x47, 0xc0, 0x46, 0x50, 0x53, 0x4d, 0x20, + 0x4f, 0x4b, 0x0a, 0x00, 0x50, 0x53, 0x4d, 0x20, + 0x46, 0x41, 0x49, 0x4c, 0x0a, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x01, 0x40, 0x00, 0x00, 0x01, 0x00, + 0x0c, 0x00, 0x01, 0x40, 0x04, 0x20, 0x01, 0x40, + 0x04, 0x30, 0x01, 0x40, 0x64, 0x00, 0x00, 0x10, + 0x6c, 0x00, 0x00, 0x10, 0x00, 0x40, 0x03, 0x40, + ]) + + # Core 0 sends the Pico SDK multicore_launch_core1_raw() FIFO sequence= to + # synthetic ROM on core 1. Core 1 echoes the sequence, jumps to the + # supplied entry point, calls send_ack() using the supplied stack, and + # sends 0xd01e back through FIFO. + CORE1_WORKING_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x11, 0x00, 0x00, 0x10, + 0x55, 0x00, 0x00, 0x10, 0x55, 0x00, 0x00, 0x10, + 0x2f, 0x4c, 0x00, 0x27, 0x06, 0x2f, 0x0b, 0xd0, + 0xb8, 0x00, 0x2e, 0x4e, 0x36, 0x58, 0x30, 0x46, + 0x00, 0xf0, 0x22, 0xf8, 0x00, 0xf0, 0x1a, 0xf8, + 0xb0, 0x42, 0x13, 0xd1, 0x01, 0x37, 0xf1, 0xe7, + 0x00, 0xf0, 0x14, 0xf8, 0x28, 0x49, 0x88, 0x42, + 0x0c, 0xd1, 0x28, 0x48, 0x00, 0xf0, 0x1a, 0xf8, + 0xfe, 0xe7, 0x00, 0xf0, 0x01, 0xf8, 0xfe, 0xe7, + 0x00, 0xb5, 0x21, 0x4c, 0x22, 0x48, 0x00, 0xf0, + 0x0b, 0xf8, 0x00, 0xbd, 0x22, 0x48, 0x00, 0xf0, + 0x0d, 0xf8, 0xfe, 0xe7, 0x20, 0x6d, 0x01, 0x21, + 0x08, 0x42, 0xfb, 0xd0, 0xa0, 0x6d, 0x70, 0x47, + 0x21, 0x6d, 0x02, 0x22, 0x11, 0x42, 0xfb, 0xd0, + 0x60, 0x65, 0x70, 0x47, 0x1b, 0x4a, 0x01, 0x78, + 0x00, 0x29, 0x02, 0xd0, 0x11, 0x60, 0x01, 0x30, + 0xf9, 0xe7, 0x70, 0x47, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, + 0x00, 0x10, 0x04, 0x20, 0x43, 0x00, 0x00, 0x10, + 0x63, 0x6f, 0x72, 0x65, 0x20, 0x30, 0x20, 0x61, + 0x6e, 0x64, 0x20, 0x63, 0x6f, 0x72, 0x65, 0x20, + 0x31, 0x20, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, + 0x67, 0x20, 0x21, 0x0a, 0x00, 0x43, 0x4f, 0x52, + 0x45, 0x31, 0x20, 0x57, 0x4f, 0x52, 0x4b, 0x49, + 0x4e, 0x47, 0x20, 0x46, 0x41, 0x49, 0x4c, 0x0a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, + 0x84, 0x00, 0x00, 0x10, 0x1e, 0xd0, 0x00, 0x00, + 0x9c, 0x00, 0x00, 0x10, 0xb9, 0x00, 0x00, 0x10, + 0x00, 0x40, 0x03, 0x40, + ]) + + # No-SDK regression derived from the Pico SDK flash_safe_execute smoke + # test. Core 0 launches core 1 through the synthetic ROM FIFO protocol, + # core 1 installs a FIFO IRQ lockout handler, core 0 takes a SIO spinl= ock, + # locks out core 1, calls the synthetic flash_range_erase/program help= ers, + # verifies XIP contents and helper counters, then releases core 1. + FLASH_SAFE_MULTICORE_TEST_BIN =3D bytes.fromhex( + '00200420010100108b0200108b02001000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '4701001000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '00f03cf800f050f8704800f095f840bf6f4800f0aaf800f050f86e4800f08cf8' + '40bf6d4800f0a1f800f043f86b4800f0a3f8fee76a486b4901606b486b490160' + '62b620bffde71fb4744600f07df85f49884210d15e4800f06ff820bf6448016d' + '01200142f9d000f06ff85a49884202d1594800f061f80fbc01b0204730b55d4c' + '06252068002803d100f066f840bf206800f052f840bf00f057f82168884274d1' + '0434013dedd130bd5348016800296cd07047514800210160704770b54f480021' + '002201231b028154013101329a42fad14b4c4c48606001200003a0600020e060' + '202020614848206048480021002201231b02855cff26b54247d101329a42f8d1' + '404860603d48a06001200002e060404820603e480021002201231b02855c8d42' + '33d1013101329a42f8d1206d01282cd1606d012829d170bd0eb52d490a6d0223' + '1a42fbd048650ebd0eb529490a6d01231a42fbd0886d0ebd0fb525490a6d0123' + '1a4201d0886df9e70fbd10b50446fff7ebffa04209d110bd06b526490278002a' + '02d00a600130f9e706bd2348fff7f4fffee7c046000000000000000001000000' + '000000100010042035010010464c4153482053414645204f4b0a00464c415348' + '2053414645204641494c0a00edfecefa0100cefaefbecefa0200cefaac020010' + '08ed00e00000001000e100e000000100000000d094020010000100d000000020' + '0000ff5f0010100052450000001010105250000000400340bb020010' + ) + + # Similar to FLASH_SAFE_MULTICORE_TEST_BIN, but after locking out core= 1 + # in SRAM it starts a page program through the low-level SSI path and + # verifies that an XIP read while the flash is busy raises HardFault. + FLASH_BUSY_LOCKOUT_TEST_BIN =3D bytes.fromhex( + '0020042001010010e5010010e501001000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + 'e501001000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '664800f066f800f027f800f00ff800f02ef8634800f03df840bf624800f052f8' + '614800f056f800f027f85be030b55f4c06252068002803d100f03bf840bf2068' + '00f027f840bf00f02cf82168884249d10434013dedd130bd55485649564a9042' + '04d203680b6004300431f8e7704753480168002936d0704751485249524a9042' + '04d203680b6004300431f8e74d48013000470eb54d490a6d02231a42fbd04865' + '0ebd0eb549490a6d01231a42fbd0886d0ebd0fb545490a6d01231a4201d0886d' + 'f9e70fbd10b50446fff7ebffa04209d110bd06b53e490278002a02d00a600130' + 'f9e706bd3b48fff7f4fffee73a483b490160002141603a49314a891a314a8918' + 'c16038483449016037485a210160374c0120a060206106202066002020610120' + '2061022020661020206610202066002020665a202066002020612d4800782d48' + '1f49401a1f49401800f009f8fee72a481b49401a1b49401800f001f8fee71c49' + '0278002a02d00a600130f9e77047c046464c4153482042555359204c4f434b4f' + '5554204f4b0a00464c4153482042555359204e4f204641554c540a00c1040010' + 'edfecefa0100cefad304001064040010fc0200100010002064040010000100d0' + 'ec01001000010020fc020010000000d000400340930400100000002000200420' + '4f02001008ed00e0000200200000001800101010870200107002001000100420' + '0111002000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000013110020' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000000000000' + '0000000000000000000000000000000000000000000000000000000012481349' + '016013481349016062b620bffde71fb4744600f012f81049884204d10f4800f0' + '05f820bffde70fbc01b020470c490a6d02231a42fbd04865704709490a6d0123' + '1a42fbd0886d704708ed00e00010002000e100e000000100edfecefa0100cefa' + '000000d0000000000000000001000000001000200010042001110020464c4153' + '482042555359204c4f434b4f5554204f4b0a00464c4153482042555359204c4f' + '434b4f5554204641494c0a00464c4153482042555359204e4f204641554c540a' + '00464c41534820425553592053544152540a00464c4153482042555359204c4f' + '434b45440a00c046' + ) + + # Calls synthetic boot ROM helpers through the RP2040 rom_table_lookup= () + # ABI and verifies bit, byte-memory, and word-memory helper results. + BOOTROM_HELPERS_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x09, 0x00, 0x00, 0x10, + 0x00, 0xf0, 0x11, 0xf8, 0x00, 0x28, 0x03, 0xd1, + 0x48, 0x48, 0x00, 0xf0, 0x70, 0xf8, 0xfe, 0xe7, + 0x47, 0x48, 0x00, 0xf0, 0x6c, 0xf8, 0xfe, 0xe7, + 0x10, 0xb5, 0x46, 0x48, 0x00, 0x88, 0x46, 0x4b, + 0x1b, 0x88, 0x98, 0x47, 0x10, 0xbd, 0x30, 0xb5, + 0x44, 0x49, 0xff, 0xf7, 0xf5, 0xff, 0x04, 0x46, + 0x43, 0x48, 0xa0, 0x47, 0x10, 0x28, 0x58, 0xd1, + 0x42, 0x49, 0xff, 0xf7, 0xed, 0xff, 0x04, 0x46, + 0x41, 0x48, 0xa0, 0x47, 0x08, 0x28, 0x50, 0xd1, + 0x40, 0x49, 0xff, 0xf7, 0xe5, 0xff, 0x04, 0x46, + 0x3f, 0x48, 0xa0, 0x47, 0x0f, 0x28, 0x48, 0xd1, + 0x3e, 0x49, 0xff, 0xf7, 0xdd, 0xff, 0x04, 0x46, + 0x3d, 0x48, 0xa0, 0x47, 0x3c, 0x49, 0x88, 0x42, + 0x3f, 0xd1, 0x3c, 0x49, 0xff, 0xf7, 0xd4, 0xff, + 0x04, 0x46, 0x3b, 0x48, 0x5a, 0x21, 0x04, 0x22, + 0xa0, 0x47, 0x39, 0x4d, 0x28, 0x78, 0x5a, 0x28, + 0x33, 0xd1, 0xe8, 0x78, 0x5a, 0x28, 0x30, 0xd1, + 0x36, 0x49, 0xff, 0xf7, 0xc5, 0xff, 0x04, 0x46, + 0x35, 0x4d, 0x36, 0x48, 0x28, 0x60, 0x36, 0x48, + 0x29, 0x46, 0x04, 0x22, 0xa0, 0x47, 0x33, 0x49, + 0x00, 0x68, 0x88, 0x42, 0x21, 0xd1, 0x33, 0x49, + 0xff, 0xf7, 0xb6, 0xff, 0x04, 0x46, 0x32, 0x48, + 0x32, 0x49, 0x02, 0x22, 0xa0, 0x47, 0x30, 0x4d, + 0x28, 0x68, 0x88, 0x42, 0x15, 0xd1, 0x68, 0x68, + 0x88, 0x42, 0x12, 0xd1, 0x2e, 0x49, 0xff, 0xf7, + 0xa7, 0xff, 0x04, 0x46, 0x2d, 0x4d, 0x2e, 0x48, + 0x28, 0x60, 0x2e, 0x48, 0x68, 0x60, 0x2e, 0x48, + 0x29, 0x46, 0x02, 0x22, 0xa0, 0x47, 0x2a, 0x49, + 0x00, 0x68, 0x88, 0x42, 0x01, 0xd1, 0x00, 0x20, + 0x30, 0xbd, 0x01, 0x20, 0x30, 0xbd, 0x29, 0x4a, + 0x01, 0x78, 0x00, 0x29, 0x02, 0xd0, 0x11, 0x60, + 0x01, 0x30, 0xf9, 0xe7, 0x70, 0x47, 0xc0, 0x46, + 0x42, 0x4f, 0x4f, 0x54, 0x52, 0x4f, 0x4d, 0x20, + 0x48, 0x45, 0x4c, 0x50, 0x45, 0x52, 0x53, 0x20, + 0x4f, 0x4b, 0x0a, 0x00, 0x42, 0x4f, 0x4f, 0x54, + 0x52, 0x4f, 0x4d, 0x20, 0x48, 0x45, 0x4c, 0x50, + 0x45, 0x52, 0x53, 0x20, 0x46, 0x41, 0x49, 0x4c, + 0x0a, 0x00, 0x00, 0x00, 0x08, 0x01, 0x00, 0x10, + 0x1c, 0x01, 0x00, 0x10, 0x14, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x50, 0x33, 0x00, 0x00, + 0x0f, 0xf0, 0xf0, 0xf0, 0x4c, 0x33, 0x00, 0x00, + 0x00, 0x00, 0xf0, 0x00, 0x54, 0x33, 0x00, 0x00, + 0x00, 0x80, 0x00, 0x00, 0x52, 0x33, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x80, 0x4d, 0x53, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x20, 0x4d, 0x43, 0x00, 0x00, + 0x10, 0x00, 0x00, 0x20, 0x44, 0x33, 0x22, 0x11, + 0x20, 0x00, 0x00, 0x20, 0x53, 0x34, 0x00, 0x00, + 0x30, 0x00, 0x00, 0x20, 0x5a, 0x5a, 0xa5, 0xa5, + 0x43, 0x34, 0x00, 0x00, 0x40, 0x00, 0x00, 0x20, + 0x88, 0x77, 0x66, 0x55, 0xcc, 0xbb, 0xaa, 0x99, + 0x50, 0x00, 0x00, 0x20, 0x00, 0x40, 0x03, 0x40, + ]) + + # Calls synthetic RP2040 boot ROM SF/SD table helpers directly and che= cks + # representative single/double arithmetic and conversion results. + BOOTROM_FLOAT_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x09, 0x00, 0x00, 0x10, + 0x4a, 0x4c, 0x23, 0x78, 0x65, 0x78, 0x2d, 0x02, + 0x2b, 0x43, 0xa5, 0x78, 0x2d, 0x04, 0x2b, 0x43, + 0xe5, 0x78, 0x2d, 0x06, 0x2b, 0x43, 0x1c, 0x46, + 0x45, 0x48, 0x46, 0x49, 0xa0, 0x47, 0x46, 0x4a, + 0x90, 0x42, 0x62, 0xd1, 0x45, 0x4c, 0x23, 0x78, + 0x65, 0x78, 0x2d, 0x02, 0x2b, 0x43, 0xa5, 0x78, + 0x2d, 0x04, 0x2b, 0x43, 0xe5, 0x78, 0x2d, 0x06, + 0x2b, 0x43, 0x1c, 0x46, 0x3e, 0x48, 0x40, 0x49, + 0xa0, 0x47, 0x40, 0x4a, 0x90, 0x42, 0x50, 0xd1, + 0x3f, 0x4c, 0x23, 0x78, 0x65, 0x78, 0x2d, 0x02, + 0x2b, 0x43, 0xa5, 0x78, 0x2d, 0x04, 0x2b, 0x43, + 0xe5, 0x78, 0x2d, 0x06, 0x2b, 0x43, 0x1c, 0x46, + 0x38, 0x48, 0x3a, 0x49, 0xa0, 0x47, 0x33, 0x4a, + 0x90, 0x42, 0x3e, 0xd1, 0x38, 0x4c, 0x23, 0x78, + 0x65, 0x78, 0x2d, 0x02, 0x2b, 0x43, 0xa5, 0x78, + 0x2d, 0x04, 0x2b, 0x43, 0xe5, 0x78, 0x2d, 0x06, + 0x2b, 0x43, 0x1c, 0x46, 0x2a, 0x48, 0xa0, 0x47, + 0x00, 0x28, 0x2e, 0xd1, 0x31, 0x4a, 0x91, 0x42, + 0x2b, 0xd1, 0x31, 0x4c, 0x23, 0x78, 0x65, 0x78, + 0x2d, 0x02, 0x2b, 0x43, 0xa5, 0x78, 0x2d, 0x04, + 0x2b, 0x43, 0xe5, 0x78, 0x2d, 0x06, 0x2b, 0x43, + 0x1c, 0x46, 0x00, 0x20, 0x2b, 0x49, 0x00, 0x22, + 0x2b, 0x4b, 0xa0, 0x47, 0x00, 0x28, 0x18, 0xd1, + 0x2a, 0x4a, 0x91, 0x42, 0x15, 0xd1, 0x2a, 0x4c, + 0x23, 0x78, 0x65, 0x78, 0x2d, 0x02, 0x2b, 0x43, + 0xa5, 0x78, 0x2d, 0x04, 0x2b, 0x43, 0xe5, 0x78, + 0x2d, 0x06, 0x2b, 0x43, 0x1c, 0x46, 0x00, 0x20, + 0x20, 0x49, 0xa0, 0x47, 0x23, 0x4a, 0x90, 0x42, + 0x03, 0xd1, 0x23, 0x48, 0x00, 0xf0, 0x05, 0xf8, + 0xfe, 0xe7, 0x22, 0x48, 0x00, 0xf0, 0x01, 0xf8, + 0xfe, 0xe7, 0x21, 0x4a, 0x01, 0x78, 0x00, 0x29, + 0x02, 0xd0, 0x11, 0x60, 0x01, 0x30, 0xf9, 0xe7, + 0x70, 0x47, 0xc0, 0x46, 0x42, 0x4f, 0x4f, 0x54, + 0x52, 0x4f, 0x4d, 0x20, 0x46, 0x4c, 0x4f, 0x41, + 0x54, 0x20, 0x4f, 0x4b, 0x0a, 0x00, 0x42, 0x4f, + 0x4f, 0x54, 0x52, 0x4f, 0x4d, 0x20, 0x46, 0x4c, + 0x4f, 0x41, 0x54, 0x20, 0x46, 0x41, 0x49, 0x4c, + 0x0a, 0x00, 0x00, 0x00, 0xc2, 0x03, 0x00, 0x00, + 0x00, 0x00, 0xa0, 0x3f, 0x00, 0x00, 0x20, 0x40, + 0x00, 0x00, 0x70, 0x40, 0xca, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0xf0, 0x40, + 0xce, 0x03, 0x00, 0x00, 0x00, 0x00, 0x40, 0x40, + 0x3e, 0x04, 0x00, 0x00, 0x00, 0x00, 0xf4, 0x3f, + 0x62, 0x04, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x3f, + 0x00, 0x00, 0x10, 0x40, 0x00, 0x00, 0x16, 0x40, + 0xde, 0x04, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x3f, + 0x0c, 0x01, 0x00, 0x10, 0x1e, 0x01, 0x00, 0x10, + 0x00, 0x40, 0x03, 0x40, + ]) + + # Exercises DMA channel 0 as a synchronous memory copy engine, then as= a + # fill engine by keeping READ_ADDR fixed and incrementing WRITE_ADDR. + DMA_TEST_BIN =3D bytes([ + 0x00, 0x20, 0x04, 0x20, 0x11, 0x00, 0x00, 0x10, + 0x95, 0x00, 0x00, 0x10, 0x95, 0x00, 0x00, 0x10, + 0x2e, 0x4c, 0x2f, 0x4d, 0x01, 0x23, 0x9b, 0x02, + 0x1b, 0x19, 0x2e, 0x48, 0x28, 0x60, 0x2e, 0x48, + 0x68, 0x60, 0x01, 0x20, 0x00, 0x03, 0x18, 0x60, + 0x25, 0x60, 0x10, 0x20, 0x40, 0x19, 0x60, 0x60, + 0x02, 0x20, 0xa0, 0x60, 0x29, 0x48, 0xe0, 0x60, + 0x28, 0x69, 0x26, 0x49, 0x88, 0x42, 0x25, 0xd1, + 0x68, 0x69, 0x25, 0x49, 0x88, 0x42, 0x21, 0xd1, + 0x18, 0x68, 0x01, 0x21, 0x08, 0x42, 0x1d, 0xd0, + 0x19, 0x60, 0x18, 0x68, 0x08, 0x42, 0x19, 0xd1, + 0x21, 0x48, 0x28, 0x62, 0x20, 0x20, 0x40, 0x19, + 0x20, 0x60, 0x30, 0x20, 0x40, 0x19, 0x60, 0x60, + 0x03, 0x20, 0xa0, 0x60, 0x1d, 0x48, 0xe0, 0x60, + 0x1b, 0x49, 0x28, 0x6b, 0x88, 0x42, 0x09, 0xd1, + 0x68, 0x6b, 0x88, 0x42, 0x06, 0xd1, 0xa8, 0x6b, + 0x88, 0x42, 0x03, 0xd1, 0x18, 0x48, 0x00, 0xf0, + 0x09, 0xf8, 0xfe, 0xe7, 0x17, 0x48, 0x00, 0xf0, + 0x05, 0xf8, 0xfa, 0xe7, 0x16, 0x48, 0x00, 0xf0, + 0x01, 0xf8, 0xf6, 0xe7, 0x15, 0x4a, 0x01, 0x78, + 0x00, 0x29, 0x02, 0xd0, 0x11, 0x60, 0x01, 0x30, + 0xf9, 0xe7, 0x70, 0x47, 0x44, 0x4d, 0x41, 0x20, + 0x4f, 0x4b, 0x0a, 0x00, 0x44, 0x4d, 0x41, 0x20, + 0x46, 0x41, 0x49, 0x4c, 0x0a, 0x00, 0x44, 0x4d, + 0x41, 0x20, 0x46, 0x41, 0x55, 0x4c, 0x54, 0x0a, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, + 0x00, 0x00, 0x00, 0x20, 0x44, 0x33, 0x22, 0x11, + 0xdd, 0xcc, 0xbb, 0xaa, 0x39, 0x80, 0x1f, 0x00, + 0xaa, 0x55, 0xaa, 0x55, 0x29, 0x80, 0x1f, 0x00, + 0xac, 0x00, 0x00, 0x10, 0xb4, 0x00, 0x00, 0x10, + 0xbe, 0x00, 0x00, 0x10, 0x00, 0x40, 0x03, 0x40, + ]) + + # Exercises UART0 paced DMA. Channel 0 first sends a trace string thro= ugh + # UART0 TX DREQ and waits for DMA IRQ0. It then receives "HELLO" throu= gh + # UART0 RX DREQ, waits for DMA IRQ0 again, verifies the bytes in SRAM,= and + # sends the success string through UART0 TX DREQ. + DMA_UART_TEST_BIN =3D bytes.fromhex( + '0020042071000010350100103501001000000000000000000000000000000000' + '0000000000000000000000003501001000000000000000003501001035010010' + '3501001035010010350100103501001035010010350100103501001035010010' + '3501001035010010350100102901001072b63d481021c1623c49016303218164' + '3b493c480022d24302603b4802603b48016062b63a480f2100f019f839480521' + '00f023f837483849052203780c78a34208d101300131013af7d134480c2100f0' + '06f837e032480e2100f001f832e010b50c4600f018f82f4a1060234b53609460' + '2d4bd36000f018f810bd10b50c4600f00af8284a1c4b136050609460274bd360' + '00f00af810bd264a00231360254a01231360254a13607047214a136801242342' + '01d130bff9d070471e48016801601c480160704730bffde7444d412055415254' + '2054524143450a48454c4c4f444d412055415254204f4b0a444d412055415254' + '204641494c0a000000400340010300000008000080e100e080e200e000e100e0' + '3801001010000020470100104c010010580100100000005011000a0021800a00' + '000000200004005004040050' + ) + + # Same UART paced DMA test as DMA_UART_TEST_BIN, but using UART1 at + # 0x40038000 and DREQ_UART1_TX/RX. + DMA_UART1_TEST_BIN =3D bytes.fromhex( + '0020042071000010350100103501001000000000000000000000000000000000' + '0000000000000000000000003501001000000000000000003501001035010010' + '3501001035010010350100103501001035010010350100103501001035010010' + '3501001035010010350100102901001072b63e481021c1623d49016303218164' + '3c493d480022d24302603c4802603c48016062b63b48102100f019f83a480521' + '00f023f838483949052203780c78a34208d101300131013af7d135480d2100f0' + '06f837e033480f2100f001f832e010b50c4600f018f8304a1060244b53609460' + '2e4bd36000f018f810bd10b50c4600f00af8294a1d4b136050609460284bd360' + '00f00af810bd274a00231360264a01231360264a13607047224a136801242342' + '01d130bff9d070471f48016801601d480160704730bffde7444d412055415254' + '312054524143450a48454c4c4f444d41205541525431204f4b0a444d41205541' + '525431204641494c0a00000000800340010300000008000080e100e080e200e0' + '00e100e03801001010000020480100104d0100105a0100100000005011000b00' + '21800b00000000200004005004040050' + ) + + BOOT2_W25Q080_BIN =3D bytes.fromhex( + '00b5324b212058609868022188439860' + 'd860186158612e4b0021996002215961' + '0121f02299502b491960012199603520' + '00f044f80222904214d00621196600f0' + '34f8196e01211966002018661a6600f0' + '2cf8196e196e196e052000f02ff80121' + '0842f9d1002199601b49196000215960' + '1a491b48016001219960eb211966a021' + '196600f012f800219960164914480160' + '0121996001bc002800d0004712481349' + '086003c880f30888084703b5996a0420' + '0142fbd001200142f8d103bd02b51866' + '1866fff7f2ff186e186e02bd00000240' + '000000180000070000035f0021220000' + 'f4000018222000a00001001008ed00e0' + '00000000000000000000000074b24e7a' + ) + + @staticmethod + def make_xip_elf(payload): + return RaspiPicoMachine.make_xip_elf_with_phdrs(payload, ()) + + @staticmethod + def make_xip_elf_with_phdrs(payload, extra_phdrs): + load_addr =3D 0x10000000 + payload_off =3D 0x100 + phnum =3D 1 + len(extra_phdrs) + + elf_header =3D struct.pack( + '<16sHHIIIIIHHHHHH', + b'\x7fELF\x01\x01\x01' + b'\x00' * 9, + 2, # ET_EXEC + 40, # EM_ARM + 1, # EV_CURRENT + load_addr, + 52, # e_phoff + 0, # e_shoff + 0x05000200, # EABI version 5, soft-float + 52, # e_ehsize + 32, # e_phentsize + phnum, + 0, + 0, + 0, + ) + phdr =3D struct.pack( + ' 476: + raise ValueError('single-block test UF2 payload is too large') + + struct.pack_into( + ' (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 1783290544833468.42191276338417; Sun, 5 Jul 2026 15:29:04 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wgVJw-0006mb-St; Sun, 05 Jul 2026 18:28:16 -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 1wgUx7-0002OO-GJ; Sun, 05 Jul 2026 18:04:41 -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 1wgUx2-000286-5L; Sun, 05 Jul 2026 18:04:39 -0400 Received: from smtp02.univ-lille.fr (smtp02.univ-lille.fr [194.254.129.96]) by smtp-out-2.univ-lille.fr (Postfix) with ESMTPS id 4gthMH1Yrvz85YH; Mon, 6 Jul 2026 00:03:23 +0200 (CEST) Received: from mac.home (lfbn-lil-1-243-101.w90-45.abo.wanadoo.fr [90.45.87.101]) (Authenticated sender: gilles.grimaud) by smtp02.univ-lille.fr (Postfix) with ESMTPSA id 4gthMH0P6yz6tBv; Mon, 6 Jul 2026 00:03:23 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=univ-lille.fr; s=dkim; t=1783289003; bh=kRwJ0NIP3fzTSU6HBy5i8C8rimwG0J2wuge5zhQh0OI=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZmRc8mWShWHAP7Wdywtlf6dgg71a35AnaIOVSnMSNZ2Da49E8q75xxM0WJ2s9Z10H 6rPAEgIvhtjFB+Ttsw2c72/ep9Rj6OTKL6xVM257oi9mO0Vyj4KD9Fc4EKxL0mKlBq K/3pfmUND20cntvJnlwzvMkSr24Vc7Zgkn0+5tMEzxKZy5ktsYP/NoPya/sAOMxWbq bp7kEe3Bonk6JVro0xXCXwoHdq8MN1PfOH2nJHViFGUpLJezQYdOq/1VGbj7YFlNbD wmgHU+8ScUgn/ntd9N7cvaDtCVCKq0YfFPzM0+ttvlIu0G4Ocq48ysfOv1locHtOMc l3C67E+TLpVfA== From: Gilles Grimaud To: qemu-devel@nongnu.org Cc: qemu-arm@nongnu.org, Gilles Grimaud Subject: [PATCH RFC v1 9/9] docs/system/arm: document Raspberry Pi Pico machine Date: Mon, 6 Jul 2026 00:03:20 +0200 Message-ID: <20260705220320.90395-10-gilles.grimaud@univ-lille.fr> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260705220320.90395-1-gilles.grimaud@univ-lille.fr> References: <20260705220320.90395-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-Mailman-Approved-At: Sun, 05 Jul 2026 18:28:16 -0400 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: 1783290549483158500 Content-Type: text/plain; charset="utf-8" Document the raspi-pico machine, boot paths, -kernel ELF/UF2/raw loading, f= lash-file persistence, UART options, synthetic ROM behaviour, optional -bio= s use with an external RP2040 mask ROM, supported devices and known limitat= ions. Signed-off-by: Gilles Grimaud --- docs/system/arm/raspi-pico.rst | 616 +++++++++++++++++++++++++++++++++ docs/system/target-arm.rst | 1 + 2 files changed, 617 insertions(+) create mode 100644 docs/system/arm/raspi-pico.rst diff --git a/docs/system/arm/raspi-pico.rst b/docs/system/arm/raspi-pico.rst new file mode 100644 index 0000000000..7c8f1ee88e --- /dev/null +++ b/docs/system/arm/raspi-pico.rst @@ -0,0 +1,616 @@ +.. 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. + * 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, + VREG_AND_CHIP_RESET and BUSCTRL programmer-visible registers + * 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 USBCTRL_REGS storage, without USB packet-level + emulation + +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, USB host, endpoint state machines and BOOTSEL mass-storage + programming are not implemented. The model only provides USB DPRAM and + shallow ``USBCTRL_REGS`` storage for firmware that probes early USB sta= te. + * SPI0, SPI1, I2C0, I2C1, PWM, ADC and the temperature sensor are not + implemented. + * 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 is a supported QEMU execution path for running Pico + firmware. It sometimes uses QEMU-only pseudo-device shortcuts to produ= ce + the same architectural result quickly, notably for boot ROM helper calls + around floating-point routines and flash operations. The external mask= ROM + path is also available through ``-bios`` for user-provided RP2040 ROM + images such as ``pipico.rom``; QEMU does not ship that ROM image, and U= SB + 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. + +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, USB DPRAM and +USBCTRL_REGS, 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 functional tests keep coverage self-contained by reproducing t= he +important 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 bus fabric control model implements the documented ``BUS_PRIORITY``, +``BUS_PRIORITY_ACK`` and four ``PERFCTR``/``PERFSEL`` performance-counter +pairs. See datasheet section 2.1, pages 14 to 20. Priority changes are +acknowledged immediately. The performance counters expose the reset values +and software-visible selection/clear behaviour; selected counters advance = on +read rather than counting real AHB-Lite bus arbitration events. + +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 keeps the ROM, SDK and hardware responsibilities +separate. The SIO model provides inter-core FIFOs, FIFO IRQs and spinlock= s; +the Pico SDK implements higher-level protocols such as +``multicore_lockout``; the boot ROM function table only provides the low +level flash helper entry points. QEMU exposes counters and trace events f= or +synthetic calls to ``connect_internal_flash``, ``flash_exit_xip``, +``flash_flush_cache``, ``flash_enter_cmd_xip``, ``flash_range_erase`` and +``flash_range_program`` so SDK smoke tests can confirm which ROM helper pa= th +was used. + +For the synthetic ROM path, ``flash_range_erase`` and +``flash_range_program`` update the QEMU XIP flash backing immediately. Th= is +is an atomic compatibility service intended to validate SDK code paths and= to +keep CI-oriented firmware tests fast and deterministic. In this mode, the +synthetic ROM is allowed to use QEMU-only pseudo-device services when they +produce the same architectural result more simply than exercising the full +hardware path. It is therefore a functional acceleration path, not a claim +that the real RP2040 mask ROM behaves that way internally. + +The synthetic ROM also provides a CI-friendly program-exit path for Pico S= DK +firmware. The SDK ``_exit()`` implementation executes ``bkpt #0`` so a re= al +debug probe can stop at program exit. When no debugger intercepts that +instruction, the synthetic ROM arranges for the application HardFault vect= or +read from XIP to resolve to a ROM handler, without modifying the stored fl= ash +contents. That handler verifies that the faulting instruction is exactly +``bkpt #0`` and then asks QEMU's synthetic pseudo-device to shut down with= the +stacked ``r0`` value as the process exit status. Other HardFaults still h= ang +in the ROM handler. This behaviour is only enabled for the synthetic ROM = path +and is intended as a valid way for CI tests to terminate QEMU cleanly afte= r a +firmware test calls ``exit(status)``. When QEMU's ARM M-profile ``BKPT`` +handling is routed to an attached gdbstub, the debugger sees the breakpoint +first and this synthetic HardFault exit path is not used. + +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 the real ROM's hardware interactions, provided the ROM stays wi= thin +the subset of RP2040 hardware modeled by QEMU. It is not a USB BOOTSEL +emulation path: guest-visible USB mass-storage programming is outside the +current model. + +The synthetic ROM is also a valid QEMU execution path. It is designed for +running Pico firmware efficiently and repeatably under QEMU, and it may use +QEMU-only pseudo-device services when they produce the same guest-visible +architectural result more simply than stepping through the real ROM and the +low-level hardware sequence. The floating-point boot ROM helpers and the +atomic synthetic flash helper path are examples of these shortcuts. + +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 +minimal command set is: + + * ``0x06`` write enable + * ``0x05`` read status + * ``0x03`` 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. + * ``BUSCTRL`` performance counters are software-visible counters, not real + bus-fabric event counters. They are sufficient for SDK entropy paths b= ut + not for measuring emulated bus contention. + * The synthetic ROM supports the direct boot2/application launch path and= the + core1 FIFO launch sequence, but not every RP2040 boot ROM function table + entry; unsupported entries report an explicit QEMU ``LOG_UNIMP`` diagno= stic + before faulting. The external ``-bios`` path can run a user-provided r= eal + RP2040 mask ROM image, but it is limited by the same missing peripheral + models as the rest of the machine, notably USB BOOTSEL mass-storage mod= e. + * QEMU does not emulate the Pico BOOTSEL USB mass-storage programming mod= e. + UF2 images are accepted only as host-loaded ``-kernel`` inputs. + * USB, PIO and most peripherals are not yet implemented. USB DPRAM is + present as RAM and ``USBCTRL_REGS`` stores register state, but USB + packet-level behavior is not modeled. 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 wrappi= ng, + 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 PL0= 11 + 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. + * ``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, SRAM bank and USB DPRAM windows + return memory transaction errors. ``DBGFORCE`` is stored but not conne= cted + 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.53.0