blob: f653a2fa4fb515e15b2ed3cbf9f59e9ae0cd16d8 [file] [log] [blame]
//! Matcha hardware mailbox.
//! TODO(aappleby): Rework this to match the RTL implementation.
use core::mem::transmute;
use kernel::common::registers::{register_structs, ReadWrite};
use kernel::common::StaticRef;
use crate::dprintf;
use crate::*;
pub const MAILBOX0_BASE: u32 = 0x400F0000;
pub const MAILBOX1_BASE: u32 = 0x400F1000;
pub const MAILBOX_SIZE_DWORDS: usize = 8;
register_structs! {
pub MailboxRegisters {
(0x000 => message: [ReadWrite<u32>; 8]),
(0x020 => irq_send: ReadWrite<u32>),
(0x024 => irq_recv: ReadWrite<u32>),
(0x028 => @END),
}
}
pub struct Mailbox {
regs: StaticRef<MailboxRegisters>,
plic_irq_send: isize,
plic_irq_recv: isize,
}
pub const MAILBOX0: Mailbox = Mailbox {
regs: unsafe { StaticRef::new(MAILBOX0_BASE as *mut MailboxRegisters) },
plic_irq_send: 100,
plic_irq_recv: 101,
};
pub const MAILBOX1: Mailbox = Mailbox {
regs: unsafe { StaticRef::new(MAILBOX1_BASE as *mut MailboxRegisters) },
plic_irq_send: 102,
plic_irq_recv: 103,
};
// 32-bit-word bitfield helper for the PLIC's irq enable lines.
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);
}
impl Mailbox {
pub unsafe fn get_message(&self, message: &mut [u32]) {
if message.len() > MAILBOX_SIZE_DWORDS {
dprintf!("get_message() - Bad message size {}\n", message.len());
return;
}
// Empty the mailbox - must be done with dword-sized writes
for i in 0..message.len() {
message[i] = self.regs.message[i].get();
self.regs.message[i].set(0);
}
}
pub unsafe fn set_message(&self, message: &[u32]) {
if message.len() > MAILBOX_SIZE_DWORDS {
dprintf!("set_message() - Bad message size {}\n", message.len());
return;
}
// Fill the mailbox - must be done with dword-sized writes
for i in 0..message.len() {
self.regs.message[i].set(message[i]);
}
}
// This requires a small bit of explanation - if an interrupt were to happen
// between the 'if (*flag == 0)' and 'asm("wfi")' statements, we could end up
// waiting forever for an interrupt that had already arrived. To prevent that,
// we have to globally disable interrupts around those statements. Once
// an interrupt has been triggered, 'wfi' will continue and the isr will
// execute immediately after global interrupts are re-enabled.
unsafe fn wait_until_full(&self) {
while self.regs.irq_send.get() == 0 {
clear_mstatus_bits(MIE_BIT | SIE_BIT);
if self.regs.irq_send.get() == 0 {
asm!("wfi");
}
set_mstatus_bits(MIE_BIT | SIE_BIT);
}
}
unsafe fn wait_until_empty(&self) {
while self.regs.irq_send.get() != 0 {
clear_mstatus_bits(MIE_BIT | SIE_BIT);
if self.regs.irq_send.get() != 0 {
asm!("wfi");
}
set_mstatus_bits(MIE_BIT | SIE_BIT);
}
}
unsafe fn wait_until_recv(&self) {
while self.regs.irq_recv.get() == 0 {
clear_mstatus_bits(MIE_BIT | SIE_BIT);
if self.regs.irq_recv.get() == 0 {
asm!("wfi");
}
set_mstatus_bits(MIE_BIT | SIE_BIT);
}
}
pub unsafe fn send_message(&self, message: &[u32], wait_for_ack: bool) {
self.wait_until_empty();
self.set_message(message);
self.regs.irq_send.set(1);
if wait_for_ack {
// Enable the RECV irq line, then wait for RECV. The ISR will disable the
// irq line once it fires.
set_bit(plic::PLIC_EN0, self.plic_irq_recv);
self.wait_until_recv();
self.regs.irq_recv.set(0);
}
}
pub unsafe fn recv_message(&self, message: &mut [u32], send_ack: bool) {
// Enable the SEND irq line and wait for new mail. The ISR will disable the
// irq line once it fires.
set_bit(plic::PLIC_EN1, self.plic_irq_send);
self.wait_until_full();
self.regs.irq_send.set(0);
// Message has arrived.
self.get_message(message);
if send_ack {
// Trigger RECV
self.regs.irq_recv.set(1);
}
}
}