Massive reorganization. Most files moved, lots of stuff renamed.

master
James T. Martin 2020-07-21 15:13:20 -07:00
parent ae7e7d3a80
commit bc850fecd6
Signed by: james
GPG Key ID: 4B7F3DA9351E577C
22 changed files with 235 additions and 182 deletions

7
run.sh
View File

@ -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

View File

@ -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.

3
src/driver/mod.rs Normal file
View File

@ -0,0 +1,3 @@
pub mod graphic_display;
pub mod text_display;
pub mod tty;

View File

@ -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;
}
}

View File

@ -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<Glyph = G> + 'f),
frame: TerminalFrame,
frame: TextDisplayFrame,
bg: RGB,
fg: RGB,
}
impl<G: Glyph> DisplayTerminal<'_, '_, G> {
impl<G: Glyph> GraphicTextDisplay<'_, '_, G> {
pub fn new<'d, 'f>
(display: &'d mut (dyn Display + 'd), font: &'f (dyn Font<Glyph = G> + 'f),
(display: &'d mut (dyn GraphicDisplay + 'd), font: &'f (dyn Font<Glyph = G> + '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<G: Glyph> Terminal for DisplayTerminal<'_, '_, G> {
fn get_frame<'a>(&'a self) -> &'a TerminalFrame {
impl<G: Glyph> 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
}

17
src/driver/tty.rs Normal file
View File

@ -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);
}

View File

@ -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.
}
}

View File

@ -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<String>,
}
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::<Vec<_>>().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();
}
}

View File

@ -1,5 +0,0 @@
pub mod color;
pub mod display;
pub mod font;
pub mod terminal;
pub mod tty;

2
src/graphics/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod color;
pub mod font;

View File

@ -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);
}

View File

@ -1,53 +0,0 @@
use alloc::vec::Vec;
pub struct TerminalFrame {
resolution: (usize, usize),
buf: Vec<char>,
}
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;
}
}

View File

@ -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);
}

View File

@ -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<String>,
}
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::<Vec<_>>().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();
}
}

View File

@ -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 {

View File

@ -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<Boot>) -> 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<Boot>) -> 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<Boot>) -> 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())

1
src/memory/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod allocator;