working on kernel

This commit is contained in:
FantasyPvP
2024-11-18 17:48:00 +00:00
parent ac4cf9a27b
commit 4c157ab05e
27 changed files with 975 additions and 164 deletions
+4 -1
View File
@@ -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"
-2
View File
@@ -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
+14 -7
View File
@@ -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"
+64
View File
@@ -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();
}
+31 -26
View File
@@ -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()
}
-118
View File
@@ -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<Option<FramebufferWriter<'static>>> = 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);
}
+67
View File
@@ -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)
);
}
+11
View File
@@ -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
@@ -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<ChainedPics> =
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());
}
}
+4
View File
@@ -0,0 +1,4 @@
pub mod interrupts;
mod pics;
pub use interrupts::*;
+137
View File
@@ -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<u8>,
command: Port<u8>,
}
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<u8> = 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();
}
}
}
@@ -0,0 +1,7 @@
pub mod textwriter;
pub mod render;
mod font;
//
@@ -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<Option<FramebufferWriter<'static>>> = 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);
}
}
}
}
@@ -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<TextWriter> = 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)*)));
}
+2
View File
@@ -0,0 +1,2 @@
pub mod framebuffer;
pub mod serial;
@@ -0,0 +1,6 @@
pub mod serial;
pub use serial::{
_serial_write,
serial_read
};
@@ -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<SerialWriter> = 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)*)));
}
+2
View File
@@ -0,0 +1,2 @@
pub mod drivers;
pub mod cpu;
+6 -1
View File
@@ -1,2 +1,7 @@
pub mod std;
mod kernel;
// TODO: make this private
pub mod kernel;
#[cfg(any(test, feature = "qemu"))]
pub mod qemu;
+43
View File
@@ -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)
}
+1
View File
@@ -0,0 +1 @@
// mod file for standard library in kernel;
+22
View File
@@ -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();
}
+20
View File
@@ -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();
}
+23
View File
@@ -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<T> Testable for T
where
T: Fn(),
{
fn run(&self) {
serial_print!("{}...\t", core::any::type_name::<T>());
self();
serial_println!("[ok]");
}
}
View File
+69 -9
View File
@@ -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