Skip to content

Commit

Permalink
Merge pull request #3899 from tyler-potyondy/15.4-syscall
Browse files Browse the repository at this point in the history
15.4 Receive Ring Buffer
  • Loading branch information
alevy committed Mar 15, 2024
2 parents f5672de + d348c50 commit 11024ff
Showing 1 changed file with 121 additions and 7 deletions.
128 changes: 121 additions & 7 deletions capsules/extra/src/ieee802154/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,61 @@
//! Implements a userspace interface for sending and receiving IEEE 802.15.4
//! frames. Also provides a minimal list-based interface for managing keys and
//! known link neighbors, which is needed for 802.15.4 security.
//!
//! The driver functionality can be divided into three aspects: sending
//! packets, receiving packets, and managing the 15.4 state (i.e. keys, neighbors,
//! buffers, addressing, etc). The general design and procedure for sending and
//! receiving is discussed below.
//!
//! Sending - The driver supports two modes of sending: Raw and Parse. In Raw mode,
//! the userprocess fully forms the 15.4 frame and passes it to the driver. In Parse
//! mode, the userprocess provides the payload and relevant metadata. From this
//! the driver forms the 15.4 header and secures the payload. To send a packet,
//! the userprocess issues the respective send command syscall (corresponding to
//! raw or parse mode of sending). The 15.4 capsule will then schedule an upcall,
//! upon completion of the transmission, to notify the process.
//!
//! Receiving - The driver receives 15.4 frames and passes them to the userprocess.
//! To accomplish this, the userprocess must first `allow` a read/write ring buffer
//! to the kernel. The kernel will then fill this buffer with received frames and
//! schedule an upcall upon receipt of the first packet. When handling the upcall
//! the userprocess must first `unallow` the buffer as described in section 4.4 of
//! TRD104-syscalls. After unallowing the buffer, the userprocess must then immediately
//! clear all pending/scheduled receive upcalls. This is done by either unsubscribing
//! the receive upcall or subscribing a new receive upcall. Because the userprocess
//! provides the buffer, it is responsible for adhering to this procedure. Failure
//! to comply may result in dropped or malformed packets.
//!
//! The ring buffer provided by the userprocess must be of the form:
//!
//! ```text
//! | read index | write index | user_frame 0 | user_frame 1 | ... | user_frame n |
//! ```
//!
//! `user_frame` denotes the 15.4 frame in addition to the relevant 3 bytes of
//! metadata (offset to data payload, length of data payload, and the MIC len). The
//! capsule assumes that this is the form of the buffer. Errors or deviation in
//! the form of the provided buffer will likely result in incomplete or dropped packets.
//!
//! Because the scheduled receive upcall must be handled by the userprocess, there is
//! no guarantee as to when this will occur and if additional packets will be received
//! prior to the upcall being handled. Without a ring buffer (or some equivalent data
//! structure), the original packet will be lost. The ring buffer allows for the upcall
//! to be scheduled and for all received packets to be passed to the process. The ring
//! buffer is designed to overwrite old packets if the buffer becomes full. If the
//! userprocess notices a high number of "dropped" packets, this may be the cause. The
//! userproceess can mitigate this issue by increasing the size of the ring buffer
//! provided to the capsule.

use crate::ieee802154::{device, framer};
use crate::net::ieee802154::{AddressMode, Header, KeyId, MacAddress, PanID, SecurityLevel};
use crate::net::stream::{decode_bytes, decode_u8, encode_bytes, encode_u8, SResult};

use core::cell::Cell;
use core::cmp::min;

use kernel::deferred_call::{DeferredCall, DeferredCallClient};
use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
use kernel::hil::radio::{MAX_FRAME_SIZE, PSDU_OFFSET};
use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer};
use kernel::syscall::{CommandReturn, SyscallDriver};
use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell};
Expand All @@ -25,6 +70,9 @@ use kernel::{ErrorCode, ProcessId};
const MAX_NEIGHBORS: usize = 4;
const MAX_KEYS: usize = 4;

const USER_FRAME_MAX_SIZE: usize = USER_FRAME_METADATA_SIZE + MAX_FRAME_SIZE; // 127B max payload + 3B metadata
const USER_FRAME_METADATA_SIZE: usize = 3; // 3B metadata (offset, len, mic_len)

/// IDs for subscribed upcalls.
mod upcall {
/// Frame is received
Expand Down Expand Up @@ -1042,12 +1090,78 @@ impl<'a, M: device::MacDevice<'a>> device::RxClient for RadioDriver<'a, M> {
.get_readwrite_processbuffer(rw_allow::READ)
.and_then(|read| {
read.mut_enter(|rbuf| {
let len = min(rbuf.len(), data_offset + data_len);
// Copy the entire frame over to userland, preceded by two
// bytes: the data offset and the data length.
rbuf[..len].copy_from_slice(&buf[..len]);
rbuf[0].set(data_offset as u8);
rbuf[1].set(data_len as u8);
///////////////////////////////////////////////////////////////////////////////////////////
// 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 index | write index | user_frame 0 | user_frame 1 | ... | user_frame n |
// user_frame format:
// | data_offset | data_len | mic_len | 15.4 frame |
///////////////////////////////////////////////////////////////////////////////////////////

// 2 bytes for the readwrite buffer metadata (read / write index)
const RING_BUF_METADATA_SIZE: usize = 2;

// 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).
if rbuf.len() <= RING_BUF_METADATA_SIZE
|| (rbuf.len() - RING_BUF_METADATA_SIZE) % USER_FRAME_MAX_SIZE != 0
{
// kernel::debug!("[15.4 Driver] Error - improperly formatted readwrite buffer provided");
return false;
}

let mic_len = header.security.map_or(0, |sec| sec.level.mic_len());
let frame_len = data_offset + data_len + mic_len;

// Frame length incorporates the PSDU offset, so we must subtract this value
// in addition to incorporating the user frame metadata size to obtain the
// length of the user frame.
let user_frame_len = frame_len + USER_FRAME_METADATA_SIZE - PSDU_OFFSET;

let mut read_index = rbuf[0].get() as usize;
let mut write_index = rbuf[1].get() 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)
if read_index >= max_pending_rx || write_index >= max_pending_rx {
// kernel::debug!("[15.4 driver] Invalid read or write index");
return false;
}

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 data offset, the data length, and the MIC length.
rbuf[(offset + USER_FRAME_METADATA_SIZE)..(offset + user_frame_len)]
.copy_from_slice(&buf[PSDU_OFFSET..frame_len]);
rbuf[offset].set(data_offset as u8);
rbuf[offset + 1].set(data_len as u8);
rbuf[offset + 2].set(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].set(read_index as u8);
// kernel::debug!("[15.4 driver] Provided RX buffer is full");
}

// update write index metadata (we do not modify the read index
// in the recv functionality so we do not need to update this metadata)
rbuf[1].set(write_index as u8);
true
})
})
Expand Down

0 comments on commit 11024ff

Please sign in to comment.