| //! Matcha hardware mailbox. |
| |
| // We want constants & registers to stay together even if they're not currently |
| // 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::*; |
| |
| //------------------------------------------------------------------------------ |
| |
| // 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; |
| const INTR_ENABLE_BIT_EIRQ: u32 = 0b100; |
| |
| const STATUS_BIT_EMPTY: u32 = 0b0001; |
| const STATUS_BIT_FULL: u32 = 0b0010; |
| const STATUS_BIT_WFIFOL: u32 = 0b0100; |
| const STATUS_BIT_RFIFOL: u32 = 0b1000; |
| |
| //------------------------------------------------------------------------------ |
| |
| register_structs! { |
| pub MailboxRegisters { |
| (0x000 => reg_intr_state: ReadWrite<u32>), |
| (0x004 => reg_intr_enable: ReadWrite<u32>), |
| (0x008 => reg_intr_test: ReadWrite<u32>), |
| (0x00C => reg_mboxw: WriteOnly<u32>), |
| (0x010 => reg_mboxr: ReadOnly<u32>), |
| (0x014 => reg_status: ReadOnly<u32>), |
| (0x018 => reg_error: ReadOnly<u32>), |
| (0x01C => reg_wirqt: ReadWrite<u32>), |
| (0x020 => reg_rirqt: ReadWrite<u32>), |
| (0x024 => reg_ctrl: WriteOnly<u32>), |
| (0x028 => reg_fifor: ReadOnly<u32>), |
| (0x02C => reg_fifow: ReadOnly<u32>), |
| (0x030 => @END), |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| 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 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>, |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| 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), |
| }; |
| |
| // 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); |
| } |
| |
| return inst; |
| } |
| |
| 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(); |
| } |
| } |
| |
| 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()); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |
| |
| 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; |
| |
| 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 { |
| 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()); |
| } |
| } |
| } |
| return Ok(message_size as usize); |
| } |
| |
| fn send_message_sync(&self, message_size: usize, buf: &[u8]) -> Result<usize, usize> { |
| unsafe { |
| 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); |
| |
| 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); |
| } |
| |
| 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); |
| } |
| } |
| |
| //------------------------------------------------------------------------------ |