Skip to content

Commit

Permalink
Merge pull request tock#3851 from tyler-potyondy/15.4-syscall
Browse files Browse the repository at this point in the history
802.15.4 Send Raw Syscall
  • Loading branch information
bradjc authored Feb 26, 2024
2 parents 20246e6 + 7a57025 commit 81602d4
Show file tree
Hide file tree
Showing 4 changed files with 259 additions and 73 deletions.
19 changes: 19 additions & 0 deletions capsules/extra/src/ieee802154/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,25 @@ pub trait MacDevice<'a> {
security_needed: Option<(SecurityLevel, KeyId)>,
) -> Result<Frame, &'static mut [u8]>;

/// Creates an IEEE 802.15.4 Frame object that is compatible with the
/// MAC transmit and append payload methods. This serves to provide
/// functionality for sending packets fully formed by the userprocess
/// and that the 15.4 capsule does not modify. The len field may be less
/// than the length of the buffer as the len field is the length of
/// the current frame while the buffer is the maximum 15.4 frame size.
///
/// - `buf`: The buffer to be used for the frame
/// - `len`: The length of the frame
///
/// Returns a Result:
/// - on success a Frame object.
/// - on failure an error returning the buffer.
fn buf_to_frame(
&self,
buf: &'static mut [u8],
len: usize,
) -> Result<Frame, (ErrorCode, &'static mut [u8])>;

/// Transmits a frame that has been prepared by the above process. If the
/// transmission process fails, the buffer inside the frame is returned so
/// that it can be re-used.
Expand Down
176 changes: 127 additions & 49 deletions capsules/extra/src/ieee802154/driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,9 +177,41 @@ impl KeyDescriptor {
}
}

/// Denotes the type of pending transmission. A `Parse(..)` PendingTX
/// indicates that the 15.4 framer will need to form the packet header
/// from the provided address and security level. A `Raw` PendingTX
/// is formed by the userprocess and passes through the Framer unchanged.
enum PendingTX {
Parse(u16, Option<(SecurityLevel, KeyId)>),
Raw,
Empty,
}

impl Default for PendingTX {
/// The default PendingTX is `Empty`
fn default() -> Self {
PendingTX::Empty
}
}

impl PendingTX {
/// Returns true if the PendingTX state is `Empty`
fn is_empty(&self) -> bool {
match self {
PendingTX::Empty => true,
_ => false,
}
}

/// Take the pending transmission, replacing it with `Empty` and return the current PendingTx
fn take(&mut self) -> PendingTX {
core::mem::replace(self, PendingTX::Empty)
}
}

#[derive(Default)]
pub struct App {
pending_tx: Option<(u16, Option<(SecurityLevel, KeyId)>)>,
pending_tx: PendingTX,
}

