@@ -205,6 +205,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),
@@ -34,6 +34,9 @@ static void bcm2838_peripherals_init(Object *obj)
bc->peri_low_size);
sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->peri_low_mr);
+ /* 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);
@@ -88,6 +91,17 @@ 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;
+ }
+ memory_region_add_subregion(
+ &s_base->peri_mr, RNG_OFFSET,
+ sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->rng200), 0));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->rng200), 0,
+ qdev_get_gpio_in_named(DEVICE(&s_base->ic), BCM2835_IC_GPU_IRQ,
+ INTERRUPT_RNG));
+
/* Extended Mass Media Controller 2 */
object_property_set_uint(OBJECT(&s->emmc2), "sd-spec-version", 3,
&error_abort);
@@ -67,7 +67,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",
"brcm,bcm2711-genet-v5",
};
new file mode 100644
@@ -0,0 +1,405 @@
+/*
+ * BCM2838 Random Number Generator emulation
+ *
+ * Copyright (C) 2022 Sergey Pushkarev <sergey.pushkarev@auriga.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qom/object_interfaces.h"
+#include "qapi/error.h"
+#include "hw/qdev-properties.h"
+#include "hw/misc/bcm2838_rng200.h"
+#include "hw/registerfields.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)
+{
+ const uint8_t *buf;
+ 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 = 0;
+
+ while (to_read) {
+ buf = fifo8_pop_buf(fifo, to_read, &num);
+ memcpy(p, buf, num);
+ 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_NATIVE_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(DeviceState *dev)
+{
+ BCM2838Rng200State *s = BCM2838_RNG200(dev);
+
+ bcm2838_rng200_rbg_reset(s);
+ bcm2838_rng200_rng_reset(s);
+}
+
+static Property bcm2838_rng200_properties[] = {
+ DEFINE_PROP_LINK("rng", BCM2838Rng200State, rng,
+ TYPE_RNG_BACKEND, RngBackend *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void bcm2838_rng200_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = bcm2838_rng200_realize;
+ dc->reset = bcm2838_rng200_reset;
+ 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)
@@ -91,6 +91,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'))
@@ -341,3 +341,12 @@ djmemc_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRI
# iosb.c
iosb_read(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u"
iosb_write(int reg, uint64_t value, unsigned int size) "reg=0x%x value=0x%"PRIx64" size=%u"
+# 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
@@ -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/sd/sdhci.h"
#include "hw/gpio/bcm2838_gpio.h"
@@ -64,6 +65,7 @@ struct BCM2838PeripheralState {
MemoryRegion peri_low_mr_alias;
MemoryRegion mphi_mr_alias;
+ BCM2838Rng200State rng200;
SDHCIState emmc2;
BCM2838PcieHostState pcie_host;
BCM2838GpioState gpio;
new file mode 100644
@@ -0,0 +1,43 @@
+/*
+ * BCM2838 Random Number Generator emulation
+ *
+ * Copyright (C) 2022 Sergey Pushkarev <sergey.pushkarev@auriga.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef BCM2838_RNG200_H
+#define BCM2838_RNG200_H
+
+#include <stdbool.h>
+#include "qom/object.h"
+#include "qemu/fifo8.h"
+#include "sysemu/rng.h"
+#include "hw/sysbus.h"
+#include "hw/ptimer.h"
+#include "hw/qdev-clock.h"
+#include "hw/irq.h"
+
+#define TYPE_BCM2838_RNG200 "bcm2838-rng200"
+OBJECT_DECLARE_SIMPLE_TYPE(BCM2838Rng200State, BCM2838_RNG200)
+
+#define N_BCM2838_RNG200_REGS 9
+
+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 */
Signed-off-by: Sergey Kambalin <sergey.kambalin@auriga.com> --- hw/arm/bcm2838.c | 4 + hw/arm/bcm2838_peripherals.c | 14 + hw/arm/raspi4b.c | 1 - hw/misc/bcm2838_rng200.c | 405 +++++++++++++++++++++++++++ hw/misc/meson.build | 1 + hw/misc/trace-events | 9 + include/hw/arm/bcm2838_peripherals.h | 2 + include/hw/misc/bcm2838_rng200.h | 43 +++ 8 files changed, 478 insertions(+), 1 deletion(-) create mode 100644 hw/misc/bcm2838_rng200.c create mode 100644 include/hw/misc/bcm2838_rng200.h