Add Display output

This commit is contained in:
Berkus Decker 2020-11-18 23:17:00 +02:00
parent 6896b7a1cc
commit e72fac01b0
4 changed files with 390 additions and 0 deletions

View File

@ -0,0 +1,199 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use snafu::Snafu;
/* Character cells are 8x8 */
pub const CHARSIZE_X: u32 = 8;
pub const CHARSIZE_Y: u32 = 8;
pub struct Size2d {
pub x: u32,
pub y: u32,
}
pub struct Color(pub u32);
impl Color {
pub const fn rgb(r: u8, g: u8, b: u8) -> Color {
// @todo use u32::from(g) when it's declared const
// (see https://doc.rust-lang.org/src/core/convert/num.rs.html#49-54)
Color((b as u32) << 16 | (g as u32) << 8 | (r as u32))
}
pub const fn black() -> Color {
Color::rgb(0, 0, 0)
}
pub const fn white() -> Color {
Color::rgb(255, 255, 255)
}
pub const fn red() -> Color {
Color::rgb(255, 0, 0)
}
pub const fn green() -> Color {
Color::rgb(0, 255, 0)
}
pub const fn blue() -> Color {
Color::rgb(0, 0, 255)
}
}
#[derive(PartialEq)]
pub enum PixelOrder {
BGR,
RGB,
}
pub struct Display {
base: u32,
size: u32,
depth: u32,
pitch: u32,
max_x: u32,
max_y: u32,
pub width: u32,
height: u32,
order: PixelOrder,
}
#[derive(Debug, Snafu)]
pub enum DrawError {
#[snafu(display("Character not in font"))]
CharacterNotInFont,
}
type Result<T, E = DrawError> = ::core::result::Result<T, E>;
// https://github.com/david-griffith/rust-bitmap/blob/master/src/lib.rs
#[rustfmt::skip]
static CHAR_ARRAY: [u64; 95] = [
0x0000_0000_0000_0000, // space
0x183c_3c18_1800_1800, 0x3636_0000_0000_0000, 0x3636_7f36_7f36_3600, // ! " #
0x0c3e_031e_301f_0c00, 0x0063_3318_0c66_6300, 0x1c36_1c6e_3b33_6e00, // $ % &
0x0606_0300_0000_0000, 0x180c_0606_060c_1800, 0x060c_1818_180c_0600, // ' ( )
0x0066_3cff_3c66_0000, 0x000c_0c3f_0c0c_0000, 0x0000_0000_000c_0c06, // * + ,
0x0000_003f_0000_0000, 0x0000_0000_000c_0c00, 0x6030_180c_0603_0100, // - . /
0x3e63_737b_6f67_3e00, 0x0c0e_0c0c_0c0c_3f00, 0x1e33_301c_0633_3f00, // 0 1 2
0x1e33_301c_3033_1e00, 0x383c_3633_7f30_7800, 0x3f03_1f30_3033_1e00, // 3 4 5
0x1c06_031f_3333_1e00, 0x3f33_3018_0c0c_0c00, 0x1e33_331e_3333_1e00, // 6 7 8
0x1e33_333e_3018_0e00, 0x000c_0c00_000c_0c00, 0x000c_0c00_000c_0c06, // 9 : ;
0x180c_0603_060c_1800, 0x0000_3f00_003f_0000, 0x060c_1830_180c_0600, // < = >
0x1e33_3018_0c00_0c00, 0x3e63_7b7b_7b03_1e00, 0x0c1e_3333_3f33_3300, // ? @ A
0x3f66_663e_6666_3f00, 0x3c66_0303_0366_3c00, 0x1f36_6666_6636_1f00, // B C D
0x7f46_161e_1646_7f00, 0x7f46_161e_1606_0f00, 0x3c66_0303_7366_7c00, // E F G
0x3333_333f_3333_3300, 0x1e0c_0c0c_0c0c_1e00, 0x7830_3030_3333_1e00, // H I J
0x6766_361e_3666_6700, 0x0f06_0606_4666_7f00, 0x6377_7f7f_6b63_6300, // K L M
0x6367_6f7b_7363_6300, 0x1c36_6363_6336_1c00, 0x3f66_663e_0606_0f00, // N O P
0x1e33_3333_3b1e_3800, 0x3f66_663e_3666_6700, 0x1e33_070e_3833_1e00, // Q R S
0x3f2d_0c0c_0c0c_1e00, 0x3333_3333_3333_3f00, 0x3333_3333_331e_0c00, // T U V
0x6363_636b_7f77_6300, 0x6363_361c_1c36_6300, 0x3333_331e_0c0c_1e00, // W X Y
0x7f63_3118_4c66_7f00, 0x1e06_0606_0606_1e00, 0x0306_0c18_3060_4000, // Z [ \
0x1e18_1818_1818_1e00, 0x081c_3663_0000_0000, 0x0000_0000_0000_00ff, // ] ^ _
0x0c0c_1800_0000_0000, 0x0000_1e30_3e33_6e00, 0x0706_063e_6666_3b00, // ` a b
0x0000_1e33_0333_1e00, 0x3830_303e_3333_6e00, 0x0000_1e33_3f03_1e00, // c d e
0x1c36_060f_0606_0f00, 0x0000_6e33_333e_301f, 0x0706_366e_6666_6700, // f g h
0x0c00_0e0c_0c0c_1e00, 0x3000_3030_3033_331e, 0x0706_6636_1e36_6700, // i j k
0x0e0c_0c0c_0c0c_1e00, 0x0000_337f_7f6b_6300, 0x0000_1f33_3333_3300, // l m n
0x0000_1e33_3333_1e00, 0x0000_3b66_663e_060f, 0x0000_6e33_333e_3078, // o p q
0x0000_3b6e_6606_0f00, 0x0000_3e03_1e30_1f00, 0x080c_3e0c_0c2c_1800, // r s t
0x0000_3333_3333_6e00, 0x0000_3333_331e_0c00, 0x0000_636b_7f7f_3600, // u v w
0x0000_6336_1c36_6300, 0x0000_3333_333e_301f, 0x0000_3f19_0c26_3f00, // x y z
0x380c_0c07_0c0c_3800, 0x1818_1800_1818_1800, 0x070c_0c38_0c0c_0700, // { | }
0x6e3b_0000_0000_0000, // ~
];
impl Display {
pub fn new(
base: u32,
size: u32,
depth: u32,
pitch: u32,
max_x: u32,
max_y: u32,
width: u32,
height: u32,
order: PixelOrder,
) -> Self {
Display {
base,
size,
depth,
pitch,
max_x,
max_y,
width,
height,
order,
}
}
#[inline]
fn color_component(&self, chan: u16) -> u32 {
u32::from(if self.order == PixelOrder::BGR {
2 - chan
} else {
chan
})
}
#[inline]
fn write_pixel_component(&self, x: u32, y: u32, chan: u16, c: u32) {
unsafe {
*(self.base as *mut u8).offset(
(y * self.pitch + x * (self.depth >> 3) + self.color_component(chan)) as isize,
) = c as u8;
}
}
/// Set a pixel value on display at given coordinates.
#[inline]
pub fn putpixel(&mut self, x: u32, y: u32, color: u32) {
self.write_pixel_component(x, y, 0, color & 0xff); // R
self.write_pixel_component(x, y, 1, (color >> 8) & 0xff); // G
self.write_pixel_component(x, y, 2, (color >> 16) & 0xff); // B
}
pub fn rect(&mut self, x1: u32, y1: u32, x2: u32, y2: u32, color: Color) {
for y in y1..y2 {
for x in x1..x2 {
self.putpixel(x, y, color.0);
}
}
}
pub fn clear(&mut self, color: Color) {
self.rect(0, 0, self.width, self.height, color)
}
pub fn draw_text(&mut self, x: u32, y: u32, text: &str, color: Color) -> Result<()> {
for i in 0..8 {
// Take an 8 bit slice from each array value.
for (char_off, my_char) in text.as_bytes().iter().enumerate() {
let off = (char_off * 8) as u32;
if (*my_char as isize - 0x20 > 95) || (*my_char as isize - 0x20 < 0) {
return Err(DrawError::CharacterNotInFont);
}
let mut myval = CHAR_ARRAY[*my_char as usize - 0x20];
myval = myval.swap_bytes();
// do initial shr.
myval >>= i * 8;
for mycount in 0..8 {
if myval & 1 == 1 {
self.putpixel(x + off + mycount, y + i, color.0);
}
myval >>= 1;
if myval == 0 {
break;
}
}
}
}
Ok(())
}
}

