Update mailbox to support 'short' (28-byte) and 'long' (4k) messages.

MatchaApp has a trivial message-handling loop that just edits messages and sends them back.
Added support for async wait in MatchaApp, implemented in mailbox_client.rs
I also ran "cargo fmt", which shuffled a few "use" lines.
MailboxHAL is now static_init'd by MatchaPlatform and components/capsules can hold a static ref to it.
Mailbox IRQs now go through a trivial ISR trait.
Platform-specific mailbox constants moved to matcha/config/src/lib.rs

Change-Id: I8860a76b4b8e3c712206dff61bfb1faee156a657
diff --git a/app/src/mailbox_client.rs b/app/src/mailbox_client.rs
new file mode 100644
index 0000000..ca21744
--- /dev/null
+++ b/app/src/mailbox_client.rs
@@ -0,0 +1,69 @@
+use core::cell::Cell;
+use core::ptr;
+use libtock::futures;
+use libtock::syscalls;
+use matcha_config::*;
+
+//------------------------------------------------------------------------------
+
+pub struct MailboxClient {}
+
+impl MailboxClient {
+    pub fn init() {
+        let _ = syscalls::command(CAPSULE_MAILBOX, CMD_MAILBOX_INIT, 0, 0);
+    }
+
+    pub async fn wait_message_async(buffer: &mut [u8]) -> usize {
+        // To coordinate with async/await we need a "global-ish" flag that can
+        // have multiple mutable references. The Tock workaround is to put those
+        // kind of things in 'Cells'.
+        let message_size: Cell<usize> = Cell::new(0);
+
+        // The mailbox capsule will notify us using this callback,
+        unsafe extern "C" fn subscribe_callback(_ok: usize, _len: usize, _: usize, data: usize) {
+            // which just sets the message flag to true.
+            let message_size = &*(data as *const Cell<usize>);
+            message_size.set(_len)
+        }
+
+        // We give Tock a reference to our message and our callback+flag
+        unsafe {
+            syscalls::raw::allow(
+                CAPSULE_MAILBOX,
+                CMD_MAILBOX_RECV,
+                buffer.as_ptr() as *mut u8,
+                buffer.len(),
+            );
+            syscalls::raw::subscribe(
+                CAPSULE_MAILBOX,
+                CMD_MAILBOX_RECV,
+                subscribe_callback as *const _,
+                &message_size as *const _ as usize,
+            );
+        }
+
+        // and asynchronously wait until the callback sets the flag.
+        futures::wait_until(|| (message_size.get() > 0)).await;
+
+        // Then we clear Tock's references to our message and callback.
+        unsafe {
+            syscalls::raw::subscribe(CAPSULE_MAILBOX, 0, ptr::null(), 0);
+            syscalls::raw::allow(CAPSULE_MAILBOX, 0, ptr::null_mut(), 0);
+        }
+
+        return message_size.get();
+    }
+
+    pub fn send_message_sync(size: usize, buffer: &[u8]) {
+        unsafe {
+            syscalls::raw::allow(
+                CAPSULE_MAILBOX,
+                CMD_MAILBOX_SEND,
+                buffer.as_ptr() as *mut u8,
+                buffer.len(),
+            );
+            let _ = syscalls::command(CAPSULE_MAILBOX, CMD_MAILBOX_SEND, size, 0);
+            syscalls::raw::allow(CAPSULE_MAILBOX, CMD_MAILBOX_SEND, ptr::null_mut(), 0);
+        }
+    }
+}
diff --git a/app/src/main.rs b/app/src/main.rs
index f06d653..dcb381b 100644
--- a/app/src/main.rs
+++ b/app/src/main.rs
@@ -1,22 +1,58 @@
 #![no_std]
 
+mod dprintf;
+mod mailbox_client;
+
+use crate::mailbox_client::MailboxClient;
 use libtock::result::TockResult;
 use libtock::syscalls;
 use matcha_config::*;
 
-mod dprintf;
-
 libtock_core::stack_size! {0x1000}
 
+// Global static message buffer. The mailbox driver will copy incoming messages
+// here when they arrive.
+static mut MESSAGE_BUF: [u8; 4096] = [0; 4096];
+
 //------------------------------------------------------------------------------
 
 #[libtock::main]
 async fn main() -> TockResult<()> {
-    dprintf!("sw/matcha/app/src/main.rs::main()\n");
+    dprintf!("SEC: sw/matcha/app/src/main.rs::main()\n");
 
-    dprintf!("Booting sel4 from TockOS app!\n");
+    dprintf!("SEC: Booting sel4 from TockOS app!\n");
     let _result = syscalls::command(CAPSULE_ELFLOADER, CMD_ELFLOADER_BOOT_SEL4, 0, 0);
-    dprintf!("Booting sel4 from TockOS app done!\n");
+    dprintf!("SEC: Booting sel4 from TockOS app done!\n");
 
-    Ok(())
+    MailboxClient::init();
+
+    unsafe {
+        loop {
+            dprintf!("SEC: Waiting for request\n");
+            let message_size = MailboxClient::wait_message_async(&mut MESSAGE_BUF).await;
+            dprintf!("SEC: Request arrived, {} bytes\n", message_size);
+
+            let dst: *mut u32 = core::mem::transmute(MESSAGE_BUF.as_ptr());
+
+            // We don't actually have any message handling set up, so we just
+            // tweak the first and last dword of the message so the sender can
+            // tell we were able to modify it.
+            let last = (message_size / 4) - 1;
+            dprintf!("request[0] = {:X}\n", dst.offset(0).read());
+            dprintf!(
+                "request[{}] = {:X}\n",
+                last,
+                dst.offset(last as isize).read()
+            );
+
+            dst.offset(0).write(0x12345678);
+            dst.offset(last as isize).write(0x87654321);
+
+            dprintf!("SEC: Sending response\n");
+            MailboxClient::send_message_sync(message_size, &MESSAGE_BUF);
+
+            dprintf!("SEC: Response sent\n");
+        }
+    }
+    // Unreachable
 }
