Got a working graphical display, text terminal, and tty!

master
James T. Martin 2020-07-16 13:30:42 -07:00
parent 4acc215cf4
commit dffee5652c
Signed by: james
GPG Key ID: 4B7F3DA9351E577C
13 changed files with 360 additions and 103 deletions

2
run.sh
View File

@ -2,4 +2,4 @@
profile=${1:-"debug"}
mkdir -p drive/EFI/Boot
cp "target/x86_64-unknown-uefi/$profile/bootproof.efi" drive/EFI/Boot/BootX64.efi
qemu-system-x86_64 -nodefaults -cpu host -smp 8 -m 512M -machine "q35,accel=kvm:tcg" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS.fd,readonly=on" -drive "format=raw,file=fat:rw:drive" -display gtk,gl=on -vga virtio -serial stdio
qemu-system-x86_64 -nodefaults -cpu host -smp 8 -m 1G -machine "q35,accel=kvm:tcg" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_CODE.fd,readonly=on" -drive "if=pflash,format=raw,file=/usr/share/OVMF/OVMF_VARS.fd,readonly=on" -drive "format=raw,file=fat:rw:drive" -display gtk,gl=on -vga virtio -serial stdio

View File

@ -1,6 +1,6 @@
use alloc::alloc::GlobalAlloc;
use core::alloc::Layout;
use uefi::table::boot::{AllocateType, MemoryDescriptor, MemoryType};
use uefi::table::boot::{AllocateType, MemoryType};
pub enum Allocator {
None,
@ -11,10 +11,9 @@ unsafe impl GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
match self {
Allocator::Uefi(st) => {
crate::log!("Allocate {:?}", layout);
st.boot_services().allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, layout.size())
.expect("Failed to allocate memory!")
.expect("Failed to allocate memory! 2")
.unwrap()
as *mut u8
},
Allocator::None => panic!("No allocator available!")
@ -24,8 +23,9 @@ unsafe impl GlobalAlloc for Allocator {
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
match self {
Allocator::Uefi(st) => {
crate::log!("Free {:?}", layout);
st.boot_services().free_pages(ptr as u64, layout.size());
st.boot_services().free_pages(ptr as u64, layout.size())
.expect("Failed to free memory!")
.unwrap();
},
Allocator::None => {
panic!("No allocator available!");

View File

@ -1,85 +1,34 @@
mod font;
pub mod color;
pub mod display;
pub mod font;
pub mod terminal;
pub mod tty;
use crate::println;
use uefi::proto::console::gop::*;
fn px_index(stride: usize, x: usize, y: usize) -> usize {
4 * (y * stride + x)
}
fn make_px(r: u8, g: u8, b: u8) -> [u8; 4] {
[b, g, r, 0]
}
unsafe fn draw_char(fb: &mut FrameBuffer, stride: usize, cx: usize, cy: usize, c: char) {
let font = font::font();
let glyph = font.lookup(c).expect("Character missing from font.");
let color = make_px(255, 255, 255);
for dx in 0..num_integer::div_ceil(glyph.width(), 8) * 8 {
for dy in 0..glyph.height() {
if glyph.get(dx, dy) {
let scale = 2;
for sdx in 0..scale {
for sdy in 0..scale {
let px = cx + scale * dx as usize + sdx;
let py = cy + scale * dy as usize + sdy;
fb.write_value(px_index(stride, px, py), color);
}
}
}
}
}
}
unsafe fn draw_str(fb: &mut FrameBuffer, stride: usize, mut cx: usize, cy: usize, text: &str) {
let width = font::font().width as usize * 2;
for c in text.chars() {
draw_char(fb, stride, cx, cy, c);
cx += width;
}
}
fn draw(fb: &mut FrameBuffer, stride: usize, width: usize, height: usize) {
for x in 0..width {
for y in 0..height {
let i = px_index(stride, x, y);
let r = (x * 256 / width) as u8;
let g = (y * 256 / height) as u8;
let b = 255 - ((r as u16 + g as u16) / 2) as u8;
let px = make_px(r, g, b);
unsafe {
fb.write_value(i, px);
}
}
}
let c_width = width / 8;
let c_height = height / 16;
unsafe {
draw_str(fb, stride, 8, 8, "✔ Hello, world! ♡");
}
}
use crate::graphics::color::{COLOR_BLACK, COLOR_WHITE};
use crate::graphics::display::gop::GopDisplay;
use crate::graphics::font::font;
use crate::graphics::terminal::display::DisplayTerminal;
use crate::graphics::tty::Tty;
use crate::graphics::tty::terminal::TerminalTty;
pub fn do_graphics(st: &uefi::prelude::SystemTable<uefi::prelude::Boot>) {
let gop = st.boot_services().locate_protocol::<GraphicsOutput>()
.unwrap()
.expect("UEFI Graphics Output Protocol (GOP) is not present.");
let mut gop = unsafe { &mut *gop.get() };
let mut mode = None;
for gop_mode in gop.modes() {
let gop_mode = gop_mode.expect("Warning while accessing GOP mode.");
if let PixelFormat::BGR = gop_mode.info().pixel_format() {
mode = Some(gop_mode);
} else {
println!("Ignoring non-BGR pixel format.");
let display = GopDisplay::init(st.boot_services());
let terminal = DisplayTerminal::create(display, font(), COLOR_BLACK, COLOR_WHITE);
let mut tty = TerminalTty::create(terminal);
for _ in 0..30 {
for c in 'a'..'z' {
tty.putc(c);
tty.putc('\n');
}
}
let mode = mode.expect("No usable pixel formats found.");
let (width, height) = mode.info().resolution();
let stride = mode.info().stride();
println!("Using mode: {}x{} {:?}", width, height, mode.info().pixel_format());
gop.set_mode(&mode).unwrap().expect("Failed to set UEFI Graphics Output mode.");
let mut fb = gop.frame_buffer();
draw(&mut fb, stride, width, height);
for _ in 0..20 {
for c in 'a'..'z' {
tty.putc(c);
}
}
tty.putc('\n');
tty.puts("✔ Hello, world! ♡");
tty.flush();
}

29
src/graphics/color.rs Normal file
View File

@ -0,0 +1,29 @@
#[derive(Copy, Clone)]
pub struct RGB {
r: u8,
g: u8,
b: u8
}
pub trait Color: Copy {
fn r(&self) -> u8;
fn g(&self) -> u8;
fn b(&self) -> u8;
fn into_rgb(&self) -> RGB {
RGB {
r: self.r(),
g: self.g(),
b: self.b()
}
}
}
impl Color for RGB {
fn r(&self) -> u8 { self.r }
fn g(&self) -> u8 { self.g }
fn b(&self) -> u8 { self.b }
}
pub const COLOR_BLACK: RGB = RGB { r: 0x23, g: 0x23, b: 0x23 };
pub const COLOR_WHITE: RGB = RGB { r: 0xFF, g: 0xFF, b: 0xFF };

56
src/graphics/display.rs Normal file
View File

@ -0,0 +1,56 @@
use crate::graphics::color::Color;
use crate::graphics::font::psf::PSFGlyph;
pub mod gop;
pub trait Display {
fn resolution(&self) -> (usize, usize);
fn width(&self) -> usize { self.resolution().0 }
fn height(&self) -> usize { self.resolution().1 }
unsafe fn set_pixel(&mut self, color: impl Color, x: usize, y: usize);
fn set_pixel_ignore_oob(&mut self, color: impl Color, x: usize, y: usize) {
if x > self.width() || y > self.height() {
return;
}
unsafe {
self.set_pixel(color, x, y);
}
}
fn clear(&mut self, color: impl Color);
unsafe fn draw_glyph(&mut self, color: impl Color, x: usize, y: usize, glyph: PSFGlyph) {
// Glyphs may actually be larger than their nominal bounding box.
// In fact, the Cozette font is like this: the heart symbol is 7 pixels wide,
// despite nominally being a 6x13 font.
// However, despite not being an intended use of the format, that extra pixel
// can still be stored in the padding bits of the glyph (and is!).
// Therefore, we just continue writing those extra bits if they are present.
// Note that there is no similar trick for the height,
// because the height doesn't have padding.
for glyph_x in 0..glyph.width() {
for glyph_y in 0..glyph.height() {
if glyph.get(glyph_x, glyph_y) {
self.set_pixel(color, x + glyph_x as usize, y + glyph_y as usize);
}
}
}
// Sometimes, a font may actually have pixels outside its bounding box!
// For example, in Cozette, a 6x13 font, ♡ is actually 7 pixels wide.
// This data is still stored in the padding bits of the glyph.
// Note that there is no similar trick for height because height doesn't have padding.
// Futhermore, this only works on fonts whose width is not a multiple of eight.
for glyph_x in glyph.width()..num_integer::div_ceil(glyph.width(), 8) * 8 {
for glyph_y in 0..glyph.height() {
if glyph.get(glyph_x, glyph_y) {
// These pixels *nominally* aren't supposed to be there,
// so we only force the pixels inside the bounding box.
self.set_pixel_ignore_oob(color, x + glyph_x as usize, y + glyph_y as usize);
}
}
}
}
}

View File

@ -0,0 +1,65 @@
use crate::graphics::color::Color;
use crate::graphics::display::Display;
use uefi::proto::console::gop::{FrameBuffer, GraphicsOutput, ModeInfo, PixelFormat};
const PIXEL_WIDTH_BYTES: usize = 4;
pub struct GopDisplay<'a> {
fb: FrameBuffer<'a>,
mode: ModeInfo,
}
impl GopDisplay<'_> {
pub fn init<'boot>(bs: &'boot uefi::table::boot::BootServices) -> GopDisplay<'boot> {
let gop = bs.locate_protocol::<GraphicsOutput>()
.expect("UEFI Graphics Output Protocol (GOP) is not present.")
.unwrap();
let gop = unsafe { &mut *gop.get() };
let mut mode = None;
for gop_mode in gop.modes() {
let gop_mode = gop_mode.unwrap();
if let PixelFormat::BGR = gop_mode.info().pixel_format() {
mode = Some(gop_mode);
}
}
let mode = mode.expect("No usable pixel formats found.");
let (width, height) = mode.info().resolution();
crate::log!("Using mode: {}x{} {:?}", width, height, mode.info().pixel_format());
gop.set_mode(&mode).expect("Failed to set UEFI Graphics Output mode.").unwrap();
let info = gop.current_mode_info();
GopDisplay {
fb: gop.frame_buffer(),
mode: info,
}
}
// Convert a color to a BGR-formatted byte array.
fn make_pixel(&self, color: impl Color) -> [u8; 4] {
[color.b(), color.g(), color.r(), 0]
}
fn pixel_index(&self, x: usize, y: usize) -> usize {
PIXEL_WIDTH_BYTES * (self.mode.stride() * y + x)
}
}
impl Display for GopDisplay<'_> {
fn resolution(&self) -> (usize, usize) { self.mode.resolution() }
unsafe fn set_pixel(&mut self, color: impl Color, x: usize, y: usize) {
self.fb.write_value(self.pixel_index(x, y), self.make_pixel(color));
}
fn clear(&mut self, color: impl Color) {
let (width, height) = self.resolution();
let px = self.make_pixel(color);
for x in 0..width {
for y in 0..height {
unsafe {
self.fb.write_value(self.pixel_index(x, y), px);
}
}
}
}
}

View File

@ -2,7 +2,7 @@ use alloc::sync::Arc;
use alloc::vec::Vec;
use psf::*;
mod psf;
pub mod psf;
static mut FONT: Option<Arc<PSF>> = None;
@ -16,20 +16,6 @@ pub fn font() -> Arc<PSF> {
}
}
fn pad(slice: &[u8]) -> [u8; 4] {
if slice.len() == 1 {
[slice[0], 0, 0, 0]
} else if slice.len() == 2 {
[slice[0], slice[1], 0, 0]
} else if slice.len() == 3 {
[slice[0], slice[1], slice[2], 0]
} else if slice.len() == 4 {
[slice[0], slice[1], slice[2], slice[3]]
} else {
crate::panic!("Bad character length {}", slice.len())
}
}
fn parse_font() -> PSF {
use core::convert::TryInto;
let font = core::include_bytes!("font/cozette.psf");
@ -37,14 +23,13 @@ fn parse_font() -> PSF {
let charsize = u32::from_le_bytes(font[20..24].try_into().unwrap());
let height = u32::from_le_bytes(font[24..28].try_into().unwrap());
let width = u32::from_le_bytes(font[28..32].try_into().unwrap());
crate::log!("{} {} {}", width, height, charsize);
let glyphs_size = (length * charsize) as usize;
let mut glyphs = Vec::with_capacity(glyphs_size);
glyphs.extend_from_slice(&font[32..glyphs_size + 32]);
let mut unicode_map = Vec::new();
let mut unicode_info = &font[glyphs_size + 32..];
let unicode_info = &font[glyphs_size + 32..];
let mut glyph = 0;
let mut i = 0;
while i < unicode_info.len() {

View File

@ -51,7 +51,6 @@ impl PSFGlyph<'_> {
pub fn get(&self, x: u32, y: u32) -> bool {
let line_size = num_integer::div_ceil(self.width, 8);
let char_size = line_size * self.height;
let (line_byte_index, bit_index) = num_integer::div_rem(x, 8);
let mask = 0b10000000 >> bit_index;
let byte = self.bitmap[(y * line_size + line_byte_index) as usize];

12
src/graphics/terminal.rs Normal file
View File

@ -0,0 +1,12 @@
pub mod display;
pub trait Terminal {
fn resolution(&self) -> (usize, usize);
fn width(&self) -> usize { self.resolution().0 }
fn height(&self) -> usize { self.resolution().1 }
fn set_char(&mut self, x: usize, y: usize, c: char);
fn clear(&mut self);
fn refresh(&mut self);
}

View File

@ -0,0 +1,78 @@
use alloc::sync::Arc;
use alloc::vec::Vec;
use crate::graphics::color::{Color, RGB};
use crate::graphics::display::Display;
use crate::graphics::display::gop::GopDisplay;
use crate::graphics::font::psf::PSF;
use crate::graphics::terminal::Terminal;
pub struct DisplayTerminal<'a> {
dp: GopDisplay<'a>,
font: Arc<PSF>,
buf: Vec<char>,
bg: RGB,
fg: RGB,
}
impl DisplayTerminal<'_> {
pub fn create<'a>(dp: GopDisplay<'a>, font: Arc<PSF>, bg: impl Color, fg: impl Color) -> DisplayTerminal<'a> {
let (dp_width, dp_height) = dp.resolution();
let (font_width, font_height) = (font.width, font.height);
DisplayTerminal {
dp: dp,
font: font,
buf: {
let char_count = (dp_width / font_width as usize) * (dp_height / font_height as usize);
let mut buf = Vec::with_capacity(char_count);
for _ in 0..char_count {
buf.push(' ');
}
buf
},
bg: bg.into_rgb(),
fg: fg.into_rgb(),
}
}
fn get_index(&self, x: usize, y: usize) -> usize {
self.width() * y + x
}
fn get_char(&self, x: usize, y: usize) -> char {
let i = self.get_index(x, y);
self.buf[i]
}
}
impl Terminal for DisplayTerminal<'_> {
fn resolution(&self) -> (usize, usize) {
let width = self.dp.width() / self.font.width as usize;
let height = self.dp.height() / self.font.height as usize;
(width, height)
}
fn set_char(&mut self, x: usize, y: usize, c: char) {
let i = self.get_index(x, y);
self.buf.as_mut_slice()[i] = c;
}
fn clear(&mut self) {
for x in 0..self.width() {
for y in 0..self.height() {
self.set_char(x, y, ' ');
}
}
}
fn refresh(&mut self) {
self.dp.clear(self.bg);
for x in 0..self.width() {
for y in 0..self.height() {
let glyph = self.font.lookup(self.get_char(x, y)).expect("Character missing from font.");
unsafe {
self.dp.draw_glyph(self.fg, self.font.width as usize * x, self.font.height as usize * y, glyph);
}
}
}
}
}

