diff --git a/nucleus/src/platform/rpi3/mailbox.rs b/nucleus/src/platform/rpi3/mailbox.rs new file mode 100644 index 0000000..025715e --- /dev/null +++ b/nucleus/src/platform/rpi3/mailbox.rs @@ -0,0 +1,509 @@ +/* + * SPDX-License-Identifier: BlueOak-1.0.0 + * Copyright (c) Berkus Decker + * + * Based on https://github.com/rust-embedded/rust-raspi3-tutorial/blob/master/04_mailboxes/src/mbox.rs + * by Andre Richter of Tock OS. + */ + +//! Broadcom mailbox interface between the VideoCore and the ARM Core. +//! + +#![allow(dead_code)] + +use { + super::BcmHost, + crate::println, + core::{ + ops::Deref, + ptr::NonNull, + sync::atomic::{compiler_fence, Ordering}, + }, + cortex_a::barrier, + register::{mmio::*, register_bitfields}, +}; + +/// Public interface to the mailbox. +/// The address for the buffer needs to be 16-byte aligned +/// so that the VideoCore can handle it properly. +/// The reason is that lowest 4 bits of the address will contain the channel number. +pub struct Mailbox { + // pub buffer: &'a mut [u32], + base_addr: usize, + buffer: NonNull<[u32]>, +} + +/// Mailbox that is ready to be called. +/// This prevents invalid use of the mailbox until it is fully prepared. +pub struct PreparedMailbox(Mailbox); + +const MAILBOX_ALIGNMENT: usize = 16; +const MAILBOX_ITEMS_COUNT: usize = 36; + +/// We've identity mapped the MMIO register region on kernel start. +const MAILBOX_BASE: usize = BcmHost::get_peripheral_address() + 0xb880; +/// Lowest 4-bits are channel ID. +const CHANNEL_MASK: u32 = 0xf; + +// Mailbox Peek Read/Write Status Sender Config +// 0 0x10 0x00 0x18 0x14 0x1c +// 1 0x30 0x20 0x38 0x34 0x3c +// +// Only mailbox 0's status can trigger interrupts on the ARM, so Mailbox 0 is +// always for communication from VC to ARM and Mailbox 1 is for ARM to VC. +// +// The ARM should never write Mailbox 0 or read Mailbox 1. + +register_bitfields! { + u32, + + STATUS [ + /* Bit 31 set in status register if the write mailbox is full */ + FULL OFFSET(31) NUMBITS(1) [], + /* Bit 30 set in status register if the read mailbox is empty */ + EMPTY OFFSET(30) NUMBITS(1) [] + ] +} + +#[allow(non_snake_case)] +#[repr(C)] +pub struct RegisterBlock { + READ: ReadOnly, // 0x00 This is Mailbox0 read for ARM, can't write + __reserved_0: [u32; 5], // 0x04 + STATUS: ReadOnly, // 0x18 + __reserved_1: u32, // 0x1C + WRITE: WriteOnly, // 0x20 This is Mailbox1 write for ARM, can't read +} + +pub enum MailboxError { + Response, + Unknown, + Timeout, +} + +impl core::fmt::Display for MailboxError { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "{}", + match self { + MailboxError::Response => "ResponseError", + MailboxError::Unknown => "UnknownError", + MailboxError::Timeout => "Timeout", + } + ) + } +} + +pub type Result = ::core::result::Result; + +/* + * Source https://elinux.org/RPi_Framebuffer + * Source for channels 8 and 9: https://github.com/raspberrypi/firmware/wiki/Mailboxes + */ +#[allow(non_upper_case_globals)] +pub mod channel { + pub const Power: u32 = 0; + pub const FrameBuffer: u32 = 1; + pub const VirtualUart: u32 = 2; + pub const VChiq: u32 = 3; + pub const Leds: u32 = 4; + pub const Buttons: u32 = 5; + pub const TouchScreen: u32 = 6; + // Count = 7, + pub const PropertyTagsArmToVc: u32 = 8; + pub const PropertyTagsVcToArm: u32 = 9; +} + +// Single code indicating request +pub const REQUEST: u32 = 0; + +// Possible responses +pub mod response { + pub const SUCCESS: u32 = 0x8000_0000; + pub const ERROR: u32 = 0x8000_0001; // error parsing request buffer (partial response) + /** When responding, the VC sets this bit in val_len to indicate a response. */ + /** Each tag with this bit set will contain VC response data. */ + pub const VAL_LEN_FLAG: u32 = 0x8000_0000; +} + +#[allow(non_upper_case_globals)] +pub mod tag { + pub const GetBoardRev: u32 = 0x0001_0002; + pub const GetMacAddress: u32 = 0x0001_0003; + pub const GetBoardSerial: u32 = 0x0001_0004; + pub const GetArmMemory: u32 = 0x0001_0005; + pub const GetPowerState: u32 = 0x0002_0001; + pub const SetPowerState: u32 = 0x0002_8001; + pub const GetClockRate: u32 = 0x0003_0002; + pub const SetClockRate: u32 = 0x0003_8002; + // GPU + pub const AllocateMemory: u32 = 0x0003_000c; //< Allocate contiguous memory buffer + pub const LockMemory: u32 = 0x0003_000d; + pub const UnlockMemory: u32 = 0x0003_000e; + pub const ReleaseMemory: u32 = 0x003_000f; + pub const ExecuteCode: u32 = 0x0003_0010; + pub const GetDispmanxResourceMemHandle: u32 = 0x0003_0014; + pub const GetEdidBlock: u32 = 0x0003_0020; + // FB + pub const AllocateBuffer: u32 = 0x0004_0001; //< Allocate framebuffer + pub const ReleaseBuffer: u32 = 0x0004_8001; + pub const BlankScreen: u32 = 0x0004_0002; + /* Physical means output signal */ + pub const GetPhysicalWH: u32 = 0x0004_0003; + pub const TestPhysicalWH: u32 = 0x0004_4003; + pub const SetPhysicalWH: u32 = 0x0004_8003; + /* Virtual means display buffer */ + pub const GetVirtualWH: u32 = 0x0004_0004; + pub const TestVirtualWH: u32 = 0x0004_4004; + pub const SetVirtualWH: u32 = 0x0004_8004; + pub const GetDepth: u32 = 0x0004_0005; + pub const TestDepth: u32 = 0x0004_4005; + pub const SetDepth: u32 = 0x0004_8005; + pub const GetPixelOrder: u32 = 0x0004_0006; + pub const TestPixelOrder: u32 = 0x0004_4006; + pub const SetPixelOrder: u32 = 0x0004_8006; + pub const GetAlphaMode: u32 = 0x0004_0007; + pub const TestAlphaMode: u32 = 0x0004_4007; + pub const SetAlphaMode: u32 = 0x0004_8007; + pub const GetPitch: u32 = 0x0004_0008; + /* Offset of display window within buffer */ + pub const GetVirtualOffset: u32 = 0x0004_0009; + pub const TestVirtualOffset: u32 = 0x0004_4009; + pub const SetVirtualOffset: u32 = 0x0004_8009; + pub const GetOverscan: u32 = 0x0004_000a; + pub const TestOverscan: u32 = 0x0004_400a; + pub const SetOverscan: u32 = 0x0004_800a; + pub const GetPalette: u32 = 0x0004_000b; + pub const TestPalette: u32 = 0x0004_400b; + pub const SetPalette: u32 = 0x0004_800b; + pub const SetCursorInfo: u32 = 0x0000_8010; + pub const SetCursorState: u32 = 0x0000_8011; + pub const GetGpioState: u32 = 0x0003_0041; + pub const SetGpioState: u32 = 0x0003_8041; + pub const End: u32 = 0; +} + +pub mod power { + pub const SDHCI: u32 = 0; + pub const UART0: u32 = 1; + pub const UART1: u32 = 2; + pub const USB_HCD: u32 = 3; + pub const I2C0: u32 = 4; + pub const I2C1: u32 = 5; + pub const I2C2: u32 = 6; + pub const SPI: u32 = 7; + pub const CCP2TX: u32 = 8; + + pub mod response { + pub const ON: u32 = 1; + pub const NO_DEV: u32 = 2; /* Device doesn't exist */ + } + pub mod request { + pub const ON: u32 = 1; + pub const WAIT: u32 = 2; + } +} + +pub mod clock { + pub const EMMC: u32 = 1; + pub const UART: u32 = 2; + pub const ARM: u32 = 3; + pub const CORE: u32 = 4; + pub const V3D: u32 = 5; + pub const H264: u32 = 6; + pub const ISP: u32 = 7; + pub const SDRAM: u32 = 8; + pub const PIXEL: u32 = 9; + pub const PWM: u32 = 10; +} + +pub mod alpha_mode { + pub const OPAQUE_0: u32 = 0; // 255 is transparent + pub const TRANSPARENT_0: u32 = 1; // 255 is opaque + pub const IGNORED: u32 = 2; +} + +fn write(regs: &RegisterBlock, buf: *const u32, channel: u32) -> Result<()> { + let mut count: u32 = 0; + let buf_ptr: u32 = buf as u32; + + // This address adjustment will be performed from the outside when necessary + // (see FrameBuffer for example). + // let buf_ptr = BcmHost::phys2bus(buf_ptr); not used for PropertyTags channel + + println!("Mailbox::write {:x}/{:x}", buf_ptr, channel); + + // Insert a compiler fence that ensures that all stores to the + // mailbox buffer are finished before the GPU is signaled (which is + // done by a store operation as well). + compiler_fence(Ordering::Release); + + while regs.STATUS.is_set(STATUS::FULL) { + count += 1; + if count > (1 << 25) { + return Err(MailboxError::Timeout); + } + } + unsafe { + barrier::dmb(barrier::SY); + } + regs.WRITE + .set((buf_ptr & !CHANNEL_MASK) | (channel & CHANNEL_MASK)); + Ok(()) +} + +fn read(regs: &RegisterBlock, expected: u32, channel: u32) -> Result<()> { + loop { + let mut count: u32 = 0; + while regs.STATUS.is_set(STATUS::EMPTY) { + count += 1; + if count > (1 << 25) { + println!("Timed out waiting for mailbox response"); + return Err(MailboxError::Timeout); + } + } + + /* Read the data + * Data memory barriers as we've switched peripheral + */ + unsafe { + barrier::dmb(barrier::SY); + } + let data: u32 = regs.READ.get(); + unsafe { + barrier::dmb(barrier::SY); + } + + println!( + "Received mailbox response {:#08x}, expecting {:#08x}", + data, expected + ); + + // is it a response to our message? + if ((data & CHANNEL_MASK) == channel) && ((data & !CHANNEL_MASK) == expected) { + // is it a valid successful response? + return Ok(()); + } else { + // ignore invalid responses and loop again. + // will return Timeout above if no matching response is received. + } + } +} + +/// Deref to RegisterBlock +/// +/// Allows writing +/// ``` +/// self.STATUS.read() +/// ``` +/// instead of something along the lines of +/// ``` +/// unsafe { (*Mailbox::ptr()).STATUS.read() } +/// ``` +impl Deref for PreparedMailbox { + type Target = RegisterBlock; + + fn deref(&self) -> &Self::Target { + unsafe { &*self.ptr() } + } +} + +impl core::fmt::Debug for Mailbox { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let count = unsafe { self.buffer.as_ref()[0] } / 4; + assert_eq!(unsafe { self.buffer.as_ref()[0] }, count * 4); + assert!(count <= 36); + for i in 0usize..count as usize { + writeln!(f, "[{:02}] {:08x}", i, unsafe { self.buffer.as_ref()[i] })?; + } + Ok(()) + } +} + +impl Default for Mailbox { + fn default() -> Self { + Self::new(MAILBOX_BASE).expect("Couldn't allocate a default mailbox") + } +} + +impl Mailbox { + /// Create a new mailbox in the DMA-able memory area. + pub fn new(base_addr: usize) -> ::core::result::Result { + use core::alloc::AllocRef; + crate::DMA_ALLOCATOR + .lock(|dma| { + dma.alloc_zeroed( + core::alloc::Layout::from_size_align( + MAILBOX_ITEMS_COUNT * core::mem::size_of::(), + MAILBOX_ALIGNMENT, + ) + .unwrap(), // .map_err(|_| ())?, + ) + }) + .map(|ret| { + Ok(Mailbox { + base_addr, + buffer: ret.cast::<[u32; MAILBOX_ITEMS_COUNT]>(), + }) + }) + .map_err(|_| ())? + } + + // Specific mailbox functions + + /// Start mailbox request. + /// + /// @returns index of the next available slot. + #[inline] + pub fn request(&mut self) -> usize { + unsafe { self.buffer.as_mut()[1] = REQUEST }; + 2 + } + + /// Mark mailbox payload as completed. + /// Consumes the Mailbox and returns a Preparedmailbox that can be called. + #[inline] + pub fn end(mut self, index: usize) -> PreparedMailbox { + // @todo return Result + unsafe { + self.buffer.as_mut()[index] = tag::End; + self.buffer.as_mut()[0] = (index as u32 + 1) * 4; + } + PreparedMailbox(self) + } + + /// + /// @returns index of the next available slot. + #[inline] + pub fn set_physical_wh(&mut self, index: usize, width: u32, height: u32) -> usize { + let buf = unsafe { self.buffer.as_mut() }; + buf[index] = tag::SetPhysicalWH; + buf[index + 1] = 8; // Buffer size // val buf size + buf[index + 2] = 8; // Request size // val size + buf[index + 3] = width; // Space for horizontal resolution + buf[index + 4] = height; // Space for vertical resolution + index + 5 + } + + /// + /// @returns index of the next available slot. + #[inline] + pub fn set_virtual_wh(&mut self, index: usize, width: u32, height: u32) -> usize { + let buf = unsafe { self.buffer.as_mut() }; + buf[index] = tag::SetVirtualWH; + buf[index + 1] = 8; // Buffer size // val buf size + buf[index + 2] = 8; // Request size // val size + buf[index + 3] = width; // Space for horizontal resolution + buf[index + 4] = height; // Space for vertical resolution + index + 5 + } + + /// + /// @returns index of the next available slot. + #[inline] + pub fn set_depth(&mut self, index: usize, depth: u32) -> usize { + let buf = unsafe { self.buffer.as_mut() }; + buf[index] = tag::SetDepth; + buf[index + 1] = 4; // Buffer size // val buf size + buf[index + 2] = 4; // Request size // val size + buf[index + 3] = depth; // bpp + index + 4 + } + + /// + /// @returns index of the next available slot. + #[inline] + pub fn allocate_buffer_aligned(&mut self, index: usize, alignment: u32) -> usize { + let buf = unsafe { self.buffer.as_mut() }; + buf[index] = tag::AllocateBuffer; + buf[index + 1] = 8; // Buffer size // val buf size + buf[index + 2] = 4; // Request size // val size + buf[index + 3] = alignment; // Alignment = 16 -- fb_ptr will be here + buf[index + 4] = 0; // Space for response -- fb_size will be here + index + 5 + } + + /// + /// @returns index of the next available slot. + #[inline] + pub fn set_led_on(&mut self, index: usize, enable: bool) -> usize { + let buf = unsafe { self.buffer.as_mut() }; + buf[index] = tag::SetGpioState; + buf[index + 1] = 8; // Buffer size // val buf size + buf[index + 2] = 0; // Response size // val size + buf[index + 3] = 130; // Pin Number + buf[index + 4] = if enable { 1 } else { 0 }; + index + 5 + } +} + +impl PreparedMailbox { + /// Returns a pointer to the register block + fn ptr(&self) -> *const RegisterBlock { + self.0.base_addr as *const _ + } + + pub fn write(&self, channel: u32) -> Result<()> { + write(self, self.0.buffer.as_ptr() as *const _, channel) + } + + // @todo read() should probably consume PreparedMailbox completely ? + pub fn read(&self, channel: u32) -> Result<()> { + // SAFETY: buffer is HW-mutable in the read call below! + read( + self, + self.0.buffer.as_ptr() as *const [u32] as *const u32 as u32, + channel, + )?; + + match unsafe { self.0.buffer.as_ref()[1] } { + response::SUCCESS => { + println!("\n######\nMailbox::returning SUCCESS"); + Ok(()) + } + response::ERROR => { + println!("\n######\nMailbox::returning ResponseError"); + Err(MailboxError::Response) + } + _ => { + println!("\n######\nMailbox::returning UnknownError"); + println!("{:x}\n######", unsafe { self.0.buffer.as_ref()[1] }); + Err(MailboxError::Unknown) + } + } + } + + pub fn call(&self, channel: u32) -> Result<()> { + self.write(channel)?; + self.read(channel) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // Validate the buffer is filled correctly + // Validate the buffer is properly terminated when call()ed -- this invariant must be maintained + // by the end() fn. + #[test_case] + fn test_prepare_mailbox() { + let mut mailbox = Mailbox::default(); + let index = mailbox.request(); + let index = mailbox.set_led_on(index, true); + let mailbox = mailbox.end(index); + // Instead of calling just check the filled buffer format: + assert_eq!( + unsafe { mailbox.0.buffer.as_ref()[0] } as usize, + (index + 1) * 4 + ); + assert_eq!(unsafe { mailbox.0.buffer.as_ref()[1] }, REQUEST); + assert_eq!(unsafe { mailbox.0.buffer.as_ref()[2] }, tag::SetGpioState); + assert_eq!(unsafe { mailbox.0.buffer.as_ref()[3] }, 8); + assert_eq!(unsafe { mailbox.0.buffer.as_ref()[4] }, 0); + assert_eq!(unsafe { mailbox.0.buffer.as_ref()[5] }, 130); + assert_eq!(unsafe { mailbox.0.buffer.as_ref()[6] }, 1); + assert_eq!(unsafe { mailbox.0.buffer.as_ref()[7] }, tag::End); + } +} diff --git a/nucleus/src/platform/rpi3/mod.rs b/nucleus/src/platform/rpi3/mod.rs index de8ce03..357537b 100644 --- a/nucleus/src/platform/rpi3/mod.rs +++ b/nucleus/src/platform/rpi3/mod.rs @@ -5,6 +5,8 @@ #![allow(dead_code)] +pub mod mailbox; + /// See BCM2835-ARM-Peripherals.pdf /// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.