[Qemu-devel] [PATCH v5 05/14] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories

Steffen Görtz posted 14 patches 7 years, 2 months ago
[Qemu-devel] [PATCH v5 05/14] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories
Posted by Steffen Görtz 7 years, 2 months ago
The nRF51 contains three regions of non-volatile memory (NVM):
- CODE (R/W): contains code
- FICR (R): Factory information like code size, chip id etc.
- UICR (R/W): Changeable configuration data. Lock bits, Code
  protection configuration, Bootloader address, Nordic SoftRadio
  configuration, Firmware configuration.

Read and write access to the memories is managed by the
Non-volatile memory controller.

Memory schema:
 [ CPU ] -+- [ NVM, either FICR, UICR or CODE ]
          |      |
          \- [ NVMC ]

Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>
---
 hw/nvram/Makefile.objs       |   1 +
 hw/nvram/nrf51_nvm.c         | 378 +++++++++++++++++++++++++++++++++++
 include/hw/nvram/nrf51_nvm.h |  64 ++++++
 3 files changed, 443 insertions(+)
 create mode 100644 hw/nvram/nrf51_nvm.c
 create mode 100644 include/hw/nvram/nrf51_nvm.h

diff --git a/hw/nvram/Makefile.objs b/hw/nvram/Makefile.objs
index a912d25391..3f978e6212 100644
--- a/hw/nvram/Makefile.objs
+++ b/hw/nvram/Makefile.objs
@@ -5,3 +5,4 @@ common-obj-y += fw_cfg.o
 common-obj-y += chrp_nvram.o
 common-obj-$(CONFIG_MAC_NVRAM) += mac_nvram.o
 obj-$(CONFIG_PSERIES) += spapr_nvram.o