8
src/graphics/tty.rs Normal file
View File

@ -0,0 +1,8 @@
pub mod terminal;
pub trait Tty {
fn putc(&mut self, c: char);
fn puts(&mut self, s: &str);
fn clear(&mut self);
fn flush(&mut self);
}

View File

@ -0,0 +1,77 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::graphics::terminal::Terminal;
use crate::graphics::terminal::display::DisplayTerminal;
use crate::graphics::tty::Tty;
pub struct TerminalTty<'a> {
term: DisplayTerminal<'a>,
lines: Vec<String>,
}
impl TerminalTty<'_> {
pub fn create<'a>(term: DisplayTerminal<'a>) -> TerminalTty<'a> {
TerminalTty {
term: term,
lines: {
let mut vec = Vec::new();
vec.push("".to_string());
vec
},
}
}
}
impl Tty for TerminalTty<'_> {
fn putc(&mut self, c: char) {
if c == '\n' {
self.lines.push("".to_string());
return;
}
let i = self.lines.len() - 1;
self.lines[i].push(c);
}
fn puts(&mut self, s: &str) {
for c in s.chars() {
self.putc(c);
}
}
fn clear(&mut self) {
self.lines.clear();
self.lines.push("".to_string());
}
fn flush(&mut self) {
let mut physical_lines = Vec::new();
for line in &self.lines {
let mut chars = line.chars().collect::<Vec<_>>().into_iter();
while chars.len() > 0 {
let mut physical_line = String::new();
let width = chars.len().min(self.term.width());
for _ in 0..width {
physical_line.push(chars.next().unwrap());
}
physical_lines.push(physical_line);
}
}
let mut y = physical_lines.len().min(self.term.height() - 1);
for line in physical_lines.into_iter().rev() {
let mut x = 0;
for c in line.chars() {
self.term.set_char(x, y, c);
x += 1;
}
if y == 0 {
break;
}
y -= 1;
}
self.term.refresh();
}
}

View File

@ -1,7 +1,6 @@
#![no_std]
#![no_main]
#![feature(abi_efiapi)]
#![feature(alloc)]
#![feature(alloc_error_handler)]
#![feature(asm)]
extern crate alloc;