* [PATCH 1/5] hw/misc/bcm2838_rng200: add BCM2838 RNG200
2026-07-25 1:26 [PATCH 0/5] hw/arm/raspi4b: RNG200 and thermal sensor Marcelo Manzo
@ 2026-07-25 1:26 ` Marcelo Manzo
2026-07-25 1:26 ` [PATCH 2/5] hw/arm/bcm2838: enable " Marcelo Manzo
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Marcelo Manzo @ 2026-07-25 1:26 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Marcelo Manzo
From: Sergey Kambalin <serg.oker@gmail.com>
Add the BCM2838 (BCM2711/Raspberry Pi 4B) hardware random number
generator: register model (control, FIFO data/count, int
mask/status), a Fifo8-backed entropy buffer refilled from a
qemu_rng backend on a ptimer tick, and interrupt generation once the
FIFO crosses its configured threshold.
Part of Sergey Kambalin's original Raspberry Pi 4B series (patchwork
series 829638, posted to qemu-devel in 2024, never merged). Ported
to current master by Marcelo Manzo: header paths moved under
hw/core/, class_init()'s const void *data, the Resettable phases
API, Property[] losing its DEFINE_PROP_END_OF_LIST() sentinel, and
fifo8_pop_buf() gaining a caller-supplied destination buffer instead
of returning an internal pointer (adapted bcm2838_rng200_read_fifo_data()
to the new copying API, which QEMU's own fifo8.h now recommends over
the old fifo8_pop_bufptr() this code originally used). No functional
change otherwise.
Boot-tested: guest probes it ("iproc-rng200 fe104000.rng: hwrng
registered"), /dev/hwrng exists, and reading it returns real random
bytes.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/misc/bcm2838_rng200.c | 406 +++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
hw/misc/trace-events | 10 +
include/hw/misc/bcm2838_rng200.h | 42 ++++
4 files changed, 459 insertions(+)
create mode 100644 hw/misc/bcm2838_rng200.c
create mode 100644 include/hw/misc/bcm2838_rng200.h
diff --git a/hw/misc/bcm2838_rng200.c b/hw/misc/bcm2838_rng200.c
new file mode 100644
index 00000000..2d12d45e
--- /dev/null
+++ b/hw/misc/bcm2838_rng200.c
@@ -0,0 +1,406 @@
+/*
+ * BCM2838 Random Number Generator emulation
+ *
+ * Copyright (C) 2022 Sergey Pushkarev <sergey.pushkarev@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qom/object_interfaces.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/bcm2838_rng200.h"
+#include "hw/core/registerfields.h"
+#include "hw/core/resettable.h"
+#include "migration/vmstate.h"
+#include "trace.h"
+
+/* RNG200 registers */
+REG32(RNG_CTRL, 0x00)
+ FIELD(RNG_CTRL, RBG_ENABLE, 0 , 1)
+ FIELD(RNG_CTRL, RSVD, 1 , 12)
+ FIELD(RNG_CTRL, DIV, 13 , 8)
+
+REG32(RNG_SOFT_RESET, 0x04)
+REG32(RBG_SOFT_RESET, 0x08)
+REG32(RNG_TOTAL_BIT_COUNT, 0x0C)
+REG32(RNG_TOTAL_BIT_COUNT_THRESHOLD, 0x10)
+
+REG32(RNG_INT_STATUS, 0x18)
+ FIELD(RNG_INT_STATUS, TOTAL_BITS_COUNT_IRQ, 0, 1)
+ FIELD(RNG_INT_STATUS, RSVD0, 1, 4)
+ FIELD(RNG_INT_STATUS, NIST_FAIL_IRQ, 5, 1)
+ FIELD(RNG_INT_STATUS, RSVD1, 6, 11)
+ FIELD(RNG_INT_STATUS, STARTUP_TRANSITIONS_MET_IRQ, 17, 1)
+ FIELD(RNG_INT_STATUS, RSVD2, 18, 13)
+ FIELD(RNG_INT_STATUS, MASTER_FAIL_LOCKOUT_IRQ, 30, 1)
+
+REG32(RNG_INT_ENABLE, 0x1C)
+ FIELD(RNG_INT_ENABLE, TOTAL_BITS_COUNT_IRQ, 0, 1)
+ FIELD(RNG_INT_ENABLE, RSVD0, 1, 4)
+ FIELD(RNG_INT_ENABLE, NIST_FAIL_IRQ, 5, 1)
+ FIELD(RNG_INT_ENABLE, RSVD1, 6, 11)
+ FIELD(RNG_INT_ENABLE, STARTUP_TRANSITIONS_MET_IRQ, 17, 1)
+ FIELD(RNG_INT_ENABLE, RSVD2, 18, 13)
+ FIELD(RNG_INT_ENABLE, MASTER_FAIL_LOCKOUT_IRQ, 30, 1)
+
+REG32(RNG_FIFO_DATA, 0x20)
+
+REG32(RNG_FIFO_COUNT, 0x24)
+ FIELD(RNG_FIFO_COUNT, COUNT, 0, 8)
+ FIELD(RNG_FIFO_COUNT, THRESHOLD, 8, 8)
+
+
+#define RNG_WARM_UP_PERIOD_ELAPSED 17
+
+#define SOFT_RESET 1
+#define IRQ_PENDING 1
+
+#define BCM2838_RNG200_PTIMER_POLICY (PTIMER_POLICY_CONTINUOUS_TRIGGER)
+
+static const VMStateDescription vmstate_bcm2838_rng200 = {
+ .name = "bcm2838_rng200",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(rng_fifo_cap, BCM2838Rng200State),
+ VMSTATE_ARRAY(regs, BCM2838Rng200State, N_BCM2838_RNG200_REGS, 0,
+ vmstate_info_uint32, uint32_t),
+
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static bool is_rbg_enabled(BCM2838Rng200State *s)
+{
+ return FIELD_EX32(s->regs[R_RNG_CTRL], RNG_CTRL, RBG_ENABLE);
+}
+
+static void increment_bit_counter_by(BCM2838Rng200State *s, uint32_t inc_val)
+{
+ s->regs[R_RNG_TOTAL_BIT_COUNT] += inc_val;
+}
+
+static void bcm2838_rng200_update_irq(BCM2838Rng200State *s)
+{
+ qemu_set_irq(s->irq,
+ !!(s->regs[R_RNG_INT_ENABLE] & s->regs[R_RNG_INT_STATUS]));
+}
+
+static void bcm2838_rng200_update_rbg_period(void *opaque, ClockEvent event)
+{
+ BCM2838Rng200State *s = (BCM2838Rng200State *)opaque;
+
+ ptimer_transaction_begin(s->ptimer);
+ ptimer_set_period_from_clock(s->ptimer, s->clock, s->rng_fifo_cap * 8);
+ ptimer_transaction_commit(s->ptimer);
+}
+
+static void bcm2838_rng200_update_fifo(void *opaque, const void *buf,
+ size_t size)
+{
+ BCM2838Rng200State *s = (BCM2838Rng200State *)opaque;
+ Fifo8 *fifo = &s->fifo;
+ size_t num = MIN(size, fifo8_num_free(fifo));
+ uint32_t num_bits = num * 8;
+ uint32_t bit_count = 0;
+ uint32_t bit_count_thld = 0;
+ uint32_t fifo_thld = 0;
+
+ bit_count = s->regs[R_RNG_TOTAL_BIT_COUNT];
+ bit_count_thld = s->regs[R_RNG_TOTAL_BIT_COUNT_THRESHOLD];
+
+ if (bit_count + num_bits < bit_count_thld) {
+ increment_bit_counter_by(s, num_bits);
+
+ fifo8_push_all(fifo, buf, num);
+
+ fifo_thld = FIELD_EX32(s->regs[R_RNG_FIFO_COUNT],
+ RNG_FIFO_COUNT, THRESHOLD);
+
+ if (fifo8_num_used(fifo) > fifo_thld) {
+ s->regs[R_RNG_INT_STATUS] = FIELD_DP32(s->regs[R_RNG_INT_STATUS],
+ RNG_INT_STATUS,
+ TOTAL_BITS_COUNT_IRQ, 1);
+ }
+ }
+
+ s->regs[R_RNG_FIFO_COUNT] = FIELD_DP32(s->regs[R_RNG_FIFO_COUNT],
+ RNG_FIFO_COUNT,
+ COUNT,
+ fifo8_num_used(fifo) >> 2);
+ bcm2838_rng200_update_irq(s);
+ trace_bcm2838_rng200_update_fifo(num, fifo8_num_used(fifo));
+}
+
+static void bcm2838_rng200_fill_fifo(BCM2838Rng200State *s)
+{
+ rng_backend_request_entropy(s->rng, fifo8_num_free(&s->fifo),
+ bcm2838_rng200_update_fifo, s);
+}
+
+static void bcm2838_rng200_disable_rbg(void)
+{
+ trace_bcm2838_rng200_disable_rbg();
+}
+
+static void bcm2838_rng200_enable_rbg(BCM2838Rng200State *s)
+{
+ s->regs[R_RNG_TOTAL_BIT_COUNT] = RNG_WARM_UP_PERIOD_ELAPSED;
+
+ bcm2838_rng200_fill_fifo(s);
+
+ trace_bcm2838_rng200_enable_rbg();
+}
+
+static void bcm2838_rng200_rng_reset(BCM2838Rng200State *s)
+{
+ memset(s->regs, 0, sizeof(s->regs));
+ s->regs[R_RNG_INT_STATUS] = FIELD_DP32(s->regs[R_RNG_INT_STATUS],
+ RNG_INT_STATUS,
+ STARTUP_TRANSITIONS_MET_IRQ,
+ IRQ_PENDING);
+ fifo8_reset(&s->fifo);
+
+ trace_bcm2838_rng200_rng_soft_reset();
+}
+
+static void bcm2838_rng200_rbg_reset(BCM2838Rng200State *s)
+{
+ trace_bcm2838_rng200_rbg_soft_reset();
+}
+
+static uint32_t bcm2838_rng200_read_fifo_data(BCM2838Rng200State *s)
+{
+ Fifo8 *fifo = &s->fifo;
+ uint32_t to_read = MIN(fifo8_num_used(fifo), 4);
+ uint8_t byte_buf[4] = {};
+ uint8_t *p = byte_buf;
+ uint32_t ret = 0;
+ uint32_t num;
+
+ while (to_read) {
+ num = fifo8_pop_buf(fifo, p, to_read);
+ p += num;
+ to_read -= num;
+ }
+ ret = ldl_le_p(byte_buf);
+
+ s->regs[R_RNG_FIFO_COUNT] = FIELD_DP32(s->regs[R_RNG_FIFO_COUNT],
+ RNG_FIFO_COUNT,
+ COUNT,
+ fifo8_num_used(fifo) >> 2);
+
+ bcm2838_rng200_fill_fifo(s);
+
+ return ret;
+}
+
+static void bcm2838_rng200_ctrl_write(BCM2838Rng200State *s, uint32_t value)
+{
+ bool currently_enabled = is_rbg_enabled(s);
+ bool enable_requested = FIELD_EX32(value, RNG_CTRL, RBG_ENABLE);
+
+ s->regs[R_RNG_CTRL] = value;
+
+ if (!currently_enabled && enable_requested) {
+ bcm2838_rng200_enable_rbg(s);
+ } else if (currently_enabled && !enable_requested) {
+ bcm2838_rng200_disable_rbg();
+ }
+}
+
+static uint64_t bcm2838_rng200_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ BCM2838Rng200State *s = (BCM2838Rng200State *)opaque;
+ uint32_t res = 0;
+
+ switch (offset) {
+ case A_RNG_CTRL:
+ res = s->regs[R_RNG_CTRL];
+ break;
+ case A_RNG_SOFT_RESET:
+ case A_RBG_SOFT_RESET:
+ break;
+ case A_RNG_INT_STATUS:
+ res = s->regs[R_RNG_INT_STATUS];
+ break;
+ case A_RNG_INT_ENABLE:
+ res = s->regs[R_RNG_INT_ENABLE];
+ break;
+ case A_RNG_FIFO_DATA:
+ res = bcm2838_rng200_read_fifo_data(s);
+ break;
+ case A_RNG_FIFO_COUNT:
+ res = s->regs[R_RNG_FIFO_COUNT];
+ break;
+ case A_RNG_TOTAL_BIT_COUNT:
+ res = s->regs[R_RNG_TOTAL_BIT_COUNT];
+ break;
+ case A_RNG_TOTAL_BIT_COUNT_THRESHOLD:
+ res = s->regs[R_RNG_TOTAL_BIT_COUNT_THRESHOLD];
+ break;
+ default:
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "bcm2838_rng200_read: Bad offset 0x%" HWADDR_PRIx "\n",
+ offset
+ );
+ res = 0;
+ break;
+ }
+
+ trace_bcm2838_rng200_read(offset, size, res);
+ return res;
+}
+
+static void bcm2838_rng200_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ BCM2838Rng200State *s = (BCM2838Rng200State *)opaque;
+
+ trace_bcm2838_rng200_write(offset, value, size);
+
+ switch (offset) {
+ case A_RNG_CTRL:
+ bcm2838_rng200_ctrl_write(s, value);
+ break;
+ case A_RNG_SOFT_RESET:
+ if (value & SOFT_RESET) {
+ bcm2838_rng200_rng_reset(s);
+ }
+ break;
+ case A_RBG_SOFT_RESET:
+ if (value & SOFT_RESET) {
+ bcm2838_rng200_rbg_reset(s);
+ }
+ break;
+ case A_RNG_INT_STATUS:
+ s->regs[R_RNG_INT_STATUS] &= ~value;
+ bcm2838_rng200_update_irq(s);
+ break;
+ case A_RNG_INT_ENABLE:
+ s->regs[R_RNG_INT_ENABLE] = value;
+ bcm2838_rng200_update_irq(s);
+ break;
+ case A_RNG_FIFO_COUNT:
+ s->regs[R_RNG_FIFO_COUNT] = value;
+ break;
+ case A_RNG_TOTAL_BIT_COUNT_THRESHOLD:
+ s->regs[R_RNG_TOTAL_BIT_COUNT_THRESHOLD] = value;
+ s->regs[R_RNG_TOTAL_BIT_COUNT] = value + 1;
+ break;
+ default:
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "bcm2838_rng200_write: Bad offset 0x%" HWADDR_PRIx "\n",
+ offset
+ );
+ break;
+ }
+}
+
+static const MemoryRegionOps bcm2838_rng200_ops = {
+ .read = bcm2838_rng200_read,
+ .write = bcm2838_rng200_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .max_access_size = 4,
+ .min_access_size = 4,
+ },
+ .valid = {
+ .max_access_size = 4,
+ .min_access_size = 4
+ },
+};
+
+static void bcm2838_rng200_realize(DeviceState *dev, Error **errp)
+{
+ BCM2838Rng200State *s = BCM2838_RNG200(dev);
+
+ if (s->rng == NULL) {
+ Object *default_backend = object_new(TYPE_RNG_BUILTIN);
+
+ if (!user_creatable_complete(USER_CREATABLE(default_backend),
+ errp)) {
+ object_unref(default_backend);
+ error_setg(errp, "Failed to create user creatable RNG backend");
+ return;
+ }
+
+ object_property_add_child(OBJECT(dev), "default-backend",
+ default_backend);
+ object_unref(default_backend);
+
+ object_property_set_link(OBJECT(dev), "rng", default_backend,
+ errp);
+ }
+
+ fifo8_create(&s->fifo, s->rng_fifo_cap);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+}
+
+static void bcm2838_rng200_init(Object *obj)
+{
+ BCM2838Rng200State *s = BCM2838_RNG200(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ s->rng_fifo_cap = 128;
+
+ s->clock = qdev_init_clock_in(DEVICE(s), "rbg-clock",
+ bcm2838_rng200_update_rbg_period, s,
+ ClockPreUpdate);
+ if (s->clock == NULL) {
+ error_setg(&error_fatal, "Failed to init RBG clock");
+ return;
+ }
+
+ memory_region_init_io(&s->iomem, obj, &bcm2838_rng200_ops, s,
+ TYPE_BCM2838_RNG200, 0x28);
+ sysbus_init_mmio(sbd, &s->iomem);
+}
+
+static void bcm2838_rng200_reset(Object *obj, ResetType type)
+{
+ BCM2838Rng200State *s = BCM2838_RNG200(obj);
+
+ bcm2838_rng200_rbg_reset(s);
+ bcm2838_rng200_rng_reset(s);
+}
+
+static const Property bcm2838_rng200_properties[] = {
+ DEFINE_PROP_LINK("rng", BCM2838Rng200State, rng,
+ TYPE_RNG_BACKEND, RngBackend *),
+};
+
+static void bcm2838_rng200_class_init(ObjectClass *klass, const void *data)
+{
+ static ResettablePhases unused_parent_phases;
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = bcm2838_rng200_realize;
+ resettable_class_set_parent_phases(rc, NULL, bcm2838_rng200_reset, NULL,
+ &unused_parent_phases);
+ dc->vmsd = &vmstate_bcm2838_rng200;
+
+ device_class_set_props(dc, bcm2838_rng200_properties);
+}
+
+static const TypeInfo bcm2838_rng200_info = {
+ .name = TYPE_BCM2838_RNG200,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(BCM2838Rng200State),
+ .class_init = bcm2838_rng200_class_init,
+ .instance_init = bcm2838_rng200_init,
+};
+
+static void bcm2838_rng200_register_types(void)
+{
+ type_register_static(&bcm2838_rng200_info);
+}
+
+type_init(bcm2838_rng200_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 23265f60..cf73ab4c 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -94,6 +94,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_thermal.c',
'bcm2835_cprman.c',
'bcm2835_powermgt.c',
+ 'bcm2838_rng200.c',
))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index c9a868b3..90e178ed 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -442,3 +442,13 @@ iommu_testdev_dma_read(uint64_t gva, uint32_t len) "gva=0x%" PRIx64 " len=%u"
iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actual=0x%x"
iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
iommu_testdev_dma_armed(bool armed) "armed=%d"
+
+# bcm2838_rng200.c
+bcm2838_rng200_rng_soft_reset(void) "RNumG soft reset"
+bcm2838_rng200_rbg_soft_reset(void) "RBitG soft reset"
+bcm2838_rng200_enable_rbg(void) "RBitG enabled"
+bcm2838_rng200_disable_rbg(void) "RBitG disabled"
+bcm2838_rng200_update_fifo(uint32_t len, uint32_t fifo_len) "len %u, fifo_len %u"
+bcm2838_rng200_fifo_full(void) "RNumG FIFO full"
+bcm2838_rng200_write(uint64_t addr, uint64_t value, unsigned size) "addr: 0x%"PRIx64" value: 0x%016" PRIx64 " size: %u"
+bcm2838_rng200_read(uint64_t addr, unsigned size, uint64_t value) "addr: 0x%"PRIx64" size: %u value: 0x%016" PRIx64
diff --git a/include/hw/misc/bcm2838_rng200.h b/include/hw/misc/bcm2838_rng200.h
new file mode 100644
index 00000000..05bc87fa
--- /dev/null
+++ b/include/hw/misc/bcm2838_rng200.h
@@ -0,0 +1,42 @@
+/*
+ * BCM2838 Random Number Generator emulation
+ *
+ * Copyright (C) 2022 Sergey Pushkarev <sergey.pushkarev@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef BCM2838_RNG200_H
+#define BCM2838_RNG200_H
+
+#include <stdbool.h>
+#include "qom/object.h"
+#include "qemu/fifo8.h"
+#include "system/rng.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/ptimer.h"
+#include "hw/core/qdev-clock.h"
+#include "hw/core/irq.h"
+
+#define TYPE_BCM2838_RNG200 "bcm2838-rng200"
+OBJECT_DECLARE_SIMPLE_TYPE(BCM2838Rng200State, BCM2838_RNG200)
+
+#define N_BCM2838_RNG200_REGS 10
+
+struct BCM2838Rng200State {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ ptimer_state *ptimer;
+ RngBackend *rng;
+ Clock *clock;
+
+ uint32_t rng_fifo_cap;
+ Fifo8 fifo;
+
+ qemu_irq irq;
+
+ uint32_t regs[N_BCM2838_RNG200_REGS];
+};
+
+#endif /* BCM2838_RNG200_H */
--
2.47.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 2/5] hw/arm/bcm2838: enable BCM2838 RNG200
2026-07-25 1:26 [PATCH 0/5] hw/arm/raspi4b: RNG200 and thermal sensor Marcelo Manzo
2026-07-25 1:26 ` [PATCH 1/5] hw/misc/bcm2838_rng200: add BCM2838 RNG200 Marcelo Manzo
@ 2026-07-25 1:26 ` Marcelo Manzo
2026-07-25 1:26 ` [PATCH 3/5] hw/misc/bcm2838_thermal: add BCM2838 thermal sensor Marcelo Manzo
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Marcelo Manzo @ 2026-07-25 1:26 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
Sergey Kambalin, Marcelo Manzo
Wire the BCM2838 RNG200 device (added in the previous patch) into
the SoC: instantiate it in the peripherals block, map its register
region at the real BCM2711 physical address, and connect its
interrupt through both the GIC and the legacy GPU interrupt
controller path (matching how the device's own realize() wires it,
mirroring the pattern used for the older BCM2835 RNG on raspi0-3).
Also stop raspi4b from masking out the "brcm,bcm2711-rng200"
device-tree node, now that the controller it describes is actually
implemented.
Adapted from patch 16/41 of Sergey Kambalin's Raspberry Pi 4B series
(patchwork series 829638) against current QEMU master.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/arm/bcm2838.c | 4 ++++
hw/arm/bcm2838_peripherals.c | 17 +++++++++++++++++
hw/arm/raspi4b.c | 1 -
include/hw/arm/bcm2838_peripherals.h | 2 ++
4 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/hw/arm/bcm2838.c b/hw/arm/bcm2838.c
index f47b96c3..9d96dc11 100644
--- a/hw/arm/bcm2838.c
+++ b/hw/arm/bcm2838.c
@@ -209,6 +209,10 @@ static void bcm2838_realize(DeviceState *dev, Error **errp)
sysbus_connect_irq(SYS_BUS_DEVICE(&ps_base->dwc2), 0,
qdev_get_gpio_in(gicdev, GIC_SPI_INTERRUPT_DWC2));
+ /* Connect RNG200 to the interrupt controller */
+ sysbus_connect_irq(SYS_BUS_DEVICE(&ps->rng200), 0,
+ qdev_get_gpio_in(gicdev, GIC_SPI_INTERRUPT_RNG200));
+
/* Connect DMA 0-6 to the interrupt controller */
for (int n = GIC_SPI_INTERRUPT_DMA_0; n <= GIC_SPI_INTERRUPT_DMA_6; n++) {
sysbus_connect_irq(SYS_BUS_DEVICE(&ps_base->dma),
diff --git a/hw/arm/bcm2838_peripherals.c b/hw/arm/bcm2838_peripherals.c
index de49f48b..7c202b00 100644
--- a/hw/arm/bcm2838_peripherals.c
+++ b/hw/arm/bcm2838_peripherals.c
@@ -40,6 +40,9 @@ static void bcm2838_peripherals_init(Object *obj)
/* Extended Mass Media Controller 2 */
object_initialize_child(obj, "emmc2", &s->emmc2, TYPE_SYSBUS_SDHCI);
+ /* Random Number Generator */
+ object_initialize_child(obj, "rng200", &s->rng200, TYPE_BCM2838_RNG200);
+
/* PCIe Host Bridge */
object_initialize_child(obj, "pcie-host", &s->pcie_host,
TYPE_BCM2838_PCIE_HOST);
@@ -82,6 +85,8 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
BCMSocPeripheralBaseState *s_base = BCM_SOC_PERIPHERALS_BASE(dev);
MemoryRegion *regs_mr;
MemoryRegion *mmio_mr;
+ MemoryRegion *rng200_mr;
+ qemu_irq rng_200_irq;
int n;
bcm_soc_peripherals_common_realize(dev, errp);
@@ -94,6 +99,18 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
BCM2838_VC_PERI_LOW_BASE,
&s->peri_low_mr_alias, 1);
+ /* Random Number Generator */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->rng200), errp)) {
+ return;
+ }
+
+ rng200_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->rng200), 0);
+ memory_region_add_subregion(&s_base->peri_mr, RNG_OFFSET, rng200_mr);
+
+ rng_200_irq = qdev_get_gpio_in_named(DEVICE(&s_base->ic),
+ BCM2835_IC_GPU_IRQ, INTERRUPT_RNG);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->rng200), 0, rng_200_irq);
+
/* Extended Mass Media Controller 2 */
object_property_set_uint(OBJECT(&s->emmc2), "sd-spec-version", 3,
&error_abort);
diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c
index 79f60932..1c063a1c 100644
--- a/hw/arm/raspi4b.c
+++ b/hw/arm/raspi4b.c
@@ -64,7 +64,6 @@ static void raspi4_modify_dtb(const struct arm_boot_info *info, void *fdt)
/* Temporarily disable following devices until they are implemented */
const char *nodes_to_remove[] = {
- "brcm,bcm2711-rng200",
"brcm,bcm2711-thermal",
};
diff --git a/include/hw/arm/bcm2838_peripherals.h b/include/hw/arm/bcm2838_peripherals.h
index 5da340f2..52eabf67 100644
--- a/include/hw/arm/bcm2838_peripherals.h
+++ b/include/hw/arm/bcm2838_peripherals.h
@@ -10,6 +10,7 @@
#define BCM2838_PERIPHERALS_H
#include "hw/arm/bcm2835_peripherals.h"
+#include "hw/misc/bcm2838_rng200.h"
#include "hw/arm/bcm2838_pcie.h"
#include "hw/net/bcm2838_genet.h"
#include "hw/sd/sdhci.h"
@@ -68,6 +69,7 @@ struct BCM2838PeripheralState {
MemoryRegion peri_low_mr_alias;
MemoryRegion mphi_mr_alias;
+ BCM2838Rng200State rng200;
SDHCIState emmc2;
BCM2838PcieHostState pcie_host;
BCM2838GenetState genet;
--
2.47.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 3/5] hw/misc/bcm2838_thermal: add BCM2838 thermal sensor
2026-07-25 1:26 [PATCH 0/5] hw/arm/raspi4b: RNG200 and thermal sensor Marcelo Manzo
2026-07-25 1:26 ` [PATCH 1/5] hw/misc/bcm2838_rng200: add BCM2838 RNG200 Marcelo Manzo
2026-07-25 1:26 ` [PATCH 2/5] hw/arm/bcm2838: enable " Marcelo Manzo
@ 2026-07-25 1:26 ` Marcelo Manzo
2026-07-25 1:26 ` [PATCH 4/5] hw/arm/bcm2838: enable " Marcelo Manzo
2026-07-25 1:26 ` [PATCH 5/5] tests/functional/aarch64: add raspi4b RNG200/thermal test Marcelo Manzo
4 siblings, 0 replies; 6+ messages in thread
From: Marcelo Manzo @ 2026-07-25 1:26 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
Sergey Kambalin, Sergey Kambalin, Marcelo Manzo
From: Sergey Kambalin <serg.oker@gmail.com>
Add a dummy BCM2838 (BCM2711/Raspberry Pi 4B) thermal sensor: a
single status register that always reports a fixed 25C reading with
both "valid" bits set. Real hardware reports live temperature; a
fixed reading is enough for guests that just probe the sensor and
read it, without modeling actual thermal behavior.
Part of Sergey Kambalin's original Raspberry Pi 4B series (patchwork
series 829638, posted to qemu-devel in 2024, never merged). Ported
to current master by Marcelo Manzo: header paths moved under
hw/core/, class_init()'s const void *data. No functional change
otherwise (this device has no reset or migration state to begin
with).
Boot-tested: guest's thermal_zone0/temp reports 25310 (25.31C),
matching the fixed reading this device returns.
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com>
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/misc/bcm2838_thermal.c | 98 +++++++++++++++++++++++++++++++
hw/misc/meson.build | 1 +
include/hw/misc/bcm2838_thermal.h | 23 ++++++++
3 files changed, 122 insertions(+)
create mode 100644 hw/misc/bcm2838_thermal.c
create mode 100644 include/hw/misc/bcm2838_thermal.h
diff --git a/hw/misc/bcm2838_thermal.c b/hw/misc/bcm2838_thermal.c
new file mode 100644
index 00000000..40148ddb
--- /dev/null
+++ b/hw/misc/bcm2838_thermal.c
@@ -0,0 +1,98 @@
+/*
+ * BCM2838 dummy thermal sensor
+ *
+ * Copyright (C) 2022 Maksim Kopusov <maksim.kopusov@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/misc/bcm2838_thermal.h"
+#include "hw/core/registerfields.h"
+#include "migration/vmstate.h"
+#include "qemu/error-report.h"
+
+REG32(STAT, 0x200)
+FIELD(STAT, DATA, 0, 10)
+FIELD(STAT, VALID_1, 10, 1)
+FIELD(STAT, VALID_2, 16, 1)
+
+#define BCM2838_THERMAL_SIZE 0xf00
+
+#define THERMAL_OFFSET_C 410040
+#define THERMAL_COEFF (-487.0f)
+#define MILLIDEGREE_COEFF 1000
+
+static uint16_t bcm2838_thermal_temp2adc(int temp_C)
+{
+ return (temp_C * MILLIDEGREE_COEFF - THERMAL_OFFSET_C) / THERMAL_COEFF;
+}
+
+static uint64_t bcm2838_thermal_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint32_t val = 0;
+
+ switch (addr) {
+ case A_STAT:
+ /* Temperature is always 25°C */
+ val = FIELD_DP32(val, STAT, DATA, bcm2838_thermal_temp2adc(25));
+ val = FIELD_DP32(val, STAT, VALID_1, 1);
+ val = FIELD_DP32(val, STAT, VALID_2, 1);
+
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s can't access addr: 0x%"HWADDR_PRIx,
+ TYPE_BCM2838_THERMAL, addr);
+ }
+ return val;
+}
+
+static void bcm2838_thermal_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: write 0x%" PRIx64
+ " to 0x%" HWADDR_PRIx "\n",
+ __func__, value, addr);
+}
+
+static const MemoryRegionOps bcm2838_thermal_ops = {
+ .read = bcm2838_thermal_read,
+ .write = bcm2838_thermal_write,
+ .impl.max_access_size = 4,
+ .valid.min_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void bcm2838_thermal_realize(DeviceState *dev, Error **errp)
+{
+ Bcm2838ThermalState *s = BCM2838_THERMAL(dev);
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &bcm2838_thermal_ops,
+ s, TYPE_BCM2838_THERMAL, BCM2838_THERMAL_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem);
+}
+
+static void bcm2838_thermal_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = bcm2838_thermal_realize;
+
+ /* This device has no state: no need for vmstate or reset */
+}
+
+static const TypeInfo bcm2838_thermal_info = {
+ .name = TYPE_BCM2838_THERMAL,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(Bcm2838ThermalState),
+ .class_init = bcm2838_thermal_class_init,
+};
+
+static void bcm2838_thermal_register_types(void)
+{
+ type_register_static(&bcm2838_thermal_info);
+}
+
+type_init(bcm2838_thermal_register_types)
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index cf73ab4c..b4ac064d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -95,6 +95,7 @@ system_ss.add(when: 'CONFIG_RASPI', if_true: files(
'bcm2835_cprman.c',
'bcm2835_powermgt.c',
'bcm2838_rng200.c',
+ 'bcm2838_thermal.c',
))
system_ss.add(when: 'CONFIG_SLAVIO', if_true: files('slavio_misc.c'))
system_ss.add(when: 'CONFIG_ZYNQ', if_true: files('zynq_slcr.c'))
diff --git a/include/hw/misc/bcm2838_thermal.h b/include/hw/misc/bcm2838_thermal.h
new file mode 100644
index 00000000..1ba524ed
--- /dev/null
+++ b/include/hw/misc/bcm2838_thermal.h
@@ -0,0 +1,23 @@
+/*
+ * BCM2838 dummy thermal sensor
+ *
+ * Copyright (C) 2022 Maksim Kopusov <maksim.kopusov@auriga.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef BCM2838_THERMAL_H
+#define BCM2838_THERMAL_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_BCM2838_THERMAL "bcm2838-thermal"
+OBJECT_DECLARE_SIMPLE_TYPE(Bcm2838ThermalState, BCM2838_THERMAL)
+
+struct Bcm2838ThermalState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+};
+
+#endif /* BCM2838_THERMAL_H */
--
2.47.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 4/5] hw/arm/bcm2838: enable BCM2838 thermal sensor
2026-07-25 1:26 [PATCH 0/5] hw/arm/raspi4b: RNG200 and thermal sensor Marcelo Manzo
` (2 preceding siblings ...)
2026-07-25 1:26 ` [PATCH 3/5] hw/misc/bcm2838_thermal: add BCM2838 thermal sensor Marcelo Manzo
@ 2026-07-25 1:26 ` Marcelo Manzo
2026-07-25 1:26 ` [PATCH 5/5] tests/functional/aarch64: add raspi4b RNG200/thermal test Marcelo Manzo
4 siblings, 0 replies; 6+ messages in thread
From: Marcelo Manzo @ 2026-07-25 1:26 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
Sergey Kambalin, Marcelo Manzo
Wire the BCM2838 thermal sensor (added in the previous patch) into
the SoC: instantiate it in the peripherals block and map its
register region at the real BCM2711 physical address.
This is also the last device masked out of the raspi4b DTB, so drop
the "brcm,bcm2711-thermal" node-removal entry -- and with it the
whole nodes_to_remove[] mechanism in raspi4_modify_dtb(), which is
now empty (GENET, PCIe, RNG200, and this thermal sensor were the
only four devices it ever masked, and all four are now implemented).
Adapted from patch 17/41 of Sergey Kambalin's Raspberry Pi 4B series
(patchwork series 829638) against current QEMU master.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
hw/arm/bcm2838_peripherals.c | 11 +++++++++++
hw/arm/raspi4b.c | 18 ------------------
include/hw/arm/bcm2838_peripherals.h | 2 ++
3 files changed, 13 insertions(+), 18 deletions(-)
diff --git a/hw/arm/bcm2838_peripherals.c b/hw/arm/bcm2838_peripherals.c
index 7c202b00..c268fffd 100644
--- a/hw/arm/bcm2838_peripherals.c
+++ b/hw/arm/bcm2838_peripherals.c
@@ -43,6 +43,9 @@ static void bcm2838_peripherals_init(Object *obj)
/* Random Number Generator */
object_initialize_child(obj, "rng200", &s->rng200, TYPE_BCM2838_RNG200);
+ /* Thermal */
+ object_initialize_child(obj, "thermal", &s->thermal, TYPE_BCM2838_THERMAL);
+
/* PCIe Host Bridge */
object_initialize_child(obj, "pcie-host", &s->pcie_host,
TYPE_BCM2838_PCIE_HOST);
@@ -87,6 +90,7 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
MemoryRegion *mmio_mr;
MemoryRegion *rng200_mr;
qemu_irq rng_200_irq;
+ MemoryRegion *thermal_mr;
int n;
bcm_soc_peripherals_common_realize(dev, errp);
@@ -111,6 +115,13 @@ static void bcm2838_peripherals_realize(DeviceState *dev, Error **errp)
BCM2835_IC_GPU_IRQ, INTERRUPT_RNG);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->rng200), 0, rng_200_irq);
+ /* THERMAL */
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->thermal), errp)) {
+ return;
+ }
+ thermal_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->thermal), 0);
+ memory_region_add_subregion(&s->peri_low_mr, 0x15D2000, thermal_mr);
+
/* Extended Mass Media Controller 2 */
object_property_set_uint(OBJECT(&s->emmc2), "sd-spec-version", 3,
&error_abort);
diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c
index 1c063a1c..b92840e1 100644
--- a/hw/arm/raspi4b.c
+++ b/hw/arm/raspi4b.c
@@ -62,24 +62,6 @@ static void raspi4_modify_dtb(const struct arm_boot_info *info, void *fdt)
{
uint64_t ram_size;
- /* Temporarily disable following devices until they are implemented */
- const char *nodes_to_remove[] = {
- "brcm,bcm2711-thermal",
- };
-
- for (int i = 0; i < ARRAY_SIZE(nodes_to_remove); i++) {
- const char *dev_str = nodes_to_remove[i];
- int offset;
-
- offset = fdt_node_offset_by_compatible(fdt, -1, dev_str);
- while (offset >= 0) {
- if (fdt_nop_node(fdt, offset) == 0) {
- warn_report("bcm2711 dtb: %s has been disabled!", dev_str);
- }
- offset = fdt_node_offset_by_compatible(fdt, offset, dev_str);
- }
- }
-
ram_size = board_ram_size(info->board_id);
if (info->ram_size > UPPER_RAM_BASE) {
diff --git a/include/hw/arm/bcm2838_peripherals.h b/include/hw/arm/bcm2838_peripherals.h
index 52eabf67..49ca780b 100644
--- a/include/hw/arm/bcm2838_peripherals.h
+++ b/include/hw/arm/bcm2838_peripherals.h
@@ -11,6 +11,7 @@
#include "hw/arm/bcm2835_peripherals.h"
#include "hw/misc/bcm2838_rng200.h"
+#include "hw/misc/bcm2838_thermal.h"
#include "hw/arm/bcm2838_pcie.h"
#include "hw/net/bcm2838_genet.h"
#include "hw/sd/sdhci.h"
@@ -70,6 +71,7 @@ struct BCM2838PeripheralState {
MemoryRegion mphi_mr_alias;
BCM2838Rng200State rng200;
+ Bcm2838ThermalState thermal;
SDHCIState emmc2;
BCM2838PcieHostState pcie_host;
BCM2838GenetState genet;
--
2.47.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH 5/5] tests/functional/aarch64: add raspi4b RNG200/thermal test
2026-07-25 1:26 [PATCH 0/5] hw/arm/raspi4b: RNG200 and thermal sensor Marcelo Manzo
` (3 preceding siblings ...)
2026-07-25 1:26 ` [PATCH 4/5] hw/arm/bcm2838: enable " Marcelo Manzo
@ 2026-07-25 1:26 ` Marcelo Manzo
4 siblings, 0 replies; 6+ messages in thread
From: Marcelo Manzo @ 2026-07-25 1:26 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-arm, Peter Maydell, Philippe Mathieu-Daudé,
Sergey Kambalin, Marcelo Manzo
Add test_arm_raspi4_rng_thermal, exercising the raspi4b RNG200 and
thermal sensor devices added in this series: boot, confirm the
RNG200 driver probes it ("hwrng registered" in the kernel log),
reach a shell, and confirm the thermal zone reports the expected
fixed reading and /dev/hwrng actually yields data rather than just
existing.
Verified locally against this series with the exact kernel/initrd
assets this file already pins.
Signed-off-by: Marcelo Manzo <marcelomanzo@gmail.com>
---
tests/functional/aarch64/test_raspi4.py | 32 +++++++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/tests/functional/aarch64/test_raspi4.py b/tests/functional/aarch64/test_raspi4.py
index 0e254a20..69748ce5 100755
--- a/tests/functional/aarch64/test_raspi4.py
+++ b/tests/functional/aarch64/test_raspi4.py
@@ -122,5 +122,37 @@ def test_arm_raspi4_genet(self):
exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted')
+ def test_arm_raspi4_rng_thermal(self):
+ kernel_path = self.archive_extract(self.ASSET_KERNEL_20190215,
+ member='boot/kernel8.img')
+ dtb_path = self.archive_extract(self.ASSET_KERNEL_20190215,
+ member='boot/bcm2711-rpi-4-b.dtb')
+ initrd_path = self.uncompress(self.ASSET_INITRD)
+
+ self.set_machine('raspi4b')
+ self.vm.set_console()
+ kernel_command_line = (self.KERNEL_COMMON_COMMAND_LINE +
+ 'earlycon=pl011,mmio32,0xfe201000 ' +
+ 'console=ttyAMA0,115200 ' +
+ 'panic=-1 noreboot ' +
+ 'dwc_otg.fiq_fsm_enable=0')
+ self.vm.add_args('-kernel', kernel_path,
+ '-dtb', dtb_path,
+ '-initrd', initrd_path,
+ '-append', kernel_command_line,
+ '-no-reboot')
+ self.vm.launch()
+ self.wait_for_console_pattern(
+ 'iproc-rng200 fe104000.rng: hwrng registered')
+ self.wait_for_console_pattern('Boot successful.')
+
+ exec_command_and_wait_for_pattern(
+ self, 'cat /sys/devices/virtual/thermal/thermal_zone0/temp',
+ '25310')
+ exec_command_and_wait_for_pattern(self, 'head -c 8 /dev/hwrng | xxd',
+ '00000000:')
+ exec_command_and_wait_for_pattern(self, 'halt', 'reboot: System halted')
+
+
if __name__ == '__main__':
LinuxKernelTest.main()
--
2.47.1
^ permalink raw reply related [flat|nested] 6+ messages in thread