summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNatrixAeria <upezu@student.kit.edu>2021-04-15 22:52:47 +0200
committerNatrixAeria <upezu@student.kit.edu>2021-04-15 22:52:47 +0200
commit9fcfcbbf16d05d294aadb9b88db0f004ba802bea (patch)
tree789c83f75e3391ecd3722e94e9a275229ec065e3
parent8b810a009f8d29ba61a3c377fdeac06a3bbb151d (diff)
Rewrite a memory safe vga text buffer
-rw-r--r--kernel/src/interrupts/interrupt_handlers.rs4
-rw-r--r--kernel/src/io/mod.rs1
-rw-r--r--kernel/src/io/panic_screen.rs46
-rw-r--r--kernel/src/io/vga_text.rs362
-rw-r--r--kernel/src/lib.rs4
-rw-r--r--kernel/src/main.rs8
-rw-r--r--kernel/src/testing/runner.rs18
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"<kernel panic>");
- 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$}",
+ "<kernel panic>",
+ 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<u8> 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::<VgaChar>(),
+ );
+ }
+ 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::<VgaChar>(),
+ )
}
}
- 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::<VgaChar>(),
+ );
+ *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::<VgaChar>() * chars.len(),
+ );
+ }
}
}
- pub fn set_state(&mut self, state: CharState) {
- self.state = state
+ pub fn put_const_byte_str<const N: usize>(&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);
}