From: gilles grimaud <[email protected]>

Model the RP2040 XOSC and ROSC register blocks and expose their clock outputs. 
Use virtual time for the ROSC COUNT register and provide either QEMU guest 
entropy or a machine-selectable deterministic seed for RANDOMBIT. Add qtests 
for the ROSC register behavior and deterministic stream.

Signed-off-by: gilles grimaud <[email protected]>
---
 hw/arm/Kconfig                 |   2 +
 hw/arm/raspi_pico.c            |  56 +++++
 hw/arm/rp2040.c                |  14 +-
 hw/misc/Kconfig                |   6 +
 hw/misc/meson.build            |   2 +
 hw/misc/rp2040_rosc.c          | 430 +++++++++++++++++++++++++++++++++
 hw/misc/rp2040_xosc.c          | 237 ++++++++++++++++++
 include/hw/arm/rp2040.h        |   4 +
 include/hw/misc/rp2040_rosc.h  |  42 ++++
 include/hw/misc/rp2040_xosc.h  |  33 +++
 tests/qtest/meson.build        |   1 +
 tests/qtest/rp2040-rosc-test.c | 146 +++++++++++
 12 files changed, 971 insertions(+), 2 deletions(-)
 create mode 100644 hw/misc/rp2040_rosc.c
 create mode 100644 hw/misc/rp2040_xosc.c
 create mode 100644 include/hw/misc/rp2040_rosc.h
 create mode 100644 include/hw/misc/rp2040_xosc.h
 create mode 100644 tests/qtest/rp2040-rosc-test.c

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 89c479ab6d..b55a75fd5a 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -379,10 +379,12 @@ config RP2040
     select ARM_V7M
     select PL011
     select RP2040_NYI
+    select RP2040_ROSC
     select RP2040_SYSCFG
     select RP2040_SYSINFO
     select RP2040_TBMAN
     select RP2040_VREG
+    select RP2040_XOSC
     select UNIMP
 
 config STM32F100_SOC
diff --git a/hw/arm/raspi_pico.c b/hw/arm/raspi_pico.c
index 2bb7d4e511..2a65176dc6 100644
--- a/hw/arm/raspi_pico.c
+++ b/hw/arm/raspi_pico.c
@@ -14,6 +14,7 @@
 #include "hw/arm/rp2040.h"
 #include "hw/core/boards.h"
 #include "hw/core/qdev-properties.h"
+#include "qemu/cutils.h"
 #include "system/address-spaces.h"
 #include "system/system.h"
 #include "qom/object.h"
@@ -28,8 +29,43 @@ struct RaspiPicoMachineState {
 
     RP2040State soc;
     MemoryRegion flash;
+    char *rosc_random_seed;
+    uint64_t rosc_random_seed_value;
+    bool rosc_random_seed_set;
 };
 
+static char *raspi_pico_get_rosc_random_seed(Object *obj, Error **errp)
+{
+    RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+    return g_strdup(s->rosc_random_seed ?: "");
+}
+
+static void raspi_pico_set_rosc_random_seed(Object *obj, const char *value,
+                                            Error **errp)
+{
+    RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+    uint64_t seed;
+
+    if (!value || !*value) {
+        g_free(s->rosc_random_seed);
+        s->rosc_random_seed = NULL;
+        s->rosc_random_seed_value = 0;
+        s->rosc_random_seed_set = false;
+        return;
+    }
+
+    if (qemu_strtou64(value, NULL, 0, &seed) < 0) {
+        error_setg(errp, "invalid ROSC random seed '%s'", value);
+        return;
+    }
+
+    g_free(s->rosc_random_seed);
+    s->rosc_random_seed = g_strdup(value);
+    s->rosc_random_seed_value = seed;
+    s->rosc_random_seed_set = true;
+}
+
 static void raspi_pico_init(MachineState *machine)
 {
     RaspiPicoMachineState *s = RASPI_PICO_MACHINE(machine);
@@ -38,6 +74,10 @@ static void raspi_pico_init(MachineState *machine)
     object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_RP2040);
     qdev_prop_set_chr(DEVICE(&s->soc), "serial0", serial_hd(0));
     qdev_prop_set_chr(DEVICE(&s->soc), "serial1", serial_hd(1));
+    qdev_prop_set_uint64(DEVICE(&s->soc.rosc), "random-seed",
+                         s->rosc_random_seed_value);
+    qdev_prop_set_bit(DEVICE(&s->soc.rosc), "random-seed-set",
+                      s->rosc_random_seed_set);
     if (machine->firmware) {
         qdev_prop_set_string(DEVICE(&s->soc), "bootrom-file",
                              machine->firmware);
@@ -55,6 +95,13 @@ static void raspi_pico_init(MachineState *machine)
                        RP2040_XIP_BASE, PICO_FLASH_SIZE);
 }
 
