[Qemu-devel] [RFC v3] arm: Add NRF51 random number generator peripheral

Steffen Görtz posted 1 patch 5 years, 9 months ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20180713154007.3853-1-contrib@steffen-goertz.de
Test checkpatch passed
Test docker-mingw@fedora passed
Test docker-quick@centos7 passed
hw/misc/Makefile.objs       |   1 +
hw/misc/nrf51_rng.c         | 269 ++++++++++++++++++++++++++++++++++++
include/hw/misc/nrf51_rng.h |  71 ++++++++++
3 files changed, 341 insertions(+)
create mode 100644 hw/misc/nrf51_rng.c
create mode 100644 include/hw/misc/nrf51_rng.h
[Qemu-devel] [RFC v3] arm: Add NRF51 random number generator peripheral
Posted by Steffen Görtz 5 years, 9 months ago
Add a model of the NRF51 random number generator peripheral.

Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
---
Changes since v3:
  - Replace bitfields
  - Add VMState / reset
  - Add reference to reference manual

Changes since v2: 
  - Add missing 'qapi/error.h' for error_abort

Changes since v1: 
  - Add implementation access size hints to MemoryRegionOps
  - Fail on error if qcrypto_random_bytes fails
  - Add references to Nrf51 datasheets


 hw/misc/Makefile.objs       |   1 +
 hw/misc/nrf51_rng.c         | 269 ++++++++++++++++++++++++++++++++++++
 include/hw/misc/nrf51_rng.h |  71 ++++++++++
 3 files changed, 341 insertions(+)
 create mode 100644 hw/misc/nrf51_rng.c
 create mode 100644 include/hw/misc/nrf51_rng.h

diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs
index 00e834d0f0..fd8cc97249 100644
--- a/hw/misc/Makefile.objs
+++ b/hw/misc/Makefile.objs
@@ -70,3 +70,4 @@ obj-$(CONFIG_AUX) += auxbus.o
 obj-$(CONFIG_ASPEED_SOC) += aspeed_scu.o aspeed_sdmc.o
 obj-y += mmio_interface.o
 obj-$(CONFIG_MSF2) += msf2-sysreg.o
