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/io/vga_text.rs | 362 +++++++++++++++++++++++++++++++++------------- 1 file changed, 260 insertions(+), 102 deletions(-) (limited to 'kernel/src/io/vga_text.rs') 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(); + }}; +} -- cgit v1.2.3-54-g00ecf