diff --git a/capsules/src/elfloader_capsule.rs b/capsules/src/elfloader_capsule.rs
index 393fec5..5cd6780 100644
--- a/capsules/src/elfloader_capsule.rs
+++ b/capsules/src/elfloader_capsule.rs
@@ -2,8 +2,21 @@
 
 use kernel::{AppId, AppSlice, Callback, Driver, ReturnCode, Shared};
 use matcha_hal::dprintf;
+use matcha_hal::mailbox_hal::MailboxHAL;
 
-pub struct ElfLoaderCapsule {}
+pub struct ElfLoaderCapsule {
+    mailbox_hal: Option<&'static dyn MailboxHAL>,
+}
+
+impl ElfLoaderCapsule {
+    pub fn new() -> Self {
+        Self { mailbox_hal: None }
+    }
+
+    pub fn set_mailbox(&mut self, mailbox: &'static dyn MailboxHAL) {
+        self.mailbox_hal = Some(mailbox);
+    }
+}
 
 impl Driver for ElfLoaderCapsule {
     fn subscribe(&self, _: usize, _: Option<Callback>, _: AppId) -> ReturnCode {
@@ -14,7 +27,7 @@
         dprintf!("ElfLoaderCapsule::command()\n");
 
         if minor_num == matcha_config::CMD_ELFLOADER_BOOT_SEL4 {
-            matcha_utils::load_sel4();
+            self.mailbox_hal.map(|mb| matcha_utils::load_sel4(mb));
             return ReturnCode::SUCCESS;
         }
 
diff --git a/capsules/src/mailbox_capsule.rs b/capsules/src/mailbox_capsule.rs
index 1b93b0d..e097131 100644
--- a/capsules/src/mailbox_capsule.rs
+++ b/capsules/src/mailbox_capsule.rs
@@ -1,29 +1,207 @@
-//! Stub Shodan mailbox driver capsule.
-
+use core::cell::Cell;
 use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared};
+use matcha_hal::dprintf;
+use matcha_hal::mailbox_hal::MailboxHAL;
 
-#[derive(Default)]
+use matcha_config::*;
+
+//------------------------------------------------------------------------------
+
 pub struct AppData {
-    pub buffer: Option<AppSlice<Shared, u8>>,
+    pub send_callback: Option<Callback>,
+    pub recv_callback: Option<Callback>,
+    pub send_buffer: Option<AppSlice<Shared, u8>>,
+    pub recv_buffer: Option<AppSlice<Shared, u8>>,
 }
 
+impl Default for AppData {
+    fn default() -> AppData {
+        AppData {
+            send_callback: None,
+            recv_callback: None,
+            send_buffer: None,
+            recv_buffer: None,
+        }
+    }
+}
+
+//------------------------------------------------------------------------------
+// MailboxCapsule is a global static singleton - its members have to be
+// pseudo-const.
+
 pub struct MailboxCapsule {
     pub app_data_grant: Grant<AppData>,
+    pub mailbox_hal: Cell<Option<&'static dyn MailboxHAL>>,
+    pub current_app: Cell<Option<AppId>>,
 }
 
+//----------------------------------------
+
+impl MailboxCapsule {
+    pub fn new(app_data_grant: Grant<AppData>) -> Self {
+        dprintf!("MailboxCapsule::new()\n");
+
+        return MailboxCapsule {
+            app_data_grant: app_data_grant,
+            mailbox_hal: Cell::new(None),
+            current_app: Cell::new(None),
+        };
+    }
+
+    pub fn set_mailbox(&self, mailbox: &'static dyn MailboxHAL) {
+        self.mailbox_hal.set(Some(mailbox));
+    }
+
+    pub fn handle_command(
+        &self,
+        app_id: AppId,
+        app_data: &mut AppData,
+        cmd_num: usize,
+        arg2: usize,
+        _arg3: usize,
+    ) -> ReturnCode {
+        match cmd_num {
+            CMD_MAILBOX_INIT => {
+                self.current_app.set(Some(app_id));
+                ReturnCode::SUCCESS
+            }
+            CMD_MAILBOX_SEND => {
+                let message_len = arg2;
+                match &app_data.send_buffer {
+                    Some(buffer) => {
+                        self.mailbox_hal.get().map(|mb| {
+                            let _ = mb.send_message_slice_sync(message_len, buffer);
+                        });
+                        ReturnCode::SUCCESS
+                    }
+                    None => ReturnCode::FAIL,
+                }
+            }
+            CMD_MAILBOX_RECV => {
+                // Nothing to do here yet?
+                ReturnCode::SUCCESS
+            }
+            _ => {
+                dprintf!("MailboxCapsule::handle_command({:?}, ????)\n", app_id);
+                ReturnCode::EINVAL
+            }
+        }
+    }
+
+    pub fn handle_subscribe(
+        &self,
+        _app_id: AppId,
+        app_data: &mut AppData,
+        minor_num: usize,
+        callback: Option<Callback>,
+    ) -> ReturnCode {
+        match minor_num {
+            CMD_MAILBOX_SEND => {
+                app_data.send_callback = callback;
+                ReturnCode::SUCCESS
+            }
+            CMD_MAILBOX_RECV => {
+                app_data.recv_callback = callback;
+                ReturnCode::SUCCESS
+            }
+            _ => ReturnCode::EINVAL,
+        }
+    }
+
+    pub fn handle_allow(
+        &self,
+        _app_id: AppId,
+        app_data: &mut AppData,
+        minor_num: usize,
+        slice: Option<AppSlice<Shared, u8>>,
+    ) -> ReturnCode {
+        match minor_num {
+            CMD_MAILBOX_SEND => {
+                app_data.send_buffer = slice;
+                ReturnCode::SUCCESS
+            }
+            CMD_MAILBOX_RECV => {
+                app_data.recv_buffer = slice;
+                ReturnCode::SUCCESS
+            }
+            _ => ReturnCode::EINVAL,
+        }
+    }
+}
+
+//------------------------------------------------------------------------------
+// Our "driver" trait impl just unwraps the app data grant and delegates to
+// MailboxCapsule.
+
 impl Driver for MailboxCapsule {
-    fn subscribe(&self, _: usize, _: Option<Callback>, _: AppId) -> ReturnCode {
-        ReturnCode::EINVAL
+    fn subscribe(&self, minor_num: usize, callback: Option<Callback>, app_id: AppId) -> ReturnCode {
+        self.app_data_grant
+            .enter(app_id, |app_data, _| {
+                self.handle_subscribe(app_id, app_data, minor_num, callback)
+            })
+            .unwrap_or_else(|err| err.into())
     }
 
-    fn command(&self, _minor_num: usize, _r2: usize, _r3: usize, _app_id: AppId) -> ReturnCode {
-        return ReturnCode::EINVAL;
+    fn command(&self, cmd_num: usize, r2: usize, r3: usize, app_id: AppId) -> ReturnCode {
+        self.app_data_grant
+            .enter(app_id, |app_data, _| {
+                self.handle_command(app_id, app_data, cmd_num, r2, r3)
+            })
+            .unwrap_or_else(|err| err.into())
     }
 
-    fn allow(&self, app_id: AppId, _: usize, slice: Option<AppSlice<Shared, u8>>) -> ReturnCode {
-        let _ = self.app_data_grant.enter(app_id, |app_data, _| {
-            app_data.buffer = slice;
+    fn allow(
+        &self,
+        app_id: AppId,
+        minor_num: usize,
+        slice: Option<AppSlice<Shared, u8>>,
+    ) -> ReturnCode {
+        self.app_data_grant
+            .enter(app_id, |app_data, _| {
+                self.handle_allow(app_id, app_data, minor_num, slice)
+            })
+            .unwrap_or_else(|err| err.into())
+    }
+}
+
+//------------------------------------------------------------------------------
+
+impl matcha_hal::mailbox_hal::MailboxISR for MailboxCapsule {
+    fn on_wtirq(&self) {
+        // Nothing to do here at the moment, sends are synchronous and the
+        // polarity of the ISR is backwards.
+    }
+
+    // When a message arrives, we copy it to the waiting app's buffer (if there
+    // is one) and then fire the app's callback. Async waiting is handled in
+    // app/src/mailbox_client.rs
+    fn on_rtirq(&self) {
+        // Unwrap all our optionals. There's probably a better way to do this.
+        self.current_app.get().map(|app_id| {
+            self.app_data_grant.enter(app_id, |app_data, _| {
+                app_data.recv_buffer.as_ref().map(|buffer| {
+                    self.mailbox_hal.get().map(|mailbox| {
+                        // Copy the message to the app's buffer and then
+                        // schedule the app callback.
+                        match mailbox.get_message_slice_sync(&buffer) {
+                            Ok(len) => {
+                                app_data.recv_callback.map(|mut callback| {
+                                    callback.schedule(1, len, 0);
+                                });
+                            }
+                            Err(len) => {
+                                app_data.recv_callback.map(|mut callback| {
+                                    callback.schedule(0, len, 0);
+                                });
+                            }
+                        }
+                    });
+                });
+            })
         });
-        return ReturnCode::SUCCESS;
+    }
+
+    fn on_eirq(&self) {
+        // We should probably do something here eventually.
     }
 }
diff --git a/config/src/lib.rs b/config/src/lib.rs
index 155222f..f7a74cc 100644
--- a/config/src/lib.rs
+++ b/config/src/lib.rs
@@ -15,8 +15,21 @@
 pub const CAPSULE_ELFLOADER: usize = 0x50004;
 pub const CAPSULE_MAILBOX: usize = 0x50005;
 
+pub const CMD_MAILBOX_INIT: usize = 1;
+pub const CMD_MAILBOX_SEND: usize = 2;
+pub const CMD_MAILBOX_RECV: usize = 3;
+pub const CMD_MAILBOX_SEND_PAGE: usize = 4;
+pub const CMD_MAILBOX_RECV_PAGE: usize = 5;
+
 pub const CMD_ELFLOADER_BOOT_SEL4: usize = 10;
 pub const CMD_DPRINTF_PRINT: usize = 0;
 
 pub const IRQ_UART0_TX_WATERMARK: u32 = 1;   // kTopMatchaPlicIrqIdUart0TxWatermark @ top_matcha.h
 pub const IRQ_UART0_RX_PARITY_ERR: u32 = 8;  // kTopMatchaPlicIrqIdUart0RxParityErr @ top_matcha.h
+
+pub const PLIC_BASE: u32 = 0x48000000;    // TOP_MATCHA_RV_PLIC_BASE_ADDR
+
+pub const MAILBOX_BASE: u32 = 0x40800000; // TOP_MATCHA_MAILBOX_SEC_BASE_ADDR
+pub const MAILBOX_WTIRQ: u32 = 181;       // kTopMatchaPlicIrqIdMailboxSecWtirq
+pub const MAILBOX_RTIRQ: u32 = 182;       // kTopMatchaPlicIrqIdMailboxSecRtirq
+pub const MAILBOX_EIRQ: u32 = 183;        // kTopMatchaPlicIrqIdMailboxSecEirq
\ No newline at end of file
diff --git a/hal/src/mailbox_hal.rs b/hal/src/mailbox_hal.rs
index cff310d..f28a76f 100644
--- a/hal/src/mailbox_hal.rs
+++ b/hal/src/mailbox_hal.rs
@@ -4,22 +4,28 @@
 // used in this file.
 #![allow(dead_code)]
 
+use core::cell::Cell;
 use core::mem::transmute;
+use core::slice;
 use kernel::common::registers::{register_structs, ReadOnly, ReadWrite, WriteOnly};
 use kernel::common::StaticRef;
+use kernel::{AppSlice, Shared};
 
 use crate::*;
 
-const MAILBOX_BASE: u32 = 0x40800000; // TOP_MATCHA_MAILBOX_SEC_BASE_ADDR
-const MAILBOX_WTIRQ: u32 = 181; // kTopMatchaPlicIrqIdMailboxSecWtirq
-const MAILBOX_RTIRQ: u32 = 182; // kTopMatchaPlicIrqIdMailboxSecRtirq
-const MAILBOX_EIRQ: u32 = 183; // kTopMatchaPlicIrqIdMailboxSecEirq
+//------------------------------------------------------------------------------
+
+// The high bit of the message header is used to distinguish between "inline"
+// messages that fit in the mailbox and "long" messages that contain the
+// physical address of a memory page containing the message.
+const HEADER_FLAG_LONG_MESSAGE: u32 = 0x80000000;
 
 const MAILBOX_SIZE_DWORDS: u32 = 8;
 
 const INTR_STATE_BIT_WTIRQ: u32 = 0b001;
 const INTR_STATE_BIT_RTIRQ: u32 = 0b010;
 const INTR_STATE_BIT_EIRQ: u32 = 0b100;
+const INTR_STATE_MASK: u32 = 0b111;
 
 const INTR_ENABLE_BIT_WTIRQ: u32 = 0b001;
 const INTR_ENABLE_BIT_RTIRQ: u32 = 0b010;
@@ -30,6 +36,8 @@
 const STATUS_BIT_WFIFOL: u32 = 0b0100;
 const STATUS_BIT_RFIFOL: u32 = 0b1000;
 
+//------------------------------------------------------------------------------
+
 register_structs! {
     pub MailboxRegisters {
         (0x000 => reg_intr_state:  ReadWrite<u32>),
@@ -42,96 +50,232 @@
         (0x01C => reg_wirqt:       ReadWrite<u32>),
         (0x020 => reg_rirqt:       ReadWrite<u32>),
         (0x024 => reg_ctrl:        WriteOnly<u32>),
-        (0x028 => @END),
+        (0x028 => reg_fifor:       ReadOnly<u32>),
+        (0x02C => reg_fifow:       ReadOnly<u32>),
+        (0x030 => @END),
     }
 }
 
-pub struct MailboxHAL {
+//------------------------------------------------------------------------------
+
+pub trait MailboxISR {
+    fn on_wtirq(&self);
+    fn on_rtirq(&self);
+    fn on_eirq(&self);
+}
+
+//------------------------------------------------------------------------------
+
+pub trait MailboxHAL {
+    fn get_message_sync(&self, buf: &mut [u8]) -> Result<usize, usize>;
+    fn send_message_sync(&self, message_size: usize, buf: &[u8]) -> Result<usize, usize>;
+    fn get_message_slice_sync(&self, slice: &AppSlice<Shared, u8>) -> Result<usize, usize>;
+    fn send_message_slice_sync(
+        &self,
+        message_len: usize,
+        slice: &AppSlice<Shared, u8>,
+    ) -> Result<usize, usize>;
+}
+
+//------------------------------------------------------------------------------
+
+pub struct MailboxImpl {
     regs: StaticRef<MailboxRegisters>,
-    plic_wtirq: u32,
-    plic_rtirq: u32,
-    plic_eirq: u32,
+    
+    // PLIC line for write interrupt
+    wtirq: u32, 
+    
+    // PLIC line for read interrupt
+    rtirq: u32,
+
+    // PLIC line for error interrupt
+    eirq: u32,
+
+    // Client interrupt callback
+    client_isr: Cell<Option<&'static dyn MailboxISR>>,
+
+    // Physical address of the memory page used to send the last message, if it
+    // was a long message.
+    message_page: Cell<u32>,
 }
 
-pub const MAILBOX: MailboxHAL = MailboxHAL {
-    regs: unsafe { StaticRef::new(MAILBOX_BASE as *mut MailboxRegisters) },
-    plic_wtirq: MAILBOX_WTIRQ,
-    plic_rtirq: MAILBOX_RTIRQ,
-    plic_eirq: MAILBOX_EIRQ,
-};
+//------------------------------------------------------------------------------
 
-// 32-bit-word bitfield helpers for the PLIC's irq enable lines.
+impl MailboxImpl {
+    pub fn new(mailbox_base_addr: u32, wtirq: u32, rtirq: u32, eirq: u32) -> Self {
+        let inst = Self {
+            regs: unsafe { StaticRef::new(mailbox_base_addr as *mut MailboxRegisters) },
+            wtirq: wtirq,
+            rtirq: rtirq,
+            eirq: eirq,
+            client_isr: Cell::new(None),
+            message_page: Cell::new(0),
+        };
 
-pub unsafe fn set_bit(base: u32, bit_index: isize) {
-    let buf: *mut u32 = transmute(base);
-    let mut bits = buf.offset(bit_index >> 5).read_volatile();
-    bits |= 1 << (bit_index & 31);
-    buf.offset(bit_index >> 5).write_volatile(bits);
-}
+        // Clear any old interrupts and enable receive and error interrupts.
+        inst.regs.reg_wirqt.set(0);
+        inst.regs.reg_rirqt.set(0);
+        inst.regs.reg_intr_state.set(INTR_STATE_MASK); // W1C
+        inst.regs
+            .reg_intr_enable
+            .set(INTR_ENABLE_BIT_RTIRQ | INTR_ENABLE_BIT_EIRQ);
+        unsafe {
+            MailboxImpl::set_bit(plic_hal::PLIC_EN0, inst.rtirq as isize);
+            MailboxImpl::set_bit(plic_hal::PLIC_EN0, inst.eirq as isize);
+        }
 
-pub unsafe fn clear_bit(base: u32, bit_index: isize) {
-    let buf: *mut u32 = transmute(base);
-    let mut bits = buf.offset(bit_index >> 5).read_volatile();
-    bits &= !(1 << (bit_index & 31));
-    buf.offset(bit_index >> 5).write_volatile(bits);
-}
+        return inst;
+    }
 
-impl MailboxHAL {
-    // Synchronously send a message. Blocks the caller if there's no room in the
-    // outbox.
-    pub fn send_message_sync(&self, message: &[u32]) {
-        for i in 0..message.len() {
-            // NOTE: The spec for the TLUL mailbox says that the write interrupt
-            // fires when the FIFO is _above_ the threshold, which is useless
-            // for us as we need to wait for it to fall _below_ a threshold.
-            // So, we have to busy-wait here, which is slightly annoying.
-            while (self.regs.reg_status.get() & STATUS_BIT_FULL) != 0 {}
-            self.regs.reg_mboxw.set(message[i]);
+    pub fn set_client_isr(&self, client: &'static dyn MailboxISR) {
+        self.client_isr.set(Some(client));
+    }
+
+    //--------------------------------------------------------------------------
+    // 32-bit-word bitfield helpers for the PLIC's irq enable lines.
+
+    unsafe fn set_bit(base: u32, bit_index: isize) {
+        let buf: *mut u32 = transmute(base);
+        let mut bits = buf.offset(bit_index >> 5).read_volatile();
+        bits |= 1 << (bit_index & 31);
+        buf.offset(bit_index >> 5).write_volatile(bits);
+    }
+
+    unsafe fn clear_bit(base: u32, bit_index: isize) {
+        let buf: *mut u32 = transmute(base);
+        let mut bits = buf.offset(bit_index >> 5).read_volatile();
+        bits &= !(1 << (bit_index & 31));
+        buf.offset(bit_index >> 5).write_volatile(bits);
+    }
+
+    fn enqueue_u32(&self, x: u32) {
+        while (self.regs.reg_status.get() & STATUS_BIT_FULL) == STATUS_BIT_FULL {}
+        self.regs.reg_mboxw.set(x);
+    }
+
+    fn dequeue_u32(&self) -> u32 {
+        while (self.regs.reg_status.get() & STATUS_BIT_EMPTY) == STATUS_BIT_EMPTY {}
+        return self.regs.reg_mboxr.get();
+    }
+
+    pub fn drain_read_fifo(&self) {
+        while (self.regs.reg_status.get() & STATUS_BIT_EMPTY) == 0 {
+            let _ = self.regs.reg_mboxr.get();
         }
     }
 
-    // Synchronously wait on a message to arrive. Blocks the caller if no
-    // message is in the inbox.
+    pub fn dump(&self) {
+        dprintf!("reg_intr_state  {:x}\n", self.regs.reg_intr_state.get());
+        dprintf!("reg_intr_enable {:x}\n", self.regs.reg_intr_enable.get());
+        dprintf!("reg_intr_test   {:x}\n", self.regs.reg_intr_test.get());
+        dprintf!("reg_status      {:x}\n", self.regs.reg_status.get());
+        dprintf!("reg_error       {:x}\n", self.regs.reg_error.get());
+        dprintf!("reg_wirqt       {:x}\n", self.regs.reg_wirqt.get());
+        dprintf!("reg_rirqt       {:x}\n", self.regs.reg_rirqt.get());
+        dprintf!("reg_fifor       {:x}\n", self.regs.reg_fifor.get());
+        dprintf!("reg_fifow       {:x}\n", self.regs.reg_fifow.get());
+    }
+}
 
-    pub fn wait_recv_sync(&self) {
-        // Set the mailbox to fire the read interrupt when there's more than
-        // zero items in it and enable the corresponding IRQ line in the PLIC so
-        // the interrupt will make it through to the CPU.
-        self.regs.reg_rirqt.set(0);
-        self.regs.reg_intr_enable.set(INTR_ENABLE_BIT_RTIRQ);
+//------------------------------------------------------------------------------
 
-        unsafe {
-            set_bit(plic_hal::PLIC_EN0, self.plic_rtirq as isize);
-        }
+impl MailboxHAL for MailboxImpl {
+    fn get_message_sync(&self, buf: &mut [u8]) -> Result<usize, usize> {
+        let header = self.dequeue_u32();
+        let message_size = header & !HEADER_FLAG_LONG_MESSAGE;
 
-        // Wait for the read interrupt to fire.
-        while (self.regs.reg_intr_state.get() & INTR_STATE_BIT_RTIRQ) == 0 {
+        if (header & HEADER_FLAG_LONG_MESSAGE) != 0 {
+            // Long message, copy from message page to result buffer.
+            let message_page = self.dequeue_u32();
+            self.message_page.set(message_page);
             unsafe {
-                asm!("wfi");
+                let src: *const u8 = transmute(message_page);
+                let dst: *mut u8 = transmute(buf.as_ptr());
+                core::ptr::copy_nonoverlapping(src, dst, message_size as usize);
+            }
+        } else {
+            // Short message, copy from mailbox to result buffer.
+            unsafe {
+                let dst: *mut u32 = transmute(buf.as_ptr());
+                let message_dwords = message_size / 4;
+                for i in 0..message_dwords {
+                    dst.offset(i as isize).write(self.dequeue_u32());
+                }
             }
         }
-        // NOTE: We don't need to deal with the PLIC claim/complete registers here
-        // as we're disabling the interrupt immediately after it arrives and before
-        // any other bit of code has a chance to claim it.
+        return Ok(message_size as usize);
+    }
 
-        // Turn the interrupt and IRQ line back off.
+    fn send_message_sync(&self, message_size: usize, buf: &[u8]) -> Result<usize, usize> {
         unsafe {
-            clear_bit(plic_hal::PLIC_EN0, self.plic_rtirq as isize);
-        }
-        self.regs.reg_intr_enable.set(0);
+            let message_page = self.message_page.get();
+            if message_page != 0 {
+                // Long message, reply using the same physical page as the request.
+                let src: *const u8 = transmute(buf.as_ptr());
+                let dst: *mut u8 = transmute(message_page);
+                core::ptr::copy_nonoverlapping(src, dst, message_size);
 
-        // Ack it in the mailbox so it doesn't re-fire later.
+                self.enqueue_u32(0x80000000 | message_size as u32);
+                self.enqueue_u32(message_page);
+
+                // Response sent, clear cached page address
+                self.message_page.set(0);
+            } else {
+                // Short message, respond via the mailbox.
+                let src: *const u32 = transmute(buf.as_ptr());
+                let message_dwords = message_size / 4;
+                self.enqueue_u32(message_size as u32);
+                for i in 0..message_dwords {
+                    self.enqueue_u32(src.offset(i as isize).read());
+                }
+            }
+        }
+
+        return Ok(message_size);
+    }
+
+    fn get_message_slice_sync(&self, slice: &AppSlice<Shared, u8>) -> Result<usize, usize> {
+        unsafe {
+            let buf: &mut [u8] = slice::from_raw_parts_mut(slice.ptr() as *mut u8, slice.len());
+            return self.get_message_sync(buf);
+        }
+    }
+
+    fn send_message_slice_sync(
+        &self,
+        message_len: usize,
+        slice: &AppSlice<Shared, u8>,
+    ) -> Result<usize, usize> {
+        unsafe {
+            let buf: &[u8] = slice::from_raw_parts(slice.ptr() as *const u8, slice.len());
+            return self.send_message_sync(message_len, buf);
+        }
+    }
+}
+
+//------------------------------------------------------------------------------
+// The interrupt handler in platform/src/chip.rs will call these handlers, which
+// in turn call the capsule-level handlers and then clear the interrupt.
+
+impl MailboxISR for MailboxImpl {
+    fn on_wtirq(&self) {
+        self.client_isr.get().map(|c| c.on_wtirq());
+        self.regs.reg_intr_state.set(INTR_STATE_BIT_WTIRQ);
+    }
+
+    fn on_rtirq(&self) {
+        self.client_isr.get().map(|c| c.on_rtirq());
+        if (self.regs.reg_status.get() & STATUS_BIT_EMPTY) == 0 {
+            self.drain_read_fifo();
+        }
         self.regs.reg_intr_state.set(INTR_STATE_BIT_RTIRQ);
     }
 
-    // Synchronously receive a message. Blocks the caller if no message is in
-    // the inbox.
-    pub unsafe fn recv_message_sync(&self, message: &mut [u32]) {
-        for i in 0..message.len() {
-            while (self.regs.reg_status.get() & STATUS_BIT_EMPTY) != 0 {
-                self.wait_recv_sync();
-            }
-            message[i] = self.regs.reg_mboxr.get();
-        }
+    fn on_eirq(&self) {
+        dprintf!("MailboxImpl::on_eirq fired - this is probably bad\n");
+        self.client_isr.get().map(|c| c.on_eirq());
+        self.regs.reg_intr_state.set(INTR_STATE_BIT_EIRQ);
     }
 }
+
+//------------------------------------------------------------------------------
diff --git a/platform/src/chip.rs b/platform/src/chip.rs
index f339bbf..96d1b4a 100644
--- a/platform/src/chip.rs
+++ b/platform/src/chip.rs
@@ -13,6 +13,7 @@
 use crate::uart;
 use matcha_hal::plic_hal;
 use matcha_config::*;
+use matcha_hal::mailbox_hal::MailboxISR;
 
 PMPConfigMacro!(4);
 
@@ -25,6 +26,7 @@
     userspace_kernel_boundary: SysCall,
     pmp: PMP,
     scheduler_timer: kernel::VirtualSchedulerTimer<A>,
+    mailbox_isr: Cell<Option<&'static dyn MailboxISR>>,
 }
 
 impl<A: 'static + Alarm<'static>> Matcha<A> {
@@ -33,9 +35,14 @@
             userspace_kernel_boundary: SysCall::new(),
             pmp: PMP::new(),
             scheduler_timer: kernel::VirtualSchedulerTimer::new(alarm),
+            mailbox_isr: Cell::new(None),
         }
     }
 
+    pub fn set_mailbox_isr(&self, mailbox_isr : &'static dyn MailboxISR) {
+        self.mailbox_isr.set(Some(mailbox_isr));
+    }
+
     pub unsafe fn enable_plic_interrupts(&self) {
         plic_hal::disable_all();
         plic_hal::clear_all_pending();
@@ -46,6 +53,9 @@
         while let Some(interrupt) = plic_hal::next_pending() {
             match interrupt {
                 IRQ_UART0_TX_WATERMARK..=IRQ_UART0_RX_PARITY_ERR => uart::UART0.handle_interrupt(),
+                MAILBOX_WTIRQ => {self.mailbox_isr.get().map(|mb| mb.on_wtirq());},
+                MAILBOX_RTIRQ => {self.mailbox_isr.get().map(|mb| mb.on_rtirq());},
+                MAILBOX_EIRQ  => {self.mailbox_isr.get().map(|mb| mb.on_eirq());},
                 _ => debug!("Pidx {}", interrupt),
             }
             plic_hal::complete(interrupt);
diff --git a/platform/src/main.rs b/platform/src/main.rs
index 9a811b2..00935f9 100644
--- a/platform/src/main.rs
+++ b/platform/src/main.rs
@@ -19,6 +19,7 @@
 use matcha_capsules::dprintf_capsule::DprintfCapsule;
 use matcha_capsules::elfloader_capsule::ElfLoaderCapsule;
 use matcha_capsules::storage_capsule::StorageCapsule;
+use matcha_capsules::mailbox_capsule::MailboxCapsule;
 use matcha_config::*;
 use matcha_hal::dprintf;
 use rv32i::csr;
@@ -70,6 +71,7 @@
     dprintf_capsule: &'static DprintfCapsule,
     storage_capsule: &'static StorageCapsule,
     elfloader_capsule: &'static ElfLoaderCapsule,
+    mailbox_capsule: &'static MailboxCapsule,
     lldb_capsule: &'static capsules::low_level_debug::LowLevelDebug<
         'static,
         capsules::virtual_uart::UartDevice<'static>,
@@ -88,6 +90,7 @@
             CAPSULE_DPRINTF => f(Some(self.dprintf_capsule)),
             CAPSULE_STORAGE => f(Some(self.storage_capsule)),
             CAPSULE_ELFLOADER => f(Some(self.elfloader_capsule)),
+            CAPSULE_MAILBOX => f(Some(self.mailbox_capsule)),
             CAPSULE_LLDB => f(Some(self.lldb_capsule)),
             _ => f(None),
         }
@@ -197,17 +200,46 @@
         )
     );
 
+    // Construct a mailbox that points to the platform-specific base address and
+    // PLIC lines
+    let mailbox_hal : &'static mut matcha_hal::mailbox_hal::MailboxImpl = static_init!(
+        matcha_hal::mailbox_hal::MailboxImpl,
+        matcha_hal::mailbox_hal::MailboxImpl::new(
+            MAILBOX_BASE,
+            MAILBOX_WTIRQ,
+            MAILBOX_RTIRQ,
+            MAILBOX_EIRQ,
+        )
+    );
+
     let elfloader_capsule = static_init!(
         matcha_capsules::elfloader_capsule::ElfLoaderCapsule,
-        matcha_capsules::elfloader_capsule::ElfLoaderCapsule {}
+        matcha_capsules::elfloader_capsule::ElfLoaderCapsule::new()
     );
 
+    let mailbox_capsule = static_init!(
+        matcha_capsules::mailbox_capsule::MailboxCapsule,
+        matcha_capsules::mailbox_capsule::MailboxCapsule::new(
+            board_kernel.create_grant(&memory_allocation_cap),
+        )
+    );
+
+    // Mailbox interrupts are delegated to the capsule, except for the
+    // bottom-level bit twiddling.
+    mailbox_hal.set_client_isr(mailbox_capsule);
+
+    // Our other capsules and our "chip" hold references to the mailbox.
+    mailbox_capsule.set_mailbox(mailbox_hal);
+    elfloader_capsule.set_mailbox(mailbox_hal);
+    chip.set_mailbox_isr(mailbox_hal);
+
     let platform = MatchaPlatform {
         console_capsule: console_capsule,
         alarm_capsule: alarm_capsule,
         dprintf_capsule: dprintf_capsule,
         storage_capsule: storage_capsule,
         elfloader_capsule: elfloader_capsule,
+        mailbox_capsule: mailbox_capsule,
         lldb_capsule: lldb_capsule,
     };
 
diff --git a/utils/src/lib.rs b/utils/src/lib.rs
index 03c45c6..37b2cd8 100644
--- a/utils/src/lib.rs
+++ b/utils/src/lib.rs
@@ -1,7 +1,7 @@
 #![no_std]
 
-use core::mem::transmute;
 use matcha_hal::dprintf;
+use matcha_hal::mailbox_hal::MailboxHAL;
 
 pub mod elf_loader;
 pub mod tar_loader;
@@ -13,7 +13,7 @@
 }
 
 // TODO(aappleby): Break this down into smaller pieces and move into app/main
-pub fn load_sel4() {
+pub fn load_sel4(mailbox : &'static dyn MailboxHAL) {
     unsafe {
         // Look in our tar file for the ELFs we need to boot Shodan.
         let sel4_elf = elf_loader::find_elf("kernel");
@@ -50,31 +50,15 @@
             - (elf_loader::elf_virt_min(sel4_segments)
                 - elf_loader::elf_phys_min(sel4_segments));
 
-        let message = [
-            transmute(sel4_pend),
-            transmute(
-                sel4_pend
-                    + round_up_to_page(
-                        elf_loader::elf_phys_max(capdl_segments)
-                            - elf_loader::elf_phys_min(capdl_segments),
-                    ),
-            ),
-            transmute(sel4_pend - elf_loader::elf_phys_min(capdl_segments)),
-            transmute((*capdl_elf).e_entry),
+        let message : [u32; 4] = [
+            sel4_pend,
+            sel4_pend + round_up_to_page(elf_loader::elf_phys_max(capdl_segments) - elf_loader::elf_phys_min(capdl_segments)),
+            sel4_pend - elf_loader::elf_phys_min(capdl_segments),
+            (*capdl_elf).e_entry,
         ];
-        matcha_hal::mailbox_hal::MAILBOX.send_message_sync(&message);
+        let _ = mailbox.send_message_sync(16, core::mem::transmute(message.as_ref()));
         dprintf!("seL4 boot message posted\n");
 
         SMC_CONTROL_BLOCK.write_volatile(entry_point);
-
-        dprintf!("SMC started, waiting on ACK\n");
-
-        let mut reply : [u32;1] = [0];
-        matcha_hal::mailbox_hal::MAILBOX.recv_message_sync(&mut reply);
-
-        dprintf!("ACK = 0x{:08X} received.\n", reply[0]);
-
-        // This message is checked in shodan_boot.robot - keep in sync.
-        dprintf!("load_sel4() completed successfully\n");
     }
 }