commented, reformatted

This commit is contained in:
mtgmonkey 2025-03-28 19:20:59 -04:00
parent b4dab5c048
commit 723662b2a1
6 changed files with 127 additions and 67 deletions

View file

@ -3,15 +3,24 @@ use x86_64::structures::gdt::{Descriptor, GlobalDescriptorTable, SegmentSelector
use x86_64::structures::tss::TaskStateSegment;
use x86_64::VirtAddr;
// struct Selectors ::: selectors returned by GDT
// code_selector: SegmentSelector :field: indicates where the CS register
// needs to be put in case of a double fault
// tss_selector: SegmentSelector :field: indicates where the TSS ought to start
// in case of a double fault
struct Selectors {
code_selector: SegmentSelector,
tss_selector: SegmentSelector,
}
// const DOUBLE_FAULT_IST_INDEX: u16 ::: virtual location of double fault stack
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
lazy_static! {
// GDT: (GlobalDescriptorTable, Selectors) ::: antiquated, used to specify TSS
// SAFETY ensure the correct selectors are returned
static ref GDT: (GlobalDescriptorTable, Selectors) = {
let mut gdt = GlobalDescriptorTable::new();
// SAFETY ensure these values are safe in init()
let code_selector = gdt.add_entry(Descriptor::kernel_code_segment());
let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS));
(
@ -22,12 +31,8 @@ lazy_static! {
},
)
};
}
// IST index to use for double fault stack
pub const DOUBLE_FAULT_IST_INDEX: u16 = 0;
lazy_static! {
// TSS: TaskStateSegment ::: holds stack tables
static ref TSS: TaskStateSegment = {
let mut tss = TaskStateSegment::new();
tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = {
@ -41,6 +46,7 @@ lazy_static! {
};
}
// fn init ::: initializes and loads GDT and TSS and places CS accordingly
pub fn init() {
use x86_64::instructions::segmentation::{Segment, CS};
use x86_64::instructions::tables::load_tss;

View file

@ -4,9 +4,11 @@ use pic8259::ChainedPics;
use spin;
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode};
// const {PIC_1_OFFSET, PIC_2_OFFSET}: u8 ::: defines where PICs are located
pub const PIC_1_OFFSET: u8 = 32;
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
// enum InterruptIndex ::: index in IDT of each interrupt
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum InterruptIndex {
@ -15,27 +17,34 @@ pub enum InterruptIndex {
}
impl InterruptIndex {
// fn as_u8 :::
// self :param:
// -> u8 :ret:
fn as_u8(self) -> u8 {
self as u8
}
// fn as_usize :::
// self :param:
// -> usize :ret: pointer-sized type for IDT index
fn as_usize(self) -> usize {
usize::from(self.as_u8())
}
}
// set offsets for PICs
// SAFETY
// PICS: spin::Mutex<ChainedPics> ::: magic words to create mutable PICs at PIC_1_OFFSET
// SAFETY ensure PIC_1_OFFSET and PIC_2_OFFSET are not 0-15 and are otherwise unused memory
pub static PICS: spin::Mutex<ChainedPics> =
spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
// only init idt in memory when first referred to
// allows static mut
lazy_static! {
// IDT: InterruptDescriptorTable ::: declares handlers for each interrupt
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
// SAFETY ensure index is valid and not passed to any other exceptions
// SAFETY ensure index is valid stack index and otherwise unused
unsafe {
idt.double_fault.set_handler_fn(double_fault_handler).set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
}
@ -46,12 +55,15 @@ lazy_static! {
};
}
// handle breakpoint exceptions
// fn breakpoint_handler ::: prints error message
// stack_frame: InterruptStackFrame :param:
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
}
// handle double fault exceptions
// fn double_fault_handler ::: prints error message
// stack_frame: InterruptStackFrame :param:
// _error_code: u64 :param:
extern "x86-interrupt" fn double_fault_handler(
stack_frame: InterruptStackFrame,
_error_code: u64,
@ -59,17 +71,21 @@ extern "x86-interrupt" fn double_fault_handler(
panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
}
// init idt
// fn init_idt ::: loads IDT
pub fn init_idt() {
IDT.load();
}
// fn keyboard_interrupt_handler ::: prints keys when pressed
// _keyboard_stack_frame: InterruptStackFrame :param:
extern "x86-interrupt" fn keyboard_interrupt_handler(_keyboard_stack_frame: InterruptStackFrame) {
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
use spin::Mutex;
use x86_64::instructions::port::Port;
lazy_static! {
// KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> ::: keyboard to compare
// key_event to
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
Mutex::new(Keyboard::new(
ScancodeSet1::new(),
@ -81,6 +97,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_keyboard_stack_frame: Inte
let mut keyboard = KEYBOARD.lock();
let mut port = Port::new(0x60);
// SAFETY ensure port is set to 0x60, the keyboard port
let scancode: u8 = unsafe { port.read() };
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
@ -97,6 +114,9 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_keyboard_stack_frame: Inte
}
}
// fn page_fault_handler ::: prints error message
// stack_frame: InterruptStackFrame :param:
// error_code: PageFaultErrorCode :param:
extern "x86-interrupt" fn page_fault_handler(
stack_frame: InterruptStackFrame,
error_code: PageFaultErrorCode,
@ -110,12 +130,14 @@ extern "x86-interrupt" fn page_fault_handler(
hlt_loop();
}
// forcefully throws breakpoint exception
// fn test_breakpoint_exception ::: checks if breakpoint exception gets thrown correctly
#[test_case]
fn test_breakpoint_exception() {
x86_64::instructions::interrupts::int3();
}
// fn timer_interrupt_handler ::: prints . when timer interrupt
// _stack_frame: InterruptStackFrame :param:
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
print!(".");
unsafe {

View file

@ -12,7 +12,7 @@ pub mod vga_buffer;
use core::panic::PanicInfo;
// wizardry for Testable Fn() type
// trait Testable ::: defines run() for all test functions
pub trait Testable {
fn run(&self) -> ();
}
@ -28,7 +28,9 @@ where
}
}
// defines QemuExitCodes
// enum QemuExitCode ::: used when exiting with Qemu's isa-debug-exit device
// Success :field:
// Failed :field:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u32)]
pub enum QemuExitCode {
@ -36,6 +38,7 @@ pub enum QemuExitCode {
Failed = 0x11,
}
// fn _start ::: runs tests
#[cfg(test)]
#[unsafe(no_mangle)]
pub extern "C" fn _start() -> ! {
@ -44,8 +47,8 @@ pub extern "C" fn _start() -> ! {
hlt_loop();
}
// exits qemu with an exit code
// qemu has special escape port at 0xf4
// fn exit_qemu ::: uses Qemu's isa-debug-exit device to quit
// exit_code: QemuExitCode :param:
pub fn exit_qemu(exit_code: QemuExitCode) {
use x86_64::instructions::port::Port;
unsafe {
@ -54,29 +57,32 @@ pub fn exit_qemu(exit_code: QemuExitCode) {
}
}
// fn hlt_loop ::: idles the cpu
pub fn hlt_loop() -> ! {
loop {
x86_64::instructions::hlt();
}
}
// common init fn for kernel
// fn init ::: initializes everything; ought be the first thing that's called
pub fn init() {
gdt::init();
interrupts::init_idt();
// SAFETY ensure the PICs are properly defined in interrupts.rs
unsafe { interrupts::PICS.lock().initialize() };
x86_64::instructions::interrupts::enable();
}
// prints error code for failed test and exits qemu with a failure
// fn test_panic_handler ::: prints error info, exits qemu
// info: &PanicInfo :param:
pub fn test_panic_handler(info: &PanicInfo) -> ! {
serial_println!("[failed]\n");
serial_println!("Error: {}\n", info);
exit_qemu(QemuExitCode::Failed);
hlt_loop();
hlt_loop(); // unreachable but Rust doesn't know that
}
// runs tests
// fn test_runner ::: cycles through all Testable
// tests: &[&dyn Testable] :param: every Testable function in scope
pub fn test_runner(tests: &[&dyn Testable]) {
serial_println!("Running {} tests", tests.len());
for test in tests {
@ -85,7 +91,8 @@ pub fn test_runner(tests: &[&dyn Testable]) {
exit_qemu(QemuExitCode::Success);
}
// called on panic
// fn panic ::: calls test_panic_handler, which is basically a public wrapper
// info: &PanicInfo :param:
#[cfg(test)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {

View file

@ -7,21 +7,12 @@
use core::panic::PanicInfo;
use rustos::println;
// SAFETY: make sure no other global fn _start exists
// called on start
#[unsafe(no_mangle)]
// fn _start ::: called on start
#[unsafe(no_mangle)] // SAFETY: make sure no other global fn _start exists
pub extern "C" fn _start() -> ! {
println!("Hello World{}", "!");
rustos::init(); // initializes kernel
use x86_64::registers::control::Cr3;
let (level_4_page_table, _) = Cr3::read();
println!(
"Level 4 page table at: {:?}",
level_4_page_table.start_address()
);
#[cfg(test)]
test_main();
@ -29,19 +20,15 @@ pub extern "C" fn _start() -> ! {
rustos::hlt_loop();
}
// called on test panic
// prints to console error message
#[cfg(test)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
rustos::test_panic_handler(info)
}
// called on panic
// prints to vga error message
#[cfg(not(test))]
// fn panic ::: prints panic message
// info: &PanicInfo :param: passed by panic_handler, includes panic information
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
#[cfg(test)]
rustos::test_panic_handler(info);
#[cfg(not(test))]
{
println!("{}", info);
rustos::hlt_loop();
};
}

View file

@ -2,11 +2,13 @@ use lazy_static::lazy_static;
use spin::Mutex;
use uart_16550::SerialPort;
// macro serial_print ::: prints arg to serial port
#[macro_export]
macro_rules! serial_print {
($($arg:tt)*) => { $crate::serial::_print(format_args!($($arg)*)); };
}
// macro serial_println ::: prints arg to serial port with \n appended
#[macro_export]
macro_rules! serial_println {
() => ($crate::serial_print!("\n"));
@ -15,17 +17,23 @@ macro_rules! serial_println {
}
lazy_static! {
// SERIAL1: Mutex<SerialPort> ::: serial port to write to
pub static ref SERIAL1: Mutex<SerialPort> = {
// SAFETY ensure correct SerialPort is initialized to write output to
let mut serial_port = unsafe { SerialPort::new(0x3F8) };
serial_port.init();
Mutex::new(serial_port)
};
}
// fn _print ::: prints to serial port; internal fn
// args: ::core::fmt::Arguments :param: goofy ah macro magic
#[doc(hidden)]
pub fn _print(args: ::core::fmt::Arguments) {
use core::fmt::Write;
use x86_64::instructions::interrupts;
// locked to avoid race conditions
interrupts::without_interrupts(|| {
SERIAL1
.lock()

View file

@ -3,21 +3,20 @@ use lazy_static::lazy_static;
use spin::Mutex;
use volatile::Volatile;
// macros exported on root ie $crate::print
// macro print ::: prints to vga_buffer
#[macro_export]
macro_rules! print {
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
}
// macro println ::: prints to vga_buffer with \n appended
#[macro_export]
macro_rules! println {
() => ($crate::print!("\n"));
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}
// program ignores unused code
// enable semantics for the type
// represent it as u8
// enum Color ::: defines color bytes for VGA buffer as u8
#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
@ -40,28 +39,38 @@ pub enum Color {
White = 15,
}
// const BUFFER_HEIGHT :::
// const BUFFER_WIDTH :::
const BUFFER_HEIGHT: usize = 25;
const BUFFER_WIDTH: usize = 80;
// struct Buffer :::
// chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT] :field: char array of text buffer.
// Volatile ensures it'll never be optimized away because this buffer is a side effect
#[repr(transparent)]
struct Buffer {
chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}
// full byte color code
// repr(transparent) ensures struct has same memory layout as type, in this case u8
// struct ColorCode ::: formatted for VGA buffer; first 4 foreground, last 4 background
// (u8) :field:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
struct ColorCode(u8);
// implementation of colorCode
impl ColorCode {
// fn new ::: creates color code to VGA specs
// foreground: Color :param:
// background: Color :param:
// : ColorCode :ret:
fn new(foreground: Color, background: Color) -> ColorCode {
ColorCode((background as u8) << 4 | (foreground as u8))
}
}
// uses C-like struct to gurantee field ordering
// struct ScreenChar ::: specifies character and color of 1 character for VGA buffer
// ascii_character: u8 :field:
// color_code: ColorCode :field:
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
struct ScreenChar {
@ -69,7 +78,10 @@ struct ScreenChar {
color_code: ColorCode,
}
// 'static tells the runtime how long the reference is valid; 'static is forever
// struct Writer :::
// column_position: usize :field:
// color_code: ColorCode :field:
// buffer: &'static mut Buffer :field: buffer to write to
pub struct Writer {
column_position: usize,
color_code: ColorCode,
@ -77,40 +89,49 @@ pub struct Writer {
}
impl Writer {
// writes byte to screen, automatically handling newlines
// expects a printable byte
// fn write_byte ::: writes byte to screen
// &mut self :param: self whose buffer will be written to
// byte: u8 :param: byte to write to screen. Must be writable
pub fn write_byte(&mut self, byte: u8) {
match byte {
b'\n' => self.new_line(),
byte => {
// wraps automatically
if self.column_position >= BUFFER_WIDTH {
self.new_line();
}
// bottom row of screen will be written to
let row = BUFFER_HEIGHT - 1;
let col = self.column_position;
// .write with Volatile<ScreenChar> ensures this will never be optimised away
let color_code = self.color_code;
self.buffer.chars[row][col].write(ScreenChar {
ascii_character: byte,
color_code,
});
// moves on to next byte's position preemptively
self.column_position += 1;
}
}
}
// doesn't expect printable bytes
// fn write_string :::
// &mut self :param: self whose buffer will be written to
// s: &str :param: string to write
pub fn write_string(&mut self, s: &str) {
for byte in s.bytes() {
match byte {
0x20..=0x7e | b'\n' => self.write_byte(byte),
// filters out non-printable bytes
// placeholder for unknown bytes
_ => self.write_byte(0xfe),
}
}
}
// iterates each character 1 row up and replaces cursor
// fn new_line ::: shifts buffer contents up, replaces cursor on the left
// &mut self :param: self whose buffer will be written to
fn new_line(&mut self) {
for row in 1..BUFFER_HEIGHT {
for col in 0..BUFFER_WIDTH {
@ -121,7 +142,10 @@ impl Writer {
self.clear_row(BUFFER_HEIGHT - 1);
self.column_position = 0;
}
// overwrites a row with spaces
// fn clear_row ::: replaces row with spaces
// &mut self :param: self whose buffer will be written to
// row: usize :param: row of buffer to clear
fn clear_row(&mut self, row: usize) {
let blank = ScreenChar {
ascii_character: b' ',
@ -133,17 +157,19 @@ impl Writer {
}
}
// required by fmt: write_str (self, str) -> fmt::Result
impl fmt::Write for Writer {
// fn write_str :::
// &mut self :param: self whose buffer will be written to
// s: &str :param: string to be written
// : fmt::Result :ret:
fn write_str(&mut self, s: &str) -> fmt::Result {
self.write_string(s);
Ok(())
}
}
// static WRITER evaluates lazily
lazy_static! {
// spin Mutex 'locks' thread without OS features
// WRITER: Mutex<Writer> :::
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
column_position: 0,
color_code: ColorCode::new(Color::Magenta, Color::Black),
@ -151,7 +177,8 @@ lazy_static! {
});
}
// hides from generated docs despite publicity
// fn _print ::: used for macro magic
// args: fmt::Arguments :param:
#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
use core::fmt::Write;
@ -161,6 +188,7 @@ pub fn _print(args: fmt::Arguments) {
});
}
// fn test_println_many :::
#[test_case]
fn test_println_many() {
for _ in 0..1023 {
@ -168,6 +196,7 @@ fn test_println_many() {
}
}
// fn test_println_output ::: checks that what is getting written is getting printed
#[test_case]
fn test_println_output() {
use core::fmt::Write;
@ -185,6 +214,7 @@ fn test_println_output() {
});
}
// fn test_println_simple :::
#[test_case]
fn test_println_simple() {
println!("test_println_simple output");