View File

@ -336,12 +336,25 @@ impl core::fmt::Debug for Mailbox {
}
}
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<Mailbox, ()> {
@ -464,6 +477,54 @@ impl Mailbox {
index + 6
}
/// NB: Do not intermix Get/Set and Test tags in one request!
/// See https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
/// * 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 https://github.com/raspberrypi/firmware/wiki/Mailbox-property-interface
/// * 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() };

View File

@ -5,12 +5,14 @@
#![allow(dead_code)]
pub mod display;
pub mod fb;
pub mod gpio;
pub mod mailbox;
pub mod mini_uart;
pub mod pl011_uart;
pub mod power;
pub mod vc;
/// See BCM2835-ARM-Peripherals.pdf
/// See https://www.raspberrypi.org/forums/viewtopic.php?t=186090 for more details.

View File

@ -0,0 +1,128 @@
/*
* SPDX-License-Identifier: BlueOak-1.0.0
* Copyright (c) Berkus Decker <berkus+vesper@metta.systems>
*/
use {
super::{
display::{Display, PixelOrder, CHARSIZE_X, CHARSIZE_Y},
mailbox::{self, channel, response::VAL_LEN_FLAG, Mailbox, MailboxOps},
BcmHost,
},
crate::println,
core::convert::TryInto,
snafu::Snafu,
};
pub struct VC;
#[derive(Debug, Snafu)]
pub enum VcError {
#[snafu(display("VC setup failed in mailbox operation"))]
MailboxError,
#[snafu(display("VC setup failed due to bad mailbox response {:x}", response))]
MailboxResponseError { response: u32 },
#[snafu(display("Unknown pixel order received in mailbox response"))]
InvalidPixelOrder,
}
type Result<T, E = VcError> = ::core::result::Result<T, E>;
impl VC {
// Use framebuffer mailbox interface to initialize
// https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=185116
pub fn init_fb(w: u32, h: u32, depth: u32) -> Result<Display> {
/*
* * All tags in the request are processed in one operation.
* * It is not valid to mix Test tags with Get/Set tags
* in the same operation and no tags will be returned.
* * Get tags will be processed after all Set tags.
* * If an allocate buffer tag is omitted when setting parameters,
* then no change occurs unless it can be accommodated without changing
* the buffer base or size.
* * When an allocate buffer response is returned, the old buffer area
* (if the base or size has changed) is implicitly freed.
*/
let mut mbox = Mailbox::default();
let index = mbox.request();
let index = mbox.set_physical_wh(index, w, h);
let index = mbox.set_virtual_wh(index, w, h);
let index = mbox.set_depth(index, depth);
let index = mbox.allocate_buffer_aligned(index, 16);
let mbox = mbox.end(index);
mbox.call(channel::PropertyTagsArmToVc).map_err(|e| {
println!("Mailbox call returned error {}", e);
println!("Mailbox contents: {:?}", mbox);
VcError::MailboxError
})?;
if (mbox.value_at(18) & VAL_LEN_FLAG) == 0 {
return Err(VcError::MailboxResponseError {
response: mbox.value_at(18),
});
}
let fb_ptr = BcmHost::bus2phys(mbox.value_at(19).try_into().unwrap());
let fb_size = mbox.value_at(20);
// SetPixelOrder doesn't work in QEMU, however TestPixelOrder does.
// Apparently, QEMU doesn't care about intermixing Get/Set and Test tags either.
let mut mbox = Mailbox::default();
let index = mbox.request();
#[cfg(qemu)]
let index = mbox.test_pixel_order(index, 1);
#[cfg(not(qemu))]
let index = mbox.set_pixel_order(index, 1);
let index = mbox.set_alpha_mode(index, mailbox::alpha_mode::IGNORED);
let index = mbox.get_pitch(index);
let mbox = mbox.end(index);
// let index = mbox.test_pixel_order(index, 1);
mbox.call(channel::PropertyTagsArmToVc)
.map_err(|_| VcError::MailboxError)?;
if (mbox.value_at(4) & VAL_LEN_FLAG) == 0 {
return Err(VcError::MailboxResponseError {
response: mbox.value_at(4),
});
}
if (mbox.value_at(12) & VAL_LEN_FLAG) == 0 {
return Err(VcError::MailboxResponseError {
response: mbox.value_at(12),
});
}
let order = match mbox.value_at(5) {
0 => PixelOrder::BGR,
1 => PixelOrder::RGB,
_ => return Err(VcError::InvalidPixelOrder),
};
let pitch = mbox.value_at(13);
/* Need to set up max_x/max_y before using Display::write */
let max_x = w / CHARSIZE_X;
let max_y = h / CHARSIZE_Y;
let x_offset = 0;
let y_offset = 0;
println!(
"[i] VC init: {}x{}, {}x{}, d{}, --{}--, +{}x{}, {}@{:x}",
w, h, w, h, depth, pitch, x_offset, y_offset, fb_size, fb_ptr
);
Ok(Display::new(
fb_ptr.try_into().unwrap(),
fb_size,
depth,
pitch,
max_x,
max_y,
w,
h,
order,
))
}
}