/* * 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::asm::barrier, tock_registers::{interfaces::*, register_bitfields, registers::*}, }; /// 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; /// Typical operations with a mailbox. pub trait MailboxOps { /// Deref from self to a mailbox RegisterBlock. Used by Deref implementations. fn ptr(&self) -> *const RegisterBlock; fn write(&self, channel: u32) -> Result<()>; fn read(&self, channel: u32) -> Result<()>; fn call(&self, channel: u32) -> Result<()> { self.write(channel)?; self.read(channel) } } /* * 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; /// Channel number is ignored. Use for implementations of MailboxOps that use hardcoded /// channel number. pub const Ignored: u32 = !0; } // 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; } pub 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 {:#08x}/{:#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(()) } pub 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 core::fmt::Debug for PreparedMailbox { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { self.0.fmt(f) } } impl Default for Mailbox { fn default() -> Self { Self::new(MAILBOX_BASE).expect("Couldn't allocate a default mailbox") } } // @todo Probably need a ResultMailbox for accessing data after call()? impl PreparedMailbox { pub fn value_at(&self, index: usize) -> u32 { unsafe { self.0.buffer.as_ref()[index] } } } impl Mailbox { /// Create a new mailbox in the DMA-able memory area. pub fn new(base_addr: usize) -> ::core::result::Result { use core::alloc::Allocator; crate::DMA_ALLOCATOR .lock(|dma| { dma.allocate_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 } #[inline] pub fn set_clock_rate(&mut self, index: usize, channel: u32, rate: u32) -> usize { let buf = unsafe { self.buffer.as_mut() }; buf[index] = tag::SetClockRate; buf[index + 1] = 12; // Buffer size // val buf size buf[index + 2] = 8; // Response size // val size buf[index + 3] = channel; // mailbox::clock::* buf[index + 4] = rate; buf[index + 5] = 0; // skip turbo setting index + 6 } /// NB: Do not intermix Get/Set and Test tags in one request! /// See /// * It is not valid to mix Test tags with Get/Set tags in the same operation /// and no tags will be returned. #[inline] pub fn set_pixel_order(&mut self, index: usize, order: u32) -> usize { let buf = unsafe { self.buffer.as_mut() }; buf[index] = tag::SetPixelOrder; buf[index + 1] = 4; // Buffer size // val buf size buf[index + 2] = 4; // Response size // val size buf[index + 3] = order; index + 4 } /// NB: Do not intermix Get/Set and Test tags in one request! /// See /// * It is not valid to mix Test tags with Get/Set tags in the same operation /// and no tags will be returned. #[inline] pub fn test_pixel_order(&mut self, index: usize, order: u32) -> usize { let buf = unsafe { self.buffer.as_mut() }; buf[index] = tag::TestPixelOrder; buf[index + 1] = 4; // Buffer size // val buf size buf[index + 2] = 4; // Response size // val size buf[index + 3] = order; index + 4 } #[inline] pub fn set_alpha_mode(&mut self, index: usize, mode: u32) -> usize { let buf = unsafe { self.buffer.as_mut() }; buf[index] = tag::SetAlphaMode; buf[index + 1] = 4; // Buffer size // val buf size buf[index + 2] = 4; // Response size // val size buf[index + 3] = mode; index + 4 } #[inline] pub fn get_pitch(&mut self, index: usize) -> usize { let buf = unsafe { self.buffer.as_mut() }; buf[index] = tag::GetPitch; buf[index + 1] = 4; // Buffer size // val buf size buf[index + 2] = 4; // Response size // val size buf[index + 3] = 0; // Result placeholder index + 4 } #[inline] pub fn set_device_power(&mut self, index: usize, device_id: u32, power_flags: u32) -> usize { let buf = unsafe { self.buffer.as_mut() }; buf[index] = tag::SetPowerState; buf[index + 1] = 8; // Buffer size // val buf size buf[index + 2] = 8; // Response size // val size buf[index + 3] = device_id; buf[index + 4] = power_flags; // bit 0: off, bit 1: no wait index + 5 } } impl MailboxOps for PreparedMailbox { /// Returns a pointer to the register block fn ptr(&self) -> *const RegisterBlock { self.0.base_addr as *const _ } fn write(&self, channel: u32) -> Result<()> { write(self, self.0.buffer.as_ptr() as *const _, channel) } // @todo read() should probably consume PreparedMailbox completely ? 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) } } } } #[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); } }