+obj-$(CONFIG_NRF51_SOC) += nrf51_nvm.o
diff --git a/hw/nvram/nrf51_nvm.c b/hw/nvram/nrf51_nvm.c
new file mode 100644
index 0000000000..b3702ebe5e
--- /dev/null
+++ b/hw/nvram/nrf51_nvm.c
@@ -0,0 +1,378 @@
+/*
+ * Nordic Semiconductor nRF51 non-volatile memory
+ *
+ * It provides an interface to erase regions in flash memory.
+ * Furthermore it provides the user and factory information registers.
+ *
+ * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
+ *
+ * See nRF51 reference manual and product sheet sections:
+ * + Non-Volatile Memory Controller (NVMC)
+ * + Factory Information Configuration Registers (FICR)
+ * + User Information Configuration Registers (UICR)
+ *
+ * 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 "qapi/error.h"
+#include "qemu/log.h"
+#include "exec/address-spaces.h"
+#include "hw/arm/nrf51.h"
+#include "hw/nvram/nrf51_nvm.h"
+
+/*
+ * FICR Registers Assignments
+ * CODEPAGESIZE      0x010
+ * CODESIZE          0x014
+ * CLENR0            0x028
+ * PPFC              0x02C
+ * NUMRAMBLOCK       0x034
+ * SIZERAMBLOCKS     0x038
+ * SIZERAMBLOCK[0]   0x038
+ * SIZERAMBLOCK[1]   0x03C
+ * SIZERAMBLOCK[2]   0x040
+ * SIZERAMBLOCK[3]   0x044
+ * CONFIGID          0x05C
+ * DEVICEID[0]       0x060
+ * DEVICEID[1]       0x064
+ * ER[0]             0x080
+ * ER[1]             0x084
+ * ER[2]             0x088
+ * ER[3]             0x08C
+ * IR[0]             0x090
+ * IR[1]             0x094
+ * IR[2]             0x098
+ * IR[3]             0x09C
+ * DEVICEADDRTYPE    0x0A0
+ * DEVICEADDR[0]     0x0A4
+ * DEVICEADDR[1]     0x0A8
+ * OVERRIDEEN        0x0AC
+ * NRF_1MBIT[0]      0x0B0
+ * NRF_1MBIT[1]      0x0B4
+ * NRF_1MBIT[2]      0x0B8
+ * NRF_1MBIT[3]      0x0BC
+ * NRF_1MBIT[4]      0x0C0
+ * BLE_1MBIT[0]      0x0EC
+ * BLE_1MBIT[1]      0x0F0
+ * BLE_1MBIT[2]      0x0F4
+ * BLE_1MBIT[3]      0x0F8
+ * BLE_1MBIT[4]      0x0FC
+ */
+static const uint32_t ficr_content[64] = {
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000400,
+    0x00000100, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000002, 0x00002000,
+    0x00002000, 0x00002000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000003,
+    0x12345678, 0x9ABCDEF1, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
+    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
+};
+
+static uint64_t ficr_read(void *opaque, hwaddr offset, unsigned int size)
+{
+    assert(offset <= sizeof(ficr_content));
+    return ficr_content[offset / 4];
+}
+
+static void ficr_write(void *opaque, hwaddr offset, uint64_t value,
+        unsigned int size)
+{
+    /* Intentionally do nothing */
+}
+
+static const MemoryRegionOps ficr_ops = {
+    .read = ficr_read,
+    .write = ficr_write,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+    .endianness = DEVICE_LITTLE_ENDIAN
+};
+
+/*
+ * UICR Registers Assignments
+ * CLENR0           0x000
+ * RBPCONF          0x004
+ * XTALFREQ         0x008
+ * FWID             0x010
+ * BOOTLOADERADDR   0x014
+ * NRFFW[0]         0x014
+ * NRFFW[1]         0x018
+ * NRFFW[2]         0x01C
+ * NRFFW[3]         0x020
+ * NRFFW[4]         0x024
+ * NRFFW[5]         0x028
+ * NRFFW[6]         0x02C
+ * NRFFW[7]         0x030
+ * NRFFW[8]         0x034
+ * NRFFW[9]         0x038
+ * NRFFW[10]        0x03C
+ * NRFFW[11]        0x040
+ * NRFFW[12]        0x044
+ * NRFFW[13]        0x048
+ * NRFFW[14]        0x04C
+ * NRFHW[0]         0x050
+ * NRFHW[1]         0x054
+ * NRFHW[2]         0x058
+ * NRFHW[3]         0x05C
+ * NRFHW[4]         0x060
+ * NRFHW[5]         0x064
+ * NRFHW[6]         0x068
+ * NRFHW[7]         0x06C
+ * NRFHW[8]         0x070
+ * NRFHW[9]         0x074
+ * NRFHW[10]        0x078
+ * NRFHW[11]        0x07C
+ * CUSTOMER[0]      0x080
+ * CUSTOMER[1]      0x084
+ * CUSTOMER[2]      0x088
+ * CUSTOMER[3]      0x08C
+ * CUSTOMER[4]      0x090
+ * CUSTOMER[5]      0x094
+ * CUSTOMER[6]      0x098
+ * CUSTOMER[7]      0x09C
+ * CUSTOMER[8]      0x0A0
+ * CUSTOMER[9]      0x0A4
+ * CUSTOMER[10]     0x0A8
+ * CUSTOMER[11]     0x0AC
+ * CUSTOMER[12]     0x0B0
+ * CUSTOMER[13]     0x0B4
+ * CUSTOMER[14]     0x0B8
+ * CUSTOMER[15]     0x0BC
+ * CUSTOMER[16]     0x0C0
+ * CUSTOMER[17]     0x0C4
+ * CUSTOMER[18]     0x0C8
+ * CUSTOMER[19]     0x0CC
+ * CUSTOMER[20]     0x0D0
+ * CUSTOMER[21]     0x0D4
+ * CUSTOMER[22]     0x0D8
+ * CUSTOMER[23]     0x0DC
+ * CUSTOMER[24]     0x0E0
+ * CUSTOMER[25]     0x0E4
+ * CUSTOMER[26]     0x0E8
+ * CUSTOMER[27]     0x0EC
+ * CUSTOMER[28]     0x0F0
+ * CUSTOMER[29]     0x0F4
+ * CUSTOMER[30]     0x0F8
+ * CUSTOMER[31]     0x0FC
+ */
+
+static uint64_t uicr_read(void *opaque, hwaddr offset, unsigned int size)
+{
+    NRF51NVMState *s = NRF51_NVM(opaque);
+
+    assert(offset <= sizeof(s->uicr_content));
+    return s->uicr_content[offset / 4];
+}
+
+static void uicr_write(void *opaque, hwaddr offset, uint64_t value,
+        unsigned int size)
+{
+    NRF51NVMState *s = NRF51_NVM(opaque);
+
+    assert(offset <= sizeof(s->uicr_content));
+    s->uicr_content[offset / 4] = value;
+}
+
+static const MemoryRegionOps uicr_ops = {
+    .read = uicr_read,
+    .write = uicr_write,
+    .impl.min_access_size = 4,
+    .impl.max_access_size = 4,
+    .endianness = DEVICE_LITTLE_ENDIAN
+};
+
+
+static uint64_t io_read(void *opaque, hwaddr offset, unsigned int size)
+{
+    NRF51NVMState *s = NRF51_NVM(opaque);
+    uint64_t r = 0;
+
+    switch (offset) {
+    case NRF51_NVMC_READY:
+        r = NRF51_NVMC_READY_READY;
+        break;
+    case NRF51_NVMC_CONFIG:
+        r = s->config;
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                "%s: bad read offset 0x%" HWADDR_PRIx "\n", __func__, offset);
+        break;
+    }
+
+    return r;
+}
+
+static void io_write(void *opaque, hwaddr offset, uint64_t value,
+        unsigned int size)
+{
+    NRF51NVMState *s = NRF51_NVM(opaque);
+
+    switch (offset) {
+    case NRF51_NVMC_CONFIG:
+        s->config = value & NRF51_NVMC_CONFIG_MASK;
+        break;
+    case NRF51_NVMC_ERASEPCR0:
+    case NRF51_NVMC_ERASEPCR1:
+        if (s->config & NRF51_NVMC_CONFIG_EEN) {
+            /* Mask in-page sub address*/
+            value &= ~(NRF51_PAGE_SIZE - 1);
+            if (value < (s->flash_size - NRF51_PAGE_SIZE)) {
+                memset(s->storage + value / 4, 0xFF, NRF51_PAGE_SIZE);
+            }
+        } else {
+            qemu_log_mask(LOG_GUEST_ERROR,
+            "%s: Flash erase at 0x%" HWADDR_PRIx" while flash not erasable.\n",
+            __func__, offset);
+        }
+        break;
+    case NRF51_NVMC_ERASEALL:
+        if (value == NRF51_NVMC_ERASE) {
+            if (s->config & NRF51_NVMC_CONFIG_EEN) {
+                memset(s->storage, 0xFF, s->flash_size);
+                memset(s->uicr_content, 0xFF, sizeof(s->uicr_content));
+            } else {
+                qemu_log_mask(LOG_GUEST_ERROR, "%s: Flash not erasable.\n",
+                              __func__);
+            }
+        }
+        break;
+    case NRF51_NVMC_ERASEUICR:
+        if (value == NRF51_NVMC_ERASE) {
+            memset(s->uicr_content, 0xFF, sizeof(s->uicr_content));
+        }
+        break;
+
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                "%s: bad write offset 0x%" HWADDR_PRIx "\n", __func__, offset);
+    }
+}
+
+static const MemoryRegionOps io_ops = {
+        .read = io_read,
+        .write = io_write,
+        .impl.min_access_size = 4,
+        .impl.max_access_size = 4,
+        .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+
+static void flash_write(void *opaque, hwaddr offset, uint64_t value,
+        unsigned int size)
+{
+    NRF51NVMState *s = NRF51_NVM(opaque);
+
+    if (s->config & NRF51_NVMC_CONFIG_WEN) {
+        assert(offset <= s->flash_size);
+        /* NOR Flash only allows bits to be flipped from 1's to 0's on write */
+        s->storage[offset / 4] &= value;
+    } else {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                "%s: Flash write 0x%" HWADDR_PRIx" while flash not writable.\n",
+                __func__, offset);
+    }
+}
+
+
+
+static const MemoryRegionOps flash_ops = {
+    .write = flash_write,
+    .valid.min_access_size = 4,
+    .valid.max_access_size = 4,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void nrf51_nvm_init(Object *obj)
+{
+    NRF51NVMState *s = NRF51_NVM(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+    memory_region_init_io(&s->mmio, obj, &io_ops, s, "nrf51_soc.nvmc",
+                          NRF51_NVMC_SIZE);
+    sysbus_init_mmio(sbd, &s->mmio);
+
+    memory_region_init_io(&s->ficr, NULL, &ficr_ops, s, "nrf51_soc.ficr",
+                          sizeof(ficr_content));
+    sysbus_init_mmio(sbd, &s->ficr);
+
+    memset(s->uicr_content, 0xFF, sizeof(s->uicr_content));
+    memory_region_init_io(&s->uicr, NULL, &uicr_ops, s, "nrf51_soc.uicr",
+                          sizeof(s->uicr_content));
+    sysbus_init_mmio(sbd, &s->uicr);
+}
+
+static void nrf51_nvm_realize(DeviceState *dev, Error **errp)
+{
+    NRF51NVMState *s = NRF51_NVM(dev);
+    Error *err = NULL;
+
+    memory_region_init_rom_device(&s->flash, OBJECT(dev), &flash_ops, s,
+        "nrf51_soc.flash", s->flash_size, &err);
+    if (err) {
+        error_propagate(errp, err);
+        return;
+    }
+
+    s->storage = memory_region_get_ram_ptr(&s->flash);
+    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->flash);
+}
+
+static void nrf51_nvm_reset(DeviceState *dev)
+{
+    NRF51NVMState *s = NRF51_NVM(dev);
+
+    s->config = 0x00;
+}
+
+static Property nrf51_nvm_properties[] = {
+    DEFINE_PROP_UINT32("flash-size", NRF51NVMState, flash_size, 0x40000),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static const VMStateDescription vmstate_nvm = {
+    .name = "nrf51_soc.nvm",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(uicr_content, NRF51NVMState,
+                NRF51_UICR_FIXTURE_SIZE),
+        VMSTATE_UINT32(config, NRF51NVMState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void nrf51_nvm_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->props = nrf51_nvm_properties;
+    dc->vmsd = &vmstate_nvm;
+    dc->realize = nrf51_nvm_realize;
+    dc->reset = nrf51_nvm_reset;
+}
+
+static const TypeInfo nrf51_nvm_info = {
+    .name = TYPE_NRF51_NVM,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(NRF51NVMState),
+    .instance_init = nrf51_nvm_init,
+    .class_init = nrf51_nvm_class_init
+};
+
+static void nrf51_nvm_register_types(void)
+{
+    type_register_static(&nrf51_nvm_info);
+}
+
+type_init(nrf51_nvm_register_types)
diff --git a/include/hw/nvram/nrf51_nvm.h b/include/hw/nvram/nrf51_nvm.h
new file mode 100644
index 0000000000..0a8b41b358
--- /dev/null
+++ b/include/hw/nvram/nrf51_nvm.h
@@ -0,0 +1,64 @@
+/*
+ * Nordic Semiconductor nRF51 non-volatile memory
+ *
+ * It provides an interface to erase regions in flash memory.
+ * Furthermore it provides the user and factory information registers.
+ *
+ * QEMU interface:
+ * + sysbus MMIO regions 0: NVMC peripheral registers
+ * + sysbus MMIO regions 1: FICR peripheral registers
+ * + sysbus MMIO regions 2: UICR peripheral registers
+ * + flash-size property: flash size in bytes.
+ *
+ * Accuracy of the peripheral model:
+ * + Code regions (MPU configuration) are disregarded.
+ *
+ * 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.
+ *
+ */
+#ifndef NRF51_NVM_H
+#define NRF51_NVM_H
+
+#include "hw/sysbus.h"
+#define TYPE_NRF51_NVM "nrf51_soc.nvm"
+#define NRF51_NVM(obj) OBJECT_CHECK(NRF51NVMState, (obj), TYPE_NRF51_NVM)
+
+#define NRF51_UICR_FIXTURE_SIZE 64
+
+#define NRF51_NVMC_SIZE         0x1000
+
+#define NRF51_NVMC_READY        0x400
+#define NRF51_NVMC_READY_READY  0x01
+#define NRF51_NVMC_CONFIG       0x504
+#define NRF51_NVMC_CONFIG_MASK  0x03
+#define NRF51_NVMC_CONFIG_WEN   0x01
+#define NRF51_NVMC_CONFIG_EEN   0x02
+#define NRF51_NVMC_ERASEPCR1    0x508
+#define NRF51_NVMC_ERASEPCR0    0x510
+#define NRF51_NVMC_ERASEALL     0x50C
+#define NRF51_NVMC_ERASEUICR    0x514
+#define NRF51_NVMC_ERASE        0x01
+
+#define NRF51_UICR_SIZE         0x100
+
+typedef struct NRF51NVMState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion mmio;
+    MemoryRegion ficr;
+    MemoryRegion uicr;
+    MemoryRegion flash;
+
+    uint32_t uicr_content[NRF51_UICR_FIXTURE_SIZE];
+    uint32_t flash_size;
+    uint32_t *storage;
+
+    uint32_t config;
+
+} NRF51NVMState;
+
+
+#endif
-- 
2.19.1


Re: [Qemu-devel] [PATCH v5 05/14] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories
Posted by Peter Maydell 7 years, 2 months ago
On 12 November 2018 at 21:42, Steffen Görtz <contrib@steffen-goertz.de> wrote:
> The nRF51 contains three regions of non-volatile memory (NVM):
> - CODE (R/W): contains code
> - FICR (R): Factory information like code size, chip id etc.
> - UICR (R/W): Changeable configuration data. Lock bits, Code
>   protection configuration, Bootloader address, Nordic SoftRadio
>   configuration, Firmware configuration.
>
> Read and write access to the memories is managed by the
> Non-volatile memory controller.
>
> Memory schema:
>  [ CPU ] -+- [ NVM, either FICR, UICR or CODE ]
>           |      |
>           \- [ NVMC ]
>
> Signed-off-by: Steffen Görtz <contrib@steffen-goertz.de>

Hi; I have some comments on this patch, but they're mostly
fairly minor.

> ---
>  hw/nvram/Makefile.objs       |   1 +
>  hw/nvram/nrf51_nvm.c         | 378 +++++++++++++++++++++++++++++++++++
>  include/hw/nvram/nrf51_nvm.h |  64 ++++++
>  3 files changed, 443 insertions(+)
>  create mode 100644 hw/nvram/nrf51_nvm.c
>  create mode 100644 include/hw/nvram/nrf51_nvm.h
>
> diff --git a/hw/nvram/Makefile.objs b/hw/nvram/Makefile.objs
> index a912d25391..3f978e6212 100644
> --- a/hw/nvram/Makefile.objs
> +++ b/hw/nvram/Makefile.objs
> @@ -5,3 +5,4 @@ common-obj-y += fw_cfg.o
>  common-obj-y += chrp_nvram.o
>  common-obj-$(CONFIG_MAC_NVRAM) += mac_nvram.o
>  obj-$(CONFIG_PSERIES) += spapr_nvram.o
> +obj-$(CONFIG_NRF51_SOC) += nrf51_nvm.o
> diff --git a/hw/nvram/nrf51_nvm.c b/hw/nvram/nrf51_nvm.c
> new file mode 100644
> index 0000000000..b3702ebe5e
> --- /dev/null
> +++ b/hw/nvram/nrf51_nvm.c
> @@ -0,0 +1,378 @@
> +/*
> + * Nordic Semiconductor nRF51 non-volatile memory
> + *
> + * It provides an interface to erase regions in flash memory.
> + * Furthermore it provides the user and factory information registers.
> + *
> + * Reference Manual: http://infocenter.nordicsemi.com/pdf/nRF51_RM_v3.0.pdf
> + *
> + * See nRF51 reference manual and product sheet sections:
> + * + Non-Volatile Memory Controller (NVMC)
> + * + Factory Information Configuration Registers (FICR)
> + * + User Information Configuration Registers (UICR)
> + *
> + * 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 "qapi/error.h"
> +#include "qemu/log.h"
> +#include "exec/address-spaces.h"
> +#include "hw/arm/nrf51.h"
> +#include "hw/nvram/nrf51_nvm.h"
> +
> +/*
> + * FICR Registers Assignments
> + * CODEPAGESIZE      0x010
> + * CODESIZE          0x014
> + * CLENR0            0x028
> + * PPFC              0x02C
> + * NUMRAMBLOCK       0x034
> + * SIZERAMBLOCKS     0x038
> + * SIZERAMBLOCK[0]   0x038
> + * SIZERAMBLOCK[1]   0x03C
> + * SIZERAMBLOCK[2]   0x040
> + * SIZERAMBLOCK[3]   0x044
> + * CONFIGID          0x05C
> + * DEVICEID[0]       0x060
> + * DEVICEID[1]       0x064
> + * ER[0]             0x080
> + * ER[1]             0x084
> + * ER[2]             0x088
> + * ER[3]             0x08C
> + * IR[0]             0x090
> + * IR[1]             0x094
> + * IR[2]             0x098
> + * IR[3]             0x09C
> + * DEVICEADDRTYPE    0x0A0
> + * DEVICEADDR[0]     0x0A4
> + * DEVICEADDR[1]     0x0A8
> + * OVERRIDEEN        0x0AC
> + * NRF_1MBIT[0]      0x0B0
> + * NRF_1MBIT[1]      0x0B4
> + * NRF_1MBIT[2]      0x0B8
> + * NRF_1MBIT[3]      0x0BC
> + * NRF_1MBIT[4]      0x0C0
> + * BLE_1MBIT[0]      0x0EC
> + * BLE_1MBIT[1]      0x0F0
> + * BLE_1MBIT[2]      0x0F4
> + * BLE_1MBIT[3]      0x0F8
> + * BLE_1MBIT[4]      0x0FC
> + */
> +static const uint32_t ficr_content[64] = {
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000400,
> +    0x00000100, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000002, 0x00002000,
> +    0x00002000, 0x00002000, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0x00000003,
> +    0x12345678, 0x9ABCDEF1, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF,
> +    0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF
> +};
> +
> +static uint64_t ficr_read(void *opaque, hwaddr offset, unsigned int size)
> +{
> +    assert(offset <= sizeof(ficr_content));

This should be "<", not "<=". If the offset is equal to
the size of the array then the access will be off the end.

> +    return ficr_content[offset / 4];
> +}
> +
> +static void ficr_write(void *opaque, hwaddr offset, uint64_t value,
> +        unsigned int size)
> +{
> +    /* Intentionally do nothing */
> +}

> +static uint64_t uicr_read(void *opaque, hwaddr offset, unsigned int size)
> +{
> +    NRF51NVMState *s = NRF51_NVM(opaque);
> +
> +    assert(offset <= sizeof(s->uicr_content));

Also "<".

> +    return s->uicr_content[offset / 4];
> +}
> +
> +static void uicr_write(void *opaque, hwaddr offset, uint64_t value,
> +        unsigned int size)
> +{
> +    NRF51NVMState *s = NRF51_NVM(opaque);
> +
> +    assert(offset <= sizeof(s->uicr_content));

Ditto.

> +    s->uicr_content[offset / 4] = value;
> +}
> +



> +static void io_write(void *opaque, hwaddr offset, uint64_t value,
> +        unsigned int size)
> +{
> +    NRF51NVMState *s = NRF51_NVM(opaque);
> +
> +    switch (offset) {
> +    case NRF51_NVMC_CONFIG:
> +        s->config = value & NRF51_NVMC_CONFIG_MASK;
> +        break;
> +    case NRF51_NVMC_ERASEPCR0:
> +    case NRF51_NVMC_ERASEPCR1:
> +        if (s->config & NRF51_NVMC_CONFIG_EEN) {
> +            /* Mask in-page sub address*/

Missing space before "*/".

> +            value &= ~(NRF51_PAGE_SIZE - 1);
> +            if (value < (s->flash_size - NRF51_PAGE_SIZE)) {
> +                memset(s->storage + value / 4, 0xFF, NRF51_PAGE_SIZE);

Can the guest try to execute from the flash storage? If so
then just updating the backing storage directly like this is
not sufficient to ensure that QEMU discards any now-stale
translated code blocks from the affected memory.

> +            }
> +        } else {
> +            qemu_log_mask(LOG_GUEST_ERROR,
> +            "%s: Flash erase at 0x%" HWADDR_PRIx" while flash not erasable.\n",
> +            __func__, offset);
> +        }
> +        break;
> +    case NRF51_NVMC_ERASEALL:
> +        if (value == NRF51_NVMC_ERASE) {
> +            if (s->config & NRF51_NVMC_CONFIG_EEN) {
> +                memset(s->storage, 0xFF, s->flash_size);
> +                memset(s->uicr_content, 0xFF, sizeof(s->uicr_content));
> +            } else {
> +                qemu_log_mask(LOG_GUEST_ERROR, "%s: Flash not erasable.\n",
> +                              __func__);
> +            }
> +        }
> +        break;
> +    case NRF51_NVMC_ERASEUICR:
> +        if (value == NRF51_NVMC_ERASE) {
> +            memset(s->uicr_content, 0xFF, sizeof(s->uicr_content));
> +        }
> +        break;
> +
> +    default:
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                "%s: bad write offset 0x%" HWADDR_PRIx "\n", __func__, offset);
> +    }
> +}
> +
> +static const MemoryRegionOps io_ops = {
> +        .read = io_read,
> +        .write = io_write,
> +        .impl.min_access_size = 4,
> +        .impl.max_access_size = 4,
> +        .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +
> +static void flash_write(void *opaque, hwaddr offset, uint64_t value,
> +        unsigned int size)
> +{
> +    NRF51NVMState *s = NRF51_NVM(opaque);
> +
> +    if (s->config & NRF51_NVMC_CONFIG_WEN) {
> +        assert(offset <= s->flash_size);

"<".

> +        /* NOR Flash only allows bits to be flipped from 1's to 0's on write */
> +        s->storage[offset / 4] &= value;
> +    } else {
> +        qemu_log_mask(LOG_GUEST_ERROR,
> +                "%s: Flash write 0x%" HWADDR_PRIx" while flash not writable.\n",
> +                __func__, offset);
> +    }
> +}
> +
> +
> +
> +static const MemoryRegionOps flash_ops = {
> +    .write = flash_write,
> +    .valid.min_access_size = 4,
> +    .valid.max_access_size = 4,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static void nrf51_nvm_init(Object *obj)
> +{
> +    NRF51NVMState *s = NRF51_NVM(obj);
> +    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
> +
> +    memory_region_init_io(&s->mmio, obj, &io_ops, s, "nrf51_soc.nvmc",
> +                          NRF51_NVMC_SIZE);
> +    sysbus_init_mmio(sbd, &s->mmio);
> +
> +    memory_region_init_io(&s->ficr, NULL, &ficr_ops, s, "nrf51_soc.ficr",
> +                          sizeof(ficr_content));

This call and the one below are the ones where the NULL second
argument should be "obj", as it is in the first call in
this function. This is what causes the "make check" failures
I mentioned in my reply to the series cover letter.

> +    sysbus_init_mmio(sbd, &s->ficr);
> +
> +    memset(s->uicr_content, 0xFF, sizeof(s->uicr_content));
> +    memory_region_init_io(&s->uicr, NULL, &uicr_ops, s, "nrf51_soc.uicr",
> +                          sizeof(s->uicr_content));
> +    sysbus_init_mmio(sbd, &s->uicr);
> +}
> +
> +static void nrf51_nvm_realize(DeviceState *dev, Error **errp)
> +{
> +    NRF51NVMState *s = NRF51_NVM(dev);
> +    Error *err = NULL;
> +
> +    memory_region_init_rom_device(&s->flash, OBJECT(dev), &flash_ops, s,
> +        "nrf51_soc.flash", s->flash_size, &err);
> +    if (err) {
> +        error_propagate(errp, err);
> +        return;
> +    }
> +
> +    s->storage = memory_region_get_ram_ptr(&s->flash);
> +    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->flash);
> +}
> +
> +static void nrf51_nvm_reset(DeviceState *dev)
> +{
> +    NRF51NVMState *s = NRF51_NVM(dev);
> +
> +    s->config = 0x00;

Shouldn't uicr_content[] and storage be reset too ?

> +}

thanks
-- PMM

Re: [Qemu-devel] [PATCH v5 05/14] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories
Posted by Steffen Görtz 7 years, 2 months ago
Hi Peter,

thank you for your remarks!

>> +};
>> +
>> +static uint64_t ficr_read(void *opaque, hwaddr offset
> 
>> +            value &= ~(NRF51_PAGE_SIZE - 1);
>> +            if (value < (s->flash_size - NRF51_PAGE_SIZE)) {
>> +                memset(s->storage + value / 4, 0xFF, NRF51_PAGE_SIZE);
> 
> Can the guest try to execute from the flash storage? If so
> then just updating the backing storage directly like this is
> not sufficient to ensure that QEMU discards any now-stale
> translated code blocks from the affected memory.

What else is necessary to invalidate stale blocks?


>> +
>> +static void nrf51_nvm_reset(DeviceState *dev)
>> +{
>> +    NRF51NVMState *s = NRF51_NVM(dev);
>> +
>> +    s->config = 0x00;
> 
> Shouldn't uicr_content[] and storage be reset too ?


Storage and uicr_content should be retained during a device reset. 

Best,
Steffen

Re: [Qemu-devel] [PATCH v5 05/14] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories
Posted by Peter Maydell 7 years, 2 months ago
On Mon, 26 Nov 2018 at 00:24, Steffen Görtz <mail@steffen-goertz.de> wrote:
>
> Hi Peter,
>
> thank you for your remarks!
>
> >> +};
> >> +
> >> +static uint64_t ficr_read(void *opaque, hwaddr offset
> >
> >> +            value &= ~(NRF51_PAGE_SIZE - 1);
> >> +            if (value < (s->flash_size - NRF51_PAGE_SIZE)) {
> >> +                memset(s->storage + value / 4, 0xFF, NRF51_PAGE_SIZE);
> >
> > Can the guest try to execute from the flash storage? If so
> > then just updating the backing storage directly like this is
> > not sufficient to ensure that QEMU discards any now-stale
> > translated code blocks from the affected memory.
>
> What else is necessary to invalidate stale blocks?

You need an AddressSpace that points to the MemoryRegion(s)
you're altering, and you need to use the operations on
the AddressSpace like address_space_write(). These will
under the hood do the right thing with TB invalidation.

> >> +static void nrf51_nvm_reset(DeviceState *dev)
> >> +{
> >> +    NRF51NVMState *s = NRF51_NVM(dev);
> >> +
> >> +    s->config = 0x00;
> >
> > Shouldn't uicr_content[] and storage be reset too ?
>
>
> Storage and uicr_content should be retained during a device reset.

If you want storage that lives over system reset, that is
painful, because it means you need to back it with a
block device (like we do for disks and for flash memory).
(This is because QEMU's system reset is a hard-power-cycle;
it should be equivalent to stopping QEMU entirely and
then restarting it, so any persistent storage needs to be
in a block device somewhere.)

thanks
-- PMM

Re: [Qemu-devel] [PATCH v5 05/14] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories
Posted by Stefan Hajnoczi 7 years, 1 month ago
On Mon, Nov 26, 2018 at 05:43:59PM +0000, Peter Maydell wrote:
> On Mon, 26 Nov 2018 at 00:24, Steffen Görtz <mail@steffen-goertz.de> wrote:
> >
> > Hi Peter,
> >
> > thank you for your remarks!
> >
> > >> +};
> > >> +
> > >> +static uint64_t ficr_read(void *opaque, hwaddr offset
> > >
> > >> +            value &= ~(NRF51_PAGE_SIZE - 1);
> > >> +            if (value < (s->flash_size - NRF51_PAGE_SIZE)) {
> > >> +                memset(s->storage + value / 4, 0xFF, NRF51_PAGE_SIZE);
> > >
> > > Can the guest try to execute from the flash storage? If so
> > > then just updating the backing storage directly like this is
> > > not sufficient to ensure that QEMU discards any now-stale
> > > translated code blocks from the affected memory.
> >
> > What else is necessary to invalidate stale blocks?
> 
> You need an AddressSpace that points to the MemoryRegion(s)
> you're altering, and you need to use the operations on
> the AddressSpace like address_space_write(). These will
> under the hood do the right thing with TB invalidation.

I'm not sure about this.  The memory region looks like this:

{parent_obj = {class = 0x5555565ee350, free = 0x0, Python Exception <class 'gdb.error'> There is no member named keys.:
properties = 0x55555672f860, ref = 1, parent = 0x5555566620f0}, romd_mode = true, ram = false, subpage = false, readonly = false,
  nonvolatile = false, rom_device = true, flush_coalesced_mmio = false, global_locking = true, dirty_log_mask = 0 '\000', is_iommu = false, ram_block = 0x555556768b40,
  owner = 0x5555566620f0, ops = 0x55555615d360 <flash_ops>, opaque = 0x5555566620f0, container = 0x0, size = 262144, addr = 0, destructor = 0x555555893f00 <memory_region_destructor_ram>,
  align = 2097152, terminates = true, ram_device = false, enabled = true, warning_printed = false, vga_logging_count = 0 '\000', alias = 0x0, alias_offset = 0, priority = 0, subregions = {
    tqh_first = 0x0, tqh_last = 0x555556662778}, subregions_link = {tqe_next = 0x0, tqe_prev = 0x0}, coalesced = {tqh_first = 0x0, tqh_last = 0x555556662798},
  name = 0x5555568033d0 "nrf51_soc.flash", ioeventfd_nb = 0, ioeventfds = 0x0}

I see nothing that invalidates TBs in the address_space_write() code for
MMIO memory regions (not RAM).  Only the RAM case calls
invalidate_and_set_dirty().

There are a few complications with this device:

1. Stores from the CPU are only honored when the NRF51_NVMC_CONFIG_WEN
   write enable bit is set.  NRF51_NVMC_ERASEPCRx and
   NRF51_NVMC_ERASEALL commands use a separate erase enable bit
   (NRF51_NVMC_CONFIG_EEN) and are therefore different from normal
   writes.

2. Stores from the CPU can only flip 1s to 0s (this is NOR flash).  When
   we erase a page of flash memory it must be set to 0xff (i.e. flip
   0s to 1s).

3. nrf51_nvm.c:flash_write() does not mark the page dirty for live
   migration.

My questions:

1. Is the current rom+mmio device approach okay or should it be modelled
   differently?

2. Erase operations cannot use ordinary address_space_write() for the
   reasons mentioned above.  Should this device directly call
   cpu_physical_memory_set_dirty_range() and tb_invalidate_phys_range()?
Re: [Qemu-devel] [PATCH v5 05/14] hw/nvram/nrf51_nvm: Add nRF51 non-volatile memories
Posted by Peter Maydell 7 years, 1 month ago
On Sun, 16 Dec 2018 at 06:20, Stefan Hajnoczi <stefanha@gmail.com> wrote:
>
> On Mon, Nov 26, 2018 at 05:43:59PM +0000, Peter Maydell wrote:
> > On Mon, 26 Nov 2018 at 00:24, Steffen Görtz <mail@steffen-goertz.de> wrote:
> > > What else is necessary to invalidate stale blocks?
> >
> > You need an AddressSpace that points to the MemoryRegion(s)
> > you're altering, and you need to use the operations on
> > the AddressSpace like address_space_write(). These will
> > under the hood do the right thing with TB invalidation.
>
> I'm not sure about this.

> I see nothing that invalidates TBs in the address_space_write() code for
> MMIO memory regions (not RAM).  Only the RAM case calls
> invalidate_and_set_dirty().

Hmm, I think I see what you mean -- the problem
is because your ram backed region is created with
memory_region_init_rom_device(), which means that
if you try to write to the MR via address_space_write()
you get the guest access path which ends up in the
MemoryRegionOps write function, whereas what we wanted
was to access the MR via the write-host-RAM code path
which updates the dirty state.

> There are a few complications with this device:
>
> 1. Stores from the CPU are only honored when the NRF51_NVMC_CONFIG_WEN
>    write enable bit is set.  NRF51_NVMC_ERASEPCRx and
>    NRF51_NVMC_ERASEALL commands use a separate erase enable bit
>    (NRF51_NVMC_CONFIG_EEN) and are therefore different from normal
>    writes.

i.e. we want guest writes to the region to go
via a write function so we can impose the right semantics.

> 2. Stores from the CPU can only flip 1s to 0s (this is NOR flash).  When
>    we erase a page of flash memory it must be set to 0xff (i.e. flip
>    0s to 1s).

i.e. we have some other operations which need to perform
writes directly on the underlying storage.

> 3. nrf51_nvm.c:flash_write() does not mark the page dirty for live
>    migration.

...and this is the bug we need to avoid: any write to the
underlying storage must update dirty status, both for
migration and for TCG cached code invalidation.

> My questions:
>
> 1. Is the current rom+mmio device approach okay or should it be modelled
>    differently?
>
> 2. Erase operations cannot use ordinary address_space_write() for the
>    reasons mentioned above.  Should this device directly call
>    cpu_physical_memory_set_dirty_range() and tb_invalidate_phys_range()?

I think that these functions are too low level for devices to be
calling directly, and we should have something at the MemoryRegion or
AddressSpace level to do what we need. Perhaps some API for
"this is a rom_device MemoryRegion, create me a new MemoryRegion
which is a pure RAM MemoryRegion view of the underlying storage".
(The device could then put that into a private AddressSpace and
use that for when it wants to do updates directly to storage.)
Or perhaps we could just say "practically all uses of rom_device
MemoryRegions will want to be able to write to the underlying
storage, we should provide an API that handles flushing for them".

Paolo, do you have a view?

(Our only other users of rom_device are hw/block/pflash_cfi0[12].c;
I think they get away with this because they mostly flip the device
out of romd mode before updating the backing storage. That works
but is very heavyweight, and is almost working by accident rather
than design.)

thanks
-- PMM