+obj-$(CONFIG_NRF51_SOC) += nrf51_rng.o
diff --git a/hw/misc/nrf51_rng.c b/hw/misc/nrf51_rng.c
new file mode 100644
index 0000000000..cae427a391
--- /dev/null
+++ b/hw/misc/nrf51_rng.c
@@ -0,0 +1,269 @@
+/*
+ * nRF51 Random Number Generator
+ *
+ * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/misc/nrf51_rng.h"
+#include "crypto/random.h"
+
+#define NRF51_RNG_SIZE         0x1000
+
+#define NRF51_RNG_TASK_START   0x000
+#define NRF51_RNG_TASK_STOP    0x004
+#define NRF51_RNG_EVENT_VALRDY 0x100
+#define NRF51_RNG_REG_SHORTS   0x200
+#define NRF51_RNG_REG_SHORTS_VALRDY_STOP 0
+#define NRF51_RNG_REG_INTEN    0x300
+#define NRF51_RNG_REG_INTEN_VALRDY 0
+#define NRF51_RNG_REG_INTENSET 0x304
+#define NRF51_RNG_REG_INTENCLR 0x308
+#define NRF51_RNG_REG_CONFIG   0x504
+#define NRF51_RNG_REG_CONFIG_DECEN 0
+#define NRF51_RNG_REG_VALUE    0x508
+
+#define NRF51_TRIGGER_TASK 0x01
+#define NRF51_EVENT_CLEAR  0x00
+
+
+static uint64_t rng_read(void *opaque, hwaddr offset, unsigned int size)
+{
+    Nrf51RNGState *s = NRF51_RNG(opaque);
+    uint64_t r = 0;
+
+    switch (offset) {
+    case NRF51_RNG_EVENT_VALRDY:
+        r = s->event_valrdy;
+        break;
+    case NRF51_RNG_REG_SHORTS:
+        r = s->shortcut_stop_on_valrdy;
+        break;
+    case NRF51_RNG_REG_INTEN:
+    case NRF51_RNG_REG_INTENSET:
+    case NRF51_RNG_REG_INTENCLR:
+        r = s->interrupt_enabled;
+        break;
+    case NRF51_RNG_REG_CONFIG:
+        r = s->filter_enabled;
+        break;
+    case NRF51_RNG_REG_VALUE:
+        r = s->value;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad read offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+    }
+
+    return r;
+}
+
+static int64_t calc_next_timeout(Nrf51RNGState *s)
+{
+    int64_t timeout = qemu_clock_get_us(QEMU_CLOCK_VIRTUAL);
+    if (s->filter_enabled) {
+        timeout += s->period_filtered_us;
+    } else {
+        timeout += s->period_unfiltered_us;
+    }
+
+    return timeout;
+}
+
+
+static void rng_update_timer(Nrf51RNGState *s)
+{
+    if (s->active) {
+        timer_mod(&s->timer, calc_next_timeout(s));
+    } else {
+        timer_del(&s->timer);
+    }
+}
+
+
+static void rng_write(void *opaque, hwaddr offset,
+                       uint64_t value, unsigned int size)
+{
+    Nrf51RNGState *s = NRF51_RNG(opaque);
+
+    switch (offset) {
+    case NRF51_RNG_TASK_START:
+        if (value == NRF51_TRIGGER_TASK) {
+            s->active = 1;
+            rng_update_timer(s);
+        }
+        break;
+    case NRF51_RNG_TASK_STOP:
+        if (value == NRF51_TRIGGER_TASK) {
+            s->active = 0;
+            rng_update_timer(s);
+        }
+        break;
+    case NRF51_RNG_EVENT_VALRDY:
+        if (value == NRF51_EVENT_CLEAR) {
+            s->event_valrdy = 0;
+            qemu_set_irq(s->eep_valrdy, 0);
+        }
+        break;
+    case NRF51_RNG_REG_SHORTS:
+        s->shortcut_stop_on_valrdy =
+                (value & BIT_MASK(NRF51_RNG_REG_SHORTS_VALRDY_STOP)) ? 1 : 0;
+        break;
+    case NRF51_RNG_REG_INTEN:
+        s->interrupt_enabled =
+                (value & BIT_MASK(NRF51_RNG_REG_INTEN_VALRDY)) ? 1 : 0;
+        break;
+    case NRF51_RNG_REG_INTENSET:
+        if (value & BIT_MASK(NRF51_RNG_REG_INTEN_VALRDY)) {
+            s->interrupt_enabled = 1;
+        }
+        break;
+    case NRF51_RNG_REG_INTENCLR:
+        if (value & BIT_MASK(NRF51_RNG_REG_INTEN_VALRDY)) {
+            s->interrupt_enabled = 0;
+        }
+        break;
+    case NRF51_RNG_REG_CONFIG:
+        s->filter_enabled =
+                      (value & BIT_MASK(NRF51_RNG_REG_CONFIG_DECEN)) ? 1 : 0;
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: bad write offset 0x%" HWADDR_PRIx "\n",
+                      __func__, offset);
+    }
+}
+
+static const MemoryRegionOps rng_ops = {
+    .read =  rng_read,
+    .write = rng_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4
+};
+
+static void nrf51_rng_timer_expire(void *opaque)
+{
+    Nrf51RNGState *s = NRF51_RNG(opaque);
+
+    qcrypto_random_bytes(&s->value, 1, &error_abort);
+
+    s->event_valrdy = 1;
+    qemu_set_irq(s->eep_valrdy, 1);
+
+    if (s->interrupt_enabled) {
+        qemu_irq_pulse(s->irq);
+    }
+
+    if (s->shortcut_stop_on_valrdy) {
+        s->active = 0;
+    }
+
+    rng_update_timer(s);
+}
+
+static void nrf51_rng_tep_start(void *opaque, int n, int level)
+{
+    Nrf51RNGState *s = NRF51_RNG(opaque);
+
+    if (level) {
+        s->active = 1;
+        rng_update_timer(s);
+    }
+}
+
+static void nrf51_rng_tep_stop(void *opaque, int n, int level)
+{
+    Nrf51RNGState *s = NRF51_RNG(opaque);
+
+    if (level) {
+        s->active = 0;
+        rng_update_timer(s);
+    }
+}
+
+
+static void nrf51_rng_init(Object *obj)
+{
+    Nrf51RNGState *s = NRF51_RNG(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    memory_region_init_io(&s->mmio, obj, &rng_ops, s,
+            TYPE_NRF51_RNG, NRF51_RNG_SIZE);
+    sysbus_init_mmio(sbd, &s->mmio);
+
+    timer_init_us(&s->timer, QEMU_CLOCK_VIRTUAL, nrf51_rng_timer_expire, s);
+
+    qdev_init_gpio_out_named(DEVICE(s), &s->irq, "irq", 1);
+
+    /* Tasks */
+    qdev_init_gpio_in_named(DEVICE(s), nrf51_rng_tep_start, "tep_start", 1);
+    qdev_init_gpio_in_named(DEVICE(s), nrf51_rng_tep_stop, "tep_stop", 1);
+
+    /* Events */
+    qdev_init_gpio_out_named(DEVICE(s), &s->eep_valrdy, "eep_valrdy", 1);
+}
+
+static void nrf51_rng_reset(DeviceState *dev)
+{
+    Nrf51RNGState *s = NRF51_RNG(dev);
+
+    rng_update_timer(s);
+}
+
+
+static Property nrf51_rng_properties[] = {
+    DEFINE_PROP_UINT16("period_unfiltered_us", Nrf51RNGState,
+            period_unfiltered_us, 167),
+    DEFINE_PROP_UINT16("period_filtered_us", Nrf51RNGState,
+            period_filtered_us, 660),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_rng = {
+    .name = "nrf51_soc.rng",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(active, Nrf51RNGState),
+        VMSTATE_UINT32(event_valrdy, Nrf51RNGState),
+        VMSTATE_UINT32(shortcut_stop_on_valrdy, Nrf51RNGState),
+        VMSTATE_UINT32(interrupt_enabled, Nrf51RNGState),
+        VMSTATE_UINT32(filter_enabled, Nrf51RNGState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void nrf51_rng_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->props = nrf51_rng_properties;
+    dc->vmsd = &vmstate_rng;
+    dc->reset = nrf51_rng_reset;
+}
+
+static const TypeInfo nrf51_rng_info = {
+    .name = TYPE_NRF51_RNG,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(Nrf51RNGState),
+    .instance_init = nrf51_rng_init,
+    .class_init = nrf51_rng_class_init
+};
+
+static void nrf51_rng_register_types(void)
+{
+    type_register_static(&nrf51_rng_info);
+}
+
+type_init(nrf51_rng_register_types)
diff --git a/include/hw/misc/nrf51_rng.h b/include/hw/misc/nrf51_rng.h
new file mode 100644
index 0000000000..b0a3b93d67
--- /dev/null
+++ b/include/hw/misc/nrf51_rng.h
@@ -0,0 +1,71 @@
+/*
+ * nRF51 Random Number Generator
+ *
+ * Copyright 2018 Steffen Görtz <contrib@steffen-goertz.de>
+ *
+ * This code is licensed under the GPL version 2 or later.  See
+ * the COPYING file in the top-level directory.
+ *
+ * See NRF51 reference manual section 21 Random Number Generator
+ * See NRF51 product specification section 8.16 Random Number Generator
+ *
+ * QEMU interface:
+ * + Property "period_unfiltered_us": Time between two biased values in
+ *   microseconds.
+ * + Property "period_filtered_us": Time between two unbiased values in
+ *   microseconds.
+ * + sysbus MMIO regions 0: Memory Region with tasks, events and registers
+ *   to be mapped to the peripherals instance address by the SOC.
+ * + Named GPIO output "irq": Interrupt line of the peripheral. Must be
+ *   connected to the associated peripheral interrupt line of the NVIC.
+ * + Named GPIO output "eep_valrdy": Event set when new random value is ready
+ *   to be read.
+ * + Named GPIO input "tep_start": Task that triggers start of continuous
+ *   generation of random values.
+ * + Named GPIO input "tep_stop": Task that ends continuous generation of
+ *   random values.
+ *
+ * Accuracy of the peripheral model:
+ * + Stochastic properties of different configurations of the random source
+ *   are not modeled.
+ * + Generation of unfiltered and filtered random values take at least the
+ *   average generation time stated in the production specification;
+ *   non-deterministic generation times are not modeled.
+ *
+ */
+#ifndef NRF51_RNG_H
+#define NRF51_RNG_H
+
+#include "hw/sysbus.h"
+#include "qemu/timer.h"
+#define TYPE_NRF51_RNG "nrf51_soc.rng"
+#define NRF51_RNG(obj) OBJECT_CHECK(Nrf51RNGState, (obj), TYPE_NRF51_RNG)
+
+typedef struct Nrf51RNGState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    qemu_irq irq;
+
+    /* Event End Points */
+    qemu_irq eep_valrdy;
+
+    QEMUTimer timer;
+
+    /* Time between generation of successive unfiltered values in us */
+    uint16_t period_unfiltered_us;
+    /* Time between generation of successive filtered values in us */
+    uint16_t period_filtered_us;
+
+    uint8_t value;
+
+    uint32_t active;
+    uint32_t event_valrdy;
+    uint32_t shortcut_stop_on_valrdy;
+    uint32_t interrupt_enabled;
+    uint32_t filter_enabled;
+
+} Nrf51RNGState;
+
+
+#endif /* NRF51_RNG_H_ */
-- 
2.18.0


Re: [Qemu-devel] [RFC v3] arm: Add NRF51 random number generator peripheral
Posted by Peter Maydell 5 years, 9 months ago
On 13 July 2018 at 16:40, Steffen Görtz <contrib@steffen-goertz.de> wrote:
> Add a model of the NRF51 random number generator peripheral.
>
> Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
> ---
> Changes since v3:
>   - Replace bitfields
>   - Add VMState / reset
>   - Add reference to reference manual
>
> Changes since v2:
>   - Add missing 'qapi/error.h' for error_abort
>
> Changes since v1:
>   - Add implementation access size hints to MemoryRegionOps
>   - Fail on error if qcrypto_random_bytes fails
>   - Add references to Nrf51 datasheets
>
>
>  hw/misc/Makefile.objs       |   1 +
>  hw/misc/nrf51_rng.c         | 269 ++++++++++++++++++++++++++++++++++++
>  include/hw/misc/nrf51_rng.h |  71 ++++++++++
>  3 files changed, 341 insertions(+)
>  create mode 100644 hw/misc/nrf51_rng.c
>  create mode 100644 include/hw/misc/nrf51_rng.h
>

> +
> +static void nrf51_rng_timer_expire(void *opaque)
> +{
> +    Nrf51RNGState *s = NRF51_RNG(opaque);
> +
> +    qcrypto_random_bytes(&s->value, 1, &error_abort);
> +
> +    s->event_valrdy = 1;
> +    qemu_set_irq(s->eep_valrdy, 1);
> +
> +    if (s->interrupt_enabled) {
> +        qemu_irq_pulse(s->irq);

Is this definitely a pulse interrupt ? Usually
devices raise an irq line and keep it high until
the guest checks them and tells them to drop it.

> +    }
> +
> +    if (s->shortcut_stop_on_valrdy) {
> +        s->active = 0;
> +    }
> +
> +    rng_update_timer(s);
> +}
> +
> +static void nrf51_rng_tep_start(void *opaque, int n, int level)
> +{
> +    Nrf51RNGState *s = NRF51_RNG(opaque);
> +
> +    if (level) {
> +        s->active = 1;
> +        rng_update_timer(s);
> +    }
> +}
> +
> +static void nrf51_rng_tep_stop(void *opaque, int n, int level)
> +{
> +    Nrf51RNGState *s = NRF51_RNG(opaque);
> +
> +    if (level) {
> +        s->active = 0;
> +        rng_update_timer(s);
> +    }
> +}
> +
> +
> +static void nrf51_rng_init(Object *obj)
> +{
> +    Nrf51RNGState *s = NRF51_RNG(obj);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> +
> +    memory_region_init_io(&s->mmio, obj, &rng_ops, s,
> +            TYPE_NRF51_RNG, NRF51_RNG_SIZE);
> +    sysbus_init_mmio(sbd, &s->mmio);
> +
> +    timer_init_us(&s->timer, QEMU_CLOCK_VIRTUAL, nrf51_rng_timer_expire, s);
> +
> +    qdev_init_gpio_out_named(DEVICE(s), &s->irq, "irq", 1);
> +
> +    /* Tasks */
> +    qdev_init_gpio_in_named(DEVICE(s), nrf51_rng_tep_start, "tep_start", 1);
> +    qdev_init_gpio_in_named(DEVICE(s), nrf51_rng_tep_stop, "tep_stop", 1);
> +
> +    /* Events */
> +    qdev_init_gpio_out_named(DEVICE(s), &s->eep_valrdy, "eep_valrdy", 1);
> +}
> +
> +static void nrf51_rng_reset(DeviceState *dev)
> +{
> +    Nrf51RNGState *s = NRF51_RNG(dev);
> +
> +    rng_update_timer(s);

Isn't this reset function missing some code to reset the
various bits of guest-modifiable state (eg s->active,
s->event_valrdy) ?

> +}

thanks
-- PMM