From f8597fec549525c24a3e8301348ba7467fc6f3c2 Mon Sep 17 00:00:00 2001 From: "Bhauraoji Gedam Anand (MS/ECL2)" Date: Mon, 19 Jun 2023 17:56:42 +0530 Subject: [PATCH 1/5] added documentations for nrf9160 hal. Updated the xtask for nrf9160 --- .cargo/config.toml | 1 + boards/bootloaders/nrf9160/.cargo/config.toml | 17 + boards/bootloaders/nrf9160/Cargo.toml | 34 + boards/bootloaders/nrf9160/README.md | 28 + boards/bootloaders/nrf9160/build.rs | 42 ++ boards/bootloaders/nrf9160/memory.x | 12 + .../nrf9160/no_region_asserts.x.in | 1 + .../nrf9160/nrf_region_asserts.x.in | 5 + boards/bootloaders/nrf9160/src/main.rs | 103 ++++ .../bootloaders/nrf9160/trustzone_memory.x.in | 31 + .../boot_fw_blinky_green/.cargo/config.toml | 17 + .../nrf9160/boot_fw_blinky_green/Cargo.toml | 35 ++ .../nrf9160/boot_fw_blinky_green/build.rs | 17 + .../nrf9160/boot_fw_blinky_green/memory.x | 35 ++ .../nrf9160/boot_fw_blinky_green/src/main.rs | 138 +++++ .../updt_fw_blinky_red/.cargo/config.toml | 17 + .../nrf9160/updt_fw_blinky_red/Cargo.toml | 31 + .../nrf9160/updt_fw_blinky_red/build.rs | 16 + .../nrf9160/updt_fw_blinky_red/memory.x | 35 ++ .../nrf9160/updt_fw_blinky_red/src/main.rs | 48 ++ boards/hal/Cargo.toml | 3 + boards/hal/src/lib.rs | 1 + boards/hal/src/nrf/mod.rs | 2 + boards/hal/src/nrf/nrf9160.rs | 581 ++++++++++++++++++ boards/update/Cargo.toml | 1 + rustBoot/Cargo.toml | 3 +- rustBoot/src/constants.rs | 11 + xtask/Cargo.toml | 1 + xtask/src/main.rs | 35 ++ 29 files changed, 1300 insertions(+), 1 deletion(-) create mode 100644 boards/bootloaders/nrf9160/.cargo/config.toml create mode 100644 boards/bootloaders/nrf9160/Cargo.toml create mode 100644 boards/bootloaders/nrf9160/README.md create mode 100644 boards/bootloaders/nrf9160/build.rs create mode 100644 boards/bootloaders/nrf9160/memory.x create mode 100644 boards/bootloaders/nrf9160/no_region_asserts.x.in create mode 100644 boards/bootloaders/nrf9160/nrf_region_asserts.x.in create mode 100644 boards/bootloaders/nrf9160/src/main.rs create mode 100644 boards/bootloaders/nrf9160/trustzone_memory.x.in create mode 100644 boards/firmware/nrf9160/boot_fw_blinky_green/.cargo/config.toml create mode 100644 boards/firmware/nrf9160/boot_fw_blinky_green/Cargo.toml create mode 100644 boards/firmware/nrf9160/boot_fw_blinky_green/build.rs create mode 100644 boards/firmware/nrf9160/boot_fw_blinky_green/memory.x create mode 100644 boards/firmware/nrf9160/boot_fw_blinky_green/src/main.rs create mode 100644 boards/firmware/nrf9160/updt_fw_blinky_red/.cargo/config.toml create mode 100644 boards/firmware/nrf9160/updt_fw_blinky_red/Cargo.toml create mode 100644 boards/firmware/nrf9160/updt_fw_blinky_red/build.rs create mode 100644 boards/firmware/nrf9160/updt_fw_blinky_red/memory.x create mode 100644 boards/firmware/nrf9160/updt_fw_blinky_red/src/main.rs create mode 100644 boards/hal/src/nrf/nrf9160.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 9e68c152..3ac78d2d 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,6 +1,7 @@ [alias] xtask = 'run -p xtask --' nrf52840 = 'run -p xtask --features nrf52840 -- nrf52840' +nrf9160 = 'run -p xtask --features nrf9160 -- nrf9160' stm32f411 = 'run -p xtask --features stm32f411 -- stm32f411' stm32f446 = 'run -p xtask --features stm32f446 -- stm32f446' stm32f469 = 'run -p xtask --features stm32f469 -- stm32f469' diff --git a/boards/bootloaders/nrf9160/.cargo/config.toml b/boards/bootloaders/nrf9160/.cargo/config.toml new file mode 100644 index 00000000..20c9eb49 --- /dev/null +++ b/boards/bootloaders/nrf9160/.cargo/config.toml @@ -0,0 +1,17 @@ +# ============================================================================= +# Build configuration options for Cortex-M +# ============================================================================= + +[build] +target = "thumbv8m.main-none-eabihf" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip nRF9160_xxAA" # runner specific to nrf52840. Replace this with probe-run option for your board. +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + # "-C", "link-arg=-Tdefmt.x", + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", +] \ No newline at end of file diff --git a/boards/bootloaders/nrf9160/Cargo.toml b/boards/bootloaders/nrf9160/Cargo.toml new file mode 100644 index 00000000..bd0cd632 --- /dev/null +++ b/boards/bootloaders/nrf9160/Cargo.toml @@ -0,0 +1,34 @@ +[package] +build = "build.rs" +edition = "2018" +name = "nrf9160" +version = "0.1.0" + +# makes `cargo check --all-targets` work +[[bin]] +bench = false +doctest = false +name = "nrf9160" +test = false + +[dependencies] +cortex-m-rt = "0.7" +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +rustBoot-hal = {path = "../../hal", features = ["nrf9160", "nrf"]} + +#trustzone-m-macros = { path = "../../../tools/trustzone-m-tools/macros" } +#trustzone-m-secure-rt = {path = "../../tools/trustzone-m-tools/secure-rt", features = ["nrf9160"]} + +nrf9160-pac = "0.12.2" + +rustBoot-update = {path = "../../update", features = ["nrf9160"]} +defmt = {version = "0.3.1", optional = true} +defmt-rtt = {version = "0.3.2", optional = true} + + +#[build-dependencies] +#trustzone-m-tools = {path = "../../../tools/trustzone-m-tools/tools"} + +[features] +default = ["defmt", "defmt-rtt"] + diff --git a/boards/bootloaders/nrf9160/README.md b/boards/bootloaders/nrf9160/README.md new file mode 100644 index 00000000..ccf18e76 --- /dev/null +++ b/boards/bootloaders/nrf9160/README.md @@ -0,0 +1,28 @@ +`rustBoot` support for [nrf9160](https://www.nordicsemi.com/Products/Development-hardware/nrf9160-dk) development board, we have one example. It has 4 leds. If you're using a different version of the board, you'll probably need to edit `firmware and hal implementations` to accomodate for differences. Just make sure you **dont change** the name of files/folders or the folder structure, as `cargo xtask` looks for these file/folder names. + +- In order to test this example you'll need a couple of things - `wolfcrypt, probe-run, python3, nrf-connect Programmer installed` +- If you've managed to install all of them, you can use below commands to build and sign all 3 packages (i.e. bootloader + bootfw + updatefw) onto the board. + - Command for build rustBoot + `cargo nrf9160 build rustBoot-only` + + - Command for build packages + `cargo nrf9160 build pkgs-for` + + - Command for sign packages + `cargo nrf9160 sign pkgs-for` + +- In order to flash all 3 binarise (i.e. bootloader + bootfw + updatefw) I've used `probe-rs-cli` and `probe-rs-cli`. + - To flash bootloader use this command + `probe-run < bootloader file name > --chip NRF9160_XXAA` + - To flash bootfw + updatefw use following command + 'probe-rs-cli download --format Bin --base-address {boot_part_addr} --chip nRF9160_xxAA nrf9160_bootfw_v_signed.bin' + +- In order to confirm that its working, I've configured the `bootfw to turn ON LED1 and blink LED2` for a few seconds, trigger an update and then reset. Upon reset, the bootloader verifies the update and swaps the contents of boot and update partitions. If everything checks out, it boots into the update, `turn ON LED3 and blink LED4` and finally sets the confirmation flag to indicate that the update was successful. + +Here's the [command line output](/boards/bootloaders/stm32h723/debug.md). + +## Blinky(s): + +**blinks green before image verification and swap, after trigger an update, blinks red after image verification and swap:** + +[![bootfw_and_updtfw](https://user-images.githubusercontent.com/92363511/173661166-bad18bd5-8e35-4429-8852-93ea29b46ed9.png)](https://user-images.githubusercontent.com/92363511/173660773-4f4d7cbd-6d43-4418-b5b5-099619054aff.mov) \ No newline at end of file diff --git a/boards/bootloaders/nrf9160/build.rs b/boards/bootloaders/nrf9160/build.rs new file mode 100644 index 00000000..65b0b7a4 --- /dev/null +++ b/boards/bootloaders/nrf9160/build.rs @@ -0,0 +1,42 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + + let mut linker_scripts = vec![( + &include_bytes!("trustzone_memory.x.in")[..], + "trustzone_memory.x", + )]; + + if cfg!(feature = "_nrf") { + linker_scripts.push(( + &include_bytes!("nrf_region_asserts.x.in")[..], + "region_asserts.x", + )); + } else { + linker_scripts.push(( + &include_bytes!("no_region_asserts.x.in")[..], + "region_asserts.x", + )); + } + + for (script_bytes, script_name) in linker_scripts { + let mut f = File::create(out.join(script_name)).unwrap(); + f.write_all(script_bytes).unwrap(); + + println!("cargo:rerun-if-changed={script_name}.in"); + } + + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=memory.x"); + +} diff --git a/boards/bootloaders/nrf9160/memory.x b/boards/bootloaders/nrf9160/memory.x new file mode 100644 index 00000000..df2c7a40 --- /dev/null +++ b/boards/bootloaders/nrf9160/memory.x @@ -0,0 +1,12 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 188K + NSC_FLASH : ORIGIN = 0x0002F000, LENGTH = 4K + NS_FLASH : ORIGIN = 0x00030000, LENGTH = 832K + + RAM : ORIGIN = 0x20000000, LENGTH = 128K + NS_RAM : ORIGIN = 0x20020000, LENGTH = 128K +} + +INCLUDE trustzone_memory.x + diff --git a/boards/bootloaders/nrf9160/no_region_asserts.x.in b/boards/bootloaders/nrf9160/no_region_asserts.x.in new file mode 100644 index 00000000..0890a9d0 --- /dev/null +++ b/boards/bootloaders/nrf9160/no_region_asserts.x.in @@ -0,0 +1 @@ +/* Purposefully left empty */ \ No newline at end of file diff --git a/boards/bootloaders/nrf9160/nrf_region_asserts.x.in b/boards/bootloaders/nrf9160/nrf_region_asserts.x.in new file mode 100644 index 00000000..aec3efcf --- /dev/null +++ b/boards/bootloaders/nrf9160/nrf_region_asserts.x.in @@ -0,0 +1,5 @@ +ASSERT(LENGTH(NSC_FLASH) <= 4096, "ERROR(trustzone): The NSC flash region cannot be bigger than 4096 bytes"); +ASSERT(LENGTH(NSC_FLASH) >= 32, "ERROR(trustzone): The NSC flash region cannot be smaller than 32 bytes"); +ASSERT((LENGTH(NSC_FLASH) & (LENGTH(NSC_FLASH) - 1)) == 0, "ERROR(trustzone): The NSC flash region must have a length that is a power of 2"); + +ASSERT(_s_flash_end == _nsc_flash_start, "ERROR(trustzone): The NSC flash region must come right after the S flash region"); diff --git a/boards/bootloaders/nrf9160/src/main.rs b/boards/bootloaders/nrf9160/src/main.rs new file mode 100644 index 00000000..cb273289 --- /dev/null +++ b/boards/bootloaders/nrf9160/src/main.rs @@ -0,0 +1,103 @@ +#![no_std] +#![no_main] +#![feature(abi_c_cmse_nonsecure_call)] +#![feature(cmse_nonsecure_entry)] +#![feature(type_alias_impl_trait)] + +#[cfg(feature = "defmt")] +use defmt_rtt as _; // global logger + +use rustBoot_hal::nrf::nrf9160::{FlashWriterEraser, initialize}; +use rustBoot_update::update::{update_flash::FlashUpdater, UpdateInterface}; + +use cortex_m_rt::entry; + + +#[entry] +fn main() -> ! { + let dp = nrf9160_pac::Peripherals::take().unwrap(); + + unsafe { + (*cortex_m::peripheral::SCB::PTR) + .shcsr + .write((1 << 19) | (1 << 18) | (1 << 17) | (1 << 16)) + }; + + initialize( + [ + (dp.SPIM0_S, dp.SPIS0_S, dp.TWIM0_S, dp.TWIS0_S, dp.UARTE0_S).into(), + (dp.SPIM1_S, dp.SPIS1_S, dp.TWIM1_S, dp.TWIS1_S, dp.UARTE1_S).into(), + (dp.SPIM2_S, dp.SPIS2_S, dp.TWIM2_S, dp.TWIS2_S, dp.UARTE2_S).into(), + (dp.SPIM3_S, dp.SPIS3_S, dp.TWIM3_S, dp.TWIS3_S, dp.UARTE3_S).into(), + (&dp.P0_S).into(), + (&dp.KMU_S, &dp.NVMC_S).into(), + (dp.CLOCK_S, dp.POWER_S).into(), + (dp.RTC0_S).into(), + (dp.RTC1_S).into(), + ], + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (0, 7), + (0, 8), + (0, 9), + (0, 10), + (0, 11), + (0, 12), + (0, 13), + (0, 14), + (0, 15), + (0, 16), + (0, 17), + (0, 18), + (0, 19), + (0, 20), + (0, 21), + (0, 22), + (0, 23), + (0, 24), + (0, 25), + (0, 26), + (0, 27), + // (0, 28), + // (0, 29), + (0, 30), + // (0, 31), + ], + [ + (0, 0), + (0, 1), + (0, 2), + (0, 3), + (0, 4), + (0, 5), + (0, 6), + (0, 7), + (0, 8), + (0, 9), + (0, 10), + (0, 11), + (0, 12), + (0, 13), + (0, 14), + (0, 15), + ], + ); + + // defmt::println!("Non secure memory initialized"); + + let updater = FlashUpdater::new(FlashWriterEraser::new(dp.NVMC_S, dp.NVMC_NS, true)); + updater.rustboot_start() +} + +#[panic_handler] // panicking behavior +fn panic(_: &core::panic::PanicInfo) -> ! { + loop { + cortex_m::asm::bkpt(); + } +} diff --git a/boards/bootloaders/nrf9160/trustzone_memory.x.in b/boards/bootloaders/nrf9160/trustzone_memory.x.in new file mode 100644 index 00000000..4376c6ad --- /dev/null +++ b/boards/bootloaders/nrf9160/trustzone_memory.x.in @@ -0,0 +1,31 @@ +_NS_VENEERS = ORIGIN(NS_FLASH); +_NSC_VENEERS = ORIGIN(NSC_FLASH); + +_s_flash_start = ORIGIN(FLASH); +_s_flash_end = _s_flash_start + LENGTH(FLASH); + +_nsc_flash_start = ORIGIN(NSC_FLASH); +_nsc_flash_end = _nsc_flash_start + LENGTH(NSC_FLASH); + +_ns_flash_start = ORIGIN(NS_FLASH); +_ns_flash_end = _ns_flash_start + LENGTH(NS_FLASH); + +_s_ram_start = ORIGIN(RAM); +_s_ram_end = _s_ram_start + LENGTH(RAM); + +_ns_ram_start = ORIGIN(NS_RAM); +_ns_ram_end = _ns_ram_start + LENGTH(NS_RAM); + +SECTIONS +{ + /* ### .ns_vectors */ + .nsc_vectors ORIGIN(NSC_FLASH) : + { + KEEP(*(.nsc_veneers.searcher)); + KEEP(*(.nsc_veneers)); + . = . + 12; /* Add an empty veneer at the end that should end up as 0's to indicate that we've reached the end */ + . = ALIGN(4); /* Pad .text to the alignment to workaround overlapping load section bug in old lld */ + } > NSC_FLASH = 0 +} + +INCLUDE region_asserts.x diff --git a/boards/firmware/nrf9160/boot_fw_blinky_green/.cargo/config.toml b/boards/firmware/nrf9160/boot_fw_blinky_green/.cargo/config.toml new file mode 100644 index 00000000..59a9efd0 --- /dev/null +++ b/boards/firmware/nrf9160/boot_fw_blinky_green/.cargo/config.toml @@ -0,0 +1,17 @@ +# ============================================================================= +# Build configuration options for Cortex-M +# ============================================================================= + +[build] +target = "thumbv8m.main-none-eabihf" + +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip nRF9160_xxAA --no-flash" # runner specific to nrf52840. Replace this with probe-run option for your board. +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", +] \ No newline at end of file diff --git a/boards/firmware/nrf9160/boot_fw_blinky_green/Cargo.toml b/boards/firmware/nrf9160/boot_fw_blinky_green/Cargo.toml new file mode 100644 index 00000000..85113ff6 --- /dev/null +++ b/boards/firmware/nrf9160/boot_fw_blinky_green/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "nrf9160_bootfw" +version = "0.1.0" +edition = "2018" +build = "build.rs" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# makes `cargo check --all-targets` work +[[bin]] +name = "nrf9160_bootfw" +bench = false +doctest = false +test = false + +[dependencies] +cortex-m = { version = "0.7.6", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7" +nrf9160-pac = "0.12.2" +#embassy-executor = { git = "https://github.com/embassy-rs/embassy.git", rev = "c53614f", features = ["nightly"] } +#embassy-nrf = { git = "https://github.com/embassy-rs/embassy.git", rev = "c53614f", features = ["nightly", "nrf9160-ns", "unstable-pac", "unstable-traits"] } +#embassy-sync = "0.1.0" +nrf9160-hal = { version = "0.16.0", default-features = false } +defmt = {version = "0.3.2", optional = true} +defmt-rtt = {version = "0.3.2", optional = true} +#trustzone-m-macros = { path = "../../../tools/trustzone-m-tools/macros" } +rustBoot-hal = {path = "../../../hal", default-features = false, features = ["nrf9160", "nrf"]} +rustBoot-update = {path = "../../../update", features = ["nrf9160"]} +panic-probe = { version = "0.3.0" } +spin = "0.9.4" + +#[build-dependencies] + + +[features] +default = ["defmt", "defmt-rtt"] \ No newline at end of file diff --git a/boards/firmware/nrf9160/boot_fw_blinky_green/build.rs b/boards/firmware/nrf9160/boot_fw_blinky_green/build.rs new file mode 100644 index 00000000..c057547b --- /dev/null +++ b/boards/firmware/nrf9160/boot_fw_blinky_green/build.rs @@ -0,0 +1,17 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/boards/firmware/nrf9160/boot_fw_blinky_green/memory.x b/boards/firmware/nrf9160/boot_fw_blinky_green/memory.x new file mode 100644 index 00000000..f1526d46 --- /dev/null +++ b/boards/firmware/nrf9160/boot_fw_blinky_green/memory.x @@ -0,0 +1,35 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* TODO Adjust these memory regions to match your device memory layout */ + /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ + /* We'll need prepend a 256-byte rustBoot header. So add an offset - 0x100 */ + FLASH (rx) : ORIGIN = 0x00040100, LENGTH = 768K + RAM (rwx) : ORIGIN = 0x20020000, LENGTH = 128K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* You may want to use this variable to locate the call stack and static + variables in different memory regions. Below is shown the default value */ +/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ + +/* You can use this symbol to customize the location of the .text section */ +/* If omitted the .text section will be placed right after the .vector_table + section */ +/* This is required only on microcontrollers that store some configuration right + after the vector table */ +/* _stext = ORIGIN(FLASH) + 0x400; */ + +/* Example of putting non-initialized variables into custom RAM locations. */ +/* This assumes you have defined a region RAM2 above, and in the Rust + sources added the attribute `#[link_section = ".ram2bss"]` to the data + you want to place there. */ +/* Note that the section will not be zero-initialized by the runtime! */ +/* SECTIONS { + .ram2bss (NOLOAD) : ALIGN(4) { + *(.ram2bss); + . = ALIGN(4); + } > RAM2 + } INSERT AFTER .bss; +*/ \ No newline at end of file diff --git a/boards/firmware/nrf9160/boot_fw_blinky_green/src/main.rs b/boards/firmware/nrf9160/boot_fw_blinky_green/src/main.rs new file mode 100644 index 00000000..f0eb6111 --- /dev/null +++ b/boards/firmware/nrf9160/boot_fw_blinky_green/src/main.rs @@ -0,0 +1,138 @@ +#![no_std] +#![no_main] +#![allow(non_snake_case)] +#![feature(cmse_nonsecure_entry)] +#![feature(abi_c_cmse_nonsecure_call)] +#![feature(once_cell)] +#![feature(type_alias_impl_trait)] + +use nrf9160_hal::{prelude::OutputPin, gpio, uarte, Uarte}; + +use cortex_m_rt::{exception, entry}; +use rustBoot_hal::{nrf::nrf9160::FlashWriterEraser, FlashInterface}; //nrf::nrf9160::GLOBAL_UART}; +use rustBoot_update::update::{update_flash::FlashUpdater, UpdateInterface}; +use core::{fmt::Write, cell::OnceCell, panic::PanicInfo}; +// use defmt::*; +use nrf9160_pac::{UARTE0_NS, P0_NS}; + +// SCB Application Interrupt and Reset Control Register Definitions +const SCB_AIRCR_VECTKEY_POS: u32 = 16; // SCB AIRCR: VECTKEY Position +const SCB_AIRCR_PRIGROUP_POS: u32 = 8; // SCB AIRCR: PRIGROUP Position +const SCB_AIRCR_PRIGROUP_MSK: u32 = 7u32 << SCB_AIRCR_PRIGROUP_POS; // SCB AIRCR: PRIGROUP Mask +const SCB_AIRCR_SYSRESETREQ_POS: u32 = 2; // SCB AIRCR: SYSRESETREQ Position +const SCB_AIRCR_SYSRESETREQ_MSK: u32 = 1u32 << SCB_AIRCR_SYSRESETREQ_POS; // SCB AIRCR: SYSRESETREQ Mask + +#[cfg(feature = "defmt")] +use defmt_rtt as _; // global logger +// use panic_probe as _; + +/// System Reset +/// +/// Initiates a system reset request to reset the MCU. +#[inline] +pub fn nvic_systemreset() -> ! { + let core_peripherals = cortex_m::Peripherals::take().unwrap(); + let scb = core_peripherals.SCB; + cortex_m::asm::dsb(); + unsafe { + scb.aircr.write( + (0x5FA << SCB_AIRCR_VECTKEY_POS) + | (scb.aircr.read() & SCB_AIRCR_PRIGROUP_MSK) + | SCB_AIRCR_SYSRESETREQ_MSK, + ); + } + cortex_m::asm::dsb(); + loop {} +} + +// A UART we can access from anywhere (with run-time lock checking). +// static GLOBAL_UART: OnceCell>>> = +// OnceCell::new(); + +// struct Printer; +// impl Write for Printer { +// fn write_str(&mut self, s: &str) -> core::fmt::Result { +// UART_OUT.lock(|uart| { +// uart.borrow_mut() +// .as_mut() +// .unwrap() +// .blocking_write(s.as_bytes()) +// .unwrap() +// }); +// Ok(()) +// } +// } + +#[entry] +fn main()-> ! { + let p = nrf9160_pac::Peripherals::take().unwrap();//unsafe { nrf9160_pac::Peripherals::steal() }; + + let p0: P0_NS = unsafe { core::mem::transmute(()) }; + let p0 = gpio::p0::Parts::new(p0); + let mut led2 = p0.p0_03.into_push_pull_output(gpio::Level::Low).degrade(); + let mut led1 = p0.p0_02.into_push_pull_output(gpio::Level::High).degrade(); + led1.set_high().unwrap(); + let mut count = 0; + + let uarte0: UARTE0_NS = p.UARTE0_NS; + + let pins = uarte::Pins { + txd: p0.p0_01.into_push_pull_output(gpio::Level::High).degrade(), + rxd: p0.p0_00.into_floating_input().degrade(), + cts: None, + rts: None, + }; + + let mut i = 0; + let flash_writer = FlashWriterEraser::new(p.NVMC_S, p.NVMC_NS, false);//{nvmc_s:p.NVMC_S, nvmc_ns: p.NVMC_NS, secure: false}; + + let updater = FlashUpdater::new(flash_writer); + while i < 5 { + led2.set_low(); + cortex_m::asm::delay(5000000); + led2.set_high(); + cortex_m::asm::delay(5000000); + i += 1; + } + + match updater.update_trigger() { + Ok(_v) => {} + Err(e) => { + panic!("couldnt trigger update: {:?}", e); + } + } + + nvic_systemreset(); +} + + + +#[exception] +unsafe fn HardFault(frame: &cortex_m_rt::ExceptionFrame) -> ! { + // defmt::println!("{:?}", frame); + let sau = &*cortex_m::peripheral::SAU::PTR; + defmt::println!("Secure ctrl: {:X}", sau.ctrl.read().0); + defmt::println!("Secure fault status register: {:X}", sau.sfsr.read().0); + defmt::println!("Secure fault address register: {:X}", sau.sfar.read().0); + + let scb = &*cortex_m::peripheral::SCB::PTR; + defmt::println!("Configurable Fault Status Register: {:X}", scb.cfsr.read()); + + cortex_m::asm::delay(u32::MAX); + + cortex_m::peripheral::SCB::sys_reset(); +} + + +#[exception] +unsafe fn DefaultHandler(irqn: i16) { + panic!("DefaultHandler IRQn = {}", irqn); +} + +// Called when our code panics. +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + cortex_m::interrupt::disable(); + defmt::println!("Panic occured"); + cortex_m::asm::udf(); +} \ No newline at end of file diff --git a/boards/firmware/nrf9160/updt_fw_blinky_red/.cargo/config.toml b/boards/firmware/nrf9160/updt_fw_blinky_red/.cargo/config.toml new file mode 100644 index 00000000..a5b5e45b --- /dev/null +++ b/boards/firmware/nrf9160/updt_fw_blinky_red/.cargo/config.toml @@ -0,0 +1,17 @@ +# ============================================================================= +# Build configuration options for Cortex-M +# ============================================================================= + +[build] +target = "thumbv8m.main-none-eabihf" + +# [target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# runner = "probe-run --chip nRF52840_xxAA" # runner specific to nrf52840. Replace this with probe-run option for your board. +rustflags = [ + "-C", "linker=flip-link", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + # This is needed if your flash or ram addresses are not aligned to 0x10000 in memory.x + # See https://github.com/rust-embedded/cortex-m-quickstart/pull/95 + "-C", "link-arg=--nmagic", +] \ No newline at end of file diff --git a/boards/firmware/nrf9160/updt_fw_blinky_red/Cargo.toml b/boards/firmware/nrf9160/updt_fw_blinky_red/Cargo.toml new file mode 100644 index 00000000..5f6ef7a1 --- /dev/null +++ b/boards/firmware/nrf9160/updt_fw_blinky_red/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "nrf9160_updtfw" +version = "0.1.0" +edition = "2018" +build = "build.rs" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +# makes `cargo check --all-targets` work +[[bin]] +name = "nrf9160_updtfw" +bench = false +doctest = false +test = false + +[dependencies] +cortex-m = "0.7" +cortex-m-rt = "0.7" +nrf9160-pac = "0.12.2" +nrf9160-hal = { version = "0.16.0", default-features = false } +defmt = {version = "0.3.2", optional = true} +defmt-rtt = {version = "0.3.2", optional = true} + +rustBoot-hal = {path = "../../../hal", default-features = false, features = ["nrf9160", "nrf"]} +rustBoot-update = {path = "../../../update", features = ["nrf9160"]} + +#[build-dependencies] + + +[features] +default = ["defmt", "defmt-rtt"] diff --git a/boards/firmware/nrf9160/updt_fw_blinky_red/build.rs b/boards/firmware/nrf9160/updt_fw_blinky_red/build.rs new file mode 100644 index 00000000..db4dd873 --- /dev/null +++ b/boards/firmware/nrf9160/updt_fw_blinky_red/build.rs @@ -0,0 +1,16 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put the linker script somewhere the linker can find it + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/boards/firmware/nrf9160/updt_fw_blinky_red/memory.x b/boards/firmware/nrf9160/updt_fw_blinky_red/memory.x new file mode 100644 index 00000000..20d304e0 --- /dev/null +++ b/boards/firmware/nrf9160/updt_fw_blinky_red/memory.x @@ -0,0 +1,35 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + /* TODO Adjust these memory regions to match your device memory layout */ + /* These values correspond to the LM3S6965, one of the few devices QEMU can emulate */ + /* We'll need prepend a 256-byte rustBoot header. So add an offset - 0x100 */ + FLASH (rx) : ORIGIN = 0x00040100, LENGTH = 768K + RAM (rwx) : ORIGIN = 0x20020000, LENGTH = 128K +} + +/* This is where the call stack will be allocated. */ +/* The stack is of the full descending type. */ +/* You may want to use this variable to locate the call stack and static + variables in different memory regions. Below is shown the default value */ +/* _stack_start = ORIGIN(RAM) + LENGTH(RAM); */ + +/* You can use this symbol to customize the location of the .text section */ +/* If omitted the .text section will be placed right after the .vector_table + section */ +/* This is required only on microcontrollers that store some configuration right + after the vector table */ +/* _stext = ORIGIN(FLASH) + 0x400; */ + +/* Example of putting non-initialized variables into custom RAM locations. */ +/* This assumes you have defined a region RAM2 above, and in the Rust + sources added the attribute `#[link_section = ".ram2bss"]` to the data + you want to place there. */ +/* Note that the section will not be zero-initialized by the runtime! */ +/* SECTIONS { + .ram2bss (NOLOAD) : ALIGN(4) { + *(.ram2bss); + . = ALIGN(4); + } > RAM2 + } INSERT AFTER .bss; +*/ \ No newline at end of file diff --git a/boards/firmware/nrf9160/updt_fw_blinky_red/src/main.rs b/boards/firmware/nrf9160/updt_fw_blinky_red/src/main.rs new file mode 100644 index 00000000..38fe1ab1 --- /dev/null +++ b/boards/firmware/nrf9160/updt_fw_blinky_red/src/main.rs @@ -0,0 +1,48 @@ +#![no_std] +#![no_main] +#![allow(non_snake_case)] +#![feature(cmse_nonsecure_entry)] +#![feature(abi_c_cmse_nonsecure_call)] + +use nrf9160_hal::{gpio, Uarte, uarte, prelude::OutputPin}; +use nrf9160_pac::{P0_NS, UARTE0_NS}; +use cortex_m_rt::entry; + +use rustBoot_hal::nrf::nrf9160::FlashWriterEraser; +use rustBoot_update::update::{update_flash::FlashUpdater, UpdateInterface}; + +#[cfg(feature = "defmt")] +use defmt_rtt as _; // global logger + +#[entry] +fn main()->! { + + let p = nrf9160_pac::Peripherals::take().unwrap(); + let p0: P0_NS = unsafe { core::mem::transmute(()) }; + let p0 = gpio::p0::Parts::new(p0); + let mut led3 = p0.p0_04.into_push_pull_output(gpio::Level::Low).degrade(); + let mut led4 = p0.p0_05.into_push_pull_output(gpio::Level::High).degrade(); + led3.set_high().unwrap(); + + let flash_writer = FlashWriterEraser::new(p.NVMC_S, p.NVMC_NS, false); + let updater = FlashUpdater::new(flash_writer); + match updater.update_success() { + Ok(_v) => {} + Err(e) => panic!("failed to confirm update: {}", e), + }; + + loop{ + led4.set_low().unwrap(); + cortex_m::asm::delay(5000000); + led4.set_high().unwrap(); + cortex_m::asm::delay(5000000); + } + +} + +/// Called when our code panics. +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + cortex_m::asm::udf(); +} + diff --git a/boards/hal/Cargo.toml b/boards/hal/Cargo.toml index c7d22ec6..cc9f5ba3 100644 --- a/boards/hal/Cargo.toml +++ b/boards/hal/Cargo.toml @@ -35,6 +35,8 @@ rustBoot = {path = "../../rustBoot", default-features = true, optional = true} tock-registers = {version = "0.8.x", default-features = false, features = ["register_types"], optional = true} # platform specific dependencies for nrf52840 nrf52840-hal = {version = "0.16.0", optional = true} +# platform specific dependencies for nrf9160 +nrf9160-pac = {version = "0.12.2", optional = true} # platform specific dependencies for stm32h7 series stm32h7xx-hal = {version = "0.12.2", features = ["stm32h735", "rt"], optional = true} # platform specific dependencies for stm32f7 series @@ -56,6 +58,7 @@ log = [] # board-specific features nrf = [] nrf52840 = ["nrf", "nrf52840-hal"] +nrf9160 = ["nrf", "nrf9160-pac"] rpi = [] rpi4 = ["rpi", "tock-registers", "cortex-a", "rustBoot"] nxp = [] diff --git a/boards/hal/src/lib.rs b/boards/hal/src/lib.rs index 4ed48dd0..e7b44b7f 100644 --- a/boards/hal/src/lib.rs +++ b/boards/hal/src/lib.rs @@ -7,6 +7,7 @@ #![feature(asm_const)] #![allow(warnings)] #![feature(core_intrinsics)] +#![feature(abi_c_cmse_nonsecure_call)] #[cfg(feature = "nrf")] pub mod nrf; #[cfg(feature = "rpi")] diff --git a/boards/hal/src/nrf/mod.rs b/boards/hal/src/nrf/mod.rs index 74a0d5be..fe382a1a 100644 --- a/boards/hal/src/nrf/mod.rs +++ b/boards/hal/src/nrf/mod.rs @@ -1,2 +1,4 @@ #[cfg(feature = "nrf52840")] pub mod nrf52840; +#[cfg(feature = "nrf9160")] +pub mod nrf9160; diff --git a/boards/hal/src/nrf/nrf9160.rs b/boards/hal/src/nrf/nrf9160.rs new file mode 100644 index 00000000..66501a1c --- /dev/null +++ b/boards/hal/src/nrf/nrf9160.rs @@ -0,0 +1,581 @@ +//! NVMC (i.e. flash) driver for the nrf52840 board, written in pure-rust. + +use core::{ + ops::{Add, Sub}, + usize, cell::{OnceCell}, +}; + +use nrf9160_pac as pac; + +use crate::FlashInterface; +use pac::{Peripherals, NVMC_S, NVMC_NS}; +use nrf9160_constants::*; +pub use pac::SPU_S as SPU; +pub const FLASH_REGION_SIZE: u32 = 32 * 1024; +pub const RAM_REGION_SIZE: u32 = 8 * 1024; +use cortex_m; + +// #[cfg(feature = "defmt")] +// use defmt_rtt as _; // global logger + +#[rustfmt::skip] +mod nrf9160_constants { + pub const FLASH_PAGE_SIZE : u32 = 4096; + pub const STACK_LOW : u32 = 0x20_000_000; + pub const STACK_UP : u32 = 0x20_040_000; + pub const RB_HDR_SIZE : u32 = 0x100; + pub const BASE_ADDR : u32 = 0x40000; + pub const VTR_TABLE_SIZE : u32 = 0x100; + pub const FW_RESET_VTR : u32 = BASE_ADDR + RB_HDR_SIZE + VTR_TABLE_SIZE + 1; +} +// include!(concat!(env!("OUT_DIR"), "/trustzone_bindings.rs")); +// extern crate trustzone_m_nonsecure_rt; + +pub struct FlashWriterEraser { + pub nvmc_s: NVMC_S, + pub nvmc_ns: NVMC_NS, + pub secure: bool +} + +impl FlashWriterEraser +{ + pub fn new(nvmc_s: NVMC_S, nvmc_ns:NVMC_NS, secure: bool) -> Self { + FlashWriterEraser { + nvmc_s: nvmc_s, + nvmc_ns: nvmc_ns, + secure: secure + } + } +} + +impl FlashInterface for FlashWriterEraser { + /// Write data at the specified address + /// + /// Arguments: + /// - address: It holds the address of flash where data has to be written + /// - data: u8 pointer holding the holding data. + /// - len : number of bytes + /// + /// Return: + /// - NONE + fn hal_flash_write(&self, address: usize, data: *const u8, len: usize) { + let address = address as u32; + let len = len as u32; + let mut idx = 0u32; + let mut src = data as *mut u32; + let mut dst = address as *mut u32; + // defmt::println!("writting {:?} at address {:?} len {:?}", data.clone() ,address.clone(), len.clone() ); + + while idx < len { + let data_ptr = (data as *const u32) as u32; + // defmt::println!("hal flash write 1"); + if self.secure { + // Check if the following holds true and do a full word write i.e. 4-byte write + // - if `len - idx` is greater than 3 (i.e. 4 bytes). + // - if the address is aligned on a word (i.e. 4-byte) boundary. + // - if the data_ptr is aligned on a word (i.e. 4-byte) boundary. + if ((len - idx > 3) + && ((((address + idx) & 0x03) == 0) && ((data_ptr + idx) & 0x03) == 0)) + { + // // defmt::println!("hal flash write 2"); + // Enable NVM writes + + self.nvmc_s.configns.write(|w| w.wen().wen()); + // // defmt::println!("Config nvmc enabled"); + while self.nvmc_s.readynext.read().readynext().is_busy() { + // // defmt::println!("waiting for busy bit"); + } + // // defmt::println!("NVMC enabled"); + unsafe { + *dst = *src; // 4-byte write + }; + // // defmt::println!("hal flash write 3"); + // Wait until writing is done + while self.nvmc_s.ready.read().ready().is_busy() {} + // // defmt::println!("writting to memory done "); + src = ((src as u32) + 4) as *mut u32; // increment pointer by 4 + dst = ((dst as u32) + 4) as *mut u32; // increment pointer by 4 + idx += 4; + } else { + // // defmt::println!("hal flash write 4"); + // else do a single byte write i.e. 1-byte write + let mut val = 0u32; + let val_bytes = ((&mut val) as *mut u32) as *mut u8; + let offset = (address + idx) - (((address + idx) >> 2) << 2); // offset from nearest word aligned address + dst = ((dst as u32) - offset) as *mut u32; // subtract offset from dst addr + unsafe { + val = *dst; // assign current val at dst to val + // store data byte at idx to `val`. `val_bytes` is a byte-pointer to val. + *val_bytes.add(offset as usize) = *data.add(idx as usize); + } + // // defmt::println!("hal flash write 5"); + // Enable NVM writes + self.nvmc_s.configns.write(|w| w.wen().wen()); + while self.nvmc_s.readynext.read().readynext().is_busy() {} + // // defmt::println!("hal flash write 6"); + unsafe { + *dst = val; // Technically this is a 1-byte write ONLY + // but only full 32-bit words can be written to Flash using the NVMC interface + }; + // Wait until writing is done + while self.nvmc_s.ready.read().ready().is_busy() {} + // defmt::println!("hal flash write 7"); + src = ((src as u32) + 1) as *mut u32; // increment pointer by 1 + dst = ((dst as u32) + 1) as *mut u32; // increment pointer by 1 + idx += 1; + // defmt::println!("doing single byte write"); + } + } + else { + // Check if the following holds true and do a full word write i.e. 4-byte write + // - if `len - idx` is greater than 3 (i.e. 4 bytes). + // - if the address is aligned on a word (i.e. 4-byte) boundary. + // - if the data_ptr is aligned on a word (i.e. 4-byte) boundary. + if ((len - idx > 3) + && ((((address + idx) & 0x03) == 0) && ((data_ptr + idx) & 0x03) == 0)) + { + // // defmt::println!("hal flash write 2"); + // Enable NVM writes + + self.nvmc_ns.configns.write(|w| w.wen().wen()); + // // defmt::println!("Config nvmc enabled"); + while self.nvmc_ns.readynext.read().readynext().is_busy() { + // // defmt::println!("waiting for busy bit"); + } + // // defmt::println!("NVMC enabled"); + unsafe { + *dst = *src; // 4-byte write + }; + // // defmt::println!("hal flash write 3"); + // Wait until writing is done + while self.nvmc_ns.ready.read().ready().is_busy() {} + // // defmt::println!("writting to memory done "); + src = ((src as u32) + 4) as *mut u32; // increment pointer by 4 + dst = ((dst as u32) + 4) as *mut u32; // increment pointer by 4 + idx += 4; + } else { + // // defmt::println!("hal flash write 4"); + // else do a single byte write i.e. 1-byte write + let mut val = 0u32; + let val_bytes = ((&mut val) as *mut u32) as *mut u8; + let offset = (address + idx) - (((address + idx) >> 2) << 2); // offset from nearest word aligned address + dst = ((dst as u32) - offset) as *mut u32; // subtract offset from dst addr + unsafe { + val = *dst; // assign current val at dst to val + // store data byte at idx to `val`. `val_bytes` is a byte-pointer to val. + *val_bytes.add(offset as usize) = *data.add(idx as usize); + } + // // defmt::println!("hal flash write 5"); + // Enable NVM writes + self.nvmc_ns.configns.write(|w| w.wen().wen()); + while self.nvmc_ns.readynext.read().readynext().is_busy() {} + // // defmt::println!("hal flash write 6"); + unsafe { + *dst = val; // Technically this is a 1-byte write ONLY + // but only full 32-bit words can be written to Flash using the NVMC interface + }; + // Wait until writing is done + while self.nvmc_ns.ready.read().ready().is_busy() {} + // defmt::println!("hal flash write 7"); + src = ((src as u32) + 1) as *mut u32; // increment pointer by 1 + dst = ((dst as u32) + 1) as *mut u32; // increment pointer by 1 + idx += 1; + // defmt::println!("doing single byte write"); + } + } + } + } + + /// Erase the sector of a given address + /// + /// Arguments: + /// - addr: Address where data has to be erased + /// - len : number of bytes to be erased + /// + /// Return: + /// - NONE + fn hal_flash_erase(&self, addr: usize, len: usize) { + let starting_page = (addr/0x1000) as u32; + let ending_page = ((addr + len)/0x1000) as u32; + + // let address = starting_page * 0x1000; + // defmt::info!("starting_page={}, ending_page={}, len={}", starting_page, ending_page, len); + for start_addr in (starting_page..ending_page) { + // Enable erasing + if self.secure{ + // Enable the erase functionality of the flash + self.nvmc_s.configns.modify(|_, w| w.wen().een()); + // Start the erase process by writing a u32 word containing all 1's to the first word of the page + // This is safe because the flash slice is page aligned, so a pointer to the first byte is valid as a pointer to a u32. + unsafe { + let first_word = (start_addr * 0x1000) as *mut u32; + first_word.write_volatile(0xFFFFFFFF); + } + // Wait for the erase to be done + while self.nvmc_s.ready.read().ready().is_busy() {} + + self.nvmc_s.configns.modify(|_, w| w.wen().ren()); + } + else { + // Enable the erase functionality of the flash + self.nvmc_ns.configns.modify(|_, w| w.wen().een()); + // Start the erase process by writing a u32 word containing all 1's to the first word of the page + // This is safe because the flash slice is page aligned, so a pointer to the first byte is valid as a pointer to a u32. + unsafe { + let first_word = (start_addr * 0x1000) as *mut u32; + first_word.write_volatile(0xFFFFFFFF); + } + // Wait for the erase to be done + while self.nvmc_ns.ready.read().ready().is_busy() {} + + self.nvmc_ns.configns.modify(|_, w| w.wen().ren()); + } + } + // Synchronize the changes + cortex_m::asm::dsb(); + cortex_m::asm::isb(); + + } + + fn hal_init() {} + fn hal_flash_lock(&self) {} + fn hal_flash_unlock(&self) {} +} + +pub fn preboot() {} + +struct RefinedUsize(u32); + +impl RefinedUsize { + /// This method is used to check the address bound of stack pointer + /// + /// Method arguments: + /// - i : starting address of stack + /// Returns: + /// - It returns u32 address of stack pointer + pub fn bounded_int(i: u32) -> Self { + assert!(i >= MIN && i <= MAX); + RefinedUsize(i) + } + /// This method is used to check the address of reset pointer + /// + /// Method arguments: + /// - i : starting address of reset + /// Returns: + /// - It returns u32 address of reset pointer + pub fn single_valued_int(i: u32) -> Self { + // defmt::println!("i {:?} == VAL {:?}", i, VAL); + assert!(i == VAL); + RefinedUsize(i) + } +} + +/// This method is used to boot the firmware from a particular address +/// +/// Method arguments: +/// - fw_base_address : address of the firmware +/// Returns: +/// - NONE +#[rustfmt::skip] +pub fn boot_from(fw_base_address: usize) -> ! { + unsafe { + let ns_vector_table_addr = fw_base_address as u32; + // Write the Non-Secure Main Stack Pointer before switching state. Its value is the first + // entry of the Non Secure Vector Table. + cortex_m::register::msp::write_ns(*(ns_vector_table_addr as *const u32)); + // Create a Non-Secure function pointer to the address of the second entry of the Non + // Secure Vector Table. + let ns_reset_vector: extern "C-cmse-nonsecure-call" fn() -> ! = + core::mem::transmute::(ns_vector_table_addr + 4); + ns_reset_vector() + } +} + +/// This method is used to initialize the trustzone memory region, Peripheral, Pins, +/// DPPI to Secure/Non-Secure/Non-SecureCallable accordingly. The method checks the provided inpupts range +/// with the one provided in memory.x file and accordingly initializes the trustzone. +/// The method uses SPM register interface to initialize the trustZone. +/// +/// Method arguments: +/// - Non-Secure Peripherals : Array of peripherals which are to initialized as Non-secure +/// - Non-Secure Pins : Array of pins which are to initialized as Non-secure +/// - Non-Secure DPPI : Array of Dppi which are to initialized as Non-secure +/// Returns: +/// - NONE +pub fn initialize( + nonsecure_peripherals: [NonSecurePeripheral; PERIPHERALS_LEN], + nonsecure_pins: [(usize, u32); PINS_LEN], + nonsecure_dppi: [(usize, u32); DPPI_LEN], +) { + extern "C" { + static _s_flash_start: u32; + static _s_flash_end: u32; + + static _nsc_flash_start: u32; + static _nsc_flash_end: u32; + + static _ns_flash_start: u32; + static _ns_flash_end: u32; + + static _s_ram_start: u32; + static _s_ram_end: u32; + + static _ns_ram_start: u32; + static _ns_ram_end: u32; + } + + let s_flash_start = unsafe { core::ptr::addr_of!(_s_flash_start) as u32 }; + let s_flash_end = unsafe { core::ptr::addr_of!(_s_flash_end) as u32 }; + let s_flash = s_flash_start..s_flash_end; + + let nsc_flash_start = unsafe { core::ptr::addr_of!(_nsc_flash_start) as u32 }; + let nsc_flash_end = unsafe { core::ptr::addr_of!(_nsc_flash_end) as u32 }; + let nsc_flash = nsc_flash_start..nsc_flash_end; + #[cfg(feature = "memory_region_assertions")] + assert_eq!(s_flash_start % FLASH_REGION_SIZE, 0, "The start of the flash region must be on a region boundary: val % {FLASH_REGION_SIZE:#X} must be 0"); + #[cfg(feature = "memory_region_assertions")] + assert_eq!(nsc_flash_end % FLASH_REGION_SIZE, 0, "The end of the nsc_flash region must be on a region boundary: val % {FLASH_REGION_SIZE:#X} must be 0"); + // defmt::println!("in init function"); + + let ns_flash_start = unsafe { core::ptr::addr_of!(_ns_flash_start) as u32 }; + let ns_flash_end = unsafe { core::ptr::addr_of!(_ns_flash_end) as u32 }; + // defmt::println!("ns_flash_start: {:?}", ns_flash_start); + // defmt::println!("ns_flash_end: {:?}", ns_flash_end); + let ns_flash = ns_flash_start..ns_flash_end; + #[cfg(feature = "memory_region_assertions")] + assert_eq!(ns_flash_start % FLASH_REGION_SIZE, 0, "The start of the ns flash region must be on a region boundary: val % {FLASH_REGION_SIZE:#X} must be 0"); + #[cfg(feature = "memory_region_assertions")] + assert_eq!(ns_flash_end % FLASH_REGION_SIZE, 0, "The end of the ns flash region must be on a region boundary: val % {FLASH_REGION_SIZE:#X} must be 0"); + + let s_ram_start = unsafe { core::ptr::addr_of!(_s_ram_start) as u32 }; + let s_ram_end = unsafe { core::ptr::addr_of!(_s_ram_end) as u32 }; + let s_ram = s_ram_start..s_ram_end; + + // #[cfg(feature = "memory_region_assertions")] + // assert_eq!(s_ram_start % RAM_REGION_SIZE, 0, "The start of the ram region must be on a region boundary: val % {RAM_REGION_SIZE:#X} must be 0"); + #[cfg(feature = "memory_region_assertions")] + assert_eq!(s_ram_end % RAM_REGION_SIZE, 0, "The end of the ram region must be on a region boundary: val % {RAM_REGION_SIZE:#X} must be 0"); + + let ns_ram_start = unsafe { core::ptr::addr_of!(_ns_ram_start) as u32 }; + let ns_ram_end = unsafe { core::ptr::addr_of!(_ns_ram_end) as u32 }; + let ns_ram = ns_ram_start..ns_ram_end; + #[cfg(feature = "memory_region_assertions")] + assert_eq!(ns_ram_start % RAM_REGION_SIZE, 0, "The start of the ns ram region must be on a region boundary: val % {RAM_REGION_SIZE:#X} must be 0"); + #[cfg(feature = "memory_region_assertions")] + assert_eq!(ns_ram_end % RAM_REGION_SIZE, 0, "The end of the ns ram region must be on a region boundary: val % {RAM_REGION_SIZE:#X} must be 0"); + + let spu = unsafe { core::mem::transmute::<_, SPU>(()) }; + // // defmt::println!("in init function 5"); + for (address, region) in spu + .flashregion + .iter() + .enumerate() + .map(|(index, region)| (index as u32 * FLASH_REGION_SIZE, region)) + { + if s_flash.contains(&address) || nsc_flash.contains(&address) { + region.perm.write(|w| { + w.execute() + .enable() + .read() + .enable() + .write() + .enable() + .secattr() + .secure() + }); + } + else if ns_flash.contains(&address) { + region.perm.write(|w| { + w.execute() + .enable() + .read() + .enable() + .write() + .enable() + .secattr() + .non_secure() + }); + } + } + + set_nsc_region(&spu, nsc_flash_start..nsc_flash_end); + + for (address, region) in spu + .ramregion + .iter() + .enumerate() + .map(|(index, region)| (0x20000000 + index as u32 * RAM_REGION_SIZE, region)) + { + if s_ram.contains(&address) { + region.perm.write(|w| { + w.execute() + .enable() + .read() + .enable() + .write() + .enable() + .secattr() + .secure() + }); + } + else if ns_ram.contains(&address) { + region.perm.write(|w| { + w.execute() + .enable() + .read() + .enable() + .write() + .enable() + .secattr() + .non_secure() + }); + } + } + // defmt::println!("ns ram initialized"); + // Set all given peripherals to nonsecure + for peripheral in nonsecure_peripherals { + spu.periphid[peripheral.id] + .perm + .write(|w| w.secattr().non_secure().dmasec().non_secure()); + } + + // Set all given pins to nonsecure + for (pin_port, pin) in nonsecure_pins { + spu.gpioport[pin_port] + .perm + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << pin)) }) + } + + // Set all given dppi channels to nonsecure + for (port, channel) in nonsecure_dppi { + spu.dppi[port] + .perm + .modify(|r, w| unsafe { w.bits(r.bits() & !(1 << channel)) }) + } + // We're using Nordic's SPU instead of the default SAU. To do that we must disable the SAU and + // set the ALLNS (All Non-secure) bit. + let sau = unsafe { core::mem::transmute::<_, cortex_m::peripheral::SAU>(()) }; + unsafe { + sau.ctrl.modify(|mut ctrl| { + ctrl.0 = 0b10; + ctrl + }); + + // Also set the stack pointer of nonsecure + cortex_m::register::msp::write_ns(ns_ram_end); + } +} + +/// This method is used to set the Non-SecureCallable region of memory. +/// +/// Method arguments: +/// - &SPU : Reference of Secure partition unit. +/// - region : Memory region which is to be set ans Non-SecureCallable +/// Returns: +/// - NONE +fn set_nsc_region(spu: &SPU, region: core::ops::Range) { + let sg_start = region.start; + let nsc_size = FLASH_REGION_SIZE - (sg_start % FLASH_REGION_SIZE); + let size_reg = (31 - nsc_size.leading_zeros()) - 4; + let region_reg = (sg_start as u32 / FLASH_REGION_SIZE) & 0x3F; // x << SPU_FLASHNSC_REGION_REGION_Pos & SPU_FLASHNSC_REGION_REGION_Msk + spu.flashnsc[0].size.write(|w| { + unsafe { + w.bits(size_reg); + } + w + }); + spu.flashnsc[0].region.write(|w| { + unsafe { + w.bits(region_reg); + } + w + }); +} + +pub struct NonSecurePeripheral { + id: usize, +} + +macro_rules! impl_ns_peripheral { + ($peripheral:ty, $id:expr) => { + impl From<$peripheral> for NonSecurePeripheral { + fn from(_: $peripheral) -> Self { + Self { id: $id } + } + } + }; +} + +#[cfg(feature = "nrf9160")] +mod nrf9160_peripheral_impl { + use super::*; + + impl_ns_peripheral!(nrf9160_pac::REGULATORS_S, 4); + impl_ns_peripheral!((nrf9160_pac::CLOCK_S, nrf9160_pac::POWER_S), 5); + impl_ns_peripheral!( + ( + nrf9160_pac::SPIM0_S, + nrf9160_pac::SPIS0_S, + nrf9160_pac::TWIM0_S, + nrf9160_pac::TWIS0_S, + nrf9160_pac::UARTE0_S + ), + 8 + ); + impl_ns_peripheral!( + ( + nrf9160_pac::SPIM1_S, + nrf9160_pac::SPIS1_S, + nrf9160_pac::TWIM1_S, + nrf9160_pac::TWIS1_S, + nrf9160_pac::UARTE1_S + ), + 9 + ); + impl_ns_peripheral!( + ( + nrf9160_pac::SPIM2_S, + nrf9160_pac::SPIS2_S, + nrf9160_pac::TWIM2_S, + nrf9160_pac::TWIS2_S, + nrf9160_pac::UARTE2_S + ), + 10 + ); + impl_ns_peripheral!( + ( + nrf9160_pac::SPIM3_S, + nrf9160_pac::SPIS3_S, + nrf9160_pac::TWIM3_S, + nrf9160_pac::TWIS3_S, + nrf9160_pac::UARTE3_S + ), + 11 + ); + impl_ns_peripheral!(nrf9160_pac::SAADC_S, 14); + impl_ns_peripheral!(nrf9160_pac::TIMER0_S, 15); + impl_ns_peripheral!(nrf9160_pac::TIMER1_S, 16); + impl_ns_peripheral!(nrf9160_pac::TIMER2_S, 17); + impl_ns_peripheral!(nrf9160_pac::RTC0_S, 20); + impl_ns_peripheral!(nrf9160_pac::RTC1_S, 21); + impl_ns_peripheral!(&nrf9160_pac::DPPIC_S, 23); + impl_ns_peripheral!(nrf9160_pac::WDT_S, 24); + impl_ns_peripheral!(nrf9160_pac::EGU0_S, 27); + impl_ns_peripheral!(nrf9160_pac::EGU1_S, 28); + impl_ns_peripheral!(nrf9160_pac::EGU2_S, 29); + impl_ns_peripheral!(nrf9160_pac::EGU3_S, 30); + impl_ns_peripheral!(nrf9160_pac::EGU4_S, 31); + impl_ns_peripheral!(nrf9160_pac::EGU5_S, 32); + impl_ns_peripheral!(nrf9160_pac::PWM0_S, 34); + impl_ns_peripheral!(nrf9160_pac::PWM1_S, 35); + impl_ns_peripheral!(nrf9160_pac::PWM2_S, 36); + impl_ns_peripheral!(nrf9160_pac::PDM_S, 38); + impl_ns_peripheral!(nrf9160_pac::I2S_S, 40); + impl_ns_peripheral!(nrf9160_pac::IPC_S, 42); + impl_ns_peripheral!(nrf9160_pac::FPU_S, 44); + impl_ns_peripheral!((&nrf9160_pac::KMU_S, + &nrf9160_pac::NVMC_S), 57); + impl_ns_peripheral!(nrf9160_pac::VMC_S, 58); + impl_ns_peripheral!(&nrf9160_pac::P0_S, 66); +} + diff --git a/boards/update/Cargo.toml b/boards/update/Cargo.toml index 1cef0403..07ebed1a 100644 --- a/boards/update/Cargo.toml +++ b/boards/update/Cargo.toml @@ -31,6 +31,7 @@ rustBoot-hal = {path = "../hal"} [features] default = [] nrf52840 = ["rustBoot/nrf52840"] +nrf9160 = ["rustBoot/nrf9160"] stm32f411 = ["rustBoot/stm32f411"] stm32f446 = ["rustBoot/stm32f446"] stm32f469 = ["rustBoot/stm32f469"] diff --git a/rustBoot/Cargo.toml b/rustBoot/Cargo.toml index 2d91f517..0557d437 100644 --- a/rustBoot/Cargo.toml +++ b/rustBoot/Cargo.toml @@ -53,7 +53,8 @@ sha256 = [] sha384 = [] # boards specific features mcu = [] -nrf52840 = ["mcu"] +nrf52840 = ["mcu"] +nrf9160 = ["mcu"] stm32f411 = ["mcu"] stm32f446 = ["mcu"] stm32f469 = ["mcu"] diff --git a/rustBoot/src/constants.rs b/rustBoot/src/constants.rs index 1eec3e62..748af573 100644 --- a/rustBoot/src/constants.rs +++ b/rustBoot/src/constants.rs @@ -13,6 +13,17 @@ pub const SWAP_PARTITION_ADDRESS: usize = 0x57000; #[cfg(feature = "nrf52840")] pub const UPDATE_PARTITION_ADDRESS: usize = 0x58000; +#[cfg(feature = "nrf9160")] +pub const SECTOR_SIZE: usize = 0x1000; +#[cfg(feature = "nrf9160")] +pub const PARTITION_SIZE: usize = 0x40000; +#[cfg(feature = "nrf9160")] +pub const BOOT_PARTITION_ADDRESS: usize = 0x40000; +#[cfg(feature = "nrf9160")] +pub const SWAP_PARTITION_ADDRESS: usize = 0xC0000; +#[cfg(feature = "nrf9160")] +pub const UPDATE_PARTITION_ADDRESS: usize = 0x80000; + #[cfg(feature = "stm32f411")] pub const SECTOR_SIZE: usize = 0x20000; #[cfg(feature = "stm32f411")] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 5de44f20..3917562a 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -12,6 +12,7 @@ xshell = "0.1.9" [features] nrf52840 = ["mcu", "rustBoot/nrf52840"] +nrf9160 = ["mcu", "rustBoot/nrf9160"] stm32f411 = ["mcu", "rustBoot/stm32f411"] stm32f446 = ["mcu", "rustBoot/stm32f446"] stm32f469 = ["mcu", "rustBoot/stm32f469"] diff --git a/xtask/src/main.rs b/xtask/src/main.rs index eadbbb7a..0cfc9f22 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -65,6 +65,9 @@ fn build_rustBoot_only(target: &&str) -> Result<(), anyhow::Error> { &"nrf52840" => { cmd!("cargo build --release").run()?; } + &"nrf9160" => { + cmd!("cargo build --release").run()?; + } &"stm32f411" => { cmd!("cargo build --release").run()?; } @@ -151,6 +154,16 @@ fn sign_packages(target: &&str, boot_ver: &&str, updt_ver: &&str) -> Result<(), cmd!("cargo run mcu-image ../boards/sign_images/signed_images/nrf52840_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der {updt_ver}").run()?; Ok(()) } + "nrf9160" => { + let _p = xshell::pushd(root_dir().join("boards/sign_images/signed_images"))?; + cmd!("rust-objcopy -I elf32-littlearm ../../target/thumbv8m.main-none-eabihf/release/nrf9160_bootfw -O binary nrf9160_bootfw.bin").run()?; + cmd!("rust-objcopy -I elf32-littlearm ../../target/thumbv8m.main-none-eabihf/release/nrf9160_updtfw -O binary nrf9160_updtfw.bin").run()?; + + let _p = xshell::pushd(root_dir().join("rbsigner"))?; + cmd!("cargo run mcu-image ../boards/sign_images/signed_images/nrf9160_bootfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der {boot_ver}").run()?; + cmd!("cargo run mcu-image ../boards/sign_images/signed_images/nrf9160_updtfw.bin nistp256 ../boards/sign_images/keygen/ecc256.der {updt_ver}").run()?; + Ok(()) + } "stm32f411" => { let _p = xshell::pushd(root_dir().join("boards/sign_images/signed_images"))?; cmd!("rust-objcopy -I elf32-littlearm ../../target/thumbv7em-none-eabihf/release/stm32f411_bootfw -O binary stm32f411_bootfw.bin").run()?; @@ -238,6 +251,15 @@ fn flash_signed_fwimages(target: &&str, boot_ver: &&str, updt_ver: &&str) -> Res cmd!("probe-rs-cli download --format Bin --base-address {updt_part_addr} --chip nRF52840_xxAA nrf52840_updtfw_v{updt_ver}_signed.bin").run()?; Ok(()) } + "nrf9160" => { + let _p = xshell::pushd(root_dir().join("boards/sign_images/signed_images"))?; + let boot_part_addr = format!("0x{:x}", BOOT_PARTITION_ADDRESS); + cmd!("probe-rs-cli download --format Bin --base-address {boot_part_addr} --chip nRF9160_xxAA nrf9160_bootfw_v{boot_ver}_signed.bin").run()?; + + let updt_part_addr = format!("0x{:x}", UPDATE_PARTITION_ADDRESS); + cmd!("probe-rs-cli download --format Bin --base-address {updt_part_addr} --chip nRF9160_xxAA nrf9160_updtfw_v{updt_ver}_signed.bin").run()?; + Ok(()) + } "stm32f411" => { let _p = xshell::pushd(root_dir().join("boards/sign_images/signed_images"))?; let boot_part_addr = format!("0x{:x}", BOOT_PARTITION_ADDRESS); @@ -312,6 +334,11 @@ fn flash_rustBoot(target: &&str) -> Result<(), anyhow::Error> { cmd!("cargo flash --chip nRF52840_xxAA --release").run()?; Ok(()) } + "nrf9160" => { + let _p = xshell::pushd(root_dir().join("boards/bootloaders").join(target))?; + cmd!("cargo flash --chip nRF9160_xxAA --release").run()?; + Ok(()) + } "stm32f411" => { let _p = xshell::pushd(root_dir().join("boards/bootloaders").join(target))?; cmd!("cargo flash --chip stm32f411vetx --release").run()?; @@ -362,6 +389,14 @@ fn full_image_flash(target: &&str, boot_ver: &&str, updt_ver: &&str) -> Result<( flash_rustBoot(target)?; Ok(()) } + "nrf9160" => { + build_rustBoot(target)?; + sign_packages(target, boot_ver, updt_ver)?; + cmd!("probe-rs-cli erase --chip nRF9160_xxAA").run()?; + flash_signed_fwimages(target, boot_ver, updt_ver)?; + flash_rustBoot(target)?; + Ok(()) + } "stm32f411" => { build_rustBoot(target)?; sign_packages(target, boot_ver, updt_ver)?; From 09f53e53c13161ef026e4298f2878000ed8f0916 Mon Sep 17 00:00:00 2001 From: "Bhauraoji Gedam Anand (MS/ECL2)" Date: Tue, 20 Jun 2023 14:46:27 +0530 Subject: [PATCH 2/5] Corrected the indentation --- xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 0cfc9f22..bb9b6906 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -65,7 +65,7 @@ fn build_rustBoot_only(target: &&str) -> Result<(), anyhow::Error> { &"nrf52840" => { cmd!("cargo build --release").run()?; } - &"nrf9160" => { + &"nrf9160" => { cmd!("cargo build --release").run()?; } &"stm32f411" => { From 02ed85d054c05313cf580ea8652d24c0a5538c3c Mon Sep 17 00:00:00 2001 From: "Bhauraoji Gedam Anand (MS/ECL2)" Date: Tue, 20 Jun 2023 15:23:05 +0530 Subject: [PATCH 3/5] Corrected the indentation --- xtask/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xtask/src/main.rs b/xtask/src/main.rs index bb9b6906..acc1242f 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -65,7 +65,7 @@ fn build_rustBoot_only(target: &&str) -> Result<(), anyhow::Error> { &"nrf52840" => { cmd!("cargo build --release").run()?; } - &"nrf9160" => { + &"nrf9160" => { cmd!("cargo build --release").run()?; } &"stm32f411" => { From a06b567f1a574b7f958849961eaf87f47561c6b7 Mon Sep 17 00:00:00 2001 From: "Bhauraoji Gedam Anand (MS/ECL2)" Date: Tue, 20 Jun 2023 16:27:14 +0530 Subject: [PATCH 4/5] Updated the ci.yaml for the running test for nrf9160 board --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 17c7a312..4901c4b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,7 @@ jobs: cargo +nightly test --package rustBoot --lib --features stm32f746 -- parser::tests --nocapture cargo +nightly test --package rustBoot --lib --features stm32f334 -- parser::tests --nocapture cargo +nightly test --package rustBoot --lib --features rp2040 -- parser::tests --nocapture + cargo +nightly test --package rustBoot --lib --features nrf9160 -- parser::tests --nocapture builds: runs-on: ${{ matrix.os }} @@ -72,6 +73,13 @@ jobs: use-cross: false command: run args: -p xtask --features nrf52840 -- nrf52840 build rustBoot-only + - name: nrf9160 + if: matrix.target == 'thumbv8m.main-none-eabihf' + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: run + args: -p xtask --features nrf9160 -- nrf9160 build rustBoot-only - name: stm32f411 if: matrix.target == 'thumbv7em-none-eabihf' uses: actions-rs/cargo@v1 From 8a3e764f59693a01ca480f4e3e225c665e0194a1 Mon Sep 17 00:00:00 2001 From: "Gedam Anand (MS/ECL2)" Date: Thu, 23 Nov 2023 15:48:02 +0530 Subject: [PATCH 5/5] Added the DCO developer certificate of origin .md file. Also added the intructions to sign off each commit Signed-off-by: Anand Gedam Signed-off-by: Gedam Anand (MS/ECL2) --- DCO.md | 85 +++++++++++++++++++++++++ README.md | 2 +- boards/bootloaders/nrf9160/Cargo.toml | 5 +- boards/bootloaders/nrf9160/src/main.rs | 1 - docs/images/dco.png | Bin 0 -> 64284 bytes 5 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 DCO.md create mode 100644 docs/images/dco.png diff --git a/DCO.md b/DCO.md new file mode 100644 index 00000000..f437b1ce --- /dev/null +++ b/DCO.md @@ -0,0 +1,85 @@ +# Developer Certificate of Origin (DCO) + +rustBoot enforces the Developer Certificate of Origin (DCO). It requires all commit messages to contain the `Signed-off-by` line with an email address that matches the commit author and the name on your GitHub account. + +The Developer Certificate of Origin (DCO) is a lightweight way for contributors to certify that they wrote or otherwise have the right to submit the code they are contributing to the project. Here is the full text of the DCO, reformatted for readability: + +```text +By making a contribution to this project, I certify that: + +The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or + +The contribution is based upon previous work that, to the best of my knowledge, is covered under an > appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or + +The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. + +I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. +``` + +Contributors sign-off that they adhere to these requirements by adding a `Signed-off-by` line to commit messages. + +## How to sign-off + +The project requires a sign-off message in the following format appear on each commit in the pull request: + +```text +feat: new feature + +Signed-off-by: John Smith +``` + +The text can either be manually added to your commit body, or you can add either `-s` or `--signoff` to your usual git commit commands. + +#### Creating your signoff + +Git has a `-s | --signoff` command-line option to append this automatically to your commit message: + +```bash +git commit --signoff --message 'This is my commit message' +``` + +```bash +git commit -s -m "This is my commit message" +``` + +This will use your default git configuration which is found in `.git/config` and usually, it is the `username systemaddress` of the machine which you are using. + +To change this, you can use the following commands (Note these only change the current repo settings, you will need to add `--global` for these commands to change the installation default). + +Your name: + +```bash +git config user.name "FIRST_NAME LAST_NAME" +``` + +Your email: + +```bash +git config user.email "MY_NAME@example.com" +``` + +#### How to amend a sign-off + +If you have authored a commit that is missing the signed-off-by line, you can amend your commits and push them to GitHub + +```bash +git commit --amend --signoff +``` + +If you've pushed your changes to GitHub already you'll need to force push your branch after this with `git push -f`. + +## DCO Failures + +The project uses a DCO bot for all GitHub pulls to verify that each commit is signed off. When you create your pull request, it will automatically be verified by this bot. An example of what to expect is below. + +![DCO Bot image](docs/images/dco.png) + +If your Pull Request fails the DCO check, it's necessary to fix the entire commit history in the PR. Although this is a situation we'd like to avoid the best practice is to squash the commit history to a single commit, append the DCO sign-off as described above or interactively in the rebase comment editing process, and force push. For example, if you have 2 commits in your history (Note the ~2): + +```bash +git rebase --interactive HEAD~2 +(interactive squash + DCO append) +git push origin --force +``` + +> Note, that in general rewriting history in this way is something that can cause issues with the review process and this should only be done to correct a DCO mistake. \ No newline at end of file diff --git a/README.md b/README.md index 35926a77..761cba60 100644 --- a/README.md +++ b/README.md @@ -51,4 +51,4 @@ rustBoot is licensed under * MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) ## Contributing: -Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions. \ No newline at end of file +Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the MIT license, shall be licensed as above, without any additional terms or conditions.We enforce [developer certificate of origin (DCO) commit signing](./DCO.md). \ No newline at end of file diff --git a/boards/bootloaders/nrf9160/Cargo.toml b/boards/bootloaders/nrf9160/Cargo.toml index bd0cd632..7da19a60 100644 --- a/boards/bootloaders/nrf9160/Cargo.toml +++ b/boards/bootloaders/nrf9160/Cargo.toml @@ -20,15 +20,12 @@ rustBoot-hal = {path = "../../hal", features = ["nrf9160", "nrf"]} #trustzone-m-secure-rt = {path = "../../tools/trustzone-m-tools/secure-rt", features = ["nrf9160"]} nrf9160-pac = "0.12.2" +spin = "0.5" rustBoot-update = {path = "../../update", features = ["nrf9160"]} defmt = {version = "0.3.1", optional = true} defmt-rtt = {version = "0.3.2", optional = true} - -#[build-dependencies] -#trustzone-m-tools = {path = "../../../tools/trustzone-m-tools/tools"} - [features] default = ["defmt", "defmt-rtt"] diff --git a/boards/bootloaders/nrf9160/src/main.rs b/boards/bootloaders/nrf9160/src/main.rs index cb273289..6e551f17 100644 --- a/boards/bootloaders/nrf9160/src/main.rs +++ b/boards/bootloaders/nrf9160/src/main.rs @@ -12,7 +12,6 @@ use rustBoot_update::update::{update_flash::FlashUpdater, UpdateInterface}; use cortex_m_rt::entry; - #[entry] fn main() -> ! { let dp = nrf9160_pac::Peripherals::take().unwrap(); diff --git a/docs/images/dco.png b/docs/images/dco.png new file mode 100644 index 0000000000000000000000000000000000000000..8cec0f83eee566708b9c9ec2ef011c464b18fb97 GIT binary patch literal 64284 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 literal 0 HcmV?d00001