diff --git a/src/arch/mod.rs b/src/arch/mod.rs new file mode 100644 index 0000000..1521257 --- /dev/null +++ b/src/arch/mod.rs @@ -0,0 +1,2 @@ +#[cfg(target_arch="x86_64")] +pub mod x86_64; diff --git a/src/arch/x86_64/gdt.rs b/src/arch/x86_64/gdt.rs new file mode 100644 index 0000000..c572f5f --- /dev/null +++ b/src/arch/x86_64/gdt.rs @@ -0,0 +1,23 @@ +use x86_64::instructions::segmentation::{load_ss, set_cs}; +use x86_64::structures::gdt::{Descriptor, DescriptorFlags, GlobalDescriptorTable}; + +static mut GDT: GlobalDescriptorTable = GlobalDescriptorTable::new(); + +fn kernel_data_segment() -> Descriptor { + use self::DescriptorFlags as Flags; + + let flags = Flags::USER_SEGMENT | Flags::PRESENT | Flags::WRITABLE; + Descriptor::UserSegment(flags.bits()) +} + +pub fn load() { + unsafe { + let cs = GDT.add_entry(Descriptor::kernel_code_segment()); + GDT.add_entry(Descriptor::user_code_segment()); + GDT.add_entry(Descriptor::user_data_segment()); + let ss = GDT.add_entry(kernel_data_segment()); + GDT.load(); + set_cs(cs); + load_ss(ss); + } +} diff --git a/src/arch/x86_64/idt.rs b/src/arch/x86_64/idt.rs new file mode 100644 index 0000000..9c614c3 --- /dev/null +++ b/src/arch/x86_64/idt.rs @@ -0,0 +1,18 @@ +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; + +static mut IDT: InterruptDescriptorTable = InterruptDescriptorTable::new(); + +pub fn load() { + unsafe { + IDT.load(); + IDT.breakpoint.set_handler_fn(breakpoint); + } +} + +extern "x86-interrupt" fn breakpoint(_: &mut InterruptStackFrame) { + use crate::graphics::tty::Tty; + use crate::graphics::tty::serial::SerialTty; + + let mut stdout = unsafe { SerialTty::new(0x3F8) }; + stdout.puts("Breakpoint reached!"); +} diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs new file mode 100644 index 0000000..ae88c44 --- /dev/null +++ b/src/arch/x86_64/mod.rs @@ -0,0 +1,18 @@ +pub mod gdt; +pub mod idt; + +/// This macro exists because the x86_64 library uses `llvm_asm!`, which I have disabled. +/// When the library ever uses plain `asm!` or a function, I will use its version instead. +#[cfg(target_arch="x86_64")] +#[macro_export] +macro_rules! software_interrupt { + ($x:expr) => { + asm!("int {}", const $x); + } +} + +pub fn breakpoint() { + unsafe { + asm!("int3"); + } +} diff --git a/src/graphics/tty.rs b/src/graphics/tty.rs index 7153844..0760dc7 100644 --- a/src/graphics/tty.rs +++ b/src/graphics/tty.rs @@ -2,7 +2,6 @@ pub mod serial; pub mod terminal; pub mod uefi; -use alloc::boxed::Box; use crate::graphics::tty::serial::SerialTty; pub trait Tty { diff --git a/src/graphics/tty/serial.rs b/src/graphics/tty/serial.rs index 5ddfb5c..dc0d262 100644 --- a/src/graphics/tty/serial.rs +++ b/src/graphics/tty/serial.rs @@ -1,17 +1,14 @@ -use alloc::string::String; use crate::graphics::tty::Tty; #[derive(Clone)] pub struct SerialTty { port: u16, - buffer: String, } impl SerialTty { pub unsafe fn new(port: u16) -> SerialTty { SerialTty { port: port, - buffer: String::new(), } } @@ -20,15 +17,25 @@ impl SerialTty { asm!("out dx, al", in("dx") self.port, in("al") cmd); } } + + fn outc(&self, c: char) { + let len = c.len_utf8(); + let bytes = (c as u32).to_le_bytes(); + for i in 0..len { + self.outb(bytes[i]); + } + } } impl Tty for SerialTty { fn putc(&mut self, c: char) { - self.buffer.push(c); + self.outc(c); } fn puts(&mut self, s: &str) { - self.buffer.push_str(s); + for c in s.chars() { + self.putc(c); + } } fn clear(&mut self) { @@ -37,9 +44,6 @@ impl Tty for SerialTty { } fn flush(&mut self) { - for b in self.buffer.bytes() { - self.outb(b); - } - self.buffer.clear(); + // This TTY doesn't support buffering. } } diff --git a/src/graphics/tty/uefi.rs b/src/graphics/tty/uefi.rs index b5ff06a..d20d3ff 100644 --- a/src/graphics/tty/uefi.rs +++ b/src/graphics/tty/uefi.rs @@ -30,7 +30,7 @@ impl Tty for UefiTty<'_> { fn clear(&mut self) { // VT100 escape code to reset the terminal: `ESC C`. - self.output.clear(); + self.output.clear().unwrap().unwrap(); } fn flush(&mut self) { diff --git a/src/main.rs b/src/main.rs index e95e6e5..06530d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,14 +3,16 @@ #![feature(abi_efiapi)] #![feature(alloc_error_handler)] #![feature(asm)] +#![feature(naked_functions)] +#![feature(abi_x86_interrupt)] extern crate alloc; mod allocator; #[macro_use] mod graphics; mod misc; +mod arch; -use alloc::boxed::Box; use core::mem; use core::slice; use crate::allocator::{Allocator, ALLOCATOR}; @@ -24,7 +26,6 @@ fn setup(st: &SystemTable, _handle: Handle) { st.stdout().reset(false).expect_success("Failed to reset UEFI stdout."); println!("Booting..."); - use core::fmt::Write; for entry in st.config_table() { use uefi::table::cfg::*; @@ -44,6 +45,8 @@ fn setup(st: &SystemTable, _handle: Handle) { } fn main(_st: SystemTable, _mmap: uefi::table::boot::MemoryMapIter) -> ! { + crate::arch::x86_64::breakpoint(); + halt() } @@ -52,9 +55,9 @@ fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { // Tasks that require the UEFI boot services. unsafe { + ALLOCATOR = Allocator::Uefi(st_boot.unsafe_clone()); STDOUT = Some(SerialTty::new(0x3F8)); STDERR = Some(SerialTty::new(0x3F8)); - ALLOCATOR = Allocator::Uefi(st_boot.unsafe_clone()); } setup(&st_boot, handle); @@ -75,6 +78,15 @@ fn efi_main(handle: Handle, st_boot: SystemTable) -> Status { st_boot.exit_boot_services(handle, mmap_buf_slice).expect_success("Failed to exit the UEFI boot services.") }; + // Replace the GDT and IDT with my own so I can start handling interrupts. + use x86_64::instructions::interrupts; + use crate::arch::x86_64::{gdt, idt}; + + interrupts::disable(); + gdt::load(); + idt::load(); + interrupts::enable(); + // Tasks that do not require the UEFI boot services. unsafe {