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
