| //! Matcha hardware mailbox. |
| |
| use core::mem::transmute; |
| use kernel::common::registers::{register_structs, ReadOnly, ReadWrite, WriteOnly}; |
| use kernel::common::StaticRef; |
| |
| use crate::*; |
| |
| // TODO(aappleby): These constants are hardcoded pending the merge of the |
| // mailbox RTL. |
| |
| pub const MAILBOX0_BASE: u32 = 0x400F0000; |
| pub const MAILBOX1_BASE: u32 = 0x400F1000; |
| pub const MAILBOX_SIZE_DWORDS: u32 = 8; |
| pub const MAILBOX_STATUS_HIWATER: u32 = 0b0100; |
| pub const MAILBOX_STATUS_LOWATER: u32 = 0b1000; |
| |
| pub const MAILBOX0_HIWATER_IRQ: isize = 100; |
| pub const MAILBOX0_LOWATER_IRQ: isize = 101; |
| |
| pub const MAILBOX1_HIWATER_IRQ: isize = 102; |
| pub const MAILBOX1_LOWATER_IRQ: isize = 103; |
| |
| pub const IRQ_ERROR : u32 = 0b001; |
| pub const IRQ_HIWATER : u32 = 0b010; |
| pub const IRQ_LOWATER : u32 = 0b100; |
| |
| register_structs! { |
| pub MailboxRegisters { |
| (0x000 => reg_istate: ReadWrite<u32>), |
| (0x004 => reg_ienable: ReadWrite<u32>), |
| (0x008 => reg_itest: ReadWrite<u32>), |
| (0x00C => reg_write: WriteOnly<u32>), |
| (0x010 => reg_read: ReadOnly<u32>), |
| (0x014 => reg_status: ReadOnly<u32>), |
| (0x018 => reg_error: ReadOnly<u32>), |
| (0x01C => reg_hiwater: ReadWrite<u32>), |
| (0x020 => reg_lowater: ReadWrite<u32>), |
| (0x024 => reg_flush: WriteOnly<u32>), |
| (0x028 => @END), |
| } |
| } |
| |
| pub struct Mailbox { |
| regs: StaticRef<MailboxRegisters>, |
| plic_irq_hiwater: isize, |
| plic_irq_lowater: isize, |
| } |
| |
| pub const MAILBOX0: Mailbox = Mailbox { |
| regs: unsafe { StaticRef::new(MAILBOX0_BASE as *mut MailboxRegisters) }, |
| plic_irq_hiwater: MAILBOX0_HIWATER_IRQ, |
| plic_irq_lowater: MAILBOX0_LOWATER_IRQ, |
| }; |
| |
| pub const MAILBOX1: Mailbox = Mailbox { |
| regs: unsafe { StaticRef::new(MAILBOX1_BASE as *mut MailboxRegisters) }, |
| plic_irq_hiwater: MAILBOX1_HIWATER_IRQ, |
| plic_irq_lowater: MAILBOX1_LOWATER_IRQ, |
| }; |
| |
| // 32-bit-word bitfield helpers 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); |
| } |
| |
| 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); |
| } |
| |
| impl Mailbox { |
| pub unsafe fn send_message(&self, message: &[u32]) { |
| self.regs.reg_lowater.set(MAILBOX_SIZE_DWORDS - 1); |
| self.regs.reg_ienable.set(IRQ_LOWATER); |
| for i in 0..message.len() { |
| self.wait_irq_lowater(); |
| self.regs.reg_write.set(message[i]); |
| } |
| self.regs.reg_ienable.set(0); |
| self.regs.reg_lowater.set(0); |
| } |
| |
| pub unsafe fn recv_message(&self, message: &mut [u32]) { |
| self.regs.reg_hiwater.set(1); |
| self.regs.reg_ienable.set(IRQ_HIWATER); |
| for i in 0..message.len() { |
| self.wait_irq_hiwater(); |
| message[i] = self.regs.reg_read.get(); |
| } |
| self.regs.reg_ienable.set(0); |
| self.regs.reg_hiwater.set(0); |
| } |
| |
| pub unsafe fn wait_hiwater(&self, hiwater : u32) { |
| self.regs.reg_hiwater.set(hiwater); |
| self.regs.reg_ienable.set(IRQ_HIWATER); |
| self.wait_irq_hiwater(); |
| self.regs.reg_ienable.set(0); |
| self.regs.reg_hiwater.set(0); |
| } |
| |
| pub unsafe fn wait_lowater(&self, lowater : u32) { |
| self.regs.reg_lowater.set(lowater); |
| self.regs.reg_ienable.set(IRQ_LOWATER); |
| self.wait_irq_lowater(); |
| self.regs.reg_ienable.set(0); |
| self.regs.reg_lowater.set(0); |
| } |
| |
| // 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_irq_hiwater(&self) { |
| while (self.regs.reg_status.get() & MAILBOX_STATUS_HIWATER) == 0 { |
| clear_mstatus_bits(MIE_BIT | SIE_BIT); |
| set_bit(plic::PLIC_EN0, self.plic_irq_hiwater); |
| if (self.regs.reg_status.get() & MAILBOX_STATUS_HIWATER) == 0 { |
| asm!("wfi"); |
| } |
| clear_bit(plic::PLIC_EN0, self.plic_irq_hiwater); |
| set_mstatus_bits(MIE_BIT | SIE_BIT); |
| } |
| self.regs.reg_istate.set(IRQ_HIWATER); |
| } |
| |
| unsafe fn wait_irq_lowater(&self) { |
| while (self.regs.reg_status.get() & MAILBOX_STATUS_LOWATER) == 0 { |
| clear_mstatus_bits(MIE_BIT | SIE_BIT); |
| set_bit(plic::PLIC_EN0, self.plic_irq_lowater); |
| if (self.regs.reg_status.get() & MAILBOX_STATUS_LOWATER) == 0 { |
| asm!("wfi"); |
| } |
| clear_bit(plic::PLIC_EN0, self.plic_irq_lowater); |
| set_mstatus_bits(MIE_BIT | SIE_BIT); |
| } |
| self.regs.reg_istate.set(IRQ_LOWATER); |
| } |
| } |