From 162b96ff60b97e0b99b303f4be7b5fbb4b018901 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wojciech=20Przytu=C5=82a?= Date: Sat, 22 Jun 2024 19:33:25 +0200 Subject: [PATCH] 802.15.4 raw stack driver support The driver provides bare yet correct support for all operations but rx. Rx support is in turn sophisticated, consisting of many higher-level abstractions that ensure memory safety and prevent losing frames. This commit introduces the driver, next commits add tests and a usage example. --- Cargo.toml | 1 + apis/net/ieee802154/Cargo.toml | 17 ++ apis/net/ieee802154/src/lib.rs | 280 +++++++++++++++++++++++++++++++++ apis/net/ieee802154/src/rx.rs | 164 +++++++++++++++++++ src/lib.rs | 7 + 5 files changed, 469 insertions(+) create mode 100644 apis/net/ieee802154/Cargo.toml create mode 100644 apis/net/ieee802154/src/lib.rs create mode 100644 apis/net/ieee802154/src/rx.rs diff --git a/Cargo.toml b/Cargo.toml index b1d956d8..10d7a29b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ libtock_console = { path = "apis/interface/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } libtock_gpio = { path = "apis/peripherals/gpio" } libtock_i2c_master = { path = "apis/peripherals/i2c_master" } +libtock_ieee802154 = { path = "apis/net/ieee802154" } libtock_i2c_master_slave = { path = "apis/peripherals/i2c_master_slave" } libtock_key_value = { path = "apis/storage/key_value" } libtock_leds = { path = "apis/interface/leds" } diff --git a/apis/net/ieee802154/Cargo.toml b/apis/net/ieee802154/Cargo.toml new file mode 100644 index 00000000..5342d42f --- /dev/null +++ b/apis/net/ieee802154/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "libtock_ieee802154" +version = "0.1.0" +authors = [ + "Tock Project Developers ", +] +license = "Apache-2.0 OR MIT" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +rust-version.workspace = true +description = "libtock raw IEEE 802.15.4 stack driver" + +[dependencies] +libtock_platform = { path = "../../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../../unittest" } diff --git a/apis/net/ieee802154/src/lib.rs b/apis/net/ieee802154/src/lib.rs new file mode 100644 index 00000000..4c57e721 --- /dev/null +++ b/apis/net/ieee802154/src/lib.rs @@ -0,0 +1,280 @@ +//! The raw IEEE 802.15.4 stack driver. + +#![no_std] + +use core::cell::Cell; +use libtock_platform as platform; +use libtock_platform::allow_ro::AllowRo; +use libtock_platform::allow_rw::AllowRw; +use libtock_platform::share; +use libtock_platform::subscribe::Subscribe; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; + +/// The raw IEEE 802.15.4 stack driver. +/// +/// It allows libraries to pass frames to and from kernel's 802.15.4 driver. +/// +/// # Example +/// ```ignore +/// use libtock::ieee802154::{Ieee802154, RxOperator, RxRingBuffer, RxSingleBufferOperator}; +/// +/// // Configure the radio +/// let pan: u16 = 0xcafe; +/// let addr_short: u16 = 0xdead; +/// let addr_long: u64 = 0xdead_dad; +/// let tx_power: i8 = -0x42; +/// let channel: u8 = 0xff; +/// +/// Ieee802154::set_pan(pan); +/// Ieee802154::set_address_short(addr_short); +/// Ieee802154::set_address_long(addr_long); +/// Ieee802154::set_tx_power(tx_power).unwrap(); +/// Ieee802154::set_channel(channel).unwrap(); +/// +/// // Don't forget to commit the config! +/// Ieee802154::commit_config(); +/// +/// Ieee802154::radio_on()?; +/// +/// // Transmit a frame +/// Ieee802154::transmit_frame(b"foobar").unwrap(); +/// +/// // Receive frames +/// let mut buf = RxRingBuffer::<2>::new(); +/// let mut operator = RxSingleBufferOperator::new(&mut buf); +/// +/// let frame = operator.receive_frame()?; +/// // Access frame data here: +/// let _body_len = frame.payload_len; +/// let _first_body_byte = frame.body[0]; +/// +/// ``` +pub struct Ieee802154(S, C); + +// Existence check +impl Ieee802154 { + /// Run a check against the console capsule to ensure it is present. + /// + /// Returns `true` if the driver was present. This does not necessarily mean + /// that the driver is working, as it may still fail to allocate grant + /// memory. + #[inline(always)] + pub fn exists() -> bool { + S::command(DRIVER_NUM, command::EXISTS, 0, 0).is_success() + } +} + +// Power management +impl Ieee802154 { + #[inline(always)] + pub fn is_on() -> bool { + S::command(DRIVER_NUM, command::STATUS, 0, 0).is_success() + } + + #[inline(always)] + pub fn radio_on() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::TURN_ON, 0, 0).to_result() + } + + #[inline(always)] + pub fn radio_off() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::TURN_OFF, 0, 0).to_result() + } +} + +// Configuration +impl Ieee802154 { + #[inline(always)] + pub fn set_address_short(short_addr: u16) { + // Setting short address can't fail, so no need to check the return value. + let _ = S::command( + DRIVER_NUM, + command::SET_SHORT_ADDR, + // Driver expects 1 added to make the value positive. + short_addr as u32 + 1, + 0, + ); + } + + #[inline(always)] + pub fn set_address_long(long_addr: u64) { + // Setting long address can't fail, so no need to check the return value. + let addr_lower: u32 = long_addr as u32; + let addr_upper: u32 = (long_addr >> 32) as u32; + let _ = S::command(DRIVER_NUM, command::SET_LONG_ADDR, addr_lower, addr_upper); + } + + #[inline(always)] + pub fn set_pan(pan: u16) { + // Setting PAN can't fail, so no need to check the return value. + let _ = S::command( + DRIVER_NUM, + command::SET_PAN, + pan as u32 + 1, // Driver expects 1 added to make the value positive. + 0, + ); + } + + #[inline(always)] + pub fn set_channel(chan: u8) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::SET_CHAN, chan as u32, 0).to_result() + } + + #[inline(always)] + pub fn set_tx_power(power: i8) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, command::SET_TX_PWR, power as i32 as u32, 0).to_result() + } + + #[inline(always)] + pub fn commit_config() { + // Committing config can't fail, so no need to check the return value. + let _ = S::command(DRIVER_NUM, command::COMMIT_CFG, 0, 0); + } + + #[inline(always)] + pub fn get_address_short() -> Result { + S::command(DRIVER_NUM, command::GET_SHORT_ADDR, 0, 0) + .to_result::() + // Driver adds 1 to make the value positive. + .map(|addr| addr as u16 - 1) + } + + #[inline(always)] + pub fn get_address_long() -> Result { + S::command(DRIVER_NUM, command::GET_LONG_ADDR, 0, 0).to_result() + } + + #[inline(always)] + pub fn get_pan() -> Result { + S::command(DRIVER_NUM, command::GET_PAN, 0, 0) + .to_result::() + // Driver adds 1 to make the value positive. + .map(|pan| pan as u16 - 1) + } + + #[inline(always)] + pub fn get_channel() -> Result { + S::command(DRIVER_NUM, command::GET_CHAN, 0, 0) + .to_result::() + .map(|chan| chan as u8) + } + + #[inline(always)] + pub fn get_tx_power() -> Result { + S::command(DRIVER_NUM, command::GET_TX_PWR, 0, 0) + .to_result::() + .map(|power| power as i32 as i8) + } +} + +// Transmission +impl Ieee802154 { + pub fn transmit_frame(frame: &[u8]) -> Result<(), ErrorCode> { + let called: Cell> = Cell::new(None); + share::scope::< + ( + AllowRo<_, DRIVER_NUM, { allow_ro::WRITE }>, + Subscribe<_, DRIVER_NUM, { subscribe::FRAME_TRANSMITTED }>, + ), + _, + _, + >(|handle| { + let (allow_ro, subscribe) = handle.split(); + + S::allow_ro::(allow_ro, frame)?; + + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::FRAME_TRANSMITTED }>( + subscribe, &called, + )?; + + S::command(DRIVER_NUM, command::TRANSMIT, 0, 0).to_result()?; + + loop { + S::yield_wait(); + if called.get().is_some() { + return Ok(()); + } + } + }) + } +} + +mod rx; +pub use rx::{Frame, RxOperator, RxRingBuffer, RxSingleBufferOperator}; + +/// System call configuration trait for `Ieee802154`. +pub trait Config: + platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config +{ +} +impl + Config for T +{ +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x30001; + +// Command IDs +/// - `0`: Driver existence check. +/// - `1`: Return radio status. Ok(())/OFF = on/off. +/// - `2`: Set short address. +/// - `4`: Set PAN ID. +/// - `5`: Set channel. +/// - `6`: Set transmission power. +/// - `7`: Commit any configuration changes. +/// - `8`: Get the short MAC address. +/// - `10`: Get the PAN ID. +/// - `11`: Get the channel. +/// - `12`: Get the transmission power. +/// - `27`: Transmit a frame. The frame must be stored in the write RO allow +/// buffer 0. The allowed buffer must be the length of the frame. The +/// frame includes the PDSU (i.e., the MAC payload) _without_ the MFR +/// (i.e., CRC) bytes. +/// - `28`: Set long address. +/// - `29`: Get the long MAC address. +/// - `30`: Turn the radio on. +/// - `31`: Turn the radio off. +mod command { + pub const EXISTS: u32 = 0; + pub const STATUS: u32 = 1; + pub const SET_SHORT_ADDR: u32 = 2; + pub const SET_PAN: u32 = 4; + pub const SET_CHAN: u32 = 5; + pub const SET_TX_PWR: u32 = 6; + pub const COMMIT_CFG: u32 = 7; + pub const GET_SHORT_ADDR: u32 = 8; + pub const GET_PAN: u32 = 10; + pub const GET_CHAN: u32 = 11; + pub const GET_TX_PWR: u32 = 12; + pub const TRANSMIT: u32 = 27; + pub const SET_LONG_ADDR: u32 = 28; + pub const GET_LONG_ADDR: u32 = 29; + pub const TURN_ON: u32 = 30; + pub const TURN_OFF: u32 = 31; +} + +mod subscribe { + /// Frame is received + pub const FRAME_RECEIVED: u32 = 0; + /// Frame is transmitted + pub const FRAME_TRANSMITTED: u32 = 1; +} + +/// Ids for read-only allow buffers +mod allow_ro { + /// Write buffer. Contains the frame payload to be transmitted. + pub const WRITE: u32 = 0; +} + +/// Ids for read-write allow buffers +mod allow_rw { + /// Read buffer. Will contain the received frame. + pub const READ: u32 = 0; +} diff --git a/apis/net/ieee802154/src/rx.rs b/apis/net/ieee802154/src/rx.rs new file mode 100644 index 00000000..db727f04 --- /dev/null +++ b/apis/net/ieee802154/src/rx.rs @@ -0,0 +1,164 @@ +use core::marker::PhantomData; + +use super::*; + +/// Maximum length of a MAC frame. +const MAX_MTU: usize = 127; + +#[derive(Debug)] +#[repr(C)] +pub struct Frame { + pub header_len: u8, + pub payload_len: u8, + pub mic_len: u8, + pub body: [u8; MAX_MTU], +} + +const EMPTY_FRAME: Frame = Frame { + header_len: 0, + payload_len: 0, + mic_len: 0, + body: [0; MAX_MTU], +}; + +/// The ring buffer that is shared with kernel using allow-rw syscall, with kernel acting +/// as a producer of frames and we acting a consumer. + +/// The `N` parameter specifies the capacity of the buffer in number of frames. +/// Unfortunately, due to a design flaw of the ring buffer, it can never be fully utilised, +/// as it's impossible to distinguish an empty buffer from a full one. The kernel code +/// actually uses up to `N - 1` slots, and then starts overwriting old frames with +/// new ones. Remember to specify `N` as `F + 1`, where `F` is the maximum expected number +/// of frames received in short succession. +/// +/// Given the non-deterministic nature of upcalls, the userprocess must carefully +/// handle receiving upcalls. There exists a risk of dropping 15.4 packets while +/// reading from the ring buffer (as the ring buffer is unallowed while reading). +/// This could be handled by utilizing two ring buffers and alternating which +/// belongs to the kernel and which is being read from. However, efforts to implement that +/// failed on Miri level - we couldn't find a sound way to achieve that. +/// Alternatively, the user can also utilize a single ring buffer if dropped frames may be permissible. +/// This is done by [RxSingleBufferOperator]. +#[derive(Debug)] +#[repr(C)] +pub struct RxRingBuffer { + /// From where the next frame will be read by process. + /// Updated by process only. + read_index: u8, + /// Where the next frame will be written by kernel. + /// Updated by kernel only. + write_index: u8, + /// Slots for received frames. + frames: [Frame; N], +} + +impl RxRingBuffer { + /// Creates a new [RxRingBuffer] that can be used to receive frames into. + pub const fn new() -> Self { + Self { + read_index: 0, + write_index: 0, + frames: [EMPTY_FRAME; N], + } + } + + fn as_mut_byte_slice(&mut self) -> &mut [u8] { + // SAFETY: any byte value is valid for any byte of Self, + // as well as for any byte of [u8], so casts back and forth + // cannot break the type system. + unsafe { + core::slice::from_raw_parts_mut( + self as *mut Self as *mut u8, + core::mem::size_of::(), + ) + } + } + + fn has_frame(&self) -> bool { + self.read_index != self.write_index + } + + fn next_frame(&mut self) -> &mut Frame { + let frame = self.frames.get_mut(self.read_index as usize).unwrap(); + self.read_index = (self.read_index + 1) % N as u8; + frame + } +} + +pub trait RxOperator { + /// Receive one new frame. + /// + /// Logically pop one frame out of the ring buffer and provide mutable access to it. + /// If no frame is ready for reception, yield_wait to kernel until one is available. + fn receive_frame(&mut self) -> Result<&mut Frame, ErrorCode>; +} + +/// Safe encapsulation that can receive frames from the kernel using a single ring buffer. +/// See [RxRingBuffer] for more information. +/// +/// This operator can lose some frames: if a frame is received in the kernel when +/// the app is examining its received frames (and hence has its buffer unallowed), +/// then the frame can be lost. Unfortunately, no alternative at the moment due to +/// soundness issues in tried implementation. +pub struct RxSingleBufferOperator<'buf, const N: usize, S: Syscalls, C: Config = DefaultConfig> { + buf: &'buf mut RxRingBuffer, + s: PhantomData, + c: PhantomData, +} + +impl<'buf, const N: usize, S: Syscalls, C: Config> RxSingleBufferOperator<'buf, N, S, C> { + /// Creates a new [RxSingleBufferOperator] that can be used to receive frames. + pub fn new(buf: &'buf mut RxRingBuffer) -> Self { + Self { + buf, + s: PhantomData, + c: PhantomData, + } + } +} +impl<'buf, const N: usize, S: Syscalls, C: Config> RxOperator + for RxSingleBufferOperator<'buf, N, S, C> +{ + fn receive_frame(&mut self) -> Result<&mut Frame, ErrorCode> { + if self.buf.has_frame() { + Ok(self.buf.next_frame()) + } else { + // If no frame is there, wait until one comes, then return it. + + Ieee802154::::receive_frame_single_buf(self.buf)?; + + // Safety: kernel schedules an upcall iff a new frame becomes available, + // i.e. when it increments `read_index`. + Ok(self.buf.next_frame()) + } + } +} + +// Reception +impl Ieee802154 { + fn receive_frame_single_buf( + buf: &mut RxRingBuffer, + ) -> Result<(), ErrorCode> { + let called: Cell> = Cell::new(None); + share::scope::< + ( + AllowRw<_, DRIVER_NUM, { allow_rw::READ }>, + Subscribe<_, DRIVER_NUM, { subscribe::FRAME_RECEIVED }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf.as_mut_byte_slice())?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::FRAME_RECEIVED }>(subscribe, &called)?; + + loop { + S::yield_wait(); + if let Some((_lqi,)) = called.get() { + // At least one frame was received. + return Ok(()); + } + } + }) + } +} diff --git a/src/lib.rs b/src/lib.rs index 0e4acd36..0ed541bc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,13 @@ pub mod i2c_master_slave { use libtock_i2c_master_slave as i2c_master_slave; pub type I2CMasterSlave = i2c_master_slave::I2CMasterSlave; } +pub mod ieee802154 { + use libtock_ieee802154 as ieee802154; + pub type Ieee802154 = ieee802154::Ieee802154; + pub use ieee802154::{Frame, RxOperator, RxRingBuffer}; + pub type RxSingleBufferOperator<'buf, const N: usize> = + ieee802154::RxSingleBufferOperator<'buf, N, super::runtime::TockSyscalls>; +} pub mod leds { use libtock_leds as leds; pub type Leds = leds::Leds;