diff options
-rw-r--r-- | Cargo.toml | 58 | ||||
-rw-r--r-- | async-flash/Cargo.toml | 10 | ||||
-rw-r--r-- | async-flash/src/lib.rs | 56 | ||||
-rw-r--r-- | async-flash/src/writer.rs | 107 | ||||
-rw-r--r-- | examples/flash/.cargo/config | 27 | ||||
-rw-r--r-- | examples/flash/Cargo.toml | 32 | ||||
-rw-r--r-- | examples/flash/build.rs | 31 | ||||
-rw-r--r-- | examples/flash/memory.x | 34 | ||||
-rw-r--r-- | examples/flash/src/main.rs | 69 | ||||
-rw-r--r-- | nrf-softdevice/Cargo.toml | 23 | ||||
-rw-r--r-- | nrf-softdevice/src/events.rs | 135 | ||||
-rw-r--r-- | nrf-softdevice/src/flash.rs | 117 | ||||
-rw-r--r-- | nrf-softdevice/src/interrupt.rs | 228 | ||||
-rw-r--r-- | nrf-softdevice/src/lib.rs | 14 | ||||
-rw-r--r-- | nrf-softdevice/src/util/depanic.rs | 10 | ||||
-rw-r--r-- | nrf-softdevice/src/util/drop_bomb.rs | 21 | ||||
-rw-r--r-- | nrf-softdevice/src/util/mod.rs | 10 | ||||
-rw-r--r-- | nrf-softdevice/src/util/signal.rs | 65 | ||||
-rw-r--r-- | nrf-softdevice/src/util/waker_store.rs | 23 |
19 files changed, 1070 insertions, 0 deletions
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..128f2f1 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,58 @@ +cargo-features = ["resolver"] + +[workspace] +resolver = "2" +members = [ + "nrf-softdevice", + "nrf-softdevice-mbr", + "nrf-softdevice-s112", + "nrf-softdevice-s113", + "nrf-softdevice-s122", + "nrf-softdevice-s132", + "nrf-softdevice-s140", + + "async-flash", + + "examples/flash", +] + +[patch.crates-io] +cortex-m = { git = "https://github.com/Dirbaio/cortex-m"} +panic-probe = { git = "https://github.com/knurling-rs/probe-run", branch="main" } +defmt-rtt = { git = "https://github.com/knurling-rs/defmt", branch="main" } +defmt = { git = "https://github.com/knurling-rs/defmt", branch="main" } +nrf52840-pac = { git = "https://github.com/Dirbaio/nrf52840-pac" } +static-executor = { git = "https://github.com/Dirbaio/static-executor" } +static-executor-cortex-m = { git = "https://github.com/Dirbaio/static-executor" } + +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true +incremental = false +opt-level = 3 +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = "fat" +opt-level = 3 +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/async-flash/Cargo.toml b/async-flash/Cargo.toml new file mode 100644 index 0000000..bd5f45e --- /dev/null +++ b/async-flash/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "async-flash" +version = "0.1.0" +authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +defmt = "0.1.0"
\ No newline at end of file diff --git a/async-flash/src/lib.rs b/async-flash/src/lib.rs new file mode 100644 index 0000000..beedaec --- /dev/null +++ b/async-flash/src/lib.rs @@ -0,0 +1,56 @@ +#![no_std] +#![feature(slice_fill)] +#![feature(generic_associated_types)] + +use core::future::Future; + +#[derive(defmt::Format, Copy, Clone, Debug, Eq, PartialEq)] +pub enum Error { + Failed, + AddressMisaligned, + BufferMisaligned, + + _NonExhaustive, +} + +pub trait Flash { + type ReadFuture<'a>: Future<Output = Result<(), Error>>; + type WriteFuture<'a>: Future<Output = Result<(), Error>>; + type ErasePageFuture<'a>: Future<Output = Result<(), Error>>; + + /// Reads data from the flash device. + /// + /// address must be a multiple of self.read_size(). + /// buf.len() must be a multiple of self.read_size(). + fn read<'a>(&'a mut self, address: usize, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; + + /// Writes data to the flash device. + /// + /// address must be a multiple of self.write_size(). + /// buf.len() must be a multiple of self.write_size(). + fn write<'a>(&'a mut self, address: usize, buf: &'a [u8]) -> Self::WriteFuture<'a>; + + /// Erases a single page from the flash device. + /// + /// address must be a multiple of self.erase_size(). + fn erase<'a>(&'a mut self, address: usize) -> Self::ErasePageFuture<'a>; + + /// Returns the total size, in bytes. + /// This is not guaranteed to be a power of 2. + fn size(&self) -> usize; + + /// Returns the read size in bytes. + /// This is guaranteed to be a power of 2. + fn read_size(&self) -> usize; + + /// Returns the write size in bytes. + /// This is guaranteed to be a power of 2. + fn write_size(&self) -> usize; + + /// Returns the erase size in bytes. + /// This is guaranteed to be a power of 2. + fn erase_size(&self) -> usize; +} + +mod writer; +pub use writer::{Writer, WriterError}; diff --git a/async-flash/src/writer.rs b/async-flash/src/writer.rs new file mode 100644 index 0000000..86a02b3 --- /dev/null +++ b/async-flash/src/writer.rs @@ -0,0 +1,107 @@ +use crate::{Error, Flash}; + +#[derive(Copy, Clone, Debug)] +pub enum WriterError { + Flash(Error), + OutOfBounds, +} + +impl From<Error> for WriterError { + fn from(e: Error) -> Self { + Self::Flash(e) + } +} + +#[repr(align(4))] +struct AlignedBuf([u8; 256]); + +pub struct Writer<'a, F: Flash> { + flash: &'a mut F, + address: usize, + length: usize, + + write_cur: usize, + erase_cur: usize, + + buf: AlignedBuf, + buf_have: usize, +} + +impl<'a, F: Flash> Writer<'a, F> { + pub fn new(flash: &'a mut F, address: usize, length: usize) -> Self { + assert_eq!(256 & (flash.write_size() - 1), 0); + assert_eq!(address & (flash.erase_size() - 1), 0); + assert_eq!(length & (flash.erase_size() - 1), 0); + + Self { + flash, + address, + length, + + write_cur: address, + erase_cur: address, + + buf: AlignedBuf([0; 256]), + buf_have: 0, + } + } + + async fn do_write(&mut self, len: usize) -> Result<(), WriterError> { + if self.write_cur + len > self.address + self.length { + return Err(WriterError::OutOfBounds); + } + + while self.write_cur + len > self.erase_cur { + self.flash.erase(self.erase_cur).await?; + self.erase_cur += self.flash.erase_size(); + } + + self.flash.write(self.write_cur, &self.buf.0[..len]).await?; + self.write_cur += len; + + Ok(()) + } + + pub async fn write(&mut self, mut data: &[u8]) -> Result<(), WriterError> { + // This code is HORRIBLE. + // + // Calls to flash write must have data aligned to 4 bytes. + // We can't guarantee `data` is, so we're forced to buffer it + // somewhere we can make aligned. + + while data.len() != 0 { + let left = self.buf.0.len() - self.buf_have; + let n = core::cmp::min(left, data.len()); + + self.buf.0[self.buf_have..][..n].copy_from_slice(&data[..n]); + self.buf_have += n; + data = &data[n..]; + + // When buffer is full, write it out + if self.buf_have == self.buf.0.len() { + self.do_write(self.buf.0.len()).await?; + self.buf_have = 0; + } + } + + // Whatever's left in the buffer stays there. + // It will be written in subsequent calls, or in flush. + + Ok(()) + } + + pub async fn flush(mut self) -> Result<(), WriterError> { + if self.buf_have != 0 { + let write_size = self.flash.write_size(); + + // round up amount + let have = (self.buf_have + write_size - 1) & (!(write_size - 1)); + + // fill the leftover bytes (if any) with 0xFF + self.buf.0[self.buf_have..have].fill(0xFF); + + self.do_write(have).await?; + } + Ok(()) + } +} diff --git a/examples/flash/.cargo/config b/examples/flash/.cargo/config new file mode 100644 index 0000000..3f319ae --- /dev/null +++ b/examples/flash/.cargo/config @@ -0,0 +1,27 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +runner = "probe-run --chip nRF52840_xxAA --defmt" + +rustflags = [ + # LLD (shipped with the Rust toolchain) is used as the default linker + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + + # if you run into problems with LLD switch to the GNU linker by commenting out + # this line + # "-C", "linker=arm-none-eabi-ld", + + # if you need to link to pre-compiled C libraries provided by a C toolchain + # use GCC as the linker by commenting out both lines above and then + # uncommenting the three lines below + # "-C", "linker=arm-none-eabi-gcc", + # "-C", "link-arg=-Wl,-Tlink.x", + # "-C", "link-arg=-nostartfiles", +] + +[build] +# Pick ONE of these compilation targets +# target = "thumbv6m-none-eabi" # Cortex-M0 and Cortex-M0+ +# target = "thumbv7m-none-eabi" # Cortex-M3 +# target = "thumbv7em-none-eabi" # Cortex-M4 and Cortex-M7 (no FPU) +target = "thumbv7em-none-eabihf" # Cortex-M4F and Cortex-M7F (with FPU) diff --git a/examples/flash/Cargo.toml b/examples/flash/Cargo.toml new file mode 100644 index 0000000..f5c7568 --- /dev/null +++ b/examples/flash/Cargo.toml @@ -0,0 +1,32 @@ +[package] +authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"] +edition = "2018" +readme = "README.md" +name = "softdevice-test" +version = "0.1.0" + +[features] +default = [ "defmt-default" ] +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +[dependencies] +async-flash = { version = "0.1.0", path = "../../async-flash" } +cortex-m = { version = "0.6.3", features = [ "inline-asm" ] } +cortex-m-rt = "0.6.12" +defmt = "0.1.0" +defmt-rtt = "0.1.0" +panic-probe = "0.1.0" +nrf52840-hal = { version = "0.11.0" } +nrf-softdevice = { version = "0.1.0", path = "../../nrf-softdevice" } +static-executor = { version = "0.1.0" } +static-executor-cortex-m = { version = "0.1.0" } + +[[bin]] +name = "softdevice-test" +test = false +bench = false diff --git a/examples/flash/build.rs b/examples/flash/build.rs new file mode 100644 index 0000000..d534cc3 --- /dev/null +++ b/examples/flash/build.rs @@ -0,0 +1,31 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + 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()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); +} diff --git a/examples/flash/memory.x b/examples/flash/memory.x new file mode 100644 index 0000000..f98ee4a --- /dev/null +++ b/examples/flash/memory.x @@ -0,0 +1,34 @@ +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 */ + FLASH : ORIGIN = 0x00027000, LENGTH = 256K + RAM : 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; +*/ diff --git a/examples/flash/src/main.rs b/examples/flash/src/main.rs new file mode 100644 index 0000000..6b33ded --- /dev/null +++ b/examples/flash/src/main.rs @@ -0,0 +1,69 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt_rtt as _; // global logger +use nrf52840_hal as _; +use panic_probe as _; +use static_executor_cortex_m as _; + +use async_flash::Flash; +use core::sync::atomic::{AtomicUsize, Ordering}; +use cortex_m_rt::entry; +use defmt::info; +use nrf_softdevice as sd; + +#[defmt::timestamp] +fn timestamp() -> u64 { + static COUNT: AtomicUsize = AtomicUsize::new(0); + // NOTE(no-CAS) `timestamps` runs with interrupts disabled + let n = COUNT.load(Ordering::Relaxed); + COUNT.store(n + 1, Ordering::Relaxed); + n as u64 +} + +macro_rules! depanic { + ($( $i:expr ),*) => { + { + defmt::error!($( $i ),*); + panic!(); + } + } +} + +#[static_executor::task] +async fn softdevice_task() { + sd::run().await; +} + +#[static_executor::task] +async fn flash_task() { + let mut f = unsafe { sd::Flash::new() }; + info!("starting erase"); + match f.erase(0x80000).await { + Ok(()) => info!("erased!"), + Err(e) => depanic!("erase failed: {:?}", e), + } + + info!("starting write"); + match f.write(0x80000, &[1, 2, 3, 4]).await { + Ok(()) => info!("write done!"), + Err(e) => depanic!("write failed: {:?}", e), + } +} + +#[entry] +fn main() -> ! { + info!("Hello World!"); + + info!("enabling softdevice"); + unsafe { sd::enable() } + info!("softdevice enabled"); + + unsafe { + softdevice_task.spawn().unwrap(); + flash_task.spawn().unwrap(); + + static_executor::run(); + } +} diff --git a/nrf-softdevice/Cargo.toml b/nrf-softdevice/Cargo.toml new file mode 100644 index 0000000..2fb0cf4 --- /dev/null +++ b/nrf-softdevice/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "nrf-softdevice" +version = "0.1.0" +authors = ["Dario Nieuwenhuis <dirbaio@dirbaio.net>"] +edition = "2018" + +[features] +defmt-default = [] +defmt-trace = [] +defmt-debug = [] +defmt-info = [] +defmt-warn = [] +defmt-error = [] + +[dependencies] +async-flash = { version = "0.1.0", path = "../async-flash" } +nrf-softdevice-s140 = { version = "0.1.1", path = "../nrf-softdevice-s140" } +cortex-m = "0.6.2" +cortex-m-rt = "0.6.12" +bare-metal = { version = "0.2.0", features = ["const-fn"] } +nrf52840-pac = { version = "0.9.0", features = ["rt"] } + +defmt = "0.1.0"
\ No newline at end of file diff --git a/nrf-softdevice/src/events.rs b/nrf-softdevice/src/events.rs new file mode 100644 index 0000000..7988d74 --- /dev/null +++ b/nrf-softdevice/src/events.rs @@ -0,0 +1,135 @@ +use core::marker::PhantomData; +use core::mem::MaybeUninit; +use defmt::info; +use nrf52840_pac::{interrupt, Interrupt}; +use nrf_softdevice_s140 as sd; + +use crate::util::Signal; + +unsafe extern "C" fn fault_handler(id: u32, pc: u32, info: u32) { + depanic!("fault_handler {:u32} {:u32} {:u32}", id, pc, info); +} + +/// safety: call at most once +pub unsafe fn enable() { + // TODO make this configurable via features or param + let clock_cfg = sd::nrf_clock_lf_cfg_t { + source: sd::NRF_CLOCK_LF_SRC_XTAL as u8, + rc_ctiv: 0, + rc_temp_ctiv: 0, + accuracy: 7, + }; + + let ret = sd::sd_softdevice_enable(&clock_cfg as _, Some(fault_handler)); + if ret != sd::NRF_SUCCESS { + depanic!("sd_softdevice_enable ret {:u32}", ret); + } + + crate::interrupt::unmask(Interrupt::SWI2_EGU2); +} + +static SWI2_SIGNAL: Signal<()> = Signal::new(); + +#[derive(defmt::Format)] +enum SocEvent { + Hfclkstarted, + PowerFailureWarning, + FlashOperationSuccess, + FlashOperationError, + RadioBlocked, + RadioCanceled, + RadioSignalCallbackinvalidReturn, + RadioSessionIdle, + RadioSessionClosed, + PowerUsbPowerReady, + PowerUsbDetected, + PowerUsbRemoved, +} + +impl SocEvent { + fn from_raw(raw: u32) -> Self { + match raw { + sd::NRF_SOC_EVTS_NRF_EVT_HFCLKSTARTED => SocEvent::Hfclkstarted, + sd::NRF_SOC_EVTS_NRF_EVT_POWER_FAILURE_WARNING => SocEvent::PowerFailureWarning, + sd::NRF_SOC_EVTS_NRF_EVT_FLASH_OPERATION_SUCCESS => SocEvent::FlashOperationSuccess, + sd::NRF_SOC_EVTS_NRF_EVT_FLASH_OPERATION_ERROR => SocEvent::FlashOperationError, + sd::NRF_SOC_EVTS_NRF_EVT_RADIO_BLOCKED => SocEvent::RadioBlocked, + sd::NRF_SOC_EVTS_NRF_EVT_RADIO_CANCELED => SocEvent::RadioCanceled, + sd::NRF_SOC_EVTS_NRF_EVT_RADIO_SIGNAL_CALLBACK_INVALID_RETURN => { + SocEvent::RadioSignalCallbackinvalidReturn + } + sd::NRF_SOC_EVTS_NRF_EVT_RADIO_SESSION_IDLE => SocEvent::RadioSessionIdle, + sd::NRF_SOC_EVTS_NRF_EVT_RADIO_SESSION_CLOSED => SocEvent::RadioSessionClosed, + sd::NRF_SOC_EVTS_NRF_EVT_POWER_USB_POWER_READY => SocEvent::PowerUsbPowerReady, + sd::NRF_SOC_EVTS_NRF_EVT_POWER_USB_DETECTED => SocEvent::PowerUsbDetected, + sd::NRF_SOC_EVTS_NRF_EVT_POWER_USB_REMOVED => SocEvent::PowerUsbRemoved, + x => depanic!("unknown soc evt {:u32}", x), + } + } +} + +#[derive(defmt::Format)] +enum BleEvent<'a> { + ToDo(PhantomData<&'a ()>), +} + +impl<'a> BleEvent<'a> { + fn from_raw(ble_evt: &'a sd::ble_evt_t, len: usize) -> Self { + Self::ToDo(PhantomData) + } +} + +fn on_soc_evt(evt: SocEvent) { + info!("soc evt {:?}", evt); + match evt { + SocEvent::FlashOperationError => crate::flash::on_flash_error(), + SocEvent::FlashOperationSuccess => crate::flash::on_flash_success(), + _ => {} + } +} + +fn on_ble_evt(evt: BleEvent<'_>) { + info!("got ble evt"); +} + +// TODO actually derive this from the headers + the ATT_MTU +const BLE_EVT_MAX_SIZE: u16 = 128; + +pub async fn run() { + loop { + SWI2_SIGNAL.wait().await; + + unsafe { + let mut evt: u32 = 0; + loop { + match sd::sd_evt_get(&mut evt as _) { + sd::NRF_SUCCESS => on_soc_evt(SocEvent::from_raw(evt)), + sd::NRF_ERROR_NOT_FOUND => break, + err => depanic!("sd_evt_get returned {:u32}", err), + } + } + + // Using u32 since the buffer has to be aligned to 4 + let mut evt: MaybeUninit<[u32; BLE_EVT_MAX_SIZE as usize / 4]> = MaybeUninit::uninit(); + + loop { + let mut len: u16 = BLE_EVT_MAX_SIZE; + match sd::sd_ble_evt_get(evt.as_mut_ptr() as *mut u8, &mut len as _) { + sd::NRF_SUCCESS => { + let evt_ref = &*(evt.as_ptr() as *const sd::ble_evt_t); + on_ble_evt(BleEvent::from_raw(evt_ref, len as usize)); + } + sd::NRF_ERROR_NO_MEM => depanic!("BUG: BLE_EVT_MAX_SIZE is too low"), + sd::NRF_ERROR_NOT_FOUND => break, + sd::BLE_ERROR_NOT_ENABLED => break, + err => depanic!("sd_ble_evt_get returned {:u32}", err), + } + } + } + } +} + +#[cortex_m_rt::interrupt] +unsafe fn SWI2_EGU2() { + SWI2_SIGNAL.signal(()); +} diff --git a/nrf-softdevice/src/flash.rs b/nrf-softdevice/src/flash.rs new file mode 100644 index 0000000..d161757 --- /dev/null +++ b/nrf-softdevice/src/flash.rs @@ -0,0 +1,117 @@ +use core::future::Future; +use defmt::{info, warn}; + +use nrf_softdevice_s140 as sd; + +use crate::util::{DropBomb, Signal}; + +pub struct Flash {} + +impl Flash { + pub const PAGE_SIZE: usize = 4096; + + /// safety: + /// - call this method at most once + /// - do not call before enabling softdevice + pub unsafe fn new() -> Self { + Self {} + } +} + +static SIGNAL: Signal<Result<(), async_flash::Error>> = Signal::new(); + +pub(crate) fn on_flash_success() { + SIGNAL.signal(Ok(())) +} + +pub(crate) fn on_flash_error() { + SIGNAL.signal(Err(async_flash::Error::Failed)) +} + +impl async_flash::Flash for Flash { + type ReadFuture<'a> = impl Future<Output = Result<(), async_flash::Error>> + 'a; + type WriteFuture<'a> = impl Future<Output = Result<(), async_flash::Error>> + 'a; + type ErasePageFuture<'a> = impl Future<Output = Result<(), async_flash::Error>> + 'a; + + fn read<'a>(&'a mut self, address: usize, data: &'a mut [u8]) -> Self::ReadFuture<'a> { + async move { + // Reading is simple since SoC flash is memory-mapped :) + // TODO check addr/len is in bounds. + + data.copy_from_slice(unsafe { + core::slice::from_raw_parts(address as *const u8, data.len()) + }); + + Ok(()) + } + } + + fn write<'a>(&'a mut self, address: usize, data: &'a [u8]) -> Self::WriteFuture<'a> { + async move { + let data_ptr = data.as_ptr(); + let data_len = data.len() as u32; + + if address % 4 != 0 { + return Err(async_flash::Error::AddressMisaligned); + } + if (data_ptr as u32) % 4 != 0 || data_len % 4 != 0 { + return Err(async_flash::Error::BufferMisaligned); + } + + // This is safe because we've checked ptr and len is aligned above + let words_ptr = data_ptr as *const u32; + let words_len = data_len / 4; + + let mut bomb = DropBomb::new(); + + let ret = unsafe { sd::sd_flash_write(address as _, words_ptr, words_len) }; + if ret != 0 { + warn!("sd_flash_write failed: {:u32}", ret); + bomb.defuse(); + return Err(async_flash::Error::Failed); + } + + let res = SIGNAL.wait().await; + bomb.defuse(); + res + } + } + + fn erase<'a>(&'a mut self, address: usize) -> Self::ErasePageFuture<'a> { + async move { + if address % Flash::PAGE_SIZE != 0 { + return Err(async_flash::Error::AddressMisaligned); + } + + let mut bomb = DropBomb::new(); + + let page_number = address / Flash::PAGE_SIZE; + let ret = unsafe { sd::sd_flash_page_erase(page_number as u32) }; + if ret != 0 { + warn!("sd_flash_page_erase failed: {:u32}", ret); + bomb.defuse(); + return Err(async_flash::Error::Failed); + } + + let res = SIGNAL.wait().await; + bomb.defuse(); + res + } + } + + fn size(&self) -> usize { + 256 * 4096 + } + + fn read_size(&self) -> usize { + 1 + } + + fn write_size(&self) -> usize { + 4 + } + + fn erase_size(&self) -> usize { + 4096 + } +} diff --git a/nrf-softdevice/src/interrupt.rs b/nrf-softdevice/src/interrupt.rs new file mode 100644 index 0000000..d026564 --- /dev/null +++ b/nrf-softdevice/src/interrupt.rs @@ -0,0 +1,228 @@ +pub use bare_metal::{CriticalSection, Mutex}; +use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; +use cortex_m::interrupt::InterruptNumber; +pub use nrf52840_pac::Interrupt; + +use nrf52840_pac::{NVIC, NVIC_PRIO_BITS}; + +const RESERVED_IRQS: [u32; 2] = [ + (1 << (Interrupt::POWER_CLOCK as u8)) + | (1 << (Interrupt::RADIO as u8)) + | (1 << (Interrupt::RTC0 as u8)) + | (1 << (Interrupt::TIMER0 as u8)) + | (1 << (Interrupt::RNG as u8)) + | (1 << (Interrupt::ECB as u8)) + | (1 << (Interrupt::CCM_AAR as u8)) + | (1 << (Interrupt::TEMP as u8)) + | (1 << (Interrupt::SWI5_EGU5 as u8)), + 0, +]; + +#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +#[repr(u8)] +pub enum Priority { + Level0 = 0, + Level1 = 1, + Level2 = 2, + Level3 = 3, + Level4 = 4, + Level5 = 5, + Level6 = 6, + Level7 = 7, +} + +impl Priority { + #[inline] + fn to_nvic(self) -> u8 { + (self as u8) << (8 - NVIC_PRIO_BITS) + } + + #[inline] + fn from_nvic(priority: u8) -> Self { + match priority >> (8 - NVIC_PRIO_BITS) { + 0 => Self::Level0, + 1 => Self::Level1, + 2 => Self::Level2, + 3 => Self::Level3, + 4 => Self::Level4, + 5 => Self::Level5, + 6 => Self::Level6, + 7 => Self::Level7, + _ => unreachable!(), + } + } +} + +static mut CS_FLAG: AtomicBool = AtomicBool::new(false); +static mut CS_MASK: [u32; 2] = [0; 2]; + +#[inline] +pub(crate) unsafe fn raw_free<F, R>(f: F) -> R +where + F: FnOnce() -> R, +{ + // TODO: assert that we're in privileged level + // Needed because disabling irqs in non-privileged level is a noop, which would break safety. + + let primask: u32; + asm!("mrs {}, PRIMASK", out(reg) primask); + + asm!("cpsid i"); + + // Prevent compiler from reordering operations inside/outside the critical section. + compiler_fence(Ordering::SeqCst); + + let r = f(); + + compiler_fence(Ordering::SeqCst); + + if primask & 1 == 0 { + asm!("cpsie i"); + } + + r +} + +/// Execute closure `f` in an interrupt-free context. +/// +/// This as also known as a "critical section". +#[inline] +pub fn free<F, R>(f: F) -> R +where + F: FnOnce(&CriticalSection) -> R, +{ + unsafe { + // TODO: assert that we're in privileged level + // Needed because disabling irqs in non-privileged level is a noop, which would break safety. + + let nvic = &*NVIC::ptr(); + + let nested_cs = CS_FLAG.load(Ordering::SeqCst); + + if !nested_cs { + raw_free(|| { + CS_FLAG.store(true, Ordering::Relaxed); + + // Store the state of irqs. + CS_MASK[0] = nvic.icer[0].read(); + CS_MASK[1] = nvic.icer[1].read(); + + // Disable only not-reserved irqs. + nvic.icer[0].write(!RESERVED_IRQS[0]); + nvic.icer[1].write(!RESERVED_IRQS[1]); + }); + } + + let r = f(&CriticalSection::new()); + + if !nested_cs { + raw_free(|| { + CS_FLAG.store(false, Ordering::Relaxed); + // restore only non-reserved irqs. + nvic.iser[0].write(CS_MASK[0] & !RESERVED_IRQS[0]); + nvic.iser[1].write(CS_MASK[1] & !RESERVED_IRQS[1]); + }); + } + + r + } +} + +#[inline] +fn is_app_accessible_irq(irq: Interrupt) -> bool { + match irq { + Interrupt::POWER_CLOCK + | Interrupt::RADIO + | Interrupt::RTC0 + | Interrupt::TIMER0 + | Interrupt::RNG + | Interrupt::ECB + | Interrupt::CCM_AAR + | Interrupt::TEMP + | Interrupt::SWI5_EGU5 => false, + _ => true, + } +} + +#[inline] +fn is_app_accessible_priority(priority: Priority) -> bool { + match priority { + Priority::Level0 | Priority::Level1 | Priority::Level4 => false, + _ => true, + } +} + +#[inline] +pub fn unmask(irq: Interrupt) { + assert!(is_app_accessible_irq(irq)); + assert!(is_app_accessible_priority(get_priority(irq))); + + unsafe { + if CS_FLAG.load(Ordering::SeqCst) { + let nr = irq.number(); + CS_MASK[usize::from(nr / 32)] |= 1 << (nr % 32); + } else { + NVIC::unmask(irq); + } + } +} + +#[inline] +pub fn mask(irq: Interrupt) { + assert!(is_app_accessible_irq(irq)); + + unsafe { + if CS_FLAG.load(Ordering::SeqCst) { + let nr = irq.number(); + CS_MASK[usize::from(nr / 32)] &= !(1 << (nr % 32)); + } else { + NVIC::mask(irq); + } + } +} + +#[inline] +pub fn is_active(irq: Interrupt) -> bool { + assert!(is_app_accessible_irq(irq)); + NVIC::is_active(irq) +} + +#[inline] +pub fn is_enabled(irq: Interrupt) -> bool { + assert!(is_app_accessible_irq(irq)); + NVIC::is_enabled(irq) +} + +#[inline] +pub fn is_pending(irq: Interrupt) -> bool { + assert!(is_app_accessible_irq(irq)); + NVIC::is_pending(irq) +} + +#[inline] +pub fn pend(irq: Interrupt) { + assert!(is_app_accessible_irq(irq)); + NVIC::pend(irq) +} + +#[inline] +pub fn unpend(irq: Interrupt) { + assert!(is_app_accessible_irq(irq)); + NVIC::unpend(irq) +} + +#[inline] +pub fn get_priority(irq: Interrupt) -> Priority { + Priority::from_nvic(NVIC::get_priority(irq)) +} + +#[inline] +pub fn set_priority(irq: Interrupt, prio: Priority) { + assert!(is_app_accessible_irq(irq)); + assert!(is_app_accessible_priority(prio)); + unsafe { + cortex_m::peripheral::Peripherals::steal() + .NVIC + .set_priority(irq, prio.to_nvic()) + } +} diff --git a/nrf-softdevice/src/lib.rs b/nrf-softdevice/src/lib.rs new file mode 100644 index 0000000..5c7d126 --- /dev/null +++ b/nrf-softdevice/src/lib.rs @@ -0,0 +1,14 @@ +#![no_std] +#![feature(asm)] +#![feature(generic_associated_types)] +#![feature(type_alias_impl_trait)] +#![feature(const_fn)] + +pub(crate) mod util; + +pub mod interrupt; + +mod events; +pub use events::*; +mod flash; +pub use flash::*; diff --git a/nrf-softdevice/src/util/depanic.rs b/nrf-softdevice/src/util/depanic.rs new file mode 100644 index 0000000..5d1bcc9 --- /dev/null +++ b/nrf-softdevice/src/util/depanic.rs @@ -0,0 +1,10 @@ +#![macro_use] + +macro_rules! depanic { + ($( $i:expr ),*) => { + { + defmt::error!($( $i ),*); + panic!(); + } + } +} diff --git a/nrf-softdevice/src/util/drop_bomb.rs b/nrf-softdevice/src/util/drop_bomb.rs new file mode 100644 index 0000000..8421d61 --- /dev/null +++ b/nrf-softdevice/src/util/drop_bomb.rs @@ -0,0 +1,21 @@ +pub struct DropBomb { + defused: bool, +} + +impl DropBomb { + pub fn new() -> Self { + Self { defused: false } + } + + pub fn defuse(&mut self) { + self.defused = true; + } +} + +impl Drop for DropBomb { + fn drop(&mut self) { + if !self.defused { + depanic!("boom") + } + } +} diff --git a/nrf-softdevice/src/util/mod.rs b/nrf-softdevice/src/util/mod.rs new file mode 100644 index 0000000..3c3f802 --- /dev/null +++ b/nrf-softdevice/src/util/mod.rs @@ -0,0 +1,10 @@ +#![macro_use] + +mod depanic; + +mod signal; +pub use signal::*; +mod waker_store; +pub use waker_store::*; +mod drop_bomb; +pub use drop_bomb::*; diff --git a/nrf-softdevice/src/util/signal.rs b/nrf-softdevice/src/util/signal.rs new file mode 100644 index 0000000..7df69ae --- /dev/null +++ b/nrf-softdevice/src/util/signal.rs @@ -0,0 +1,65 @@ +use core::cell::UnsafeCell; +use core::future::Future; +use core::pin::Pin; +use core::task::{Context, Poll}; + +use super::waker_store::WakerStore; + +pub struct Signal<T> { + inner: UnsafeCell<Inner<T>>, +} + +struct Inner<T> { + waker: WakerStore, + value: Option<T>, +} + +unsafe impl<T: Send> Send for Signal<T> {} +unsafe impl<T: Send> Sync for Signal<T> {} + +impl<T: Send> Signal<T> { + pub const fn new() -> Self { + Self { + inner: UnsafeCell::new(Inner { + waker: WakerStore::new(), + value: None, + }), + } + } + + pub fn signal(&self, val: T) { + unsafe { + crate::interrupt::raw_free(|| { + let this = &mut *self.inner.get(); + this.value = Some(val); + this.waker.wake(); + }) + } + } + + pub fn wait<'a>(&'a self) -> impl Future<Output = T> + 'a { + WaitFuture { signal: self } + } +} + +struct WaitFuture<'a, T> { + signal: &'a Signal<T>, +} + +impl<'a, T: Send> Future for WaitFuture<'a, T> { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> { + unsafe { + crate::interrupt::raw_free(|| { + let this = &mut *self.signal.inner.get(); + if let Some(val) = this.value.take() { + Poll::Ready(val) + } else { + this.waker.store(cx.waker()); + Poll::Pending + } + }) + } + } +} diff --git a/nrf-softdevice/src/util/waker_store.rs b/nrf-softdevice/src/util/waker_store.rs new file mode 100644 index 0000000..728572e --- /dev/null +++ b/nrf-softdevice/src/util/waker_store.rs @@ -0,0 +1,23 @@ +use core::task::Waker; + +pub struct WakerStore { + waker: Option<Waker>, +} + +impl WakerStore { + pub const fn new() -> Self { + Self { waker: None } + } + + pub fn store(&mut self, w: &Waker) { + match self.waker { + Some(ref w) if (w.will_wake(w)) => {} + Some(_) => panic!("Waker overflow"), + None => self.waker = Some(w.clone()), + } + } + + pub fn wake(&mut self) { + self.waker.take().map(|w| w.wake()); + } +} |