+static void raspi_pico_machine_finalize(Object *obj)
+{
+    RaspiPicoMachineState *s = RASPI_PICO_MACHINE(obj);
+
+    g_free(s->rosc_random_seed);
+}
+
 static void raspi_pico_machine_class_init(ObjectClass *oc, const void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
@@ -68,6 +115,14 @@ static void raspi_pico_machine_class_init(ObjectClass *oc, 
const void *data)
     mc->no_parallel = 1;
     mc->no_floppy = 1;
     mc->no_cdrom = 1;
+
+    object_class_property_add_str(oc, "rosc-random-seed",
+                                  raspi_pico_get_rosc_random_seed,
+                                  raspi_pico_set_rosc_random_seed);
+    object_class_property_set_description(oc, "rosc-random-seed",
+                                          "Use a deterministic seed for the "
+                                          "ROSC RANDOMBIT stream; if unset, "
+                                          "QEMU guest entropy is used");
 }
 
 static const TypeInfo raspi_pico_machine_info = {
@@ -75,6 +130,7 @@ static const TypeInfo raspi_pico_machine_info = {
     .parent = TYPE_MACHINE,
     .instance_size = sizeof(RaspiPicoMachineState),
     .class_init = raspi_pico_machine_class_init,
+    .instance_finalize = raspi_pico_machine_finalize,
     .interfaces = arm_machine_interfaces,
 };
 
diff --git a/hw/arm/rp2040.c b/hw/arm/rp2040.c
index f2a6613aa3..889decded0 100644
--- a/hw/arm/rp2040.c
+++ b/hw/arm/rp2040.c
@@ -57,7 +57,6 @@ static const struct {
     { "rp2040.ioqspi",   0x40018000, 0x4000 },
     { "rp2040.padsbank0", 0x4001c000, 0x4000 },
     { "rp2040.padsqspi", 0x40020000, 0x4000 },
-    { "rp2040.xosc",     0x40024000, 0x4000 },
     { "rp2040.pll_sys",  0x40028000, 0x4000 },
     { "rp2040.pll_usb",  0x4002c000, 0x4000 },
     { "rp2040.busctrl",  0x40030000, 0x4000 },
@@ -72,7 +71,6 @@ static const struct {
     { "rp2040.timer",    0x40054000, 0x4000 },
     { "rp2040.watchdog", 0x40058000, 0x4000 },
     { "rp2040.rtc",      0x4005c000, 0x4000 },
-    { "rp2040.rosc",     0x40060000, 0x4000 },
     { "rp2040.dma",      0x50000000, 0x1000 },
     { "rp2040.usbctrl_dpram", 0x50100000, 0x10000 },
     { "rp2040.usbctrl_regs",  0x50110000, 0x10000 },
@@ -177,8 +175,10 @@ static void rp2040_soc_init(Object *obj)
 
     object_initialize_child(obj, "syscfg", &s->syscfg, TYPE_RP2040_SYSCFG);
     object_initialize_child(obj, "sysinfo", &s->sysinfo, TYPE_RP2040_SYSINFO);
+    object_initialize_child(obj, "rosc", &s->rosc, TYPE_RP2040_ROSC);
     object_initialize_child(obj, "tbman", &s->tbman, TYPE_RP2040_TBMAN);
     object_initialize_child(obj, "vreg", &s->vreg, TYPE_RP2040_VREG);
+    object_initialize_child(obj, "xosc", &s->xosc, TYPE_RP2040_XOSC);
 
     s->irq = qemu_allocate_irqs(rp2040_set_irq, s, RP2040_NUM_IRQS);
     s->sysclk = clock_new(obj, "sysclk");
@@ -325,6 +325,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error 
**errp)
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysinfo), 0, RP2040_SYSINFO_BASE);
     rp2040_syscfg_update(s);
 
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->rosc), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->rosc), 0, RP2040_ROSC_BASE);
+
     if (!sysbus_realize(SYS_BUS_DEVICE(&s->tbman), errp)) {
         return;
     }
@@ -335,6 +340,11 @@ static void rp2040_soc_realize(DeviceState *dev, Error 
**errp)
     }
     sysbus_mmio_map(SYS_BUS_DEVICE(&s->vreg), 0, RP2040_VREG_BASE);
 
+    if (!sysbus_realize(SYS_BUS_DEVICE(&s->xosc), errp)) {
+        return;
+    }
+    sysbus_mmio_map(SYS_BUS_DEVICE(&s->xosc), 0, RP2040_XOSC_BASE);
+
     for (i = 0; i < ARRAY_SIZE(s->uart); i++) {
         qdev_connect_clock_in(DEVICE(&s->uart[i]), "clk", s->sysclk);
         if (!sysbus_realize(SYS_BUS_DEVICE(&s->uart[i]), errp)) {
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 597559186f..8f0860139f 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -104,6 +104,9 @@ config FSL_IMX8MP_CCM
 config RP2040_NYI
     bool
 
+config RP2040_ROSC
+    bool
+
 config RP2040_SYSCFG
     bool
 
@@ -116,6 +119,9 @@ config RP2040_TBMAN
 config RP2040_VREG
     bool
 
+config RP2040_XOSC
+    bool
+
 config STM32_RCC
     bool
 
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 9191b16360..7ad307beee 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -99,10 +99,12 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
   'bcm2835_powermgt.c',
 ))
 system_ss.add(when: 'CONFIG_RP2040_NYI', if_true: files('rp2040_nyi.c'))
+system_ss.add(when: 'CONFIG_RP2040_ROSC', if_true: files('rp2040_rosc.c'))
 system_ss.add(when: 'CONFIG_RP2040_SYSCFG', if_true: files('rp2040_syscfg.c'))
 system_ss.add(when: 'CONFIG_RP2040_SYSINFO', if_true: 
files('rp2040_sysinfo.c'))
 system_ss.add(when: 'CONFIG_RP2040_TBMAN', if_true: files('rp2040_tbman.c'))
 system_ss.add(when: 'CONFIG_RP2040_VREG', if_true: files('rp2040_vreg.c'))
