mailbox_client: revise message receive for race-free use
The previous structure for receiving mailbox messages was racey--it
allowed a reply to be sent without a receive posted. Rewrite the api
to ensure this cannot happen.
Fix: 300251123
Change-Id: Ied0e697f71c88de10e53217ff2ee02af28263673
diff --git a/app/src/mailbox_client.rs b/app/src/mailbox_client.rs
index 671542f..15e2f05 100644
--- a/app/src/mailbox_client.rs
+++ b/app/src/mailbox_client.rs
@@ -65,23 +65,47 @@
pub struct MailboxClient {}
+pub type WaitResult = (Option<bool>, usize, Option<usize>);
+
impl MailboxClient {
pub fn init() {
let _ = syscalls::command(CAPSULE_MAILBOX, CMD_MAILBOX_INIT, 0, 0);
let _ = syscalls::command(CAPSULE_STORAGE, CMD_STORAGE_INIT, 0, 0);
}
- // Waits for an inbound message that it writes to |buffer|. The msg
- // size (in bytes) is returned along with any associated page frame
- pub async fn wait_message_async(buffer: &mut [u8]) -> (usize, Option<usize>) {
- // Status, message size (bytes), attached page (or 0)
- type WaitResult = (Option<bool>, usize, Option<usize>);
- let message_result: Cell<WaitResult> = Cell::new((None, 0, None));
+ // Support for receiving inbound mailbox messages. This is split into
+ // multiple methods to support a race-free usage pattern:
+ //
+ // use MailboxClient::WaitResult;
+ // MailboxClient::init();
+ // let wait_result = Cell::new(WaitResult::default());
+ // MailboxClient::wait_message_init(&wait_result, unsafe { &mut MESSAGE_BUF });
+ // loop {
+ // MailboxClient::wait_message_async(&wait_result).await;
+ // // Status, message size (bytes), Option<attached page>
+ // let (_, request_size, opt_page) = wait_result.take();
+ //
+ // .. process received message ..
+ //
+ // MailboxClient::send_message_sync(reply_size, reply_slice);
+ // }
+ // MailboxClient::wait_message_cleanup();
+ //
+ // TODO(sleffler): maybe wrap in a struct w/ RAII cleanup
+ // Sets up the caller to receive a mailbox message in |buffer| using
+ // |wait_result| to pass data about the received message from the irq
+ // handler. The operation is completed asynchronously. The caller must
+ // poll the implied future until |wait_result| holds the received
+ // message data; typically using |wait_message_async|, Note that
+ // |wait_message_setup| registers a subscriber for callbacks from the
+ // irq handler; this must be cleared before |wait_result| is
+ // deallocated--usually by calling |wait_message_cleanup|.
+ pub fn wait_message_setup(wait_result: &Cell<WaitResult>, buffer: &mut [u8]) {
// Callback on recv complete.
- unsafe extern "C" fn subscribe_callback(ok: usize, len: usize, page: usize, data: usize) {
- let message_result = &*(data as *const Cell<WaitResult>);
- message_result.set((
+ unsafe extern "C" fn wait_callback(ok: usize, len: usize, page: usize, data: usize) {
+ let wait_result = &*(data as *const Cell<WaitResult>);
+ wait_result.set((
Some(ok != 0),
len,
if page == 0 { None } else { Some(page) },
@@ -101,22 +125,40 @@
syscalls::raw::subscribe(
CAPSULE_MAILBOX,
CMD_MAILBOX_RECV,
- subscribe_callback as *const _,
- &message_result as *const _ as usize,
+ wait_callback as *const _,
+ wait_result as *const _ as usize,
);
}
- futures::wait_until(|| (message_result.get().0.is_some())).await;
+ }
+ // Cleans up state setup by |wait_message_setup|.
+ pub fn wait_message_cleanup() {
unsafe {
syscalls::raw::subscribe(CAPSULE_MAILBOX, 0, ptr::null(), 0);
syscalls::raw::allow(CAPSULE_MAILBOX, 0, ptr::null_mut(), 0);
}
+ }
+ // Waits for a mailbox message to be received.
+ pub async fn wait_message_async(wait_result: &Cell<WaitResult>) {
+ futures::wait_until(|| (wait_result.get().0.is_some())).await;
+ }
- let (_, msg_size, attached_page) = message_result.into_inner();
+ // Waits for an inbound message that it writes to |buffer|. The msg
+ // size (in bytes) is returned along with any associated page frame
+ #[allow(dead_code)]
+ pub async fn wait_message_sync(buffer: &mut [u8]) -> (usize, Option<usize>) {
+ let wait_result = Cell::new(WaitResult::default());
+ Self::wait_message_setup(&wait_result, buffer);
+
+ Self::wait_message_async(&wait_result).await;
+ Self::wait_message_cleanup();
+
+ // Status, message size (bytes), attached page (or 0)
+ let (_, msg_size, attached_page) = wait_result.into_inner();
(msg_size, attached_page)
}
// Sends the message of |size| bytes that resides in |buffer|
- // XXX why isn't buffer.len() used?
+ // XXX why isn't buffer.len() used instead of |size|?
pub fn send_message_sync(size: usize, buffer: &[u8]) {
unsafe {
syscalls::raw::allow(
diff --git a/app/src/main.rs b/app/src/main.rs
index 874faa4..9ca6a24 100644
--- a/app/src/main.rs
+++ b/app/src/main.rs
@@ -1,8 +1,11 @@
#![no_std]
+use core::cell::Cell;
+
mod dprintf;
mod mailbox_client;
use mailbox_client::MailboxClient;
+use mailbox_client::WaitResult;
use libtock::result::TockResult;
use libtock::syscalls;
@@ -23,11 +26,14 @@
dprintf!("SEC: Booting seL4 from TockOS app done!\r\n");
MailboxClient::init();
+ let wait_result = Cell::new(WaitResult::default());
+ MailboxClient::wait_message_setup(&wait_result, unsafe { &mut MESSAGE_BUF });
unsafe {
loop {
- let (request_size, opt_page) =
- MailboxClient::wait_message_async(&mut MESSAGE_BUF).await;
+ MailboxClient::wait_message_async(&wait_result).await;
+ // Status, message size (bytes), Option<attached page>
+ let (_, request_size, opt_page) = wait_result.take();
let (request_slice, reply_slice) = MESSAGE_BUF.split_at_mut(request_size);
let reply_size = MailboxClient::dispatch(request_slice, opt_page, reply_slice)