diff --git a/.cargo/config.toml b/.cargo/config.toml index c891815..7e972e4 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,7 +3,7 @@ target = "x86_64-kernel" target-dir = "build/target" [unstable] -build-std = ["core", "compiler_builtins", "alloc"] +build-std = ["core", "compiler_builtins"] build-std-features = ["compiler-builtins-mem"] [env] @@ -11,3 +11,6 @@ RUST_TARGET_PATH = { value = "kernel", relative = true } [target.x86_64-kernel] runner = "scripts/run.sh" + +[target.'cfg(target_os = "none")'] +runner = "scripts/run.sh --test" diff --git a/Cargo.toml b/Cargo.toml index 3aca2dd..03f1c5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,6 @@ members = [ resolver = "2" [profile.dev] -panic = "abort" opt-level = "z" debug = true debug-assertions = true @@ -15,7 +14,6 @@ incremental = false codegen-units = 1 [profile.release] -panic = "abort" opt-level = "z" debug = false debug-assertions = false diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index f5bd835..f9dbd79 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -1,16 +1,23 @@ [package] name = "kernel" -version.workspace = true -edition.workspace = true +version = "0.1.0" +edition = "2021" [dependencies] limine = "0.3.1" -spin = "0.9.8" -bitflags = "2.4.0" -lazy_static = { version = "1.5.0", features = ["spin", "spin_no_std"] } +spin = { version = "0.9.8", features = ["lazy"] } +bitflags = { version = "2.4.0", default-features = false } +lazy_static = { version = "1.5.0", features = ["spin_no_std"] } +x86_64 = { version = "0.15.1" } + +[features] +default = [] [[bin]] name = "kernel" path = "src/main.rs" -test = false -bench = false + +# setup lib.rs +[lib] +name = "GoofyAhhOS" +path = "src/lib.rs" diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs new file mode 100644 index 0000000..3738c93 --- /dev/null +++ b/kernel/src/lib.rs @@ -0,0 +1,64 @@ +#![no_std] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::tests::test_runner)] +#![reexport_test_harness_main = "test_main"] +#![feature(abi_x86_interrupt)] + +// #[cfg(test)] +// use limine::BaseRevision; + +#[cfg(not(test))] +use core::panic::PanicInfo; + +pub mod tests; +pub mod sys; +pub mod usr; + + +pub use sys::kernel::drivers::framebuffer::textwriter::{ + _print, + _printerr, + _log, +}; + +pub use sys::kernel::drivers::serial::{ + _serial_write, + serial_read +}; + +pub fn init() { + sys::kernel::cpu::interrupts::init(); +} + +pub fn hcf() -> ! { + loop { + unsafe { core::arch::asm!("cli; hlt") } + } +} + +// Called on panic +#[cfg(not(test))] +#[panic_handler] +fn panic(_info: &PanicInfo) -> ! { + println!("{}", _info); + hcf() +} + +// code for testing etc. + +#[cfg(test)] +#[no_mangle] +pub extern "C" fn kmain() -> ! { + // Set up the base revision for Limine + // static BASE_REVISION: BaseRevision = BaseRevision::new(); + + init(); + test_main(); + + serial_println!("All tests passed! exiting."); + + sys::qemu::exit_success(); +} + + diff --git a/kernel/src/main.rs b/kernel/src/main.rs index 472805c..accc904 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -1,40 +1,45 @@ #![no_std] // Don't link the Rust standard library #![no_main] // Disable all Rust-level entry points +#![feature(custom_test_frameworks)] +#![test_runner(GoofyAhhOS::tests::test_runner)] +#![reexport_test_harness_main = "test_main"] -use core::panic::PanicInfo; use limine::*; -use limine::request::{ - FramebufferRequest, RequestsEndMarker, RequestsStartMarker, -}; - -mod font; -mod render; - -use crate::font::FONT; +use GoofyAhhOS::sys::kernel::drivers::serial::serial_read; +use GoofyAhhOS::{print, println, serial_println}; +use GoofyAhhOS::sys::kernel::drivers::framebuffer::textwriter::clear_screen; // Set the base revision static BASE_REVISION: BaseRevision = BaseRevision::new(); - -// Halt and catch fire function -fn hcf() -> ! { - loop { - unsafe { core::arch::asm!("cli; hlt") } - } -} - -// Called on panic -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - hcf() -} - // Kernel entry point #[no_mangle] pub extern "C" fn kmain() -> ! { - render::init(); - render::write_string("Welcome to GoofyAhhOS!\nthis is the superior os\nif you disagree you are a heretic in the name of steven.", 0xff0000, 0); - hcf() + GoofyAhhOS::init(); + + println!("Hello from GoofyAhhOS!"); + serial_println!("SERIAL OUT ACHIEVED :check:"); + + loop { + let input: &str = serial_read(); + + clear_screen(); + + if input.starts_with("print ") { + let input = &input[6..]; + println!("{}", input); + } else if input == "" { + let x: i32 = 239423889; + let y: i32 = 123456678; + + print!("num: {} {}", x, y); + } else { + println!("Unknown command: {}", input); + } + } + + GoofyAhhOS::hcf() } + diff --git a/kernel/src/render.rs b/kernel/src/render.rs deleted file mode 100644 index 64bc8e7..0000000 --- a/kernel/src/render.rs +++ /dev/null @@ -1,118 +0,0 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; -use lazy_static::lazy_static; -use limine::framebuffer::Framebuffer; -use spin::Mutex; -use limine::request::FramebufferRequest; - -use crate::font::FONT; - -pub struct FramebufferWriter<'a> { - framebuffer: Framebuffer<'a>, - x_pos: AtomicUsize, - y_pos: AtomicUsize, -} - -unsafe impl<'a> Send for FramebufferWriter<'a> {} -unsafe impl<'a> Sync for FramebufferWriter<'a> {} - -impl<'a> FramebufferWriter<'a> { - pub fn new(framebuffer: Framebuffer<'a>) -> Self { - Self { - framebuffer, - x_pos: AtomicUsize::new(0), - y_pos: AtomicUsize::new(0), - } - } - - pub fn write_pixel(&self, x: usize, y: usize, color: u32) { - let pitch = self.framebuffer.pitch() as usize; - let bpp = (self.framebuffer.bpp() / 8) as usize; - let pixel_offset = y * pitch + x * bpp; - - unsafe { - *(self.framebuffer.addr().add(pixel_offset) as *mut u32) = color; - } - } - - pub fn write_char(&self, x: u32, y: u32, fg_color: u32, bg_color: u32, mut c: u8) { - if c < 32 || c > 126 { - c = '?' as u8; - } - - let data: &[u8] = &FONT[c as usize * 16..(c as usize + 1) * 16]; - - for row in 0..16 { - let line: u8 = data[row]; - for col in 0..8 { - let pixel_x: u32 = x + col; - let pixel_y: u32 = y + row as u32; - if line & (0x80 >> col) != 0 { - self.write_pixel(pixel_x as usize, pixel_y as usize, fg_color); - } else { - self.write_pixel(pixel_x as usize, pixel_y as usize, bg_color); - } - } - } - } - - pub fn write_string(&self, x: u32, y: u32, fg_color: u32, bg_color: u32, s: &str) { - let mut curr_x: u32 = x; - let mut curr_y: u32 = y; - - for c in s.chars() { - if c == '\n' { - curr_x = x; - curr_y += 16; - continue; - } - - if curr_x + 8 > self.framebuffer.width() as u32 { - curr_x = x; - curr_y += 16; - } - - if curr_y + 16 > self.framebuffer.height() as u32 { - break; - } - - self.write_char(curr_x, curr_y, fg_color, bg_color, c as u8); - curr_x += 8; - } - } - - pub fn clear(&self, color: u32) { - let width = self.framebuffer.width() as usize; - let height = self.framebuffer.height() as usize; - - for y in 0..height { - for x in 0..width { - self.write_pixel(x, y, color); - } - } - } -} - -static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new(); - -lazy_static! { - pub static ref FRAMEBUFFER_WRITER: Mutex>> = Mutex::new(None); -} - -pub fn init() { - if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() { - // The framebuffer from the response has a 'static lifetime - let framebuffer = framebuffer_response.framebuffers().next().unwrap(); - *FRAMEBUFFER_WRITER.lock() = Some(FramebufferWriter::new(framebuffer)); - clear_screen(0x00000000); - } -} - -pub fn clear_screen(color: u32) { - if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_ref() { - writer.clear(color); - } -} - -pub fn write_string(s: &str, fg_color: u32, bg_color: u32) { - FRAMEBUFFER_WRITER.lock().as_ref().unwrap().write_string(0, 0, fg_color, bg_color, s); -} diff --git a/kernel/src/sys/kernel/cpu/io.rs b/kernel/src/sys/kernel/cpu/io.rs new file mode 100644 index 0000000..dbbe288 --- /dev/null +++ b/kernel/src/sys/kernel/cpu/io.rs @@ -0,0 +1,67 @@ +//! CPU I/O port operations + +use core::arch::asm; + +/// Read a byte from the specified I/O port +/// +/// # Safety +/// This function is unsafe because it performs direct I/O port operations +/// which could have unpredictable effects on hardware +#[inline] +pub unsafe fn inb(port: u16) -> u8 { + let value: u8; + asm!( + "in al, dx", + out("al") value, + in("dx") port, + options(nomem, nostack, preserves_flags) + ); + value +} + +/// Write a byte to the specified I/O port +/// +/// # Safety +/// This function is unsafe because it performs direct I/O port operations +/// which could have unpredictable effects on hardware +#[inline] +pub unsafe fn outb(port: u16, value: u8) { + asm!( + "out dx, al", + in("dx") port, + in("al") value, + options(nomem, nostack, preserves_flags) + ); +} + +/// Read a word (16 bits) from the specified I/O port +/// +/// # Safety +/// This function is unsafe because it performs direct I/O port operations +/// which could have unpredictable effects on hardware +#[inline] +pub unsafe fn inw(port: u16) -> u16 { + let value: u16; + asm!( + "in ax, dx", + out("ax") value, + in("dx") port, + options(nomem, nostack, preserves_flags) + ); + value +} + +/// Write a word (16 bits) to the specified I/O port +/// +/// # Safety +/// This function is unsafe because it performs direct I/O port operations +/// which could have unpredictable effects on hardware +#[inline] +pub unsafe fn outw(port: u16, value: u16) { + asm!( + "out dx, ax", + in("dx") port, + in("ax") value, + options(nomem, nostack, preserves_flags) + ); +} diff --git a/kernel/src/sys/kernel/cpu/mod.rs b/kernel/src/sys/kernel/cpu/mod.rs new file mode 100644 index 0000000..82585cf --- /dev/null +++ b/kernel/src/sys/kernel/cpu/mod.rs @@ -0,0 +1,11 @@ +#[cfg(target_arch = "x86_64")] +pub mod x86_64; + +#[cfg(target_arch = "x86_64")] +pub use x86_64::*; + +pub mod io; + +pub use io::*; + +// Add common CPU traits/interfaces here that all architectures must implement diff --git a/kernel/src/sys/kernel/cpu/x86_64/interrupts.rs b/kernel/src/sys/kernel/cpu/x86_64/interrupts.rs new file mode 100644 index 0000000..171e3b8 --- /dev/null +++ b/kernel/src/sys/kernel/cpu/x86_64/interrupts.rs @@ -0,0 +1,73 @@ +use core::arch::asm; + +use lazy_static::lazy_static; +use spin::lazy; +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; + +use crate::{println_log, serial_println}; + +use super::pics::ChainedPics; + +pub const PIC_1_OFFSET: u8 = 32; +pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + +pub static PICS: spin::Mutex = + spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); + +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum InterruptIndex { + Timer = PIC_1_OFFSET, +} + +impl InterruptIndex { + fn as_u8(self) -> u8 { + self as u8 + } + + fn as_usize(self) -> usize { + usize::from(self.as_u8()) + } +} + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + idt.double_fault.set_handler_fn(double_fault_handler); + idt[InterruptIndex::Timer.as_u8()].set_handler_fn(clock_handler); + idt + }; +} + +pub fn init() { + IDT.load(); +} + + +pub fn disable(func: impl Fn()) { + unsafe { + asm!("cli", options(nomem, nostack, preserves_flags)); + func(); + asm!("sti", options(nomem, nostack, preserves_flags)); + } +} + +extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) { + println_log!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); + serial_println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +} + +extern "x86-interrupt" fn double_fault_handler(stack_frame: InterruptStackFrame, _error_code: u64) -> ! { + serial_println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); + panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame) +} + +extern "x86-interrupt" fn clock_handler(stack_frame: InterruptStackFrame) { + println_log!("EXCEPTION: CLOCK\n{:#?}", stack_frame); + serial_println!("EXCEPTION: CLOCK\n{:#?}", stack_frame); + + unsafe { + PICS.lock().notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); + } +} \ No newline at end of file diff --git a/kernel/src/sys/kernel/cpu/x86_64/mod.rs b/kernel/src/sys/kernel/cpu/x86_64/mod.rs new file mode 100644 index 0000000..c97cf49 --- /dev/null +++ b/kernel/src/sys/kernel/cpu/x86_64/mod.rs @@ -0,0 +1,4 @@ +pub mod interrupts; +mod pics; + +pub use interrupts::*; diff --git a/kernel/src/sys/kernel/cpu/x86_64/pics.rs b/kernel/src/sys/kernel/cpu/x86_64/pics.rs new file mode 100644 index 0000000..457d3b6 --- /dev/null +++ b/kernel/src/sys/kernel/cpu/x86_64/pics.rs @@ -0,0 +1,137 @@ +use x86_64::instructions::port::Port; + +const CMD_INIT: u8 = 0x11; +const CMD_END_OF_INT: u8 = 0x20; +const MODE_8086: u8 = 0x01; + +struct Pic { + offset: u8, + data: Port, + command: Port, +} + +impl Pic { + /// Are we in charge of handling the specified interrupt? + /// (Each PIC handles 8 interrupts.) + fn handles_interrupt(&self, interrupt_id: u8) -> bool { + self.offset <= interrupt_id && interrupt_id < self.offset + 8 + } + + /// Notify us that an interrupt has been handled and that we're ready + /// for more. + unsafe fn end_of_interrupt(&mut self) { + self.command.write(CMD_END_OF_INT); + } + + /// Reads the interrupt mask of this PIC. + unsafe fn read_mask(&mut self) -> u8 { + self.data.read() + } + + /// Writes the interrupt mask of this PIC. + unsafe fn write_mask(&mut self, mask: u8) { + self.data.write(mask) + } +} + +/// A pair of chained PICs. This is the standard setup on x86. +pub struct ChainedPics { + pics: [Pic; 2], +} + +impl ChainedPics { + /// Create a new interface for the standard PIC1 and PIC2, + /// specifying the desired interrupt offsets. + pub const unsafe fn new(offset1: u8, offset2: u8) -> ChainedPics { + ChainedPics { + pics: [ + Pic { + offset: offset1, + command: Port::new(0x20), + data: Port::new(0x21), + }, + Pic { + offset: offset2, + command: Port::new(0xA0), + data: Port::new(0xA1), + }, + ], + } + } + + pub const unsafe fn new_contiguous(primary_offset: u8) -> ChainedPics { + Self::new(primary_offset, primary_offset + 8) + } + + pub unsafe fn initialize(&mut self) { + + let mut wait_port: Port = Port::new(0x80); + let mut wait = || wait_port.write(0); + + // Save our original interrupt masks, because I'm too lazy to + // figure out reasonable values. We'll restore these when we're + // done. + let saved_masks = self.read_masks(); + + // Tell each PIC that we're going to send it a three-byte + // initialization sequence on its data port. + self.pics[0].command.write(CMD_INIT); + wait(); + self.pics[1].command.write(CMD_INIT); + wait(); + + // Byte 1: Set up our base offsets. + self.pics[0].data.write(self.pics[0].offset); + wait(); + self.pics[1].data.write(self.pics[1].offset); + wait(); + + // Byte 2: Configure chaining between PIC1 and PIC2. + self.pics[0].data.write(4); + wait(); + self.pics[1].data.write(2); + wait(); + + // Byte 3: Set our mode. + self.pics[0].data.write(MODE_8086); + wait(); + self.pics[1].data.write(MODE_8086); + wait(); + + // Restore our saved masks. + self.write_masks(saved_masks[0], saved_masks[1]) + } + + /// Reads the interrupt masks of both PICs. + pub unsafe fn read_masks(&mut self) -> [u8; 2] { + [self.pics[0].read_mask(), self.pics[1].read_mask()] + } + + /// Writes the interrupt masks of both PICs. + pub unsafe fn write_masks(&mut self, mask1: u8, mask2: u8) { + self.pics[0].write_mask(mask1); + self.pics[1].write_mask(mask2); + } + + /// Disables both PICs by masking all interrupts. + pub unsafe fn disable(&mut self) { + self.write_masks(u8::MAX, u8::MAX) + } + + /// Do we handle this interrupt? + pub fn handles_interrupt(&self, interrupt_id: u8) -> bool { + self.pics.iter().any(|p| p.handles_interrupt(interrupt_id)) + } + + /// Figure out which (if any) PICs in our chain need to know about this + /// interrupt. This is tricky, because all interrupts from `pics[1]` + /// get chained through `pics[0]`. + pub unsafe fn notify_end_of_interrupt(&mut self, interrupt_id: u8) { + if self.handles_interrupt(interrupt_id) { + if self.pics[1].handles_interrupt(interrupt_id) { + self.pics[1].end_of_interrupt(); + } + self.pics[0].end_of_interrupt(); + } + } +} \ No newline at end of file diff --git a/kernel/src/font.rs b/kernel/src/sys/kernel/drivers/framebuffer/font.rs similarity index 100% rename from kernel/src/font.rs rename to kernel/src/sys/kernel/drivers/framebuffer/font.rs diff --git a/kernel/src/sys/kernel/drivers/framebuffer/mod.rs b/kernel/src/sys/kernel/drivers/framebuffer/mod.rs new file mode 100644 index 0000000..7c7e7b4 --- /dev/null +++ b/kernel/src/sys/kernel/drivers/framebuffer/mod.rs @@ -0,0 +1,7 @@ +pub mod textwriter; +pub mod render; + +mod font; + + +// \ No newline at end of file diff --git a/kernel/src/sys/kernel/drivers/framebuffer/render.rs b/kernel/src/sys/kernel/drivers/framebuffer/render.rs new file mode 100644 index 0000000..76e7a3f --- /dev/null +++ b/kernel/src/sys/kernel/drivers/framebuffer/render.rs @@ -0,0 +1,63 @@ +use core::panic; +use lazy_static::lazy_static; +use limine::framebuffer::Framebuffer; +use spin::Mutex; +use limine::request::FramebufferRequest; + +static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new(); + +lazy_static! { + pub static ref FRAMEBUFFER_WRITER: Mutex>> = Mutex::new({ + if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() { + let framebuffer = framebuffer_response.framebuffers().next().unwrap(); + Some(FramebufferWriter::new(framebuffer)) + } else { + panic!("Framebuffer request failed"); + } + }); +} + +pub struct FramebufferWriter<'a> { + framebuffer: Framebuffer<'a>, +} + +unsafe impl<'a> Send for FramebufferWriter<'a> {} +unsafe impl<'a> Sync for FramebufferWriter<'a> {} + +impl<'a> FramebufferWriter<'a> { + pub fn new(framebuffer: Framebuffer<'a>) -> Self { + Self { + framebuffer, + } + } + + pub fn write_pixel(&self, x: usize, y: usize, color: u32) { + let pitch = self.framebuffer.pitch() as usize; + let bpp = (self.framebuffer.bpp() / 8) as usize; + let pixel_offset = y * pitch + x * bpp; + + unsafe { + *(self.framebuffer.addr().add(pixel_offset) as *mut u32) = color; + } + } + + pub fn width(&self) -> u32 { + self.framebuffer.width() as u32 + } + + pub fn height(&self) -> u32 { + self.framebuffer.height() as u32 + } + + pub fn clear(&self) { + let width = self.framebuffer.width() as usize; + let height = self.framebuffer.height() as usize; + + for y in 0..height { + for x in 0..width { + self.write_pixel(x, y, 0x000000); + } + } + } +} + diff --git a/kernel/src/sys/kernel/drivers/framebuffer/textwriter.rs b/kernel/src/sys/kernel/drivers/framebuffer/textwriter.rs new file mode 100644 index 0000000..a81aca7 --- /dev/null +++ b/kernel/src/sys/kernel/drivers/framebuffer/textwriter.rs @@ -0,0 +1,189 @@ + +use core::fmt; + +use lazy_static::lazy_static; +use spin::Mutex; + +use crate::sys::kernel::cpu::interrupts; + +use super::{font::FONT, render::FRAMEBUFFER_WRITER}; + +static FONT_WIDTH: u32 = 8; +static FONT_HEIGHT: u32 = 16; + +lazy_static!{ + static ref TEXT_WRITER: Mutex = Mutex::new(TextWriter::new()); +} + + +pub struct TextWriter { + // these are measured in chars NOT pixels + screen_width: u32, + screen_height: u32, + + text_line: u32, // 16 pixels tall + text_col: u32, // 8 pixels wide + + fg_color: u32, + bg_color: u32 +} + +impl TextWriter { + + pub fn new() -> Self { + if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() { + Self { + screen_width: writer.width() as u32 / 8, + screen_height: writer.height() as u32 / 16, + text_line: 0, + text_col: 0, + fg_color: 0xFFFFFF, + bg_color: 0x000000 + } + } else { + panic!("Framebuffer writer not initialized"); + } + } + + pub fn write_char(&mut self, mut c: u8) { + if c == b'\n' { + self.newline(); + return; + } + + if c < 32 || c > 126 { + c = '?' as u8; + } + + // get the character data from the font array. -- each byte is a row of pixels + let data: &[u8] = &FONT[c as usize * 16..(c as usize + 1) * 16]; + + if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() { + for row in 0..16 { + let line: u8 = data[row]; + for col in 0..8 { + let pixel_x: u32 = self.text_col * FONT_WIDTH + col; + let pixel_y: u32 = self.text_line * FONT_HEIGHT + row as u32; + + if line & (0x80 >> col) != 0 { + // write the foreground color + writer.write_pixel(pixel_x as usize, pixel_y as usize, self.fg_color); + } else { + // write the background color + writer.write_pixel(pixel_x as usize, pixel_y as usize, self.bg_color); + } + } + } + } + + // go to next position + if self.text_col + 1 >= self.screen_width { + self.newline(); + } else { + self.text_col += 1; + } + } + + pub fn next_char(&mut self) { + self.text_col += 1; + } + + pub fn newline(&mut self) { + self.text_col = 0; + + if self.text_line + 1 >= self.screen_height { + self.text_line = 0; + } else { + self.text_line += 1; + } + } + + pub fn write_string(&mut self, s: &str) { + for c in s.chars() { + self.write_char(c as u8); + } + } + + pub fn set_colour(&mut self, col: (u32, u32)) { + self.fg_color = col.0; + self.bg_color = col.1; + } + + pub fn reset_colour(&mut self) { + self.fg_color = 0xFFFFFF; + self.bg_color = 0x000000; + } +} + +impl core::fmt::Write for TextWriter { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.write_string(s); + Ok(()) + } +} + +fn write(args: fmt::Arguments, fg_color: u32, bg_color: u32) { + use core::fmt::Write; + + interrupts::disable(|| { + let mut writer = TEXT_WRITER.lock(); + writer.set_colour((fg_color, bg_color)); + writer.write_fmt(args).unwrap(); + writer.reset_colour(); + }); +} + +pub fn _print(args: fmt::Arguments) { + write(args, 0xFFFFFF, 0x000000); +} + +pub fn _printerr(args: fmt::Arguments) { + write(args, 0xFF8080, 0x000000); +} + +pub fn _log(args: fmt::Arguments) { + write(args, 0xFFFF00, 0x000000); +} + +pub fn clear_screen() { + let mut writer = TEXT_WRITER.lock(); + writer.text_line = 0; + writer.text_col = 0; + + if let Some(writer) = FRAMEBUFFER_WRITER.lock().as_mut() { + writer.clear(); + } +} + +#[macro_export] +macro_rules! println_log { + () => ($crate::print_log!("\n")); + ($($arg:tt)*) => ($crate::print_log!("{}\n", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! print_log { + ($($arg:tt)*) => ($crate::_log(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! printlnerr { + () => ($crate::printerr!("\n")); + ($($arg:tt)*) => ($crate::printerr!("{}\n", format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! printerr { + ($($arg:tt)*) => ($crate::_printerr(format_args!($($arg)*))); +} \ No newline at end of file diff --git a/kernel/src/sys/kernel/drivers/mod.rs b/kernel/src/sys/kernel/drivers/mod.rs new file mode 100644 index 0000000..eccb8aa --- /dev/null +++ b/kernel/src/sys/kernel/drivers/mod.rs @@ -0,0 +1,2 @@ +pub mod framebuffer; +pub mod serial; \ No newline at end of file diff --git a/kernel/src/sys/kernel/drivers/serial/mod.rs b/kernel/src/sys/kernel/drivers/serial/mod.rs new file mode 100644 index 0000000..f0503b8 --- /dev/null +++ b/kernel/src/sys/kernel/drivers/serial/mod.rs @@ -0,0 +1,6 @@ +pub mod serial; + +pub use serial::{ + _serial_write, + serial_read +}; diff --git a/kernel/src/sys/kernel/drivers/serial/serial.rs b/kernel/src/sys/kernel/drivers/serial/serial.rs new file mode 100644 index 0000000..a056d2f --- /dev/null +++ b/kernel/src/sys/kernel/drivers/serial/serial.rs @@ -0,0 +1,117 @@ +use core::fmt; +use spin::Mutex; +use lazy_static::lazy_static; + +use crate::sys::kernel::cpu::{inb, outb}; + +static PORT: u16 = 0x3f8; +static mut BUFFER: [u8; 256] = [0; 256]; + +lazy_static!{ + static ref SERIAL_WRITER: Mutex = Mutex::new(SerialWriter::new()); +} + +struct SerialWriter { + buffer_len: usize +} + +impl fmt::Write for SerialWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + self.write(c as u8); + } + Ok(()) + } +} + +impl SerialWriter { + pub fn new() -> SerialWriter { + // first we make sure that the serial port is setup and working. + unsafe { + outb(PORT + 1, 0x00); // Disable all interrupts + outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor) + outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud + outb(PORT + 1, 0x00); // (hi byte) + outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit + outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-bytethreshold + outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set + outb(PORT + 4, 0x1E); // Set in loopback mode, test the serial chip + outb(PORT + 0, 0xAE); // Test serial chip (send byte 0xAE and check if serial returns same byte) + + if inb(PORT + 0) != 0xAE { + panic!("serial port is not working!"); + } + + outb(PORT + 4, 0x0F); + } + + SerialWriter { + buffer_len: 0 + } + } + + // returnstrue if there is new data on the serial port + unsafe fn serial_recieved(&self) -> bool { + inb(PORT + 5) & 1 != 0 + } + + // returns true if the transmit buffer is empty + unsafe fn serial_sent(&self) -> bool { + inb(PORT + 5) & 0x20 != 0 + } + + pub fn read(&self) -> u8 { unsafe { + while !self.serial_recieved() {}; + return inb(PORT + 0); + }} + + pub fn read_str_to_buffer(&mut self) { unsafe { + while !self.serial_recieved() {}; + + self.buffer_len = 0; + + while self.serial_recieved() && self.buffer_len < 256 { + let c = inb(PORT + 0); + BUFFER[self.buffer_len] = c; + if c == b'\n' { + break; + } + self.buffer_len += 1; + } + }} + + pub fn write(&self, data: u8) { unsafe { + while !self.serial_sent() {}; + outb(PORT + 0, data); + }} +} + +pub fn _serial_write(args: fmt::Arguments) { + use core::fmt::Write; + + SERIAL_WRITER.lock().write_fmt(args).unwrap(); +} + +pub fn serial_read() -> &'static str { + SERIAL_WRITER.lock().read_str_to_buffer(); + let i = SERIAL_WRITER.lock().buffer_len; + + if i == 0 { + return ""; + } + + unsafe { + return core::str::from_utf8(&BUFFER[..i - 1]).unwrap(); + } +} + +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => ($crate::_serial_write(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($($arg:tt)*) => ($crate::serial_print!("{}\n", format_args!($($arg)*))); +} \ No newline at end of file diff --git a/kernel/src/sys/kernel/mod.rs b/kernel/src/sys/kernel/mod.rs new file mode 100644 index 0000000..28c5abe --- /dev/null +++ b/kernel/src/sys/kernel/mod.rs @@ -0,0 +1,2 @@ +pub mod drivers; +pub mod cpu; \ No newline at end of file diff --git a/kernel/src/sys/mod.rs b/kernel/src/sys/mod.rs index 9daa70f..2840186 100644 --- a/kernel/src/sys/mod.rs +++ b/kernel/src/sys/mod.rs @@ -1,2 +1,7 @@ pub mod std; -mod kernel; \ No newline at end of file + +// TODO: make this private +pub mod kernel; + +#[cfg(any(test, feature = "qemu"))] +pub mod qemu; diff --git a/kernel/src/sys/qemu/mod.rs b/kernel/src/sys/qemu/mod.rs new file mode 100644 index 0000000..ac2ffa1 --- /dev/null +++ b/kernel/src/sys/qemu/mod.rs @@ -0,0 +1,43 @@ +use x86_64::instructions::port::Port; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum ExitCode { + // Writing 0x10 will make QEMU exit with code 33 ((0x10 << 1) | 1) + Success = 0x10, + // Writing 0x11 will make QEMU exit with code 35 ((0x11 << 1) | 1) + Failed = 0x11, +} + +/// Exit QEMU with the given exit code. +/// +/// This function uses the special QEMU debug exit device with +/// port 0xf4 to exit QEMU with a specified exit code. +/// QEMU will exit with code: (value << 1) | 1 +pub fn exit(exit_code: ExitCode) -> ! { + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } + loop { + unsafe { + core::arch::asm!("cli; hlt", options(nomem, nostack)) + } + } +} + +/// Exit QEMU with a success code +/// Writing 0x10 will make QEMU exit with code 33 +#[cfg(test)] +pub fn exit_success() -> ! { + exit(ExitCode::Success) +} + +/// Exit QEMU with a failed code +/// Writing 0x11 will make QEMU exit with code 35 +#[cfg(test)] +pub fn exit_failed() -> ! { + use crate::serial_println; + serial_println!("[failed]\n"); + exit(ExitCode::Failed) +} diff --git a/kernel/src/sys/std/mod.rs b/kernel/src/sys/std/mod.rs new file mode 100644 index 0000000..5046f5d --- /dev/null +++ b/kernel/src/sys/std/mod.rs @@ -0,0 +1 @@ +// mod file for standard library in kernel; \ No newline at end of file diff --git a/kernel/src/tests/kernel.rs b/kernel/src/tests/kernel.rs new file mode 100644 index 0000000..5d21680 --- /dev/null +++ b/kernel/src/tests/kernel.rs @@ -0,0 +1,22 @@ +#[cfg(test)] +use x86_64::instructions::interrupts::int3; +#[cfg(test)] +use crate::println; + +#[test_case] +pub fn test_println_many() { + for _ in 0..200 { + println!("test_println_many output"); + } +} + +#[test_case] +pub fn test_println_output() { + println!("test_println output"); +} + +#[test_case] +pub fn test_breakpoint_exception() { + // invoke a breakpoint exception + int3(); +} diff --git a/kernel/src/tests/mod.rs b/kernel/src/tests/mod.rs new file mode 100644 index 0000000..5458fda --- /dev/null +++ b/kernel/src/tests/mod.rs @@ -0,0 +1,20 @@ +mod runner; +pub use runner::test_runner; + +pub mod kernel; + +/// Called on panic +/// +/// # Arguments +/// +/// * `info` - The panic info +#[cfg(test)] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + use crate::serial_println; + + // print a failed message saying the kernel panicked + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + crate::sys::qemu::exit_failed(); +} diff --git a/kernel/src/tests/runner.rs b/kernel/src/tests/runner.rs new file mode 100644 index 0000000..3fa7c75 --- /dev/null +++ b/kernel/src/tests/runner.rs @@ -0,0 +1,23 @@ +use crate::{serial_print, serial_println}; + +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests\n", tests.len()); + for test in tests { + test.run(); + } +} + +pub trait Testable { + fn run(&self) -> (); +} + +impl Testable for T +where + T: Fn(), +{ + fn run(&self) { + serial_print!("{}...\t", core::any::type_name::()); + self(); + serial_println!("[ok]"); + } +} \ No newline at end of file diff --git a/kernel/src/usr/mod.rs b/kernel/src/usr/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/scripts/run.sh b/scripts/run.sh index 2ac5373..007fd1c 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -12,6 +12,10 @@ NC='\033[0m' # No Color set -e trap 'echo -e "${RED}${BOLD}error${NC}: build failed" >&2' ERR +# Get absolute path to project root +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd) +project_root=$(cd "$script_dir/.." &>/dev/null && pwd) + # Logging functions info() { echo -e "${BLUE}${BOLD}info${NC}: $1" @@ -25,15 +29,29 @@ warning() { echo -e "${YELLOW}${BOLD}warning${NC}: $1" >&2 } +building() { + echo -e "${GREEN}${BOLD}Building${NC} $1" +} + error() { echo -e "${RED}${BOLD}error${NC}: $1" >&2 exit 1 } -build_dir=build -iso_root=$build_dir/iso_root -kernel_path=$1 -kernel_name=$(basename $kernel_path) +build_dir="$project_root/build" +iso_root="$build_dir/iso_root" + +# Check if we're running tests +is_test=0 +if [[ $1 == *"deps"* ]]; then + is_test=1 + kernel_path="$1" +else + # Build the kernel normally + cd "$project_root" + cargo build + kernel_path="$build_dir/target/x86_64-kernel/debug/kernel" +fi # Check for required tools check_tools() { @@ -57,21 +75,23 @@ mkdir -p "$iso_root/EFI/BOOT" # Clone Limine if needed if [ ! -d "$build_dir/limine" ]; then compiling "limine bootloader" + cd "$build_dir" git clone https://github.com/limine-bootloader/limine.git --branch=v8.x-binary --depth=1 "$build_dir/limine" || error "failed to clone limine" make -C "$build_dir/limine" || error "failed to build limine" + cd "$project_root" fi # Copy files info "Copying files to ISO root" cp -v "$kernel_path" "$iso_root/boot/kernel" || error "failed to copy kernel" -cp -v config/limine.conf "$iso_root/boot/limine/" || error "failed to copy limine config" +cp -v "$project_root/config/limine.conf" "$iso_root/boot/limine/limine.conf" || error "failed to copy limine config" cp -v "$build_dir/limine/limine-bios.sys" "$build_dir/limine/limine-bios-cd.bin" \ "$build_dir/limine/limine-uefi-cd.bin" "$iso_root/boot/limine/" || error "failed to copy limine files" cp -v "$build_dir/limine/BOOTX64.EFI" "$iso_root/EFI/BOOT/" || error "failed to copy BOOTX64.EFI" cp -v "$build_dir/limine/BOOTIA32.EFI" "$iso_root/EFI/BOOT/" || error "failed to copy BOOTIA32.EFI" # Create ISO -compiling "bootable ISO image" +building "bootable ISO image" xorriso -as mkisofs -R -r -J -b boot/limine/limine-bios-cd.bin \ -no-emul-boot -boot-load-size 4 -boot-info-table -hfsplus \ -apm-block-size 2048 --efi-boot boot/limine/limine-uefi-cd.bin \ @@ -94,6 +114,22 @@ else kvm_flag="" fi +# Check if we're running in debug mode +if [[ "$(cargo metadata --format-version=1 | jq -r '.workspace_members[0]' | cut -d' ' -f2)" == "(debug)" ]]; then + debug_flags="-s -S" +else + debug_flags="" +fi + +# Set up test-specific flags +if [ $is_test -eq 1 ]; then + test_flags="-device isa-debug-exit,iobase=0xf4,iosize=0x04 -display none" + serial_flags="-serial stdio" +else + test_flags="" + serial_flags="-serial tcp:127.0.0.1:1234,server,nowait" +fi + # Run in QEMU if [[ ${QEMU_FLAGS} == *-S* ]]; then info "Running OS in QEMU with GDB debugging enabled" @@ -103,12 +139,36 @@ else info "Running OS in QEMU..." fi -exec qemu-system-x86_64 -M q35 \ +echo "test: " $is_test + +cd "$project_root" +qemu-system-x86_64 -M q35 \ ${kvm_flag} \ -cdrom "$build_dir/image.iso" \ -boot d \ -m 2G \ - -serial stdio \ + ${serial_flags} \ -no-reboot \ - -no-shutdown \ + ${test_flags} \ + ${debug_flags} \ ${QEMU_FLAGS:-} + +# Get QEMU's exit code +qemu_exit_code=$? + +# If this is a test run, translate QEMU exit codes to test exit codes +if [ $is_test -eq 1 ]; then + if [ $qemu_exit_code -eq 33 ]; then + # Success case (0x10 << 1) | 1 = 33 + exit 0 + elif [ $qemu_exit_code -eq 35 ]; then + # Failure case (0x11 << 1) | 1 = 35 + exit 1 + else + # Any other exit code is treated as a failure + exit 1 + fi +else + # For non-test runs, pass through the exit code + exit $qemu_exit_code +fi