diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 9b9e51cd1bf0..7acda764f10d 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -1956,6 +1956,17 @@ RISCV arch: labels: - "area: RISCV" +Retention: + status: maintained + maintainers: + - nordicjm + files: + - dts/bindings/retention/ + - include/zephyr/retention/ + - subsys/retention/ + labels: + - "area: Retention" + Twister: status: maintained maintainers: diff --git a/doc/develop/api/overview.rst b/doc/develop/api/overview.rst index 9ece8be8375e..3ad354895eaf 100644 --- a/doc/develop/api/overview.rst +++ b/doc/develop/api/overview.rst @@ -237,13 +237,17 @@ between major releases are available in the :ref:`zephyr_release_notes`. - Experimental - 2.4 + * - :ref:`reset_api` + - Experimental + - 3.1 + * - :ref:`retained_mem_api` - Experimental - 3.4 - * - :ref:`reset_api` + * - :ref:`retention_api` - Experimental - - 3.1 + - 3.4 * - :ref:`rtc_api` - Experimental diff --git a/doc/releases/release-notes-3.4.rst b/doc/releases/release-notes-3.4.rst index 1cfd9243a797..6a5fb485106e 100644 --- a/doc/releases/release-notes-3.4.rst +++ b/doc/releases/release-notes-3.4.rst @@ -151,6 +151,15 @@ Deprecated in this release board-specific configuration in board Kconfig fragments in the ``boards`` folder of the application. +* On nRF51 and nRF52-based boards, the behaviour of the reset reason being + provided to :c:func:`sys_reboot` and being set in the GPREGRET register has + been dropped. This function will now just reboot the device without changing + the register contents. The new method for setting this register uses the boot + mode feature of the retention subsystem, see the + :ref:`boot mode API ` for details. To restore the deprecated + functionality, enable + :kconfig:option:`CONFIG_NRF_STORE_REBOOT_TYPE_GPREGRET`. + Stable API changes in this release ================================== @@ -485,6 +494,19 @@ Libraries / Subsystems correctly, allowing other transports or other parts of the application code to use it. +* Retention + + * Retention subsystem has been added which adds enhanced features over + retained memory drivers allowing for partitioning, magic headers and + checksum validation. See :ref:`retention API ` for details. + Support for the retention subsystem is experimental. + + * Boot mode retention module has been added which allows for setting/checking + the boot mode of an application, initial support has also been added to + MCUboot to allow for applications to use this as an entrance method for + MCUboot serial recovery mode. See :ref:`boot mode API ` for + details. + * RTIO * Added policy that every ``sqe`` will generate a ``cqe`` (previously an RTIO_SQE_TRANSACTION diff --git a/doc/services/index.rst b/doc/services/index.rst index d0df06aa450d..7e2e7641348f 100644 --- a/doc/services/index.rst +++ b/doc/services/index.rst @@ -30,6 +30,7 @@ OS Services tfm/index usb/index.rst virtualization/index.rst + retention/index.rst rtio/index.rst zbus/index.rst misc.rst diff --git a/doc/services/retention/index.rst b/doc/services/retention/index.rst new file mode 100644 index 000000000000..0ae60332b0e7 --- /dev/null +++ b/doc/services/retention/index.rst @@ -0,0 +1,198 @@ +.. _retention_api: + +Retention System +################ + +The retention system provides an API which allows applications to read and +write data from and to memory areas or devices that retain the data while the +device is powered. This allows for sharing information between different +applications or within a single application without losing state information +when a device reboots. The stored data should not persist in the event of a +power failure (or during some low-power modes on some devices) nor should it be +stored to a non-volatile storage like :ref:`flash_api`, :ref:`eeprom_api`, or +battery-backed RAM. + +The retention system builds on top of the retained data driver, and adds +additional software-level features to it for ensuring the validity of data. +Optionally, a magic header can be used to check if the front of +the retained data memory section contains this specific value, and an optional +checksum (1, 2, or 4-bytes in size) of the stored data can be appended to the +end of the data. Additionally, the retention system API allows partitioning of +the retained data sections into multiple distinct areas. For example, a 64-byte +retained data area could be split up into 4 bytes for a boot mode, 16 bytes for +a timestamp, 44 bytes for a last log message. All of these sections can be +accessed or updated independently. The prefix and checksum can be set +per-instance using devicetree. + +Devicetree setup +**************** + +To use the retention system, a retained data driver must be setup for the board +you are using, there is a zephyr driver which can be used which will use some +RAM as non-init for this purpose. The retention system is then initialised as a +child node of this device 1 or more times - note that the memory region will +need to be decremented to account for this reserved portion of RAM. See the +following example (examples in this guide are based on the +:ref:`nrf52840dk_nrf52840` board and memory layout): + +.. code-block:: devicetree + + / { + sram@2003FC00 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2003FC00 DT_SIZE_K(1)>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + /* This creates a 256-byte partition */ + retention0: retention@0 { + compatible = "zephyr,retention"; + status = "okay"; + + /* The total size of this area is 256 + * bytes which includes the prefix and + * checksum, this means that the usable + * data storage area is 256 - 3 = 253 + * bytes + */ + reg = <0x0 0x100>; + + /* This is the prefix which must appear + * at the front of the data + */ + prefix = [08 04]; + + /* This uses a 1-byte checksum */ + checksum = <1>; + }; + + /* This creates a 768-byte partition */ + retention1: retention@100 { + compatible = "zephyr,retention"; + status = "okay"; + + /* Start position must be after the end + * of the previous partition. The total + * size of this area is 768 bytes which + * includes the prefix and checksum, + * this means that the usable data + * storage area is 768 - 6 = 762 bytes + */ + reg = <0x100 0x300>; + + /* This is the prefix which must appear + * at the front of the data + */ + prefix = [00 11 55 88 fa bc]; + + /* If omitted, there will be no + * checksum + */ + }; + }; + }; + }; + + /* Reduce SRAM0 usage by 1KB to account for non-init area */ + &sram0 { + reg = <0x20000000 DT_SIZE_K(255)>; + }; + +The retention areas can then be accessed using the data retention API (once +enabled with :kconfig:option:`CONFIG_RETENTION`, which requires that +:kconfig:option:`CONFIG_RETAINED_MEM` be enabled) by getting the device by +using: + +.. code-block:: C + + #include + #include + + const struct device *retention1 = DEVICE_DT_GET(DT_NODELABEL(retention1)); + const struct device *retention2 = DEVICE_DT_GET(DT_NODELABEL(retention2)); + +When the write function is called, the magic header and checksum (if enabled) +will be set on the area, and it will be marked as valid from that point +onwards. + +.. _boot_mode_api: + +Boot mode +********* + +An addition to the retention subsystem is a boot mode interface, this can be +used to dynamically change the state of an application or run a different +application with a minimal set of functions when a device is rebooted (an +example is to have a buttonless way of entering mcuboot's serial recovery +feature from the main application). + +To use the boot mode feature, a data retention entry must exist in the device +tree, which is dedicated for use as the boot mode selection (the user area data +size only needs to be a single byte), and this area be assigned to the chosen +node of ``zephyr,boot-mode``. See the following example: + +.. code-block:: devicetree + + / { + sram@2003FFFF { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2003FFFF 0x1>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + retention0: retention@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 0x1>; + }; + }; + }; + + chosen { + zephyr,boot-mode = &retention0; + }; + }; + + /* Reduce SRAM0 usage by 1 byte to account for non-init area */ + &sram0 { + reg = <0x20000000 0x3FFFF>; + }; + +The boot mode interface can be enabled with +:kconfig:option:`CONFIG_RETENTION_BOOT_MODE` and then accessed by using the +boot mode functions. If using mcuboot with serial recovery, it can be built +with ``CONFIG_MCUBOOT_SERIAL`` and ``CONFIG_BOOT_SERIAL_BOOT_MODE`` enabled +which will allow rebooting directly into the serial recovery mode by using: + +.. code-block:: C + + #include + #include + + bootmode_set(BOOT_MODE_TYPE_BOOTLOADER); + sys_reboot(0); + +API Reference +************* + +Retention system API +==================== + +.. doxygengroup:: retention_api + +Boot mode interface +=================== + +.. doxygengroup:: boot_mode_interface diff --git a/drivers/retained_mem/Kconfig b/drivers/retained_mem/Kconfig index 35d218496334..8f134f94560b 100644 --- a/drivers/retained_mem/Kconfig +++ b/drivers/retained_mem/Kconfig @@ -15,6 +15,15 @@ config RETAINED_MEM_INIT_PRIORITY help Retained memory devices initialization priority, +config RETAINED_MEM_MUTEXES + bool "Retained memory mutex support" + default y + depends on MULTITHREADING + help + Use mutexes to prevent issues with concurrent retained memory access. + Should only be disabled whereby retained memory access is required + in an ISR or for special use cases. + module = RETAINED_MEM module-str = retained_mem source "subsys/logging/Kconfig.template.log_config" diff --git a/drivers/retained_mem/retained_mem_nrf_gpregret.c b/drivers/retained_mem/retained_mem_nrf_gpregret.c index 43fc486aeaa7..3bfcdb139771 100644 --- a/drivers/retained_mem/retained_mem_nrf_gpregret.c +++ b/drivers/retained_mem/retained_mem_nrf_gpregret.c @@ -16,7 +16,7 @@ LOG_MODULE_REGISTER(retained_mem_nrf_gpregret, CONFIG_RETAINED_MEM_LOG_LEVEL); -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct nrf_gpregret_data { struct k_mutex lock; }; @@ -29,7 +29,7 @@ struct nrf_gpregret_config { static inline void nrf_gpregret_lock_take(const struct device *dev) { -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct nrf_gpregret_data *data = dev->data; k_mutex_lock(&data->lock, K_FOREVER); @@ -40,7 +40,7 @@ static inline void nrf_gpregret_lock_take(const struct device *dev) static inline void nrf_gpregret_lock_release(const struct device *dev) { -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct nrf_gpregret_data *data = dev->data; k_mutex_unlock(&data->lock); @@ -51,7 +51,7 @@ static inline void nrf_gpregret_lock_release(const struct device *dev) static int nrf_gpregret_init(const struct device *dev) { -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct nrf_gpregret_data *data = dev->data; k_mutex_init(&data->lock); @@ -115,7 +115,7 @@ static const struct retained_mem_driver_api nrf_gpregret_api = { }; #define NRF_GPREGRET_DEVICE(inst) \ - IF_ENABLED(CONFIG_MULTITHREADING, \ + IF_ENABLED(CONFIG_RETAINED_MEM_MUTEXES, \ (static struct nrf_gpregret_data nrf_gpregret_data_##inst;) \ ) \ static const struct nrf_gpregret_config nrf_gpregret_config_##inst = { \ @@ -125,7 +125,7 @@ static const struct retained_mem_driver_api nrf_gpregret_api = { DEVICE_DT_INST_DEFINE(inst, \ &nrf_gpregret_init, \ NULL, \ - COND_CODE_1(CONFIG_MULTITHREADING, \ + COND_CODE_1(CONFIG_RETAINED_MEM_MUTEXES, \ (&nrf_gpregret_data_##inst), (NULL) \ ), \ &nrf_gpregret_config_##inst, \ diff --git a/drivers/retained_mem/retained_mem_zephyr_ram.c b/drivers/retained_mem/retained_mem_zephyr_ram.c index 55c86c392c23..740318204596 100644 --- a/drivers/retained_mem/retained_mem_zephyr_ram.c +++ b/drivers/retained_mem/retained_mem_zephyr_ram.c @@ -16,7 +16,7 @@ LOG_MODULE_REGISTER(retained_mem_zephyr_ram, CONFIG_RETAINED_MEM_LOG_LEVEL); -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct zephyr_retained_mem_ram_data { struct k_mutex lock; }; @@ -29,7 +29,7 @@ struct zephyr_retained_mem_ram_config { static inline void zephyr_retained_mem_ram_lock_take(const struct device *dev) { -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct zephyr_retained_mem_ram_data *data = dev->data; k_mutex_lock(&data->lock, K_FOREVER); @@ -40,7 +40,7 @@ static inline void zephyr_retained_mem_ram_lock_take(const struct device *dev) static inline void zephyr_retained_mem_ram_lock_release(const struct device *dev) { -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct zephyr_retained_mem_ram_data *data = dev->data; k_mutex_unlock(&data->lock); @@ -51,7 +51,7 @@ static inline void zephyr_retained_mem_ram_lock_release(const struct device *dev static int zephyr_retained_mem_ram_init(const struct device *dev) { -#ifdef CONFIG_MULTITHREADING +#ifdef CONFIG_RETAINED_MEM_MUTEXES struct zephyr_retained_mem_ram_data *data = dev->data; k_mutex_init(&data->lock); @@ -116,7 +116,7 @@ static const struct retained_mem_driver_api zephyr_retained_mem_ram_api = { }; #define ZEPHYR_RETAINED_MEM_RAM_DEVICE(inst) \ - IF_ENABLED(CONFIG_MULTITHREADING, \ + IF_ENABLED(CONFIG_RETAINED_MEM_MUTEXES, \ (static struct zephyr_retained_mem_ram_data \ zephyr_retained_mem_ram_data_##inst;) \ ) \ @@ -128,7 +128,7 @@ static const struct retained_mem_driver_api zephyr_retained_mem_ram_api = { DEVICE_DT_INST_DEFINE(inst, \ &zephyr_retained_mem_ram_init, \ NULL, \ - COND_CODE_1(CONFIG_MULTITHREADING, \ + COND_CODE_1(CONFIG_RETAINED_MEM_MUTEXES, \ (&zephyr_retained_mem_ram_data_##inst), (NULL) \ ), \ &zephyr_retained_mem_ram_config_##inst, \ diff --git a/dts/arm/nordic/nrf51822.dtsi b/dts/arm/nordic/nrf51822.dtsi index 1e6b0e886e71..66e79d565a02 100644 --- a/dts/arm/nordic/nrf51822.dtsi +++ b/dts/arm/nordic/nrf51822.dtsi @@ -46,6 +46,8 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf52805.dtsi b/dts/arm/nordic/nrf52805.dtsi index c2c5ddd35328..c9280b6536ae 100644 --- a/dts/arm/nordic/nrf52805.dtsi +++ b/dts/arm/nordic/nrf52805.dtsi @@ -57,12 +57,16 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; }; gpregret2: gpregret2@40000520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x40000520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf52810.dtsi b/dts/arm/nordic/nrf52810.dtsi index be196352fe2f..a56d94f48da4 100644 --- a/dts/arm/nordic/nrf52810.dtsi +++ b/dts/arm/nordic/nrf52810.dtsi @@ -61,12 +61,16 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; }; gpregret2: gpregret2@40000520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x40000520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf52811.dtsi b/dts/arm/nordic/nrf52811.dtsi index f41565d94c6c..29cfd12dde60 100644 --- a/dts/arm/nordic/nrf52811.dtsi +++ b/dts/arm/nordic/nrf52811.dtsi @@ -65,12 +65,16 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; }; gpregret2: gpregret2@40000520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x40000520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf52820.dtsi b/dts/arm/nordic/nrf52820.dtsi index b641535a9be1..7c380be67f6b 100644 --- a/dts/arm/nordic/nrf52820.dtsi +++ b/dts/arm/nordic/nrf52820.dtsi @@ -66,12 +66,16 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; }; gpregret2: gpregret2@40000520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x40000520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf52832.dtsi b/dts/arm/nordic/nrf52832.dtsi index 3005e60e5737..09fb12b27c94 100644 --- a/dts/arm/nordic/nrf52832.dtsi +++ b/dts/arm/nordic/nrf52832.dtsi @@ -61,12 +61,16 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; }; gpregret2: gpregret2@40000520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x40000520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf52833.dtsi b/dts/arm/nordic/nrf52833.dtsi index 6994e7649d83..57ae9527b5f1 100644 --- a/dts/arm/nordic/nrf52833.dtsi +++ b/dts/arm/nordic/nrf52833.dtsi @@ -65,12 +65,16 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; }; gpregret2: gpregret2@40000520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x40000520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf52840.dtsi b/dts/arm/nordic/nrf52840.dtsi index 0fb5fb753cd4..b7abf05f9655 100644 --- a/dts/arm/nordic/nrf52840.dtsi +++ b/dts/arm/nordic/nrf52840.dtsi @@ -61,12 +61,16 @@ #size-cells = <1>; gpregret1: gpregret1@4000051c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4000051c 0x1>; status = "okay"; }; gpregret2: gpregret2@40000520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x40000520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf5340_cpuapp_peripherals.dtsi b/dts/arm/nordic/nrf5340_cpuapp_peripherals.dtsi index eeed23eb6d30..30e9b47bd82c 100644 --- a/dts/arm/nordic/nrf5340_cpuapp_peripherals.dtsi +++ b/dts/arm/nordic/nrf5340_cpuapp_peripherals.dtsi @@ -38,12 +38,16 @@ power: power@5000 { #size-cells = <1>; gpregret1: gpregret1@551c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x551c 0x1>; status = "okay"; }; gpregret2: gpregret2@5520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x5520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf5340_cpunet.dtsi b/dts/arm/nordic/nrf5340_cpunet.dtsi index aed053a5a7f2..13e39a2a9684 100644 --- a/dts/arm/nordic/nrf5340_cpunet.dtsi +++ b/dts/arm/nordic/nrf5340_cpunet.dtsi @@ -69,12 +69,16 @@ #size-cells = <1>; gpregret1: gpregret1@4100551c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x4100551c 0x1>; status = "okay"; }; gpregret2: gpregret2@41005520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x41005520 0x1>; status = "okay"; diff --git a/dts/arm/nordic/nrf91_peripherals.dtsi b/dts/arm/nordic/nrf91_peripherals.dtsi index 676dd99d60f6..bb456bc6349d 100644 --- a/dts/arm/nordic/nrf91_peripherals.dtsi +++ b/dts/arm/nordic/nrf91_peripherals.dtsi @@ -347,12 +347,16 @@ power: power@5000 { #size-cells = <1>; gpregret1: gpregret1@551c { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x551c 0x1>; status = "okay"; }; gpregret2: gpregret2@5520 { + #address-cells = <1>; + #size-cells = <1>; compatible = "nordic,nrf-gpregret"; reg = <0x5520 0x1>; status = "okay"; diff --git a/dts/bindings/retained_mem/nordic,nrf-gpreget.yaml b/dts/bindings/retained_mem/nordic,nrf-gpreget.yaml index 8f136be4b329..25aebb4dcbe8 100644 --- a/dts/bindings/retained_mem/nordic,nrf-gpreget.yaml +++ b/dts/bindings/retained_mem/nordic,nrf-gpreget.yaml @@ -8,5 +8,11 @@ compatible: "nordic,nrf-gpregret" include: base.yaml properties: + "#address-cells": + const: 1 + + "#size-cells": + const: 1 + reg: required: true diff --git a/dts/bindings/retained_mem/zephyr,retained-ram.yaml b/dts/bindings/retained_mem/zephyr,retained-ram.yaml index 4a543304d851..5f2c1df12449 100644 --- a/dts/bindings/retained_mem/zephyr,retained-ram.yaml +++ b/dts/bindings/retained_mem/zephyr,retained-ram.yaml @@ -6,3 +6,10 @@ description: Unitialised RAM-based retained memory area. compatible: "zephyr,retained-ram" include: base.yaml + +properties: + "#address-cells": + const: 1 + + "#size-cells": + const: 1 diff --git a/dts/bindings/retention/zephyr,retention.yaml b/dts/bindings/retention/zephyr,retention.yaml new file mode 100644 index 000000000000..27fa1511c855 --- /dev/null +++ b/dts/bindings/retention/zephyr,retention.yaml @@ -0,0 +1,66 @@ +# Copyright (c) 2023 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: | + Retention subsystem area, which has a retained memory parent. Example + 64-byte area with 2-byte prefix and 1-byte checksum with 61 usable bytes + for user storage: + + sram@2003FFC0 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2003FFC0 64>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + retention0: retention@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 0x40>; + prefix = [04 fa]; + checksum = <1>; + }; + }; + }; + +compatible: "zephyr,retention" + +include: base.yaml + +properties: + "#address-cells": + const: 1 + description: | + Address reg cell is for the offset of the area in parent node, can be + increased if multiple retention partitions are used or parts are + reserved. + + "#size-cells": + const: 1 + description: | + Size reg cell is for the size of the area, which includes sizes of + prefix and checksum (if enabled). + + reg: + required: true + + prefix: + description: | + An optional magic prefix, which indicates that the data has been set + (applies to the header of the data, reduces the available user data + size). + type: uint8-array + + checksum: + description: | + An optional data verification checksum, which indicates that the data is + valid (appended to the footer of the data, reduces the available user + data size). Value is size in bytes (0 for none, 1 for 8-bit CRC, 2 for + 16-bit CRC, 4 for 32-bit CRC). Default is to not use a checksum. + type: int + default: 0 diff --git a/include/zephyr/retention/bootmode.h b/include/zephyr/retention/bootmode.h new file mode 100644 index 000000000000..130efcdacaef --- /dev/null +++ b/include/zephyr/retention/bootmode.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public API for boot mode interface + */ + +#ifndef ZEPHYR_INCLUDE_RETENTION_BOOTMODE_ +#define ZEPHYR_INCLUDE_RETENTION_BOOTMODE_ + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Boot mode interface + * @defgroup boot_mode_interface Boot mode interface + * @ingroup retention + * @{ + */ + +enum BOOT_MODE_TYPES { + /** Default (normal) boot, to user application */ + BOOT_MODE_TYPE_NORMAL = 0x00, + + /** Bootloader boot mode (e.g. serial recovery for MCUboot) */ + BOOT_MODE_TYPE_BOOTLOADER, +}; + +/** + * @brief Checks if the boot mode of the device is set to a specific value. + * + * @param boot_mode Expected boot mode to check. + * + * @retval 1 If successful and boot mode matches. + * @retval 0 If boot mode does not match. + * @retval -errno Error code code. + */ +int bootmode_check(uint8_t boot_mode); + +/** + * @brief Sets boot mode of device. + * + * @param boot_mode Boot mode value to set. + * + * @retval 0 If successful. + * @retval -errno Error code code. + */ +int bootmode_set(uint8_t boot_mode); + +/** + * @brief Clear boot mode value (sets to 0) - which corresponds to + * #BOOT_MODE_TYPE_NORMAL. + * + * @retval 0 If successful. + * @retval -errno Error code code. + */ +int bootmode_clear(void); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_RETENTION_BOOTMODE_ */ diff --git a/include/zephyr/retention/retention.h b/include/zephyr/retention/retention.h new file mode 100644 index 000000000000..f00854fc0b37 --- /dev/null +++ b/include/zephyr/retention/retention.h @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Public API for retention API + */ + +#ifndef ZEPHYR_INCLUDE_RETENTION_ +#define ZEPHYR_INCLUDE_RETENTION_ + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Retention API + * @defgroup retention_api Retention API + * @ingroup retention + * @{ + */ + +typedef ssize_t (*retention_size_api)(const struct device *dev); +typedef int (*retention_is_valid_api)(const struct device *dev); +typedef int (*retention_read_api)(const struct device *dev, off_t offset, uint8_t *buffer, + size_t size); +typedef int (*retention_write_api)(const struct device *dev, off_t offset, + const uint8_t *buffer, size_t size); +typedef int (*retention_clear_api)(const struct device *dev); + +struct retention_api { + retention_size_api size; + retention_is_valid_api is_valid; + retention_read_api read; + retention_write_api write; + retention_clear_api clear; +}; + +/** + * @brief Returns the size of the retention area. + * + * @param dev Retention device to use. + * + * @retval Positive value indicating size in bytes on success, else negative errno + * code. + */ +ssize_t retention_size(const struct device *dev); + +/** + * @brief Checks if the underlying data in the retention area is valid or not. + * + * @param dev Retention device to use. + * + * @retval 1 If successful and data is valid. + * @retval 0 If data is not valid. + * @retval -ENOTSUP If there is no header/checksum configured for the retention area. + * @retval -errno Error code code. + */ +int retention_is_valid(const struct device *dev); + +/** + * @brief Reads data from the retention area. + * + * @param dev Retention device to use. + * @param offset Offset to read data from. + * @param buffer Buffer to store read data in. + * @param size Size of data to read. + * + * @retval 0 If successful. + * @retval -errno Error code code. + */ +int retention_read(const struct device *dev, off_t offset, uint8_t *buffer, size_t size); + +/** + * @brief Writes data to the retention area (underlying data does not need to be + * cleared prior to writing), once function returns with a success code, the + * data will be classed as valid if queried using retention_is_valid(). + * + * @param dev Retention device to use. + * @param offset Offset to write data to. + * @param buffer Data to write. + * @param size Size of data to be written. + * + * @retval 0 on success else negative errno code. + */ +int retention_write(const struct device *dev, off_t offset, const uint8_t *buffer, size_t size); + +/** + * @brief Clears all data in the retention area (sets it to 0) + * + * @param dev Retention device to use. + * + * @retval 0 on success else negative errno code. + */ +int retention_clear(const struct device *dev); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_RETENTION_ */ diff --git a/scripts/ci/check_compliance.py b/scripts/ci/check_compliance.py index bc65892c85a7..eb9262725a24 100755 --- a/scripts/ci/check_compliance.py +++ b/scripts/ci/check_compliance.py @@ -603,7 +603,13 @@ def check_no_undef_outside_kconfig(self, kconf): # visible to compliance. "BOOT_UPGRADE_ONLY", # Used in example adjusting MCUboot config, but # symbol is defined in MCUboot itself. - "BOOT_SERIAL_IMG_GRP_HASH", # Used in documentation + "BOOT_SERIAL_BOOT_MODE", # Used in (sysbuild-based) test/ + # documentation + "BOOT_SERIAL_CDC_ACM", # Used in (sysbuild-based) test + "BOOT_SERIAL_ENTRANCE_GPIO", # Used in (sysbuild-based) test + "BOOT_SERIAL_IMG_GRP_HASH", # Used in documentation + "BOOT_VALIDATE_SLOT0", # Used in (sysbuild-based) test + "BOOT_WATCHDOG_FEED", # Used in (sysbuild-based) test "BTTESTER_LOG_LEVEL", # Used in tests/bluetooth/tester "BTTESTER_LOG_LEVEL_DBG", # Used in tests/bluetooth/tester "CDC_ACM_PORT_NAME_", @@ -634,6 +640,10 @@ def check_no_undef_outside_kconfig(self, kconf): "MCUBOOT_LOG_LEVEL_INF", "MCUBOOT_DOWNGRADE_PREVENTION", # but symbols are defined in MCUboot # itself. + "MCUBOOT_ACTION_HOOKS", # Used in (sysbuild-based) test + "MCUBOOT_CLEANUP_ARM_CORE", # Used in (sysbuild-based) test + "MCUBOOT_SERIAL", # Used in (sysbuild-based) test/ + # documentation "MISSING", "MODULES", "MYFEATURE", diff --git a/soc/arm/nordic_nrf/Kconfig b/soc/arm/nordic_nrf/Kconfig index cc0a5e296bb9..2c386f99a531 100644 --- a/soc/arm/nordic_nrf/Kconfig +++ b/soc/arm/nordic_nrf/Kconfig @@ -136,4 +136,17 @@ config NRF_TRACE_PORT Unit) for tracing using a hardware probe. If disabled, the trace pins will be used as GPIO. +config NRF_STORE_REBOOT_TYPE_GPREGRET + bool "Set GPREGRET to reboot type (DEPRECATED)" + depends on SOC_SERIES_NRF51X || SOC_SERIES_NRF52X + depends on REBOOT + depends on !RETENTION_BOOT_MODE + select DEPRECATED + help + If this option is enabled, then the parameter supplied to the + sys_reboot() function will be set in the GPREGRET register. + + This has been replaced with the bootmode part of the retention + subsystem, which should be used instead. + endif # SOC_FAMILY_NRF diff --git a/soc/arm/nordic_nrf/nrf51/soc.c b/soc/arm/nordic_nrf/nrf51/soc.c index be0c5a938ee6..a3cfe2ededf9 100644 --- a/soc/arm/nordic_nrf/nrf51/soc.c +++ b/soc/arm/nordic_nrf/nrf51/soc.c @@ -23,13 +23,18 @@ #define LOG_LEVEL CONFIG_SOC_LOG_LEVEL LOG_MODULE_REGISTER(soc); +#ifdef CONFIG_NRF_STORE_REBOOT_TYPE_GPREGRET /* Overrides the weak ARM implementation: - Set general purpose retention register and reboot */ + * Set general purpose retention register and reboot + * This is deprecated and has been replaced with the boot mode retention + * subsystem + */ void sys_arch_reboot(int type) { nrf_power_gpregret_set(NRF_POWER, (uint8_t)type); NVIC_SystemReset(); } +#endif static int nordicsemi_nrf51_init(void) { diff --git a/soc/arm/nordic_nrf/nrf52/soc.c b/soc/arm/nordic_nrf/nrf52/soc.c index 8f89e81eeb7b..2c8bd51c657f 100644 --- a/soc/arm/nordic_nrf/nrf52/soc.c +++ b/soc/arm/nordic_nrf/nrf52/soc.c @@ -23,13 +23,18 @@ #define LOG_LEVEL CONFIG_SOC_LOG_LEVEL LOG_MODULE_REGISTER(soc); +#ifdef CONFIG_NRF_STORE_REBOOT_TYPE_GPREGRET /* Overrides the weak ARM implementation: - Set general purpose retention register and reboot */ + * Set general purpose retention register and reboot + * This is deprecated and has been replaced with the boot mode retention + * subsystem + */ void sys_arch_reboot(int type) { nrf_power_gpregret_set(NRF_POWER, (uint8_t)type); NVIC_SystemReset(); } +#endif static int nordicsemi_nrf52_init(void) { diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 607509dc0e1b..fe814ee31f18 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -31,6 +31,7 @@ add_subdirectory_ifdef(CONFIG_INPUT input) add_subdirectory_ifdef(CONFIG_JWT jwt) add_subdirectory_ifdef(CONFIG_LORAWAN lorawan) add_subdirectory_ifdef(CONFIG_NET_BUF net) +add_subdirectory_ifdef(CONFIG_RETENTION retention) add_subdirectory_ifdef(CONFIG_SETTINGS settings) add_subdirectory_ifdef(CONFIG_SHELL shell) add_subdirectory_ifdef(CONFIG_TIMING_FUNCTIONS timing) diff --git a/subsys/Kconfig b/subsys/Kconfig index 51e66c545e49..b65ef1ffa7aa 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -28,6 +28,7 @@ source "subsys/net/Kconfig" source "subsys/pm/Kconfig" source "subsys/portability/Kconfig" source "subsys/random/Kconfig" +source "subsys/retention/Kconfig" source "subsys/rtio/Kconfig" source "subsys/sd/Kconfig" source "subsys/settings/Kconfig" diff --git a/subsys/dfu/boot/mcuboot_shell.c b/subsys/dfu/boot/mcuboot_shell.c index a93f317d5336..c069edf9c31a 100644 --- a/subsys/dfu/boot/mcuboot_shell.c +++ b/subsys/dfu/boot/mcuboot_shell.c @@ -12,6 +12,13 @@ #include "mcuboot_priv.h" +#ifdef CONFIG_RETENTION_BOOT_MODE +#include +#ifdef CONFIG_REBOOT +#include +#endif +#endif + struct area_desc { const char *name; unsigned int id; @@ -129,6 +136,30 @@ static int cmd_mcuboot_request_upgrade(const struct shell *sh, size_t argc, return err; } +#ifdef CONFIG_RETENTION_BOOT_MODE +static int cmd_mcuboot_serial_recovery(const struct shell *sh, size_t argc, + char **argv) +{ + int rc; + + rc = bootmode_set(BOOT_MODE_TYPE_BOOTLOADER); + + if (rc) { + shell_error(sh, "Failed to set serial recovery mode: %d", rc); + + return rc; + } + +#ifdef CONFIG_REBOOT + sys_reboot(SYS_REBOOT_COLD); +#else + shell_error(sh, "mcuboot serial recovery mode set, please reboot your device"); +#endif + + return rc; +} +#endif + static int cmd_mcuboot_info_area(const struct shell *sh, const struct area_desc *area) { @@ -196,6 +227,9 @@ SHELL_STATIC_SUBCMD_SET_CREATE(mcuboot_cmds, SHELL_CMD_ARG(erase, NULL, "erase ", cmd_mcuboot_erase, 2, 0), SHELL_CMD_ARG(request_upgrade, NULL, "request_upgrade [permanent]", cmd_mcuboot_request_upgrade, 1, 1), +#ifdef CONFIG_RETENTION_BOOT_MODE + SHELL_CMD_ARG(serial_recovery, NULL, "serial_recovery", cmd_mcuboot_serial_recovery, 1, 0), +#endif SHELL_SUBCMD_SET_END /* Array terminated. */ ); diff --git a/subsys/retention/CMakeLists.txt b/subsys/retention/CMakeLists.txt new file mode 100644 index 000000000000..6cea5b490df8 --- /dev/null +++ b/subsys/retention/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() +zephyr_library_sources(retention.c) +zephyr_library_sources_ifdef(CONFIG_RETENTION_BOOT_MODE bootmode.c) diff --git a/subsys/retention/Kconfig b/subsys/retention/Kconfig new file mode 100644 index 000000000000..1081e236f02b --- /dev/null +++ b/subsys/retention/Kconfig @@ -0,0 +1,59 @@ +# Copyright (c) 2023, Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +menuconfig RETENTION + bool "Retention support" + depends on CRC + depends on RETAINED_MEM + depends on DT_HAS_ZEPHYR_RETENTION_ENABLED + help + Enables support for the retention system, which uses retained memory + drivers. + +if RETENTION + +config RETENTION_INIT_PRIORITY + int "Retention devices init priority" + default 86 + help + Retention device initialization priority (must be higher than init + priorities for retained memory drivers. + +config RETENTION_MUTEXES + bool "Retention mutex support" + default y + depends on MULTITHREADING + help + Use mutexes to prevent issues with concurrent retention device + access. Should only be disabled whereby retained memory access is + required in an ISR or for special use cases. + +config RETENTION_BUFFER_SIZE + int "Retention stack buffer sizes" + default 16 + range 1 4096 + help + Size of buffers (stack based) used when reading and writing data + from/to the retention device. + +menu "Retention modules" + +config RETENTION_BOOT_MODE + bool "Boot mode" + help + Adds a boot mode system that allows for changing execution flow + depending upon the value of a boot mode parameter. Can be used for + e.g. button-less bootloader serial recovery mode entering from the + application. + + In order to use this, a retention area with at least 1 usable user + byte must be created and set as the "zephyr,boot-mode" chosen node + via device tree. + +endmenu + +module = RETENTION +module-str = retention +source "subsys/logging/Kconfig.template.log_config" + +endif # RETENTION diff --git a/subsys/retention/bootmode.c b/subsys/retention/bootmode.c new file mode 100644 index 000000000000..7d661a22e50e --- /dev/null +++ b/subsys/retention/bootmode.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2023, Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(bootmode, CONFIG_RETENTION_LOG_LEVEL); + +static const struct device *boot_mode_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_boot_mode)); + +int bootmode_check(uint8_t boot_mode) +{ + int rc; + + rc = retention_is_valid(boot_mode_dev); + + if (rc == 1 || rc == -ENOTSUP) { + uint8_t stored_mode; + + rc = retention_read(boot_mode_dev, 0, &stored_mode, sizeof(stored_mode)); + + /* Only check if modes match if there was no error, otherwise return the error */ + if (rc == 0) { + if (stored_mode == boot_mode) { + rc = 1; + } + } + } + + return rc; +} + +int bootmode_set(uint8_t boot_mode) +{ + return retention_write(boot_mode_dev, 0, &boot_mode, sizeof(boot_mode)); +} + +int bootmode_clear(void) +{ + return retention_clear(boot_mode_dev); +} diff --git a/subsys/retention/retention.c b/subsys/retention/retention.c new file mode 100644 index 000000000000..db5b8d45b695 --- /dev/null +++ b/subsys/retention/retention.c @@ -0,0 +1,406 @@ +/* + * Copyright (c) 2023, Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT zephyr_retention + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(retention, CONFIG_RETENTION_LOG_LEVEL); + +#define DATA_VALID_VALUE 1 + +enum { + CHECKSUM_NONE = 0, + CHECKSUM_CRC8, + CHECKSUM_CRC16, + CHECKSUM_UNUSED, + CHECKSUM_CRC32, +}; + +struct retention_data { + bool header_written; +#ifdef CONFIG_RETENTION_MUTEXES + struct k_mutex lock; +#endif +}; + +struct retention_config { + const struct device *parent; + size_t offset; + size_t size; + size_t reserved_size; + uint8_t checksum_size; + uint8_t prefix_len; + uint8_t prefix[]; +}; + +static inline void retention_lock_take(const struct device *dev) +{ +#ifdef CONFIG_RETENTION_MUTEXES + struct retention_data *data = dev->data; + + k_mutex_lock(&data->lock, K_FOREVER); +#else + ARG_UNUSED(dev); +#endif +} + +static inline void retention_lock_release(const struct device *dev) +{ +#ifdef CONFIG_RETENTION_MUTEXES + struct retention_data *data = dev->data; + + k_mutex_unlock(&data->lock); +#else + ARG_UNUSED(dev); +#endif +} + +static int retention_checksum(const struct device *dev, uint32_t *output) +{ + const struct retention_config *config = dev->config; + int rc = -ENOSYS; + + if (config->checksum_size == CHECKSUM_CRC8 || + config->checksum_size == CHECKSUM_CRC16 || + config->checksum_size == CHECKSUM_CRC32) { + size_t pos = config->offset + config->prefix_len; + size_t end = config->offset + config->size - config->checksum_size; + uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE]; + + *output = 0; + + while (pos < end) { + uint8_t read_size = MIN((end - pos), sizeof(buffer)); + + rc = retained_mem_read(config->parent, pos, buffer, read_size); + + if (rc < 0) { + goto finish; + } + + if (config->checksum_size == CHECKSUM_CRC8) { + *output = (uint32_t)crc8(buffer, read_size, 0x12, + (uint8_t)*output, false); + } else if (config->checksum_size == CHECKSUM_CRC16) { + *output = (uint32_t)crc16_itu_t((uint16_t)*output, + buffer, read_size); + } else if (config->checksum_size == CHECKSUM_CRC32) { + *output = crc32_ieee_update(*output, buffer, read_size); + } + + pos += read_size; + } + } + +finish: + return rc; +} + +static int retention_init(const struct device *dev) +{ + const struct retention_config *config = dev->config; +#ifdef CONFIG_RETENTION_MUTEXES + struct retention_data *data = dev->data; +#endif + ssize_t area_size; + + if (!device_is_ready(config->parent)) { + LOG_ERR("Parent device is not ready"); + return -ENODEV; + } + + /* Ensure backend has a large enough storage area for the requirements of + * this retention area + */ + area_size = retained_mem_size(config->parent); + + if (area_size < 0) { + LOG_ERR("Parent initialisation failure: %d", area_size); + return area_size; + } + + if ((config->offset + config->size) > area_size) { + /* Backend storage is insufficient */ + LOG_ERR("Underlying area size is insufficient, requires: 0x%x, has: 0x%x", + (config->offset + config->size), area_size); + return -EINVAL; + } + +#ifdef CONFIG_RETENTION_MUTEXES + k_mutex_init(&data->lock); +#endif + + return 0; +} + +ssize_t retention_size(const struct device *dev) +{ + const struct retention_config *config = dev->config; + + return (config->size - config->reserved_size); +} + +int retention_is_valid(const struct device *dev) +{ + const struct retention_config *config = dev->config; + struct retention_data *data = dev->data; + int rc = 0; + uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE]; + off_t pos; + + retention_lock_take(dev); + + /* If neither the header or checksum are enabled, return a not supported error */ + if (config->prefix_len == 0 && config->checksum_size == 0) { + rc = -ENOTSUP; + goto finish; + } + + if (config->prefix_len != 0) { + /* Check magic header is present at the start of the section */ + pos = 0; + + while (pos < config->prefix_len) { + uint8_t read_size = MIN((config->prefix_len - pos), sizeof(buffer)); + + rc = retained_mem_read(config->parent, (config->offset + pos), buffer, + read_size); + + if (rc < 0) { + goto finish; + } + + if (memcmp(&config->prefix[pos], buffer, read_size) != 0) { + /* If the magic header does not match, do not check the rest of + * the validity of the data, assume it is invalid + */ + data->header_written = false; + rc = 0; + goto finish; + } + + pos += read_size; + } + + /* Header already exists so no need to re-write it again */ + data->header_written = true; + } + + if (config->checksum_size != 0) { + /* Check the checksum validity, for this all the data must be read out */ + uint32_t checksum = 0; + uint32_t expected_checksum = 0; + ssize_t data_size = config->size - config->checksum_size; + + rc = retention_checksum(dev, &checksum); + + if (rc < 0) { + goto finish; + } + + if (config->checksum_size == CHECKSUM_CRC8) { + uint8_t read_checksum; + + rc = retained_mem_read(config->parent, (config->offset + data_size), + (void *)&read_checksum, sizeof(read_checksum)); + expected_checksum = (uint32_t)read_checksum; + } else if (config->checksum_size == CHECKSUM_CRC16) { + uint16_t read_checksum; + + rc = retained_mem_read(config->parent, (config->offset + data_size), + (void *)&read_checksum, sizeof(read_checksum)); + expected_checksum = (uint32_t)read_checksum; + } else if (config->checksum_size == CHECKSUM_CRC32) { + rc = retained_mem_read(config->parent, (config->offset + data_size), + (void *)&expected_checksum, + sizeof(expected_checksum)); + } + + if (rc < 0) { + goto finish; + } + + if (checksum != expected_checksum) { + goto finish; + } + } + + /* At this point, checks have passed (if enabled), mark data as being valid */ + rc = DATA_VALID_VALUE; + +finish: + retention_lock_release(dev); + + return rc; +} + +int retention_read(const struct device *dev, off_t offset, uint8_t *buffer, size_t size) +{ + const struct retention_config *config = dev->config; + int rc; + + if (offset < 0 || ((size_t)offset + size) > (config->size - config->reserved_size)) { + /* Disallow reading past the virtual data size or before it */ + return -EINVAL; + } + + retention_lock_take(dev); + + rc = retained_mem_read(config->parent, (config->offset + config->prefix_len + + (size_t)offset), buffer, size); + + retention_lock_release(dev); + + return rc; +} + +int retention_write(const struct device *dev, off_t offset, const uint8_t *buffer, size_t size) +{ + const struct retention_config *config = dev->config; + struct retention_data *data = dev->data; + int rc; + + retention_lock_take(dev); + + if (offset < 0 || ((size_t)offset + size) > (config->size - config->reserved_size)) { + /* Disallow writing past the virtual data size or before it */ + rc = -EINVAL; + goto finish; + } + + rc = retained_mem_write(config->parent, (config->offset + config->prefix_len + + (size_t)offset), buffer, size); + + if (rc < 0) { + goto finish; + } + + /* Write optional header and footer information, these are done last to ensure data + * validity before marking it as being valid + */ + if (config->prefix_len != 0 && data->header_written == false) { + rc = retained_mem_write(config->parent, config->offset, (void *)config->prefix, + config->prefix_len); + + if (rc < 0) { + goto finish; + } + + data->header_written = true; + } + + if (config->checksum_size != 0) { + /* Generating a checksum requires reading out all the data in the region */ + uint32_t checksum = 0; + + rc = retention_checksum(dev, &checksum); + + if (rc < 0) { + goto finish; + } + + if (config->checksum_size == CHECKSUM_CRC8) { + uint8_t output_checksum = (uint8_t)checksum; + + rc = retained_mem_write(config->parent, + (config->offset + config->size - config->checksum_size), + (void *)&output_checksum, sizeof(output_checksum)); + } else if (config->checksum_size == CHECKSUM_CRC16) { + uint16_t output_checksum = (uint16_t)checksum; + + rc = retained_mem_write(config->parent, + (config->offset + config->size - config->checksum_size), + (void *)&output_checksum, sizeof(output_checksum)); + } else if (config->checksum_size == CHECKSUM_CRC32) { + rc = retained_mem_write(config->parent, + (config->offset + config->size - config->checksum_size), + (void *)&checksum, sizeof(checksum)); + } + } + +finish: + retention_lock_release(dev); + + return rc; +} + +int retention_clear(const struct device *dev) +{ + const struct retention_config *config = dev->config; + struct retention_data *data = dev->data; + int rc = 0; + uint8_t buffer[CONFIG_RETENTION_BUFFER_SIZE]; + off_t pos = 0; + + memset(buffer, 0, sizeof(buffer)); + + retention_lock_take(dev); + + data->header_written = false; + + while (pos < config->size) { + rc = retained_mem_write(config->parent, (config->offset + pos), buffer, + MIN((config->size - pos), sizeof(buffer))); + + if (rc < 0) { + goto finish; + } + + pos += MIN((config->size - pos), sizeof(buffer)); + } + +finish: + retention_lock_release(dev); + + return rc; +} + +static const struct retention_api retention_api = { + .size = retention_size, + .is_valid = retention_is_valid, + .read = retention_read, + .write = retention_write, + .clear = retention_clear, +}; + +#define RETENTION_DEVICE(inst) \ + static struct retention_data \ + retention_data_##inst = { \ + .header_written = false, \ + }; \ + static const struct retention_config \ + retention_config_##inst = { \ + .parent = DEVICE_DT_GET(DT_PARENT(DT_INST(inst, DT_DRV_COMPAT))), \ + .checksum_size = DT_INST_PROP(inst, checksum), \ + .offset = DT_INST_REG_ADDR(inst), \ + .size = DT_INST_REG_SIZE(inst), \ + .reserved_size = (COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prefix), \ + (DT_INST_PROP_LEN(inst, prefix)), (0)) + \ + DT_INST_PROP(inst, checksum)), \ + .prefix_len = COND_CODE_1(DT_INST_NODE_HAS_PROP(inst, prefix), \ + (DT_INST_PROP_LEN(inst, prefix)), (0)), \ + .prefix = DT_INST_PROP_OR(inst, prefix, {0}), \ + }; \ + DEVICE_DT_INST_DEFINE(inst, \ + &retention_init, \ + NULL, \ + &retention_data_##inst, \ + &retention_config_##inst, \ + POST_KERNEL, \ + CONFIG_RETENTION_INIT_PRIORITY, \ + &retention_api); + +DT_INST_FOREACH_STATUS_OKAY(RETENTION_DEVICE) diff --git a/tests/boot/mcuboot_recovery_retention/CMakeLists.txt b/tests/boot/mcuboot_recovery_retention/CMakeLists.txt new file mode 100644 index 000000000000..df207f0da12b --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/CMakeLists.txt @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(mcuboot_recovery_retention) + +if(NOT (DEFINED SYSBUILD)) + message(FATAL_ERROR "This test must be built with sysbuild.") +endif() + +FILE(GLOB app_sources + src/*.c +) + +target_sources(app PRIVATE ${app_sources}) diff --git a/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840.overlay b/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 000000000000..2257c1a88885 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/ { + chosen { + zephyr,boot-mode = &boot_mode0; + }; +}; + +&gpregret1 { + status = "okay"; + + boot_mode0: boot_mode@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 0x1>; + }; +}; + +/delete-node/ &boot_partition; +/delete-node/ &slot0_partition; +/delete-node/ &slot1_partition; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x00000000 0x00020000>; + }; + slot0_partition: partition@20000 { + label = "image-0"; + reg = <0x00020000 0x00020000>; + }; + slot1_partition: partition@40000 { + label = "image-1"; + reg = <0x00040000 0x00020000>; + }; + }; +}; diff --git a/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840_mem.conf b/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840_mem.conf new file mode 100644 index 000000000000..8f39b13f7eca --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840_mem.conf @@ -0,0 +1,7 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# +CONFIG_RETAINED_MEM_NRF_GPREGRET=n +CONFIG_RETAINED_MEM_ZEPHYR_RAM=y diff --git a/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840_mem.overlay b/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840_mem.overlay new file mode 100644 index 000000000000..907b1c0cb509 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/boards/nrf52840dk_nrf52840_mem.overlay @@ -0,0 +1,61 @@ +/* SPDX-License-Identifier: Apache-2.0 */ + +/ { + sram@2003FC00 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2003FC00 DT_SIZE_K(1)>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + boot_mode0: boot_mode@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 0x100>; + prefix = [08 04]; + checksum = <1>; + }; + }; + }; + + + chosen { + zephyr,boot-mode = &boot_mode0; + }; +}; + +/delete-node/ &gpregret1; +/delete-node/ &gpregret2; +/delete-node/ &boot_partition; +/delete-node/ &slot0_partition; +/delete-node/ &slot1_partition; + +&sram0 { + reg = <0x20000000 DT_SIZE_K(255)>; +}; + +&flash0 { + partitions { + compatible = "fixed-partitions"; + #address-cells = <1>; + #size-cells = <1>; + + boot_partition: partition@0 { + label = "mcuboot"; + reg = <0x00000000 0x00020000>; + }; + slot0_partition: partition@20000 { + label = "image-0"; + reg = <0x00020000 0x00020000>; + }; + slot1_partition: partition@40000 { + label = "image-1"; + reg = <0x00040000 0x00020000>; + }; + }; +}; diff --git a/tests/boot/mcuboot_recovery_retention/prj.conf b/tests/boot/mcuboot_recovery_retention/prj.conf new file mode 100644 index 000000000000..c5c2cbc676c6 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/prj.conf @@ -0,0 +1,10 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# +CONFIG_RETAINED_MEM=y +CONFIG_RETENTION=y +CONFIG_RETENTION_BOOT_MODE=y +CONFIG_REBOOT=y +CONFIG_LOG=y diff --git a/tests/boot/mcuboot_recovery_retention/src/main.c b/tests/boot/mcuboot_recovery_retention/src/main.c new file mode 100644 index 000000000000..fd1d785b72f4 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/src/main.c @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +void main(void) +{ + printf("Waiting...\n"); + k_sleep(K_SECONDS(1)); + + int rc = bootmode_set(BOOT_MODE_TYPE_BOOTLOADER); + + if (rc == 0) { + sys_reboot(SYS_REBOOT_WARM); + } else { + printf("Error, failed to set boot mode: %d\n", rc); + } +} diff --git a/tests/boot/mcuboot_recovery_retention/sysbuild.cmake b/tests/boot/mcuboot_recovery_retention/sysbuild.cmake new file mode 100644 index 000000000000..2825102e11ee --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/sysbuild.cmake @@ -0,0 +1 @@ +set(mcuboot_ZEPHYR_EXTRA_MODULES "${CMAKE_CURRENT_LIST_DIR}/test_module" CACHE INTERNAL "test_module directory") diff --git a/tests/boot/mcuboot_recovery_retention/sysbuild.conf b/tests/boot/mcuboot_recovery_retention/sysbuild.conf new file mode 100644 index 000000000000..47f00ff3cff8 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/sysbuild.conf @@ -0,0 +1 @@ +SB_CONFIG_BOOTLOADER_MCUBOOT=y diff --git a/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/boards/nrf52840dk_nrf52840.conf b/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/boards/nrf52840dk_nrf52840.conf new file mode 100644 index 000000000000..0553e1bd7467 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/boards/nrf52840dk_nrf52840.conf @@ -0,0 +1,5 @@ +CONFIG_USE_SEGGER_RTT=n +CONFIG_NORDIC_QSPI_NOR=n +CONFIG_BOOT_VALIDATE_SLOT0=n +CONFIG_MCUBOOT_CLEANUP_ARM_CORE=n +CONFIG_BOOT_WATCHDOG_FEED=n diff --git a/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/boards/nrf52840dk_nrf52840.overlay b/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/boards/nrf52840dk_nrf52840.overlay new file mode 100644 index 000000000000..186bd5113406 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/boards/nrf52840dk_nrf52840.overlay @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: Apache-2.0 */ +#include "../../../boards/nrf52840dk_nrf52840.overlay" + +/ { + chosen { + zephyr,code-partition = &boot_partition; + }; +}; + +&zephyr_udc0 { + cdc_acm_uart0 { + compatible = "zephyr,cdc-acm-uart"; + }; +}; diff --git a/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/prj.conf b/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/prj.conf new file mode 100644 index 000000000000..98f4a2a30cdc --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/sysbuild/mcuboot/prj.conf @@ -0,0 +1,11 @@ +CONFIG_MCUBOOT_SERIAL=y +CONFIG_BOOT_SERIAL_CDC_ACM=y +CONFIG_BOOT_SERIAL_ENTRANCE_GPIO=n +CONFIG_BOOT_SERIAL_BOOT_MODE=y +CONFIG_RETAINED_MEM=y +CONFIG_RETENTION=y +CONFIG_RETENTION_BOOT_MODE=y +CONFIG_FLASH=y +CONFIG_FLASH_MAP=y +CONFIG_LOG=y +CONFIG_MCUBOOT_ACTION_HOOKS=y diff --git a/tests/boot/mcuboot_recovery_retention/test_module/CMakeLists.txt b/tests/boot/mcuboot_recovery_retention/test_module/CMakeLists.txt new file mode 100644 index 000000000000..06daf24155b1 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/test_module/CMakeLists.txt @@ -0,0 +1,4 @@ +if(CONFIG_MCUBOOT) + # Append source file which outputs the current mode of mcuboot + target_sources(app PRIVATE src/hook.c) +endif() diff --git a/tests/boot/mcuboot_recovery_retention/test_module/src/hook.c b/tests/boot/mcuboot_recovery_retention/test_module/src/hook.c new file mode 100644 index 000000000000..549346ea7d29 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/test_module/src/hook.c @@ -0,0 +1,14 @@ +/* + * Copyright (c) 2023 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +void mcuboot_status_change(mcuboot_status_type_t status) +{ + printf("mcuboot_status: %d\n", status); +} diff --git a/tests/boot/mcuboot_recovery_retention/test_module/zephyr/module.yml b/tests/boot/mcuboot_recovery_retention/test_module/zephyr/module.yml new file mode 100644 index 000000000000..eb317c3ce8a6 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/test_module/zephyr/module.yml @@ -0,0 +1,2 @@ +build: + cmake: . diff --git a/tests/boot/mcuboot_recovery_retention/testcase.yaml b/tests/boot/mcuboot_recovery_retention/testcase.yaml new file mode 100644 index 000000000000..69f571fdf716 --- /dev/null +++ b/tests/boot/mcuboot_recovery_retention/testcase.yaml @@ -0,0 +1,32 @@ +# +# Copyright (c) 2023 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: Apache-2.0 +# +common: + sysbuild: true + timeout: 10 + harness: console + harness_config: + type: multi_line + regex: + - "mcuboot_status: 0" + - "mcuboot_status: 1" + - "mcuboot_status: 2" + - "Waiting..." + - "mcuboot_status: 0" + - "mcuboot_status: 1" + - "mcuboot_status: 8" + - "Starting bootloader" + - "Secondary image: magic" + - "Boot source: none" +tests: + mcuboot.recovery.retention: + platform_allow: nrf52840dk_nrf52840 + tags: mcuboot sysbuild recovery + mcuboot.recovery.retention.mem: + platform_allow: nrf52840dk_nrf52840 + extra_args: | + 'OVERLAY_CONFIG="nrf52840dk_nrf52840_mem.conf"' + 'DTC_OVERLAY_FILE="boards/nrf52840dk_nrf52840_mem.overlay"' + tags: mcuboot sysbuild recovery