+system_ss.add(when: 'CONFIG_RP2040_XOSC', if_true: files('rp2040_xosc.c'))
 system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
 system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
 system_ss.add(when: 'CONFIG_XLNX_ZYNQ_DDRC', if_true: 
files('xlnx-zynq-ddrc.c'))
diff --git a/hw/misc/rp2040_rosc.c b/hw/misc/rp2040_rosc.c
new file mode 100644
index 0000000000..dcb340ec57
--- /dev/null
+++ b/hw/misc/rp2040_rosc.c
@@ -0,0 +1,430 @@
+/*
+ * RP2040 ring oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_rosc.h"
+#include "migration/vmstate.h"
+#include "qemu/guest-random.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+
+#define ROSC_NOMINAL_HZ        6500000
+
+#define ROSC_CTRL              0x00
+#define ROSC_FREQA             0x04
+#define ROSC_FREQB             0x08
+#define ROSC_DORMANT           0x0c
+#define ROSC_DIV               0x10
+#define ROSC_PHASE             0x14
+#define ROSC_STATUS            0x18
+#define ROSC_RANDOMBIT         0x1c
+#define ROSC_COUNT             0x20
+
+#define ROSC_ENABLE_DISABLE    0xd1e
+#define ROSC_ENABLE_ENABLE     0xfab
+#define ROSC_FREQ_RANGE_LOW    0xfa4
+#define ROSC_FREQ_RANGE_MEDIUM 0xfa5
+#define ROSC_FREQ_RANGE_HIGH   0xfa7
+#define ROSC_FREQ_RANGE_TOOHIGH 0xfa6
+#define ROSC_DORMANT_VALUE     0x636f6d61
+#define ROSC_WAKE_VALUE        0x77616b65
+#define ROSC_FREQ_PASSWD       0x9696
+#define ROSC_DIV_PASS          0xaa0
+#define ROSC_PHASE_PASSWD      0xaa
+
+#define ROSC_STATUS_STABLE     BIT(31)
+#define ROSC_STATUS_BADWRITE   BIT(24)
+#define ROSC_STATUS_DIV_RUNNING BIT(16)
+#define ROSC_STATUS_ENABLED    BIT(12)
+
+#define ATOMIC_ALIAS_MASK      0x3000
+#define ATOMIC_XOR             0x1000
+#define ATOMIC_SET             0x2000
+#define ATOMIC_CLR             0x3000
+
+static uint32_t rp2040_rosc_apply_alias(uint32_t old, uint32_t value,
+                                        hwaddr alias)
+{
+    switch (alias) {
+    case ATOMIC_XOR:
+        return old ^ value;
+    case ATOMIC_SET:
+        return old | value;
+    case ATOMIC_CLR:
+        return old & ~value;
+    default:
+        return value;
+    }
+}
+
+static bool rp2040_rosc_valid_range(uint32_t range)
+{
+    return range == ROSC_FREQ_RANGE_LOW || range == ROSC_FREQ_RANGE_MEDIUM ||
+           range == ROSC_FREQ_RANGE_HIGH || range == ROSC_FREQ_RANGE_TOOHIGH;
+}
+
+static bool rp2040_rosc_enabled(RP2040RoscState *s)
+{
+    return extract32(s->ctrl, 12, 12) != ROSC_ENABLE_DISABLE;
+}
+
+static bool rp2040_rosc_running(RP2040RoscState *s)
+{
+    return rp2040_rosc_enabled(s) && s->dormant == ROSC_WAKE_VALUE;
+}
+
+static uint32_t rp2040_rosc_divisor(RP2040RoscState *s)
+{
+    uint32_t encoded = s->div & 0xfff;
+    uint32_t div;
+
+    if (encoded < ROSC_DIV_PASS) {
+        return 31;
+    }
+
+    div = encoded - ROSC_DIV_PASS;
+    if (div == 0) {
+        return 32;
+    }
+    if (div > 31) {
+        return 31;
+    }
+    return div;
+}
+
+static uint32_t rp2040_rosc_hz(RP2040RoscState *s)
+{
+    if (!rp2040_rosc_running(s)) {
+        return 0;
+    }
+
+    return ROSC_NOMINAL_HZ / rp2040_rosc_divisor(s);
+}
+
+static void rp2040_rosc_update_clock(RP2040RoscState *s)
+{
+    clock_update_hz(s->clk, rp2040_rosc_hz(s));
+}
+
+static uint32_t rp2040_rosc_count_read(RP2040RoscState *s)
+{
+    uint64_t elapsed_ns;
+    uint64_t elapsed_ticks;
+
+    if (!s->count || !rp2040_rosc_running(s)) {
+        return s->count;
+    }
+
+    elapsed_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) - s->count_start_ns;
+    elapsed_ticks = elapsed_ns * ROSC_NOMINAL_HZ / NANOSECONDS_PER_SECOND;
+    if (elapsed_ticks >= s->count) {
+        s->count = 0;
+    } else {
+        s->count -= elapsed_ticks;
+        s->count_start_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+    }
+
+    return s->count;
+}
+
+static uint32_t rp2040_rosc_status(RP2040RoscState *s)
+{
+    uint32_t value = 0;
+
+    if (rp2040_rosc_enabled(s)) {
+        value |= ROSC_STATUS_ENABLED;
+        if (s->dormant == ROSC_WAKE_VALUE) {
+            value |= ROSC_STATUS_STABLE | ROSC_STATUS_DIV_RUNNING;
+        }
+    }
+    if (s->badwrite) {
+        value |= ROSC_STATUS_BADWRITE;
+    }
+
+    return value;
+}
+
+static uint32_t rp2040_rosc_random_bit(RP2040RoscState *s)
+{
+    uint64_t x;
+    uint32_t bit;
+
+    if (!rp2040_rosc_running(s)) {
+        return 0;
+    }
+
+    if (!s->random_seed_set) {
+        if (!s->random_pool_bits) {
+            qemu_guest_getrandom_nofail(&s->random_pool,
+                                        sizeof(s->random_pool));
+            s->random_pool_bits = 64;
+        }
+
+        bit = s->random_pool & 1;
+        s->random_pool >>= 1;
+        s->random_pool_bits--;
+        return bit;
+    }
+
+    x = s->random_prng_state;
+    x ^= x >> 12;
+    x ^= x << 25;
+    x ^= x >> 27;
+    s->random_prng_state = x;
+    return (x * 0x2545f4914f6cdd1dull) >> 63;
+}
+
+static uint64_t rp2040_rosc_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RP2040RoscState *s = opaque;
+    hwaddr offset = addr & 0xfff;
+    uint64_t value;
+
+    switch (offset) {
+    case ROSC_CTRL:
+        value = s->ctrl;
+        break;
+    case ROSC_FREQA:
+        value = s->freqa;
+        break;
+    case ROSC_FREQB:
+        value = s->freqb;
+        break;
+    case ROSC_DORMANT:
+        value = s->dormant;
+        break;
+    case ROSC_DIV:
+        value = s->div;
+        break;
+    case ROSC_PHASE:
+        value = s->phase;
+        break;
+    case ROSC_STATUS:
+        value = rp2040_rosc_status(s);
+        break;
+    case ROSC_RANDOMBIT:
+        value = rp2040_rosc_random_bit(s);
+        break;
+    case ROSC_COUNT:
+        value = rp2040_rosc_count_read(s);
+        break;
+    default:
+        value = 0;
+        rp2040_log_unimplemented_read("rosc", size,
+                                      RP2040_ROSC_BASE + addr, offset,
+                                      value);
+        break;
+    }
+
+    return value;
+}
+
+static void rp2040_rosc_write_ctrl(RP2040RoscState *s, uint32_t value)
+{
+    uint32_t enable = extract32(value, 12, 12);
+    uint32_t range = extract32(value, 0, 12);
+
+    if (enable != ROSC_ENABLE_DISABLE && enable != ROSC_ENABLE_ENABLE) {
+        s->badwrite = true;
+        value = deposit32(value, 12, 12, ROSC_ENABLE_ENABLE);
+    }
+    if (!rp2040_rosc_valid_range(range)) {
+        s->badwrite = true;
+        value = deposit32(value, 0, 12, ROSC_FREQ_RANGE_LOW);
+    }
+
+    s->ctrl = value & 0x00ffffff;
+    rp2040_rosc_update_clock(s);
+}
+
+static void rp2040_rosc_write_freq(RP2040RoscState *s, uint32_t *reg,
+                                   uint32_t value)
+{
+    if (extract32(value, 16, 16) != ROSC_FREQ_PASSWD) {
+        s->badwrite = true;
+        *reg = 0;
+        return;
+    }
+
+    *reg = value & 0x96967777;
+}
+
+static void rp2040_rosc_write_div(RP2040RoscState *s, uint32_t value)
+{
+    if ((value & 0xfff) < ROSC_DIV_PASS) {
+        s->badwrite = true;
+        value = ROSC_DIV_PASS + 31;
+    }
+
+    s->div = value & 0xfff;
+    rp2040_rosc_update_clock(s);
+}
+
+static void rp2040_rosc_write_phase(RP2040RoscState *s, uint32_t value)
+{
+    if (extract32(value, 4, 8) != ROSC_PHASE_PASSWD) {
+        s->badwrite = true;
+        value = 0;
+    }
+
+    s->phase = value & 0x00000fff;
+}
+
+static void rp2040_rosc_write(void *opaque, hwaddr addr,
+                              uint64_t value64, unsigned size)
+{
+    RP2040RoscState *s = opaque;
+    hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+    hwaddr offset = addr & 0xfff;
+    uint32_t value = value64;
+
+    switch (offset) {
+    case ROSC_CTRL:
+        rp2040_rosc_write_ctrl(s, rp2040_rosc_apply_alias(s->ctrl, value,
+                                                          alias));
+        break;
+    case ROSC_FREQA:
+        rp2040_rosc_write_freq(s, &s->freqa,
+                               rp2040_rosc_apply_alias(s->freqa, value,
+                                                       alias));
+        break;
+    case ROSC_FREQB:
+        rp2040_rosc_write_freq(s, &s->freqb,
+                               rp2040_rosc_apply_alias(s->freqb, value,
+                                                       alias));
+        break;
+    case ROSC_DORMANT:
+        s->dormant = value == ROSC_DORMANT_VALUE ? value : ROSC_WAKE_VALUE;
+        if (value != ROSC_DORMANT_VALUE && value != ROSC_WAKE_VALUE) {
+            s->badwrite = true;
+        }
+        rp2040_rosc_update_clock(s);
+        break;
+    case ROSC_DIV:
+        rp2040_rosc_write_div(s, rp2040_rosc_apply_alias(s->div, value,
+                                                         alias));
+        break;
+    case ROSC_PHASE:
+        rp2040_rosc_write_phase(s, rp2040_rosc_apply_alias(s->phase, value,
+                                                           alias));
+        break;
+    case ROSC_STATUS:
+        if (value & ROSC_STATUS_BADWRITE) {
+            s->badwrite = false;
+        }
+        break;
+    case ROSC_COUNT:
+        s->count = value & 0xff;
+        s->count_start_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+        break;
+    default:
+        rp2040_log_unimplemented_write("rosc", size,
+                                       RP2040_ROSC_BASE + addr, offset,
+                                       value64);
+        break;
+    }
+}
+
+static const MemoryRegionOps rp2040_rosc_ops = {
+    .read = rp2040_rosc_read,
+    .write = rp2040_rosc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void rp2040_rosc_reset(DeviceState *dev)
+{
+    RP2040RoscState *s = RP2040_ROSC(dev);
+
+    s->ctrl = (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW;
+    s->freqa = 0;
+    s->freqb = 0;
+    s->dormant = ROSC_WAKE_VALUE;
+    s->div = ROSC_DIV_PASS + 16;
+    s->phase = 0x00000aa8;
+    s->count = 0;
+    s->count_start_ns = 0;
+    s->random_pool = 0;
+    s->random_pool_bits = 0;
+    s->random_prng_state = s->random_seed;
+    if (!s->random_prng_state) {
+        s->random_prng_state = 0x9e3779b97f4a7c15ull;
+    }
+    s->badwrite = false;
+
+    rp2040_rosc_update_clock(s);
+}
+
+static void rp2040_rosc_init(Object *obj)
+{
+    RP2040RoscState *s = RP2040_ROSC(obj);
+    DeviceState *dev = DEVICE(obj);
+
+    s->clk = qdev_init_clock_out(dev, "clk");
+    memory_region_init_io(&s->iomem, obj, &rp2040_rosc_ops, s,
+                          "rp2040.rosc", RP2040_ROSC_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_rosc_vmstate = {
+    .name = TYPE_RP2040_ROSC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(ctrl, RP2040RoscState),
+        VMSTATE_UINT32(freqa, RP2040RoscState),
+        VMSTATE_UINT32(freqb, RP2040RoscState),
+        VMSTATE_UINT32(dormant, RP2040RoscState),
+        VMSTATE_UINT32(div, RP2040RoscState),
+        VMSTATE_UINT32(phase, RP2040RoscState),
+        VMSTATE_UINT32(count, RP2040RoscState),
+        VMSTATE_INT64(count_start_ns, RP2040RoscState),
+        VMSTATE_UINT64(random_pool, RP2040RoscState),
+        VMSTATE_UINT64(random_prng_state, RP2040RoscState),
+        VMSTATE_UINT64(random_seed, RP2040RoscState),
+        VMSTATE_UINT8(random_pool_bits, RP2040RoscState),
+        VMSTATE_BOOL(random_seed_set, RP2040RoscState),
+        VMSTATE_BOOL(badwrite, RP2040RoscState),
+        VMSTATE_CLOCK(clk, RP2040RoscState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static const Property rp2040_rosc_properties[] = {
+    DEFINE_PROP_UINT64("random-seed", RP2040RoscState, random_seed, 0),
+    DEFINE_PROP_BOOL("random-seed-set", RP2040RoscState, random_seed_set,
+                     false),
+};
+
+static void rp2040_rosc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, rp2040_rosc_reset);
+    dc->vmsd = &rp2040_rosc_vmstate;
+    device_class_set_props(dc, rp2040_rosc_properties);
+}
+
+static const TypeInfo rp2040_rosc_info = {
+    .name          = TYPE_RP2040_ROSC,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RP2040RoscState),
+    .instance_init = rp2040_rosc_init,
+    .class_init    = rp2040_rosc_class_init,
+};
+
+static void rp2040_rosc_register_types(void)
+{
+    type_register_static(&rp2040_rosc_info);
+}
+type_init(rp2040_rosc_register_types)
diff --git a/hw/misc/rp2040_xosc.c b/hw/misc/rp2040_xosc.c
new file mode 100644
index 0000000000..a8340ca0c9
--- /dev/null
+++ b/hw/misc/rp2040_xosc.c
@@ -0,0 +1,237 @@
+/*
+ * RP2040 crystal oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/misc/rp2040_nyi.h"
+#include "hw/misc/rp2040_xosc.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define XOSC_HZ             12000000
+
+#define XOSC_CTRL           0x00
+#define XOSC_STATUS         0x04
+#define XOSC_DORMANT        0x08
+#define XOSC_STARTUP        0x0c
+#define XOSC_COUNT          0x1c
+
+#define XOSC_ENABLE_DISABLE 0xd1e
+#define XOSC_ENABLE_ENABLE  0xfab
+#define XOSC_FREQ_1_15MHZ   0xaa0
+#define XOSC_DORMANT_VALUE  0x636f6d61
+#define XOSC_WAKE_VALUE     0x77616b65
+
+#define XOSC_STATUS_STABLE  BIT(31)
+#define XOSC_STATUS_BADWRITE BIT(24)
+#define XOSC_STATUS_ENABLED BIT(12)
+
+#define ATOMIC_ALIAS_MASK    0x3000
+#define ATOMIC_XOR           0x1000
+#define ATOMIC_SET           0x2000
+#define ATOMIC_CLR           0x3000
+
+static bool rp2040_xosc_enabled(RP2040XoscState *s)
+{
+    return extract32(s->ctrl, 12, 12) != XOSC_ENABLE_DISABLE;
+}
+
+static void rp2040_xosc_update_clock(RP2040XoscState *s)
+{
+    clock_update_hz(s->clk, rp2040_xosc_enabled(s) ? XOSC_HZ : 0);
+}
+
+static uint32_t rp2040_xosc_status(RP2040XoscState *s)
+{
+    uint32_t value = 0;
+
+    if (rp2040_xosc_enabled(s)) {
+        value |= XOSC_STATUS_ENABLED;
+        if (s->dormant == XOSC_WAKE_VALUE) {
+            value |= XOSC_STATUS_STABLE;
+        }
+    }
+    if (s->badwrite) {
+        value |= XOSC_STATUS_BADWRITE;
+    }
+
+    return value;
+}
+
+static uint64_t rp2040_xosc_read(void *opaque, hwaddr addr, unsigned size)
+{
+    RP2040XoscState *s = opaque;
+    hwaddr offset = addr & 0xfff;
+    uint64_t value;
+
+    switch (offset) {
+    case XOSC_CTRL:
+        value = s->ctrl;
+        break;
+    case XOSC_STATUS:
+        value = rp2040_xosc_status(s);
+        break;
+    case XOSC_DORMANT:
+        value = s->dormant;
+        break;
+    case XOSC_STARTUP:
+        value = s->startup;
+        break;
+    case XOSC_COUNT:
+        value = 0;
+        break;
+    default:
+        value = 0;
+        rp2040_log_unimplemented_read("xosc", size,
+                                      RP2040_XOSC_BASE + addr, offset,
+                                      value);
+        break;
+    }
+
+    return value;
+}
+
+static uint32_t rp2040_xosc_apply_alias(uint32_t old, uint32_t value,
+                                        hwaddr alias)
+{
+    switch (alias) {
+    case ATOMIC_XOR:
+        return old ^ value;
+    case ATOMIC_SET:
+        return old | value;
+    case ATOMIC_CLR:
+        return old & ~value;
+    default:
+        return value;
+    }
+}
+
+static void rp2040_xosc_write_ctrl(RP2040XoscState *s, uint32_t value)
+{
+    uint32_t enable = extract32(value, 12, 12);
+    uint32_t freq_range = extract32(value, 0, 12);
+
+    if (freq_range != XOSC_FREQ_1_15MHZ ||
+        (enable != XOSC_ENABLE_DISABLE && enable != XOSC_ENABLE_ENABLE)) {
+        s->badwrite = true;
+    }
+
+    if (enable != XOSC_ENABLE_DISABLE && enable != XOSC_ENABLE_ENABLE) {
+        value = deposit32(value, 12, 12, XOSC_ENABLE_ENABLE);
+    }
+    value = deposit32(value, 0, 12, XOSC_FREQ_1_15MHZ);
+
+    s->ctrl = value;
+    rp2040_xosc_update_clock(s);
+}
+
+static void rp2040_xosc_write(void *opaque, hwaddr addr,
+                              uint64_t value64, unsigned size)
+{
+    RP2040XoscState *s = opaque;
+    hwaddr alias = addr & ATOMIC_ALIAS_MASK;
+    hwaddr offset = addr & 0xfff;
+    uint32_t value = value64;
+
+    switch (offset) {
+    case XOSC_CTRL:
+        rp2040_xosc_write_ctrl(s, rp2040_xosc_apply_alias(s->ctrl, value,
+                                                          alias));
+        break;
+    case XOSC_STATUS:
+        if (value & XOSC_STATUS_BADWRITE) {
+            s->badwrite = false;
+        }
+        break;
+    case XOSC_DORMANT:
+        s->dormant = value == XOSC_DORMANT_VALUE ? value : XOSC_WAKE_VALUE;
+        rp2040_xosc_update_clock(s);
+        break;
+    case XOSC_STARTUP:
+        s->startup = value & 0x00103fff;
+        break;
+    case XOSC_COUNT:
+        s->count = 0;
+        break;
+    default:
+        rp2040_log_unimplemented_write("xosc", size,
+                                       RP2040_XOSC_BASE + addr, offset,
+                                       value64);
+        break;
+    }
+}
+
+static const MemoryRegionOps rp2040_xosc_ops = {
+    .read = rp2040_xosc_read,
+    .write = rp2040_xosc_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+};
+
+static void rp2040_xosc_reset(DeviceState *dev)
+{
+    RP2040XoscState *s = RP2040_XOSC(dev);
+
+    s->ctrl = (XOSC_ENABLE_DISABLE << 12) | XOSC_FREQ_1_15MHZ;
+    s->dormant = XOSC_WAKE_VALUE;
+    s->startup = 0x000000c4;
+    s->count = 0;
+    s->badwrite = false;
+
+    rp2040_xosc_update_clock(s);
+}
+
+static void rp2040_xosc_init(Object *obj)
+{
+    RP2040XoscState *s = RP2040_XOSC(obj);
+    DeviceState *dev = DEVICE(obj);
+
+    s->clk = qdev_init_clock_out(dev, "clk");
+    memory_region_init_io(&s->iomem, obj, &rp2040_xosc_ops, s,
+                          "rp2040.xosc", RP2040_XOSC_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const VMStateDescription rp2040_xosc_vmstate = {
+    .name = TYPE_RP2040_XOSC,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(ctrl, RP2040XoscState),
+        VMSTATE_UINT32(dormant, RP2040XoscState),
+        VMSTATE_UINT32(startup, RP2040XoscState),
+        VMSTATE_UINT32(count, RP2040XoscState),
+        VMSTATE_BOOL(badwrite, RP2040XoscState),
+        VMSTATE_CLOCK(clk, RP2040XoscState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void rp2040_xosc_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    device_class_set_legacy_reset(dc, rp2040_xosc_reset);
+    dc->vmsd = &rp2040_xosc_vmstate;
+}
+
+static const TypeInfo rp2040_xosc_info = {
+    .name          = TYPE_RP2040_XOSC,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(RP2040XoscState),
+    .instance_init = rp2040_xosc_init,
+    .class_init    = rp2040_xosc_class_init,
+};
+
+static void rp2040_xosc_register_types(void)
+{
+    type_register_static(&rp2040_xosc_info);
+}
+type_init(rp2040_xosc_register_types)
diff --git a/include/hw/arm/rp2040.h b/include/hw/arm/rp2040.h
index 86037dd0d7..47a8cb2236 100644
--- a/include/hw/arm/rp2040.h
+++ b/include/hw/arm/rp2040.h
@@ -13,10 +13,12 @@
 #include "hw/char/pl011.h"
 #include "hw/core/clock.h"
 #include "hw/core/sysbus.h"
+#include "hw/misc/rp2040_rosc.h"
 #include "hw/misc/rp2040_syscfg.h"
 #include "hw/misc/rp2040_sysinfo.h"
 #include "hw/misc/rp2040_tbman.h"
 #include "hw/misc/rp2040_vreg.h"
+#include "hw/misc/rp2040_xosc.h"
 #include "qom/object.h"
 
 #define TYPE_RP2040 "rp2040"
@@ -40,8 +42,10 @@ struct RP2040State {
     PL011State uart[2];
     RP2040SysCfgState syscfg;
     RP2040SysInfoState sysinfo;
+    RP2040RoscState rosc;
     RP2040TbmanState tbman;
     RP2040VregState vreg;
+    RP2040XoscState xosc;
 
     MemoryRegion *board_memory;
     MemoryRegion rom;
diff --git a/include/hw/misc/rp2040_rosc.h b/include/hw/misc/rp2040_rosc.h
new file mode 100644
index 0000000000..6377704e22
--- /dev/null
+++ b/include/hw/misc/rp2040_rosc.h
@@ -0,0 +1,42 @@
+/*
+ * RP2040 ring oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_ROSC_H
+#define HW_MISC_RP2040_ROSC_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_ROSC "rp2040-rosc"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040RoscState, RP2040_ROSC)
+
+#define RP2040_ROSC_BASE 0x40060000
+#define RP2040_ROSC_SIZE 0x4000
+
+struct RP2040RoscState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    Clock *clk;
+
+    uint32_t ctrl;
+    uint32_t freqa;
+    uint32_t freqb;
+    uint32_t dormant;
+    uint32_t div;
+    uint32_t phase;
+    uint32_t count;
+    int64_t count_start_ns;
+    uint64_t random_pool;
+    uint64_t random_prng_state;
+    uint64_t random_seed;
+    uint8_t random_pool_bits;
+    bool random_seed_set;
+    bool badwrite;
+};
+
+#endif
diff --git a/include/hw/misc/rp2040_xosc.h b/include/hw/misc/rp2040_xosc.h
new file mode 100644
index 0000000000..7b9ef62d22
--- /dev/null
+++ b/include/hw/misc/rp2040_xosc.h
@@ -0,0 +1,33 @@
+/*
+ * RP2040 crystal oscillator emulation
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_RP2040_XOSC_H
+#define HW_MISC_RP2040_XOSC_H
+
+#include "hw/core/clock.h"
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_RP2040_XOSC "rp2040-xosc"
+OBJECT_DECLARE_SIMPLE_TYPE(RP2040XoscState, RP2040_XOSC)
+
+#define RP2040_XOSC_BASE 0x40024000
+#define RP2040_XOSC_SIZE 0x4000
+
+struct RP2040XoscState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    Clock *clk;
+
+    uint32_t ctrl;
+    uint32_t dormant;
+    uint32_t startup;
+    uint32_t count;
+    bool badwrite;
+};
+
+#endif
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 8916f68bad..441f6f294a 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -256,6 +256,7 @@ qtests_arm = \
   (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) 
+ \
   (config_all_devices.has_key('CONFIG_MICROBIT') ? ['microbit-test'] : []) + \
   (config_all_devices.has_key('CONFIG_RASPI_PICO') ? 
['rp2040-sysinfo-syscfg-test',
+                                                       'rp2040-rosc-test',
                                                        'rp2040-tbman-test',
                                                        'rp2040-vreg-test'] : 
[]) + \
   (config_all_devices.has_key('CONFIG_STM32L4X5_SOC') ? qtests_stm32l4x5 : []) 
+ \
diff --git a/tests/qtest/rp2040-rosc-test.c b/tests/qtest/rp2040-rosc-test.c
new file mode 100644
index 0000000000..2f1fb96022
--- /dev/null
+++ b/tests/qtest/rp2040-rosc-test.c
@@ -0,0 +1,146 @@
+/*
+ * QTest testcase for the RP2040 ring oscillator block.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest.h"
+#include "qemu/bitops.h"
+
+#define ROSC_BASE              0x40060000
+#define ROSC_CTRL              0x00
+#define ROSC_FREQA             0x04
+#define ROSC_DORMANT           0x0c
+#define ROSC_DIV               0x10
+#define ROSC_PHASE             0x14
+#define ROSC_STATUS            0x18
+#define ROSC_RANDOMBIT         0x1c
+#define ROSC_COUNT             0x20
+
+#define ROSC_ENABLE_ENABLE     0xfab
+#define ROSC_ENABLE_DISABLE    0xd1e
+#define ROSC_FREQ_RANGE_LOW    0xfa4
+#define ROSC_WAKE_VALUE        0x77616b65
+#define ROSC_STATUS_STABLE     BIT(31)
+#define ROSC_STATUS_BADWRITE   BIT(24)
+#define ROSC_STATUS_DIV_RUNNING BIT(16)
+#define ROSC_STATUS_ENABLED    BIT(12)
+#define ROSC_STATUS_RUNNING \
+    (ROSC_STATUS_STABLE | ROSC_STATUS_DIV_RUNNING | ROSC_STATUS_ENABLED)
+
+static QTestState *rp2040_start(void)
+{
+    return qtest_init("-machine raspi-pico");
+}
+
+static uint32_t read_random_bits(QTestState *qts, unsigned count)
+{
+    uint32_t value = 0;
+    unsigned i;
+
+    for (i = 0; i < count; i++) {
+        value |= (qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & BIT(0)) << i;
+    }
+
+    return value;
+}
+
+static void test_rosc_reset_values(void)
+{
+    QTestState *qts = rp2040_start();
+
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_CTRL), ==,
+                    (ROSC_ENABLE_ENABLE << 12) | ROSC_FREQ_RANGE_LOW);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DORMANT), ==,
+                    ROSC_WAKE_VALUE);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), ==, 0x00000ab0);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS), ==,
+                    ROSC_STATUS_RUNNING);
+
+    qtest_quit(qts);
+}
+
+static void test_rosc_badwrite_and_clear(void)
+{
+    QTestState *qts = rp2040_start();
+
+    qtest_writel(qts, ROSC_BASE + ROSC_FREQA, 0x00007777);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_FREQA), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
+                    ROSC_STATUS_BADWRITE, ==, ROSC_STATUS_BADWRITE);
+
+    qtest_writel(qts, ROSC_BASE + ROSC_STATUS, ROSC_STATUS_BADWRITE);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
+                    ROSC_STATUS_BADWRITE, ==, 0);
+
+    qtest_quit(qts);
+}
+
+static void test_rosc_count_reaches_zero(void)
+{
+    QTestState *qts = rp2040_start();
+    int i;
+
+    qtest_writel(qts, ROSC_BASE + ROSC_COUNT, 1);
+    for (i = 0; i < 16 && qtest_readl(qts, ROSC_BASE + ROSC_COUNT); i++) {
+        qtest_clock_step(qts, 1000);
+    }
+
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_COUNT), ==, 0);
+
+    qtest_quit(qts);
+}
+
+static void test_rosc_misc_values(void)
+{
+    QTestState *qts = rp2040_start();
+
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_RANDOMBIT) & ~BIT(0),
+                    ==, 0);
+    qtest_writel(qts, ROSC_BASE + ROSC_DIV, 0xaa1);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_DIV), ==, 0xaa1);
+    qtest_writel(qts, ROSC_BASE + ROSC_PHASE, 0xaa8);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_PHASE), ==, 0xaa8);
+
+    qtest_writel(qts, ROSC_BASE + ROSC_CTRL,
+                 (ROSC_ENABLE_DISABLE << 12) | ROSC_FREQ_RANGE_LOW);
+    g_assert_cmphex(qtest_readl(qts, ROSC_BASE + ROSC_STATUS) &
+                    ROSC_STATUS_ENABLED, ==, 0);
+
+    qtest_quit(qts);
+}
+
+static void test_rosc_seeded_randombit(void)
+{
+    QTestState *qts_a;
+    QTestState *qts_b;
+    uint32_t bits_a;
+    uint32_t bits_b;
+
+    qts_a = qtest_init("-machine raspi-pico,rosc-random-seed=0x1234");
+    bits_a = read_random_bits(qts_a, 32);
+    qtest_quit(qts_a);
+
+    qts_b = qtest_init("-machine raspi-pico,rosc-random-seed=0x1234");
+    bits_b = read_random_bits(qts_b, 32);
+    qtest_quit(qts_b);
+
+    g_assert_cmphex(bits_a, ==, bits_b);
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/rp2040-rosc/reset-values", test_rosc_reset_values);
+    qtest_add_func("/rp2040-rosc/badwrite-and-clear",
+                   test_rosc_badwrite_and_clear);
+    qtest_add_func("/rp2040-rosc/count-reaches-zero",
+                   test_rosc_count_reaches_zero);
+    qtest_add_func("/rp2040-rosc/misc-values", test_rosc_misc_values);
+    qtest_add_func("/rp2040-rosc/seeded-randombit",
+                   test_rosc_seeded_randombit);
+
+    return g_test_run();
+}
-- 
2.55.0


Reply via email to