pub struct RadioDriver<'a> {
Expand Down Expand Up @@ -383,7 +415,7 @@ impl<'a> RadioDriver<'a> {
for app in self.apps.iter() {
let processid = app.processid();
app.enter(|app, _| {
if app.pending_tx.is_some() {
if !app.pending_tx.is_empty() {
pending_app = Some(processid);
}
});
Expand Down Expand Up @@ -413,56 +445,77 @@ impl<'a> RadioDriver<'a> {
/// idle and the app has a pending transmission.
#[inline]
fn perform_tx_sync(&self, processid: ProcessId) -> Result<(), ErrorCode> {
self.apps.enter(processid, |app, kerel_data| {
let (dst_addr, security_needed) = match app.pending_tx.take() {
Some(pending_tx) => pending_tx,
None => {
return Ok(());
}
};
let result = self.kernel_tx.take().map_or(Err(ErrorCode::NOMEM), |kbuf| {
// Prepare the frame headers
let pan = self.mac.get_pan();
let dst_addr = MacAddress::Short(dst_addr);
let src_addr = MacAddress::Short(self.mac.get_address());
let mut frame = match self.mac.prepare_data_frame(
kbuf,
pan,
dst_addr,
pan,
src_addr,
security_needed,
) {
Ok(frame) => frame,
Err(kbuf) => {
self.kernel_tx.replace(kbuf);
return Err(ErrorCode::FAIL);
self.apps.enter(processid, |app, kernel_data| {
// The use of this take method is somewhat overkill, but serves to ensure that
// this, or future, implementations do not forget to "remove" the pending_tx
// from the app after processing.
let curr_tx = app.pending_tx.take();

// Before beginning the transmission process, confirm that the PendingTX
// is not Empty. In the Empty case, there is nothing to transmit and we
// can return Ok(()) immediately as there is nothing to transmit.
if let PendingTX::Empty = curr_tx { return Ok(()) }

// At a high level, we must form a Frame from the provided userproceess data,
// place this frame into a static buffer, and then transmit the frame. This is
// somewhat complicated by the need to error handle each of these steps and also
// provide Raw and Parse sending modes (i.e. Raw mode userprocess fully forms
// 15.4 packet and Parse mode the 15.4 framer forms the packet from the userprocess
// parameters and payload). Because we first take this kernel buffer, we must
// replace the `kernel_tx` buffer upon handling any error.
self.kernel_tx.take().map_or(Err(ErrorCode::NOMEM), |kbuf| {
match curr_tx {
PendingTX::Empty => {
unreachable!("PendingTX::Empty should have been handled earlier with guard statement.")
}
PendingTX::Raw => {
// Here we form an empty frame from the buffer to later be filled by the specified
// userprocess frame data. Note, we must allocate the needed buffer for the frame,
// but set the `len` field to 0 as the frame is empty.
self.mac.buf_to_frame(kbuf, 0).map_err(|(err, buf)| {
self.kernel_tx.replace(buf);
err
})},
PendingTX::Parse(dst_addr, security_needed) => {
// Prepare the frame headers
let pan = self.mac.get_pan();
let dst_addr = MacAddress::Short(dst_addr);
let src_addr = MacAddress::Short(self.mac.get_address());
self.mac.prepare_data_frame(
kbuf,
pan,
dst_addr,
pan,
src_addr,
security_needed,
).map_or_else(|err_buf| {
self.kernel_tx.replace(err_buf);
Err(ErrorCode::FAIL)
}, | frame|
Ok(frame)
)
}
};

// Append the payload: there must be one
let result = kerel_data
.get_readonly_processbuffer(ro_allow::WRITE)
.and_then(|write| write.enter(|payload| frame.append_payload_process(payload)))
.unwrap_or(Err(ErrorCode::INVAL));
if result != Ok(()) {
return result;
}
}).map(|mut frame| {

// Finally, transmit the frame
match self.mac.transmit(frame) {
Ok(()) => Ok(()),
Err((ecode, buf)) => {
self.kernel_tx.put(Some(buf));
Err(ecode)
}
// Obtain the payload from the userprocess, append the "payload" to the previously formed frame
// and pass the frame to be transmitted. Note, the term "payload" is somewhat misleading in the
// case of Raw transmission as the payload is the entire 15.4 frame.
kernel_data
.get_readonly_processbuffer(ro_allow::WRITE)
.and_then(|write| write.enter(|payload|
frame.append_payload_process(payload)
))?.map( |()|
{
self.mac.transmit(frame).map_or_else(|(errorcode, error_buf)| {
self.kernel_tx.replace(error_buf);
Err(errorcode)
}, |()| {self.current_app.set(processid); Ok(()) }
)
}
});
if result == Ok(()) {
self.current_app.set(processid);
}
result
)?
})?
})?
}

/// Schedule the next transmission if there is one pending. Performs the
Expand Down Expand Up @@ -623,6 +676,10 @@ impl SyscallDriver for RadioDriver<'_> {
/// 9 bytes: the key ID (might not use all bytes) +
/// 16 bytes: the key.
/// - `25`: Remove the key at an index.
/// - `26`: Transmit a frame (parse required). Take the provided payload and
/// parameters to encrypt, form headers, and transmit the frame.
/// - `27`: Transmit a frame (raw). Transmit preformed 15.4 frame (i.e.
/// headers and security etc completed by userprocess).
fn command(
&self,
command_number: usize,
Expand Down Expand Up @@ -862,7 +919,7 @@ impl SyscallDriver for RadioDriver<'_> {
26 => {
self.apps
.enter(processid, |app, kernel_data| {
if app.pending_tx.is_some() {
if !app.pending_tx.is_empty() {
// Cannot support more than one pending tx per process.
return Err(ErrorCode::BUSY);
}
Expand Down Expand Up @@ -900,7 +957,13 @@ impl SyscallDriver for RadioDriver<'_> {
if next_tx.is_none() {
return Err(ErrorCode::INVAL);
}
app.pending_tx = next_tx;

match next_tx {
Some((dst_addr, sec)) => {
app.pending_tx = PendingTX::Parse(dst_addr, sec)
}
None => app.pending_tx = PendingTX::Empty,
}
Ok(())
})
.map_or_else(
Expand All @@ -911,6 +974,21 @@ impl SyscallDriver for RadioDriver<'_> {
},
)
}
27 => {
self.apps
.enter(processid, |app, _| {
if !app.pending_tx.is_empty() {
// Cannot support more than one pending tx per process.
return Err(ErrorCode::BUSY);
}
app.pending_tx = PendingTX::Raw;
Ok(())
})
.map_or_else(
|err| CommandReturn::failure(err.into()),
|_| self.do_next_tx_sync(processid).into(),
)
}
_ => CommandReturn::failure(ErrorCode::NOSUPPORT),
}
}
Expand Down
Loading

0 comments on commit 81602d4

Please sign in to comment.