summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.toml58
-rw-r--r--async-flash/Cargo.toml10
-rw-r--r--async-flash/src/lib.rs56
-rw-r--r--async-flash/src/writer.rs107
-rw-r--r--examples/flash/.cargo/config27
-rw-r--r--examples/flash/Cargo.toml32
-rw-r--r--examples/flash/build.rs31
-rw-r--r--examples/flash/memory.x34
-rw-r--r--examples/flash/src/main.rs69
-rw-r--r--nrf-softdevice/Cargo.toml23
-rw-r--r--nrf-softdevice/src/events.rs135
-rw-r--r--nrf-softdevice/src/flash.rs117
-rw-r--r--nrf-softdevice/src/interrupt.rs228
-rw-r--r--nrf-softdevice/src/lib.rs14
-rw-r--r--nrf-softdevice/src/util/depanic.rs10
-rw-r--r--nrf-softdevice/src/util/drop_bomb.rs21
-rw-r--r--nrf-softdevice/src/util/mod.rs10
-rw-r--r--nrf-softdevice/src/util/signal.rs65
-rw-r--r--nrf-softdevice/src/util/waker_store.rs23
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());
+ }
+}