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/apis/net/ieee802154/src/tests.rs b/apis/net/ieee802154/src/tests.rs new file mode 100644 index 00000000..ea4d248e --- /dev/null +++ b/apis/net/ieee802154/src/tests.rs @@ -0,0 +1,272 @@ +use libtock_platform::{RawSyscalls, Register}; +use libtock_unittest::fake::{self, ieee802154::Frame as FakeFrame, Ieee802154Phy}; + +/// The Ieee8021514Phy userspace driver calls yield_wait() immediately after subscribe(). +/// Normally, it would wait for the kernel to receive a frame and then asynchronously +/// schedule an upcall, but in this testing framework it is required to schedule +/// an upcall before yield_wait(), because otherwise a panic is raised. +/// +/// HACK: This wraps around fake::Syscalls to hook subscribe::FRAME_RECEIVED +/// so that immediately after subscribing for the upcall, frames are received +/// by the kernel driver and the corresponding upcall is scheduled. +struct FakeSyscalls; + +unsafe impl RawSyscalls for FakeSyscalls { + unsafe fn yield1([r0]: [Register; 1]) { + libtock_unittest::fake::Syscalls::yield1([r0]) + } + + unsafe fn yield2([r0, r1]: [Register; 2]) { + libtock_unittest::fake::Syscalls::yield2([r0, r1]) + } + + unsafe fn syscall1([r0]: [Register; 1]) -> [Register; 2] { + libtock_unittest::fake::Syscalls::syscall1::([r0]) + } + + unsafe fn syscall2([r0, r1]: [Register; 2]) -> [Register; 2] { + libtock_unittest::fake::Syscalls::syscall2::([r0, r1]) + } + + unsafe fn syscall4([r0, r1, r2, r3]: [Register; 4]) -> [Register; 4] { + let trigger_rx_upcall = match CLASS { + libtock_platform::syscall_class::SUBSCRIBE => { + let driver_num: u32 = r0.try_into().unwrap(); + let subscribe_num: u32 = r1.try_into().unwrap(); + let len: usize = r3.into(); + assert_eq!(driver_num, DRIVER_NUM); + + subscribe_num == subscribe::FRAME_RECEIVED && len > 0 + } + _ => false, + }; + + let ret = libtock_unittest::fake::Syscalls::syscall4::([r0, r1, r2, r3]); + if trigger_rx_upcall { + if let Some(driver) = Ieee802154Phy::instance() { + driver.driver_receive_pending_frames(); + + if driver.has_pending_rx_frames() { + driver.trigger_rx_upcall(); + } + } + } + ret + } +} + +use crate::{subscribe, DRIVER_NUM}; + +use super::{RxOperator, RxRingBuffer}; + +type Ieee802154 = super::Ieee802154; +type RxSingleBufferOperator<'buf, const N: usize> = + super::RxSingleBufferOperator<'buf, N, FakeSyscalls>; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert!(!Ieee802154::exists()); +} + +#[test] +fn exists() { + let kernel = fake::Kernel::new(); + let driver = fake::Ieee802154Phy::new(); + kernel.add_driver(&driver); + + assert!(Ieee802154::exists()); +} + +#[test] +fn configure() { + let kernel = fake::Kernel::new(); + let driver = fake::Ieee802154Phy::new(); + kernel.add_driver(&driver); + + let pan: u16 = 0xcafe; + let addr_short: u16 = 0xdead; + let addr_long: u64 = 0xdeaddad; + 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(); + + Ieee802154::commit_config(); + + assert_eq!(Ieee802154::get_pan().unwrap(), pan); + assert_eq!(Ieee802154::get_address_short().unwrap(), addr_short); + assert_eq!(Ieee802154::get_address_long().unwrap(), addr_long); + assert_eq!(Ieee802154::get_channel().unwrap(), channel); + assert_eq!(Ieee802154::get_tx_power().unwrap(), tx_power); +} + +#[test] +fn transmit_frame() { + let kernel = fake::Kernel::new(); + let driver = fake::Ieee802154Phy::new(); + kernel.add_driver(&driver); + + Ieee802154::transmit_frame(b"foo").unwrap(); + Ieee802154::transmit_frame(b"bar").unwrap(); + assert_eq!( + driver.take_transmitted_frames(), + &[&b"foo"[..], &b"bar"[..]], + ); +} + +mod rx { + use super::*; + fn test_with_driver(test: impl FnOnce(&Ieee802154Phy)) { + let kernel = fake::Kernel::new(); + let driver = fake::Ieee802154Phy::new(); + kernel.add_driver(&driver); + + test(&driver) + } + + fn test_with_single_buf_operator( + driver: &Ieee802154Phy, + test: impl Fn(&Ieee802154Phy, &mut dyn RxOperator), + ) { + let mut buf = RxRingBuffer::::new(); + let mut operator = RxSingleBufferOperator::new(&mut buf); + + test(driver, &mut operator) + } + + fn no_frame_comes(_driver: &Ieee802154Phy, operator: &mut dyn RxOperator) { + // No frame is available, so we expect to panic in tests, + // because yield_wait is called without pending upcalls. + // THIS PANICS + let _ = operator.receive_frame(); + } + + #[test] + #[should_panic = "yield-wait called with no queued upcall"] + fn no_frame_comes_single_buf() { + test_with_driver(|driver| { + const SUPPORTED_FRAMES: usize = 2; + + test_with_single_buf_operator::(driver, no_frame_comes); + }); + } + + #[test] + fn receive_frame() { + test_with_driver(|driver| { + const SUPPORTED_FRAMES: usize = 2; + + test_with_single_buf_operator::(driver, |driver, operator| { + let frame1 = b"alamakota"; + + driver.radio_receive_frame(FakeFrame::with_body(frame1)); + // Now one frame is available. + + let got_frame1 = operator.receive_frame().unwrap(); + assert_eq!(got_frame1.payload_len as usize, frame1.len()); + assert_eq!( + &got_frame1.body[..got_frame1.payload_len as usize], + &frame1[..] + ); + }); + }); + } + + fn only_one_frame_comes(driver: &Ieee802154Phy, operator: &mut dyn RxOperator) { + let frame1 = b"alamakota"; + + // Now one frame is available. + driver.radio_receive_frame(FakeFrame::with_body(frame1)); + let got_frame1 = operator.receive_frame().unwrap(); + assert_eq!(got_frame1.payload_len as usize, frame1.len()); + assert_eq!(&got_frame1.body[..frame1.len()], frame1); + + // But only one! + // THIS PANICS + let _ = operator.receive_frame(); + } + + #[test] + #[should_panic = "yield-wait called with no queued upcall"] + fn receive_frame_only_one_single_buf() { + test_with_driver(|driver| { + const SUPPORTED_FRAMES: usize = 2; + + test_with_single_buf_operator::(driver, only_one_frame_comes); + }); + } + + #[test] + fn receive_many_frames() { + test_with_driver(|driver| { + const SUPPORTED_FRAMES: usize = 3; + + test_with_single_buf_operator::<{ SUPPORTED_FRAMES + 1 }>( + driver, + |driver, operator| { + for (times, frame) in + [1, 2, 3, 10] + .iter() + .copied() + .zip([&b"one"[..], b"two", b"three", b"ten"]) + { + for _ in 0..times { + driver.radio_receive_frame(FakeFrame::with_body(frame)); + } + + for _ in 0..core::cmp::min(times, SUPPORTED_FRAMES) { + let got_frame = operator.receive_frame().unwrap(); + let expected_frame = frame; + assert_eq!(got_frame.payload_len as usize, expected_frame.len()); + assert_eq!( + &got_frame.body[..got_frame.payload_len as usize], + expected_frame + ); + } + } + }, + ); + }); + } + + #[test] + fn receive_various_frames() { + test_with_driver(|driver| { + const SUPPORTED_FRAMES: usize = 3; + + test_with_single_buf_operator::<{ SUPPORTED_FRAMES + 1 }>( + driver, + |driver, operator| { + let frame1 = b"alamakota"; + let frame2 = b"ewamamewe"; + let frame3 = b"wojciechmalaptop"; + let frames: [&[u8]; 3] = [frame1, frame2, frame3]; + + let order = [0, 1, 2, 2, 1, 0, 2, 2, 1, 0, 2]; + for idx in order { + let times = idx + 1; + + for _ in 0..times { + driver.radio_receive_frame(FakeFrame::with_body(frames[idx])); + } + + for _ in 0..core::cmp::min(times, SUPPORTED_FRAMES) { + let got_frame = operator.receive_frame().unwrap(); + let expected_frame = frames[idx]; + assert_eq!(got_frame.payload_len as usize, expected_frame.len()); + assert_eq!( + &got_frame.body[..got_frame.payload_len as usize], + expected_frame + ); + } + } + }, + ); + }); + } +} diff --git a/examples/ieee802154.rs b/examples/ieee802154.rs new file mode 100644 index 00000000..bfd5471b --- /dev/null +++ b/examples/ieee802154.rs @@ -0,0 +1,54 @@ +//! An example showing use of IEEE 802.15.4 networking. + +#![no_main] +#![no_std] +use libtock::console::Console; +use libtock::ieee802154::{Ieee802154, RxOperator as _, RxRingBuffer, RxSingleBufferOperator}; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x600} + +fn main() { + // Configure the radio + let pan: u16 = 0xcafe; + let addr_short: u16 = 0xdead; + let addr_long: u64 = 0xdead_dad; + let tx_power: i8 = -3; + let channel: u8 = 11; + + 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(); + + // Turn the radio on + Ieee802154::radio_on().unwrap(); + assert!(Ieee802154::is_on()); + + // Transmit a frame + Ieee802154::transmit_frame(b"foobar").unwrap(); + + Console::write(b"Transmitted frame!\n").unwrap(); + + // Showcase receiving to a single buffer - there is a risk of losing some frames. + // See [RxSingleBufferOperator] docs for more details. + rx_single_buffer(); +} + +fn rx_single_buffer() { + let mut buf = RxRingBuffer::<2>::new(); + let mut operator = RxSingleBufferOperator::new(&mut buf); + + let frame1 = operator.receive_frame().unwrap(); + // Access frame1 data here: + let _body_len = frame1.payload_len; + let _first_body_byte = frame1.body[0]; + + let _frame2 = operator.receive_frame().unwrap(); + // Access frame2 data here +} diff --git a/examples/ieee802154_rx.rs b/examples/ieee802154_rx.rs new file mode 100644 index 00000000..cad7c2d7 --- /dev/null +++ b/examples/ieee802154_rx.rs @@ -0,0 +1,50 @@ +//! An example showing use of IEEE 802.15.4 networking. +//! It infinitely received a frame and prints its content to Console. + +#![no_main] +#![no_std] +use core::fmt::Write as _; +use libtock::console::Console; +use libtock::ieee802154::{Ieee802154, RxOperator as _, RxRingBuffer, RxSingleBufferOperator}; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x600} + +fn main() { + // Configure the radio + let pan: u16 = 0xcafe; + let addr_short: u16 = 0xdead; + let addr_long: u64 = 0xdead_dad; + let tx_power: i8 = 5; + let channel: u8 = 11; + + 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(); + + // Turn the radio on + Ieee802154::radio_on().unwrap(); + assert!(Ieee802154::is_on()); + + let mut buf = RxRingBuffer::<2>::new(); + let mut operator = RxSingleBufferOperator::new(&mut buf); + loop { + let frame = operator.receive_frame().unwrap(); + + let body_len = frame.payload_len; + writeln!( + Console::writer(), + "Received frame with body of len {}: {} {:?}!\n", + body_len, + core::str::from_utf8(&frame.body).unwrap(), + &frame.body[..frame.body.len() - core::mem::size_of::()] + ) + .unwrap(); + } +} diff --git a/examples/ieee802154_rx_tx.rs b/examples/ieee802154_rx_tx.rs new file mode 100644 index 00000000..3a940130 --- /dev/null +++ b/examples/ieee802154_rx_tx.rs @@ -0,0 +1,81 @@ +//! An example showing use of IEEE 802.15.4 networking. +//! It infinitely sends a frame with a constantly incremented counter, +//! and after each send receives a frame and prints it to Console. + +#![no_main] +#![no_std] +use core::fmt::Write as _; +use libtock::alarm::{Alarm, Milliseconds}; +use libtock::console::Console; +use libtock::ieee802154::{Ieee802154, RxOperator as _, RxRingBuffer, RxSingleBufferOperator}; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x600} + +fn main() { + // Configure the radio + let pan: u16 = 0xcafe; + let addr_short: u16 = 0xdead; + let addr_long: u64 = 0xdead_dad; + let tx_power: i8 = 5; + let channel: u8 = 11; + + 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(); + + // Turn the radio on + Ieee802154::radio_on().unwrap(); + assert!(Ieee802154::is_on()); + + let mut buf = RxRingBuffer::<2>::new(); + let mut operator = RxSingleBufferOperator::new(&mut buf); + + let mut counter = 0_usize; + let mut buf = [ + b'f', b'r', b'a', b'm', b'e', b' ', b'n', b'.', b'o', b'.', b' ', b'\0', b'\0', b'\0', + b'\0', + ]; + fn set_buf_cnt(buf: &mut [u8], counter: &mut usize) { + let buf_len = buf.len(); + let buf_cnt = &mut buf[buf_len - core::mem::size_of_val(&counter)..]; + buf_cnt.copy_from_slice(&counter.to_be_bytes()); + } + + loop { + Alarm::sleep_for(Milliseconds(1000)).unwrap(); + + set_buf_cnt(&mut buf, &mut counter); + + // Transmit a frame + Ieee802154::transmit_frame(&buf).unwrap(); + + writeln!(Console::writer(), "Transmitted frame {}!\n", counter).unwrap(); + + let frame = operator.receive_frame().unwrap(); + + let body_len = frame.payload_len; + writeln!( + Console::writer(), + "Received frame with body of len {}: {}-{} {:?}!\n", + body_len, + core::str::from_utf8(&frame.body[..frame.body.len() - core::mem::size_of::()]) + .unwrap_or(""), + usize::from_le_bytes( + frame.body[frame.body.len() - core::mem::size_of::()..] + .try_into() + .unwrap() + ), + frame.body + ) + .unwrap(); + + counter += 1; + } +} diff --git a/examples/ieee802154_tx.rs b/examples/ieee802154_tx.rs new file mode 100644 index 00000000..249b36e7 --- /dev/null +++ b/examples/ieee802154_tx.rs @@ -0,0 +1,60 @@ +//! An example showing use of IEEE 802.15.4 networking. +//! It infinitely sends a frame with a constantly incremented counter. + +#![no_main] +#![no_std] +use core::fmt::Write as _; + +use libtock::alarm::{Alarm, Milliseconds}; +use libtock::console::Console; +use libtock::ieee802154::Ieee802154; +use libtock::runtime::{set_main, stack_size}; + +set_main! {main} +stack_size! {0x600} + +fn main() { + // Configure the radio + let pan: u16 = 0xcafe; + let addr_short: u16 = 0xdead; + let addr_long: u64 = 0xdead_dad; + let tx_power: i8 = 5; + let channel: u8 = 11; + + 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(); + + // Turn the radio on + Ieee802154::radio_on().unwrap(); + assert!(Ieee802154::is_on()); + + let mut counter = 0_usize; + let mut buf = [ + b'f', b'r', b'a', b'm', b'e', b' ', b'n', b'.', b'o', b'.', b' ', b'\0', b'\0', b'\0', + b'\0', + ]; + fn set_buf_cnt(buf: &mut [u8], counter: &mut usize) { + let buf_len = buf.len(); + let buf_cnt = &mut buf[buf_len - core::mem::size_of_val(&counter)..]; + buf_cnt.copy_from_slice(&counter.to_be_bytes()); + } + + loop { + Alarm::sleep_for(Milliseconds(1000)).unwrap(); + + set_buf_cnt(&mut buf, &mut counter); + + // Transmit a frame + Ieee802154::transmit_frame(&buf).unwrap(); + + writeln!(Console::writer(), "Transmitted frame {}!\n", counter).unwrap(); + + counter += 1; + } +} 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; diff --git a/unittest/src/fake/ieee802154/mod.rs b/unittest/src/fake/ieee802154/mod.rs new file mode 100644 index 00000000..b853d929 --- /dev/null +++ b/unittest/src/fake/ieee802154/mod.rs @@ -0,0 +1,389 @@ +//! Fake implementation of the raw IEEE 802.15.4 API. + +use core::cell::Cell; +use libtock_platform::{CommandReturn, ErrorCode}; +use std::{ + cell::RefCell, + collections::VecDeque, + convert::TryFrom, + rc::{self, Rc}, +}; + +use crate::{command_return, DriverInfo, DriverShareRef, RoAllowBuffer, RwAllowBuffer}; + +/// Maximum length of a MAC frame. +const MAX_MTU: usize = 127; + +const PSDU_OFFSET: usize = 2; + +#[derive(Debug)] +#[repr(C)] +pub struct Frame { + pub header_len: u8, + pub payload_len: u8, + pub mic_len: u8, + pub body: [u8; MAX_MTU], +} + +impl Frame { + pub fn with_body(body: &[u8]) -> Self { + let mut frame = Self { + header_len: 0, + payload_len: u8::try_from(body.len()).unwrap(), + mic_len: 0, + body: [0_u8; 127], + }; + + frame.body[PSDU_OFFSET..PSDU_OFFSET + body.len()].copy_from_slice(body); + + frame + } +} + +pub struct Ieee802154Phy { + pan: Cell, + addr_short: Cell, + addr_long: Cell, + chan: Cell, + tx_power: Cell, + radio_on: Cell, + + tx_buf: Cell, + rx_buf: RefCell, + + transmitted_frames: Cell>>, + + frames_to_be_received: RefCell>, + + share_ref: DriverShareRef, +} + +// Needed for scheduling an receive upcall immediately after subscribing to it. +// Without that, + +thread_local!(pub(crate) static DRIVER: RefCell> = const { RefCell::new(rc::Weak::new()) }); + +impl Ieee802154Phy { + pub fn instance() -> Option> { + DRIVER.with_borrow(|driver| driver.upgrade()) + } + + pub fn new() -> Rc { + let new = Self::new_with_frames_to_be_received(std::iter::empty()); + DRIVER.with_borrow_mut(|inner| *inner = Rc::downgrade(&new)); + new + } + + pub fn new_with_frames_to_be_received( + frames_to_be_received: impl IntoIterator, + ) -> Rc { + Rc::new(Self { + pan: Default::default(), + addr_short: Default::default(), + addr_long: Default::default(), + chan: Default::default(), + tx_power: Default::default(), + radio_on: Default::default(), + tx_buf: Default::default(), + rx_buf: Default::default(), + transmitted_frames: Default::default(), + frames_to_be_received: RefCell::new(frames_to_be_received.into_iter().collect()), + share_ref: Default::default(), + }) + } + + pub fn take_transmitted_frames(&self) -> Vec> { + self.transmitted_frames.take() + } + + pub fn has_pending_rx_frames(&self) -> bool { + let rx_buf = self.rx_buf.borrow(); + + #[allow(clippy::get_first)] + let read_index = rx_buf.get(0); + let write_index = rx_buf.get(1); + + matches!((read_index, write_index), (Some(r), Some(w)) if r != w) + } + + pub fn radio_receive_frame(&self, frame: Frame) { + self.frames_to_be_received.borrow_mut().push_back(frame); + } + + pub fn driver_receive_pending_frames(&self) { + for frame in self.frames_to_be_received.borrow_mut().drain(..) { + self.driver_receive_frame(&frame.body[..frame.payload_len as usize + PSDU_OFFSET]); + } + } + + fn driver_receive_frame(&self, frame: &[u8]) { + let mut rx_buf = self.rx_buf.borrow_mut(); + Self::phy_driver_receive_frame(&mut rx_buf, frame); + } + + // Code taken and adapted from capsules/extra/src/ieee802154/phy_driver.rs. + fn phy_driver_receive_frame(rbuf: &mut [u8], frame: &[u8]) { + let frame_len = frame.len() - PSDU_OFFSET; + + //////////////////////////////////////////////////////// + // NOTE: context for the ring buffer and assumptions + // regarding the ring buffer format and usage can be + // found in the detailed comment at the top of this + // file. + // + // Ring buffer format: + // | read | write | user_frame | user_frame |...| user_frame | + // | index | index | 0 | 1 | | n | + // + // user_frame format: + // | header_len | payload_len | mic_len | 15.4 frame | + // + //////////////////////////////////////////////////////// + + const PSDU_OFFSET: usize = 2; + + // 2 bytes for the readwrite buffer metadata (read and + // write index). + const RING_BUF_METADATA_SIZE: usize = 2; + + /// 3 byte metadata (offset, len, mic_len) + const USER_FRAME_METADATA_SIZE: usize = 3; + + /// 3 byte metadata + 127 byte max payload + const USER_FRAME_MAX_SIZE: usize = USER_FRAME_METADATA_SIZE + MAX_MTU; + + // Confirm the availability of the buffer. A buffer of + // len 0 is indicative of the userprocess not allocating + // a readwrite buffer. We must also confirm that the + // userprocess correctly formatted the buffer to be of + // length 2 + n * USER_FRAME_MAX_SIZE, where n is the + // number of user frames that the buffer can store. We + // combine checking the buffer's non-zero length and the + // case of the buffer being shorter than the + // `RING_BUF_METADATA_SIZE` as an invalid buffer (e.g. + // of length 1) may otherwise errantly pass the second + // conditional check (due to unsigned integer + // arithmetic). + assert!(rbuf.len() > RING_BUF_METADATA_SIZE); + assert!((rbuf.len() - RING_BUF_METADATA_SIZE) % USER_FRAME_MAX_SIZE == 0); + + let mut read_index = rbuf[0] as usize; + let mut write_index = rbuf[1] as usize; + + let max_pending_rx = (rbuf.len() - RING_BUF_METADATA_SIZE) / USER_FRAME_MAX_SIZE; + + // Confirm user modifiable metadata is valid (i.e. + // within bounds of the provided buffer). + assert!(read_index < max_pending_rx && write_index < max_pending_rx); + + // We don't parse the received packet, so we don't know + // how long all of the pieces are. + let mic_len = 0; + let header_len = 0; + + // Start in the buffer where we are going to write this + // incoming packet. + let offset = RING_BUF_METADATA_SIZE + (write_index * USER_FRAME_MAX_SIZE); + + // Copy the entire frame over to userland, preceded by + // three metadata bytes: the header length, the data + // length, and the MIC length. + let dst_start = offset + USER_FRAME_METADATA_SIZE; + let dst_end = dst_start + frame_len; + let src_start = PSDU_OFFSET; + let src_end = src_start + frame_len; + rbuf[dst_start..dst_end].copy_from_slice(&frame[src_start..src_end]); + + rbuf[offset] = header_len as u8; + rbuf[offset + 1] = frame_len as u8; + rbuf[offset + 2] = mic_len as u8; + + // Prepare the ring buffer for the next write. The + // current design favors newness; newly received packets + // will begin to overwrite the oldest data in the event + // of the buffer becoming full. The read index must + // always point to the "oldest" data. If we have + // overwritten the oldest data, the next oldest data is + // now at the read index + 1. We must update the read + // index to reflect this. + write_index = (write_index + 1) % max_pending_rx; + if write_index == read_index { + read_index = (read_index + 1) % max_pending_rx; + rbuf[0] = read_index as u8; + } + + // Update write index metadata since we have added a + // frame. + rbuf[1] = write_index as u8; + } + + pub fn trigger_rx_upcall(&self) { + self.share_ref + .schedule_upcall(subscribe::FRAME_RECEIVED, (0, 0, 0)) + .expect("Unable to schedule upcall {}"); + } +} + +impl crate::fake::SyscallDriver for Ieee802154Phy { + fn info(&self) -> DriverInfo { + DriverInfo::new(DRIVER_NUM).upcall_count(2) + } + + fn register(&self, share_ref: DriverShareRef) { + self.share_ref.replace(share_ref); + } + + fn command(&self, command_number: u32, argument0: u32, argument1: u32) -> CommandReturn { + match command_number { + command::EXISTS => command_return::success(), + command::STATUS => { + if self.radio_on.get() { + command_return::success() + } else { + command_return::failure(ErrorCode::Off) + } + } + command::SET_SHORT_ADDR => { + self.addr_short.set(u16::try_from(argument0).unwrap()); + command_return::success() + } + command::SET_PAN => { + self.pan.set(u16::try_from(argument0).unwrap()); + command_return::success() + } + command::SET_CHAN => { + self.chan.set(u8::try_from(argument0).unwrap()); + command_return::success() + } + command::SET_TX_PWR => { + self.tx_power.set(i8::try_from(argument0 as i32).unwrap()); + command_return::success() + } + command::COMMIT_CFG => command_return::success(), + command::GET_SHORT_ADDR => command_return::success_u32(self.addr_short.get() as u32), + command::GET_PAN => command_return::success_u32(self.pan.get() as u32), + command::GET_CHAN => command_return::success_u32(self.chan.get() as u32), + command::GET_TX_PWR => command_return::success_u32(self.tx_power.get() as i32 as u32), + command::SET_LONG_ADDR => { + self.addr_long + .set(argument0 as u64 | (argument1 as u64) << 32); + command_return::success() + } + command::GET_LONG_ADDR => command_return::success_u64(self.addr_long.get()), + command::TURN_ON => { + self.radio_on.set(true); + command_return::success() + } + command::TURN_OFF => { + self.radio_on.set(false); + command_return::success() + } + command::TRANSMIT => { + let mut transmitted_frames = self.transmitted_frames.take(); + let tx_buf = self.tx_buf.take(); + transmitted_frames.push(Vec::from(tx_buf.as_ref())); + + self.tx_buf.set(tx_buf); + self.transmitted_frames.set(transmitted_frames); + self.share_ref + .schedule_upcall(subscribe::FRAME_TRANSMITTED, (2137, 0, 0)) + .expect("Unable to schedule upcall {}"); + + command_return::success() + } + _ => command_return::failure(ErrorCode::Invalid), + } + } + + fn allow_readonly( + &self, + buffer_num: u32, + buffer: crate::RoAllowBuffer, + ) -> Result { + if buffer_num == allow_ro::WRITE { + Ok(self.tx_buf.replace(buffer)) + } else { + Err((buffer, ErrorCode::Invalid)) + } + } + + fn allow_readwrite( + &self, + buffer_num: u32, + buffer: crate::RwAllowBuffer, + ) -> Result { + if buffer_num == allow_rw::READ { + Ok(self.rx_buf.replace(buffer)) + } else { + Err((buffer, ErrorCode::Invalid)) + } + } +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +// ----------------------------------------------------------------------------- +// 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/unittest/src/fake/mod.rs b/unittest/src/fake/mod.rs index ba042ab0..e2ec28a8 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -17,6 +17,7 @@ mod buttons; mod buzzer; mod console; mod gpio; +pub mod ieee802154; mod kernel; mod key_value; mod leds; @@ -36,6 +37,7 @@ pub use buttons::Buttons; pub use buzzer::Buzzer; pub use console::Console; pub use gpio::{Gpio, GpioMode, InterruptEdge, PullMode}; +pub use ieee802154::Ieee802154Phy; pub use kernel::Kernel; pub use key_value::KeyValue; pub use leds::Leds;