Add mailbox module
This commit is contained in:
parent
97e2c09871
commit
e48316cc4b
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* SPDX-License-Identifier: BlueOak-1.0.0
|
||||
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
|
||||
*
|
||||
* 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<u32>, // 0x00 This is Mailbox0 read for ARM, can't write
|
||||
__reserved_0: [u32; 5], // 0x04
|
||||
STATUS: ReadOnly<u32, STATUS::Register>, // 0x18
|
||||
__reserved_1: u32, // 0x1C
|
||||
WRITE: WriteOnly<u32>, // 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<T> = ::core::result::Result<T, MailboxError>;
|
||||
|
||||
/*
|
||||
* 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<Mailbox, ()> {
|
||||
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::<u32>(),
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
||||
|
|
Loading…
Reference in New Issue