From 9fcfcbbf16d05d294aadb9b88db0f004ba802bea Mon Sep 17 00:00:00 2001 From: NatrixAeria Date: Thu, 15 Apr 2021 22:52:47 +0200 Subject: Rewrite a memory safe vga text buffer --- kernel/src/interrupts/interrupt_handlers.rs | 4 +- kernel/src/io/mod.rs | 1 + kernel/src/io/panic_screen.rs | 46 ++-- kernel/src/io/vga_text.rs | 362 ++++++++++++++++++++-------- kernel/src/lib.rs | 4 +- kernel/src/main.rs | 8 +- kernel/src/testing/runner.rs | 18 +- 7 files changed, 308 insertions(+), 135 deletions(-) diff --git a/kernel/src/interrupts/interrupt_handlers.rs b/kernel/src/interrupts/interrupt_handlers.rs index 831211b..defe549 100644 --- a/kernel/src/interrupts/interrupt_handlers.rs +++ b/kernel/src/interrupts/interrupt_handlers.rs @@ -3,7 +3,7 @@ use x86_64::structures::idt::InterruptStackFrame; use x86_64::structures::port; pub extern "x86-interrupt" fn timer_handler(_stack_frame: &mut InterruptStackFrame) { - crate::io::vga_text::OStream::new().print(b"Timer"); + crate::vga_println!("Timer"); if let Some(apic) = unsafe { super::apic::get_local_apic() } { apic.end_of_interrupt() @@ -12,7 +12,7 @@ pub extern "x86-interrupt" fn timer_handler(_stack_frame: &mut InterruptStackFra pub extern "x86-interrupt" fn keyboard_handler(_stack_frame: &mut InterruptStackFrame) { let code: u8 = unsafe { port::PortRead::read_from_port(0x60) }; - let _ = write!(crate::io::vga_text::OStream::new(), "{}", code); + crate::vga_println!("{}", code); if let Some(apic) = unsafe { super::apic::get_local_apic() } { apic.end_of_interrupt() } diff --git a/kernel/src/io/mod.rs b/kernel/src/io/mod.rs index e5dc66f..30645ad 100644 --- a/kernel/src/io/mod.rs +++ b/kernel/src/io/mod.rs @@ -1,4 +1,5 @@ pub mod qemu; pub mod serial; +#[macro_use] pub mod vga_text; pub mod panic_screen; diff --git a/kernel/src/io/panic_screen.rs b/kernel/src/io/panic_screen.rs index 7b81676..dddf8f5 100644 --- a/kernel/src/io/panic_screen.rs +++ b/kernel/src/io/panic_screen.rs @@ -1,4 +1,5 @@ -use crate::io::vga_text::{CharState, Color, OStream}; +use crate::io::vga_text::Color; +use core::fmt::Write; const PANIC_SCREEN_MESSAGE_BUFFER_SIZE: usize = 2048; @@ -11,8 +12,8 @@ impl<'a> TextBuffer<'a> { fn new(dst: &'a mut [u8]) -> Self { Self { dst, len: 0 } } - fn get(&'a mut self) -> &'a mut [u8] { - &mut self.dst[..self.len] + fn get(&self) -> &[u8] { + &self.dst[..self.len] } } @@ -28,23 +29,30 @@ impl<'a> core::fmt::Write for TextBuffer<'a> { } } -pub fn show(args: Option<&core::fmt::Arguments>) { - let mut stderr = OStream::new(); - stderr.set_state(CharState::from_colors(Color::LightRed, Color::Red)); - stderr.clear(); - stderr.print(b"uff-os"); - stderr.set_row(10); - stderr.set_state(CharState::from_colors(Color::White, Color::Red)); - stderr.print_centered(b""); - stderr.set_row(14); - stderr.set_state(CharState::from_colors(Color::Cyan, Color::Red)); - stderr.set_centered(true); - let buffer: &mut [u8] = &mut [b' '; PANIC_SCREEN_MESSAGE_BUFFER_SIZE]; +pub fn show(args: Option<&core::fmt::Arguments>) -> core::fmt::Result { + use Color::*; + let mut vga = crate::vga_lock!(); + vga.set_color_state(LightRed, Red); + vga.clear(); + vga.put_const_byte_str(*b"uff-os"); + vga.new_lines(10); + vga.set_color_state(White, Red); + vga.put_arguments(format_args!( + "{:^width$}", + "", + width = vga.width() + )); + vga.new_lines(2); + vga.set_color_state(Cyan, Red); + + let buffer = &mut [0u8; PANIC_SCREEN_MESSAGE_BUFFER_SIZE]; let mut tbuffer = TextBuffer::new(buffer); - let _ = core::fmt::write( + core::fmt::write( &mut tbuffer, *args.unwrap_or(&format_args!("no panic information obtainable")), - ); - stderr.print(tbuffer.get()); - stderr.set_centered(false); + )?; + for line in tbuffer.get().chunks(vga.width()) { + vga.put_byte_str(line); + } + Ok(()) } diff --git a/kernel/src/io/vga_text.rs b/kernel/src/io/vga_text.rs index 72c4d85..c6d8731 100644 --- a/kernel/src/io/vga_text.rs +++ b/kernel/src/io/vga_text.rs @@ -1,3 +1,49 @@ +use spin::{Mutex, MutexGuard}; + +pub const fn color_state_from_colors(fg: Color, bg: Color) -> u8 { + fg as u8 | ((bg as u8) << 4) +} + +const DEFAULT_COLOR_STATE: u8 = color_state_from_colors(Color::White, Color::Black); +const DEFAULT_VGA_TEXT_BUFFER: *mut VgaChar = 0xb8000 as *mut VgaChar; + +/// A character of Code page 437 with a foreground and background color state. +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[repr(transparent)] +pub struct VgaChar(u16); + +impl VgaChar { + const NULL: Self = Self(0); + + const fn from_bytes(bytes: [u8; 2]) -> Self { + Self(u16::from_ne_bytes(bytes)) + } + + pub const fn from_byte(byte: u8) -> Self { + Self::from_bytes([byte, DEFAULT_COLOR_STATE]) + } + + pub const fn from_byte_with_color_state(byte: u8, color_state: u8) -> Self { + Self::from_bytes([byte, color_state]) + } + + pub fn set_byte(&mut self, byte: u8) { + let byte = u16::to_ne_bytes(self.0)[0]; + self.0 = u16::from_ne_bytes([byte, byte]); + } + + pub fn set_color(&mut self, byte: u8) { + let color = u16::to_ne_bytes(self.0)[1]; + self.0 = u16::from_ne_bytes([byte, color]); + } +} + +impl From for VgaChar { + fn from(byte: u8) -> Self { + Self::from_byte(byte) + } +} + #[allow(dead_code)] #[repr(u8)] pub enum Color { @@ -19,159 +65,271 @@ pub enum Color { White = 15, } -pub const WIDTH: usize = 80; -pub const HEIGHT: usize = 25; +pub struct VgaTerminalController { + width: usize, + height: usize, + area: usize, + start: *mut VgaChar, + cursor: Mutex<*mut VgaChar>, +} -#[derive(Clone, Copy)] -#[repr(C, packed)] -pub struct CharState(pub u8); +unsafe impl Send for VgaTerminalController {} +unsafe impl Sync for VgaTerminalController {} -impl CharState { - pub fn from_colors(fg: Color, bg: Color) -> Self { - Self((fg as u8) | ((bg as u8) << 4)) - } +pub struct VgaTerminalControllerLock<'a> { + color_state: u8, + controller: &'a VgaTerminalController, + lock: MutexGuard<'a, *mut VgaChar>, +} + +pub static DEFAULT_VGA_TERMINAL_CONTROLLER: VgaTerminalController = unsafe { + VgaTerminalController::new( + core::num::NonZeroUsize::new_unchecked(80), + core::num::NonZeroUsize::new_unchecked(25), + DEFAULT_VGA_TEXT_BUFFER, + ) +}; - pub fn set_fg(&mut self, fg: Color) { - self.0 = (self.0 & (HEIGHT as u8) - 10) | (fg as u8) +impl VgaTerminalController { + pub fn lock(&self) -> VgaTerminalControllerLock { + VgaTerminalControllerLock { + color_state: DEFAULT_COLOR_STATE, + controller: self, + lock: self.cursor.lock(), + } } - pub fn set_bg(&mut self, bg: Color) { - self.0 = (self.0 & 15) | ((bg as u8) << 4) + pub const fn width(&self) -> usize { + self.width } -} -#[derive(Clone, Copy)] -#[repr(C, packed)] -pub struct VgaChar { - pub byte: u8, - pub state: CharState, -} + pub const fn height(&self) -> usize { + self.height + } -impl VgaChar { - pub fn from_state_and_byte(state: CharState, byte: u8) -> Self { - Self { state, byte } + pub const fn area(&self) -> usize { + self.area } -} -pub struct OStream { - pos: (usize, usize), - cursor: *mut VgaChar, - state: CharState, - centered_mode: bool, -} + pub const fn address(&self) -> *mut VgaChar { + self.start + } -impl OStream { - pub fn new() -> Self { + /// Creates a new VGA terminal controller with a width, height and an memory mapped I/O + /// address to the VGA text buffer. + /// + /// # Safety + /// The address must be a memory mapped I/O VGA text buffer with the given width and height. + /// Otherwise UB is to be expected. + pub const unsafe fn new( + width: core::num::NonZeroUsize, + height: core::num::NonZeroUsize, + addr: *mut VgaChar, + ) -> Self { + let area = width.get() * height.get(); Self { - pos: (0, 0), - cursor: Self::at(0), - state: CharState::from_colors(Color::White, Color::Black), - centered_mode: false, + width: width.get(), + height: height.get(), + area, + start: addr, + cursor: Mutex::new(addr), } } +} - fn at(n: usize) -> *mut VgaChar { - (0xb8000 + (n << 1)) as *mut VgaChar +impl<'a> VgaTerminalControllerLock<'a> { + pub const fn controller(&self) -> &'a VgaTerminalController { + self.controller } - fn compute_cursor(&mut self) { - self.cursor = Self::at(self.pos.0 as usize + self.pos.1 as usize * WIDTH) + #[inline] + const fn start(&self) -> *mut VgaChar { + self.controller.start } - pub fn set_col(&mut self, col: usize) { - self.pos.0 = core::cmp::min(col, WIDTH - 1); - self.compute_cursor() - } - pub fn set_row(&mut self, row: usize) { - self.pos.1 = core::cmp::min(row, HEIGHT - 1); - self.compute_cursor() + #[inline] + const fn area(&self) -> usize { + self.controller.area } - pub fn set_cursor(&mut self, col: usize, row: usize) { - self.pos = ( - core::cmp::min(col, WIDTH - 1), - core::cmp::min(row, HEIGHT - 1), - ); - self.compute_cursor() + pub const fn width(&self) -> usize { + self.controller.width } - pub fn set_char(&mut self, c: VgaChar) { - unsafe { self.cursor.write_volatile(c) } + pub const fn height(&self) -> usize { + self.controller.height } - pub fn put_char(&mut self, c: VgaChar) { - if c.byte == b'\n' { - self.new_line(); - } else if self.pos.0 >= WIDTH { - self.new_line(); - self.put_char(c); - } else { - self.set_char(c); - self.cursor = self.cursor.wrapping_offset(1); - self.pos.0 += 1; + fn move_up(&mut self, lines: usize) { + let offset = lines * self.controller.width; + assert!(offset <= self.area()); + unsafe { + core::intrinsics::volatile_copy_memory( + self.start(), + self.start().offset(offset as isize), + (self.area() - offset) * core::mem::size_of::(), + ); + } + let pos = self.area() - offset; + *self.lock = unsafe { self.start().offset(pos as isize) }; + unsafe { + core::intrinsics::volatile_set_memory( + *self.lock, + 0u8, + offset * core::mem::size_of::(), + ) } } - pub fn put_byte(&mut self, b: u8) { - self.put_char(VgaChar::from_state_and_byte(self.state, b)) + pub fn set_color_state(&mut self, fg: Color, bg: Color) { + self.color_state = color_state_from_colors(fg, bg); } - pub fn clear(&self) { - let c = VgaChar::from_state_and_byte(self.state, b' '); - for i in 0..(WIDTH * HEIGHT) { - unsafe { Self::at(i).write_volatile(c) } + pub fn clear(&mut self) { + unsafe { + core::intrinsics::volatile_set_memory( + *self.lock, + 0u8, + self.area() * core::mem::size_of::(), + ); + *self.lock = self.start(); } } - pub fn set_centered(&mut self, b: bool) { - self.centered_mode = b + pub fn get_coord(&self) -> isize { + unsafe { self.lock.offset_from(self.start()) } } - pub fn new_line(&mut self) { - if self.pos.1 >= HEIGHT - 1 { - self.set_col(0); - for i in 0..1920 { - unsafe { Self::at(i).write_volatile(*Self::at(i + WIDTH)) } - } + pub fn set_coord(&mut self, n: usize) { + assert!(n < self.area()); + *self.lock = unsafe { self.start().offset(n as isize) }; + } + + pub fn put_vga_chars(&mut self, chars: &[VgaChar]) { + if chars.len() >= self.area() { + *self.lock = self.start(); + self.put_vga_chars(&chars[chars.len() - self.area()..]); } else { - self.set_cursor(0, self.pos.1 + 1); + let end = self.get_coord() + chars.len() as isize; + if end > self.area() as isize { + self.move_up( + (end + self.controller.width as isize - 1 - self.area() as isize) as usize + / self.controller.width, + ); + } + unsafe { + core::intrinsics::volatile_copy_memory( + *self.lock, + chars.as_ptr(), + core::mem::size_of::() * chars.len(), + ); + } } } - pub fn set_state(&mut self, state: CharState) { - self.state = state + pub fn put_const_byte_str(&mut self, bytes: [u8; N]) { + self.put_vga_chars(&bytes.map(|b| VgaChar::from_byte_with_color_state(b, self.color_state))) } - pub fn put_bytes(&mut self, s: &[u8]) { - for &b in s { - self.put_byte(b) + pub fn put_byte_str(&mut self, bytes: &[u8]) { + for &byte in bytes { + self.put_vga_chars(&[VgaChar::from_byte_with_color_state(byte, self.color_state)]); } } - pub fn print(&mut self, s: &[u8]) { - if self.centered_mode { - self.print_centered(s) + pub fn put_arguments<'b>(&mut self, args: core::fmt::Arguments<'b>) { + let _ = core::fmt::write( + &mut WritableVgaTerminalControllerLock { controller: self }, + args, + ); + } + + pub fn new_line(&mut self) { + let line = self.get_coord() / self.controller.width as isize; + let line = if line + 1 >= self.controller.height as isize { + self.move_up(1); + line } else { - self.put_bytes(s) - } + line + 1 + }; + *self.lock = unsafe { self.start().offset(line * self.controller.width as isize) }; } - pub fn print_centered(&mut self, s: &[u8]) { - for line in s.split(|&c| c == b'\n') { - if line.is_empty() { - self.new_line() - } - for chunk in line.chunks(WIDTH) { - self.set_col((WIDTH - chunk.len()) >> 1); - self.put_bytes(chunk); - self.new_line() - } + pub fn new_lines(&mut self, n: usize) { + // TODO: optimize + for _ in 0..n { + self.new_line() } } } -impl core::fmt::Write for OStream { +struct WritableVgaTerminalControllerLock<'a, 'b> { + controller: &'a mut VgaTerminalControllerLock<'b>, +} + +impl<'a, 'b> core::fmt::Write for WritableVgaTerminalControllerLock<'a, 'b> { fn write_str(&mut self, s: &str) -> core::fmt::Result { - Ok(self.print(s.as_bytes())) + Ok(self.controller.put_byte_str(s.as_bytes())) } } + +#[macro_export] +macro_rules! vga_width { + () => { + $crate::io::vga_text::DEFAULT_VGA_TERMINAL_CONTROLLER.width() + }; +} + +#[macro_export] +macro_rules! vga_height { + () => { + $crate::io::vga_text::DEFAULT_VGA_TERMINAL_CONTROLLER.height() + }; +} + +#[macro_export] +macro_rules! vga_lock { + () => { + $crate::io::vga_text::DEFAULT_VGA_TERMINAL_CONTROLLER.lock() + }; +} + +#[macro_export] +macro_rules! vga_print { + (fg: $fg:expr, bg: $bg:expr, $($arg:tt)*) => {{ + let mut lock = $crate::vga_lock!(); + lock.set_color_state($fg, $bg); + lock.put_arguments(core::format_args!($($arg)*)); + }}; + ($($arg:tt)*) => { + $crate::vga_lock!().put_arguments(core::format_args!($($arg)*)) + }; +} + +#[macro_export] +macro_rules! vga_println { + () => { $crate::io::vga_text::DEFAULT_VGA_TERMINAL_CONTROLLER.lock().new_line() }; + (fg: $fg:expr, bg: $bg:expr, $($arg:tt)*) => {{ + let mut lock = $crate::vga_lock!(); + lock.set_color_state($fg, $bg); + lock.put_arguments(core::format_args!($($arg)*)); + lock.new_line(); + }}; + ($($arg:tt)*) => {{ + let mut lock = $crate::vga_lock!(); + lock.put_arguments(core::format_args!($($arg)*)); + lock.new_line(); + }}; +} + +#[macro_export] +macro_rules! vga_clear { + () => { + $crate::vga_lock!().clear() + }; + (fg: $fg:expr, bg: $bg:expr) => {{ + let mut lock = $crate::vga_lock!(); + lock.set_color_state($fg, $bg); + lock.clear(); + }}; +} diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index f03ef36..0aff3e8 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -3,16 +3,18 @@ #![feature(abi_x86_interrupt)] #![feature(panic_info_message)] #![feature(asm)] +#![feature(core_intrinsics)] +#![feature(array_map)] #![test_runner(crate::testing::serial_test_runner)] #![reexport_test_harness_main = "test_main"] #![no_std] pub mod interrupts; +#[macro_use] pub mod io; pub mod testing; pub use io::qemu::{exit_qemu, QemuExitCode}; -pub use io::vga_text::OStream; pub use io::{qemu, serial, vga_text}; pub use qemu::*; diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 50b281f..c55826a 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -3,15 +3,17 @@ #![feature(abi_x86_interrupt)] #![feature(panic_info_message)] #![feature(asm)] +#![feature(core_intrinsics)] +#![feature(array_map)] #![test_runner(crate::testing::test_runner)] #![no_std] pub mod interrupts; +#[macro_use] pub mod io; pub mod testing; pub use io::qemu::{exit_qemu, QemuExitCode}; -pub use io::vga_text::OStream; pub use io::{qemu, serial, vga_text}; #[no_mangle] @@ -29,8 +31,8 @@ extern "C" fn _start() -> ! { x86_64::instructions::interrupts::enable(); - let mut stdout = OStream::new(); - stdout.print(b"apic initialisation complete\n"); + vga_println!("apic initialisation complete"); + x86_64::instructions::interrupts::int3(); if cfg!(test) { diff --git a/kernel/src/testing/runner.rs b/kernel/src/testing/runner.rs index 38d6d94..63b4013 100644 --- a/kernel/src/testing/runner.rs +++ b/kernel/src/testing/runner.rs @@ -1,8 +1,7 @@ -use crate::io::{qemu, serial, vga_text}; +use crate::io::{qemu, serial}; use core::fmt::Write; use qemu::{exit_qemu, QemuExitCode}; use serial::SerialStream; -use vga_text::OStream; pub fn serial_test_runner_panic(tests: &[&dyn Fn()]) { let mut stdout = SerialStream::new(); @@ -26,22 +25,25 @@ pub fn serial_test_runner(tests: &[&dyn Fn()]) { } pub fn test_runner_panic(tests: &[&dyn Fn()]) { - let mut stdout = OStream::new(); - write!(stdout, "\nRunning {} tests\n", tests.len()).unwrap(); + crate::vga_println!(); + crate::vga_println!("Running {} tests", tests.len()); for test in tests { test(); - write!(stdout, "\n[test did not panic]\n\n").unwrap(); + crate::vga_println!(); + crate::vga_println!("[test did not panic]"); + crate::vga_println!(); exit_qemu(QemuExitCode::Failed); } exit_qemu(QemuExitCode::Success); } pub fn test_runner(tests: &[&dyn Fn()]) { - let mut stdout = OStream::new(); - write!(stdout, "\nRunning {} tests\n", tests.len()).unwrap(); + crate::vga_println!(); + crate::vga_println!("Running {} tests", tests.len()); for test in tests { test(); - write!(stdout, "\n[OK]\n").unwrap(); + crate::vga_println!(); + crate::vga_println!("[OK]"); } exit_qemu(QemuExitCode::Success); } -- cgit v1.2.3-70-g09d2