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/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;