diff --git a/src/graphics/font/cozette-attribution.txt b/assets/cozette-attribution.txt similarity index 100% rename from src/graphics/font/cozette-attribution.txt rename to assets/cozette-attribution.txt diff --git a/src/graphics/font/cozette.psf b/assets/cozette.psf similarity index 100% rename from src/graphics/font/cozette.psf rename to assets/cozette.psf diff --git a/run.sh b/run.sh index a1f9077..3f8072f 100755 --- a/run.sh +++ b/run.sh @@ -2,4 +2,9 @@ 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 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 +qemu-system-x86_64 \ + -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" \ + -nodefaults -cpu host -smp 8 -m 128M -machine "q35,accel=kvm:tcg" \ + -display gtk,gl=on -vga virtio -serial stdio diff --git a/src/graphics/display.rs b/src/driver/graphic_display.rs similarity index 75% rename from src/graphics/display.rs rename to src/driver/graphic_display.rs index cfc4263..c7d59da 100644 --- a/src/graphics/display.rs +++ b/src/driver/graphic_display.rs @@ -1,12 +1,17 @@ use crate::graphics::color::RGB; use crate::graphics::font::Glyph; -pub trait Display { +pub trait GraphicDisplay { fn resolution(&self) -> (usize, usize); fn width(&self) -> usize { self.resolution().0 } fn height(&self) -> usize { self.resolution().1 } // HACK: This interface sucks. + // These interfaces don't support any color (e.g. `impl Color`) + // because then I can't use this as a `dyn` trait because of the generic. + // I *could* use `dyn Color`, but... why would you do that?? + /// Unsafe: it is the responsibility of the caller to ensure + /// that the pixel is within the boundaries of the screen. unsafe fn set_pixel(&mut self, color: RGB, x: usize, y: usize); fn set_pixel_ignore_oob(&mut self, color: RGB, x: usize, y: usize) { if x > self.width() || y > self.height() { @@ -18,8 +23,14 @@ pub trait Display { } } + /// Set the entire display to the same color, clearing everything previously drawn. fn clear(&mut self, color: RGB); + /// Display everything that was drawn to the screen. + fn refresh(&mut self); + + /// Unsafe: it is the responsibility of the caller to ensure + /// that the entire glyph fits within the boundaries of the screen. unsafe fn draw_glyph(&mut self, bounding_box: (usize, usize), x: usize, y: usize, color: RGB, glyph: &dyn Glyph) { // We only assume that space was left for pixels within the bounding box, // and that pixels outside the bounding box may be out-of-bounds. diff --git a/src/driver/mod.rs b/src/driver/mod.rs new file mode 100644 index 0000000..c462537 --- /dev/null +++ b/src/driver/mod.rs @@ -0,0 +1,3 @@ +pub mod graphic_display; +pub mod text_display; +pub mod tty; diff --git a/src/driver/text_display.rs b/src/driver/text_display.rs new file mode 100644 index 0000000..965cb2e --- /dev/null +++ b/src/driver/text_display.rs @@ -0,0 +1,66 @@ +pub mod graphic; + +use alloc::boxed::Box; + +/// A text-mode display. Basically, an array of characters that you can set in any order. +pub trait TextDisplay { + fn borrow_frame<'a>(&'a self) -> &'a TextDisplayFrame; + fn borrow_mut_frame<'a>(&'a mut self) -> &'a mut TextDisplayFrame; + /// Display all changes made to the frame. + fn refresh(&mut self); +} + +/// A frame of a text display; basically a 2d array of characters which you can set how you please. +/// However, this frame doesn't know anything about how to display itself; +/// that's what the TextDisplay trait is for. +pub struct TextDisplayFrame { + resolution: (usize, usize), + buf: Box<[char]>, +} + +impl TextDisplayFrame { + pub fn new(resolution: (usize, usize)) -> TextDisplayFrame { + use alloc::vec::Vec; + + let (width, height) = resolution; + let mut buf = Vec::new(); + buf.resize(width * height, '\u{0}'); + + TextDisplayFrame { + resolution: resolution, + buf: buf.into_boxed_slice() + } + } + + pub fn resolution(&self) -> (usize, usize) { + self.resolution + } + + pub fn width(&self) -> usize { + self.resolution.0 + } + + pub fn height(&self) -> usize { + self.resolution.1 + } + + /// Set all characters in this frame to null. + pub fn clear(&mut self) { + for i in 0..self.buf.len() { + self.buf[i] = '\u{0}'; + } + } + + fn index(&self, x: usize, y: usize) -> usize { + self.width() * y + x + } + + pub fn get(&self, x: usize, y: usize) -> char { + self.buf[self.index(x, y)] + } + + pub fn set(&mut self, x: usize, y: usize, c: char) { + let i = self.index(x, y); + self.buf[i] = c; + } +} diff --git a/src/graphics/terminal/display.rs b/src/driver/text_display/graphic.rs similarity index 63% rename from src/graphics/terminal/display.rs rename to src/driver/text_display/graphic.rs index abb4916..25a4205 100644 --- a/src/graphics/terminal/display.rs +++ b/src/driver/text_display/graphic.rs @@ -1,43 +1,43 @@ +use crate::driver::graphic_display::GraphicDisplay; +use crate::driver::text_display::{TextDisplayFrame, TextDisplay}; use crate::graphics::color::{Color, RGB}; -use crate::graphics::display::Display; use crate::graphics::font::{Font, Glyph}; -use crate::graphics::terminal::Terminal; -use crate::graphics::terminal::frame::TerminalFrame; -pub struct DisplayTerminal<'d, 'f, G: Glyph> { - display: &'d mut (dyn Display + 'd), +/// A virtual text display that renders itself onto a graphic display. +pub struct GraphicTextDisplay<'d, 'f, G: Glyph> { + display: &'d mut (dyn GraphicDisplay + 'd), font: &'f (dyn Font + 'f), - frame: TerminalFrame, + frame: TextDisplayFrame, bg: RGB, fg: RGB, } -impl DisplayTerminal<'_, '_, G> { +impl GraphicTextDisplay<'_, '_, G> { pub fn new<'d, 'f> - (display: &'d mut (dyn Display + 'd), font: &'f (dyn Font + 'f), + (display: &'d mut (dyn GraphicDisplay + 'd), font: &'f (dyn Font + 'f), bg: impl Color, fg: impl Color) - -> DisplayTerminal<'d, 'f, G> { + -> GraphicTextDisplay<'d, 'f, G> { let (dp_width, dp_height) = display.resolution(); let (ft_width, ft_height) = font.bounding_box(); let ch_width = dp_width / ft_width as usize; let ch_height = dp_height / ft_height as usize; - DisplayTerminal { + GraphicTextDisplay { display: display, font: font, - frame: TerminalFrame::new((ch_width, ch_height)), + frame: TextDisplayFrame::new((ch_width, ch_height)), bg: bg.into_rgb(), fg: fg.into_rgb(), } } } -impl Terminal for DisplayTerminal<'_, '_, G> { - fn get_frame<'a>(&'a self) -> &'a TerminalFrame { +impl TextDisplay for GraphicTextDisplay<'_, '_, G> { + fn borrow_frame<'a>(&'a self) -> &'a TextDisplayFrame { &self.frame } - fn borrow_frame<'a>(&'a mut self) -> &'a mut TerminalFrame { + fn borrow_mut_frame<'a>(&'a mut self) -> &'a mut TextDisplayFrame { &mut self.frame } diff --git a/src/driver/tty.rs b/src/driver/tty.rs new file mode 100644 index 0000000..80db461 --- /dev/null +++ b/src/driver/tty.rs @@ -0,0 +1,17 @@ +pub mod serial; +pub mod text_display; + +/// A teletypewriter, or really, because those don't exist anymore, +/// a device that behaves like or emulates a teletypewriter. +/// Basically, this is a device that lets you output text and not much else. +/// Its output may be buffered, so make sure you `flush` the output. +pub trait Tty { + /// Print a single character to the TTY. + fn putc(&mut self, c: char); + /// Print an entire string to the TTY. + fn puts(&mut self, s: &str); + /// Clear all TTY output. + fn clear(&mut self); + /// Synchronously flush any buffered output. + fn flush(&mut self); +} diff --git a/src/graphics/tty/serial.rs b/src/driver/tty/serial.rs similarity index 55% rename from src/graphics/tty/serial.rs rename to src/driver/tty/serial.rs index dc0d262..366693b 100644 --- a/src/graphics/tty/serial.rs +++ b/src/driver/tty/serial.rs @@ -1,17 +1,30 @@ -use crate::graphics::tty::Tty; +use crate::driver::tty::Tty; -#[derive(Clone)] +/// A TTY attached via a serial port. +/// +/// Serial ports don't commonly exist on physical devices anymore, +/// but many emulators support them and can map them to the host's TTY/terminal emulator, +/// which makes them useful for debugging in a VM. pub struct SerialTty { port: u16, } +/// The port used by COM1 on x86 devices. +#[cfg(target_arch = "x86_64")] +pub const COM1_PORT: u16 = 0x3F8; + impl SerialTty { + /// Creates a new serial TTY which will use the provided port for output. + /// + /// Unsafe because it is up to the caller to make sure + /// that the port is actually the port for a TTY device. pub unsafe fn new(port: u16) -> SerialTty { SerialTty { port: port, } } + #[cfg(target_arch = "x86_64")] fn outb(&self, cmd: u8) { unsafe { asm!("out dx, al", in("dx") self.port, in("al") cmd); @@ -44,6 +57,6 @@ impl Tty for SerialTty { } fn flush(&mut self) { - // This TTY doesn't support buffering. + // This TTY doesn't use buffering. } } diff --git a/src/driver/tty/text_display.rs b/src/driver/tty/text_display.rs new file mode 100644 index 0000000..ceed7b6 --- /dev/null +++ b/src/driver/tty/text_display.rs @@ -0,0 +1,89 @@ +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use crate::driver::text_display::TextDisplay; +use crate::driver::tty::Tty; + +/// A buffered virtual TTY implemented over a textual display. +pub struct TextDisplayTty<'display> { + term: &'display mut (dyn TextDisplay + 'display), + history: Vec, +} + +impl TextDisplayTty<'_> { + pub fn new<'a>(term: &'a mut dyn TextDisplay) -> TextDisplayTty<'a> { + TextDisplayTty { + term: term, + history: { + let mut vec = Vec::new(); + vec.push("".to_string()); + vec + }, + } + } +} + +impl Tty for TextDisplayTty<'_> { + fn putc(&mut self, c: char) { + if c == '\n' { + self.history.push("".to_string()); + return; + } + let i = self.history.len() - 1; + self.history[i].push(c); + } + + fn puts(&mut self, s: &str) { + for c in s.chars() { + self.putc(c); + } + } + + fn clear(&mut self) { + self.history.clear(); + self.history.push("".to_string()); + } + + fn flush(&mut self) { + // Each line of the history represents a virtual line of output. + // However, a line of output may be longer than the physical width of the display, + // in which case we may need to wrap the line so that it takes up two physical lines. + let mut physical_lines = Vec::new(); + for line in &self.history { + let mut chars = line.chars().collect::>().into_iter(); + // We iterate over all of the characters in a virtual line + // until every character has been added to a physical line. + while chars.len() > 0 { + let mut physical_line = String::new(); + // The width of a physical line may be no more than the width of the frame. + let width = chars.len().min(self.term.borrow_frame().width()); + for _ in 0..width { + physical_line.push(chars.next().unwrap()); + } + physical_lines.push(physical_line); + } + } + + // This is how many lines on the display we'll need for all of our physical lines. + // We cannot have more lines than allowed by the display. + let mut y = physical_lines.len().min(self.term.borrow_frame().height() - 1); + let frame = self.term.borrow_mut_frame(); + // We start from the lowest line and display each line until we reach the top of the screen. + // We cannot run out of physical lines because the lowest line + // is at lowest the number of physical lines necessary to display all lines. + for line in physical_lines.into_iter().rev() { + let mut x = 0; + for c in line.chars() { + frame.set(x, y, c); + x += 1; + } + + if y == 0 { + break; + } + + y -= 1; + } + + self.term.refresh(); + } +} diff --git a/src/graphics.rs b/src/graphics.rs deleted file mode 100644 index 1fe51e5..0000000 --- a/src/graphics.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod color; -pub mod display; -pub mod font; -pub mod terminal; -pub mod tty; diff --git a/src/graphics/mod.rs b/src/graphics/mod.rs new file mode 100644 index 0000000..32bf817 --- /dev/null +++ b/src/graphics/mod.rs @@ -0,0 +1,2 @@ +pub mod color; +pub mod font; diff --git a/src/graphics/terminal.rs b/src/graphics/terminal.rs deleted file mode 100644 index f0b37d6..0000000 --- a/src/graphics/terminal.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod display; -pub mod frame; - -use crate::graphics::terminal::frame::TerminalFrame; - -pub trait Terminal { - fn get_frame<'a>(&'a self) -> &'a TerminalFrame; - fn borrow_frame<'a>(&'a mut self) -> &'a mut TerminalFrame; - fn refresh(&mut self); -} diff --git a/src/graphics/terminal/frame.rs b/src/graphics/terminal/frame.rs deleted file mode 100644 index 2838b16..0000000 --- a/src/graphics/terminal/frame.rs +++ /dev/null @@ -1,53 +0,0 @@ -use alloc::vec::Vec; - -pub struct TerminalFrame { - resolution: (usize, usize), - buf: Vec, -} - -impl TerminalFrame { - pub fn new(resolution: (usize, usize)) -> TerminalFrame { - let (width, height) = resolution; - let buf_length = width * height; - let mut buf = Vec::with_capacity(buf_length); - for _ in 0..buf_length { - buf.push('\u{0}'); - } - - TerminalFrame { - resolution: resolution, - buf: buf - } - } - - pub fn resolution(&self) -> (usize, usize) { - self.resolution - } - - pub fn width(&self) -> usize { - self.resolution.0 - } - - pub fn height(&self) -> usize { - self.resolution.1 - } - - pub fn clear(&mut self) { - for i in 0..self.buf.len() { - self.buf[i] = '\u{0}'; - } - } - - fn index(&self, x: usize, y: usize) -> usize { - self.width() * y + x - } - - pub fn get(&self, x: usize, y: usize) -> char { - self.buf[self.index(x, y)] - } - - pub fn set(&mut self, x: usize, y: usize, c: char) { - let i = self.index(x, y); - self.buf[i] = c; - } -} diff --git a/src/graphics/tty.rs b/src/graphics/tty.rs deleted file mode 100644 index 6dbbcb4..0000000 --- a/src/graphics/tty.rs +++ /dev/null @@ -1,9 +0,0 @@ -pub mod serial; -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); -} diff --git a/src/graphics/tty/terminal.rs b/src/graphics/tty/terminal.rs deleted file mode 100644 index b7694b8..0000000 --- a/src/graphics/tty/terminal.rs +++ /dev/null @@ -1,77 +0,0 @@ -use alloc::string::{String, ToString}; -use alloc::vec::Vec; -use crate::graphics::terminal::Terminal; -use crate::graphics::tty::Tty; - -pub struct TerminalTty<'terminal> { - term: &'terminal mut dyn Terminal, - history: Vec, -} - -impl TerminalTty<'_> { - pub fn new<'a>(term: &'a mut dyn Terminal) -> TerminalTty<'a> { - TerminalTty { - term: term, - history: { - let mut vec = Vec::new(); - vec.push("".to_string()); - vec - }, - } - } -} - -impl Tty for TerminalTty<'_> { - fn putc(&mut self, c: char) { - if c == '\n' { - self.history.push("".to_string()); - return; - } - let i = self.history.len() - 1; - self.history[i].push(c); - } - - fn puts(&mut self, s: &str) { - for c in s.chars() { - self.putc(c); - } - } - - fn clear(&mut self) { - self.history.clear(); - self.history.push("".to_string()); - } - - fn flush(&mut self) { - let mut physical_lines = Vec::new(); - for line in &self.history { - let mut chars = line.chars().collect::>().into_iter(); - while chars.len() > 0 { - let mut physical_line = String::new(); - let width = chars.len().min(self.term.get_frame().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.get_frame().height() - 1); - let frame = self.term.borrow_frame(); - for line in physical_lines.into_iter().rev() { - let mut x = 0; - for c in line.chars() { - frame.set(x, y, c); - x += 1; - } - - if y == 0 { - break; - } - - y -= 1; - } - - self.term.refresh(); - } -} diff --git a/src/logger.rs b/src/logger.rs index 3993595..e3a03c2 100644 --- a/src/logger.rs +++ b/src/logger.rs @@ -1,7 +1,7 @@ use alloc::format; use core::cell::UnsafeCell; -use crate::graphics::tty::Tty; -use crate::graphics::tty::serial::SerialTty; +use crate::driver::tty::Tty; +use crate::driver::tty::serial::SerialTty; use log::{Record, LevelFilter, Metadata, SetLoggerError}; enum GlobalLogger { diff --git a/src/main.rs b/src/main.rs index 7a4cd4b..9df2c7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,19 +7,18 @@ #![feature(generic_associated_types)] extern crate alloc; -mod allocator; mod arch; +mod driver; mod graphics; - +mod memory; mod logger; use alloc::vec::Vec; -use crate::graphics::tty::serial::SerialTty; use uefi::prelude::*; #[entry] fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { - use crate::allocator::{ALLOCATOR, GlobalAllocator}; + use crate::memory::allocator::{ALLOCATOR, GlobalAllocator}; unsafe { // Generally speaking, we want to depend on UEFI as little as possible, // so the need for a UEFI allocator may seem a bit strange. @@ -29,14 +28,15 @@ fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { // In theory there are probably ways to get around it, but why bother? // Just taking advantage of the UEFI allocator briefly is a lot easier. // (This also lets us use `println!` prior to our main allocator being set up.) - use crate::allocator::uefi::UefiAllocator; + use crate::memory::allocator::uefi::UefiAllocator; // ABSOLUTELY DO NOT FORGET TO DISABLE THIS AFTER LEAVING UEFI BOOT SERVICES. // ALL ALLOCATIONS MUST BE STATIC OR BE FREED BEFORE BOOT SERVICES EXITS. // If the're not, Rust still try to free UEFI-allocated data using the new allocator, // which is undefined behavior. ALLOCATOR = GlobalAllocator::Uefi(UefiAllocator::new(st_boot.unsafe_clone())); - logger::set_tty(SerialTty::new(0x3F8)); + use crate::driver::tty::serial::{COM1_PORT, SerialTty}; + logger::set_tty(SerialTty::new(COM1_PORT)); logger::init().unwrap(); } @@ -62,7 +62,7 @@ fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { // HACK: I hate having to use the UEFI allocator just to set up another allocator! // There's got to be a better way. - use crate::allocator::standard::StandardAllocator; + use crate::memory::allocator::standard::StandardAllocator; let mut allocator; { let mut mmap = bs.memory_map(mmap_buf.as_mut_slice()) diff --git a/src/allocator.rs b/src/memory/allocator.rs similarity index 100% rename from src/allocator.rs rename to src/memory/allocator.rs diff --git a/src/allocator/standard.rs b/src/memory/allocator/standard.rs similarity index 100% rename from src/allocator/standard.rs rename to src/memory/allocator/standard.rs diff --git a/src/allocator/uefi.rs b/src/memory/allocator/uefi.rs similarity index 100% rename from src/allocator/uefi.rs rename to src/memory/allocator/uefi.rs diff --git a/src/memory/mod.rs b/src/memory/mod.rs new file mode 100644 index 0000000..98fe5c3 --- /dev/null +++ b/src/memory/mod.rs @@ -0,0 +1 @@ +pub mod allocator;