Add interrupts and IDT and react to double, page, general prot faults

and breakpoints, add keyboard support through the keyboar interrupt, add
paging memory management and a Frame Allocator, add a generic arch
module that initializes arch-specific things, fix framebuffer and
serialconsole being accessed from multiple places, move serial to driver
since its not actually a serial console.
This commit is contained in:
csd4ni3l
2026-03-24 13:48:31 +01:00
parent e28b898d79
commit 269d900d97
17 changed files with 334 additions and 74 deletions

17
kernel/Cargo.lock generated
View File

@@ -10,6 +10,8 @@ dependencies = [
"lazy_static",
"limine",
"micromath",
"pc-keyboard",
"pic8259",
"spin 0.10.0",
"x86_64",
]
@@ -71,6 +73,21 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "pc-keyboard"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0ca629cbb3f0d5b699c338f0129ff78c9bfd7ea8b1258ad529bff490dc8ed5a"
[[package]]
name = "pic8259"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62d9a86c292b165f757e47e7fd66855def189b2564609bc4203727b27c33db22"
dependencies = [
"x86_64",
]
[[package]]
name = "rustversion"
version = "1.0.22"

View File

@@ -13,7 +13,11 @@ font8x8 = { version = "0.3.1", default-features = false }
lazy_static = { version = "1.5.0", features = ["spin_no_std"] }
limine = "0.5"
micromath = "2.1.0"
pc-keyboard = "0.8.0"
pic8259 = "0.11.0"
spin = "0.10.0"
[target.'cfg(target_arch = "x86_64")'.dependencies]
x86_64 = "0.15.4"
[profile.dev]

54
kernel/src/arch/arch.rs Normal file
View File

@@ -0,0 +1,54 @@
use core::arch::asm;
use limine::response::{HhdmResponse, MemoryMapResponse};
#[cfg(target_arch = "x86_64")]
use crate::arch::x86_64::{
init::init_x86_64,
paging::{XunilFrameAllocator, example_mapping, initialize_paging},
};
#[cfg(target_arch = "x86_64")]
use x86_64::{
VirtAddr, registers::control::Cr3, structures::paging::OffsetPageTable,
structures::paging::Page,
};
#[cfg(target_arch = "x86_64")]
pub fn memory_management_init(
hhdm_response: &HhdmResponse,
memory_map_response: &MemoryMapResponse,
) -> OffsetPageTable<'static> {
let physical_offset = VirtAddr::new(hhdm_response.offset());
let (frame, _) = Cr3::read();
let mut mapper = unsafe { initialize_paging(physical_offset) };
let l4_virt = physical_offset + frame.start_address().as_u64() + 0xb8000;
let mut frame_allocator = XunilFrameAllocator::new(memory_map_response.entries());
let page = Page::containing_address(l4_virt);
unsafe {
example_mapping(page, &mut mapper, &mut frame_allocator);
}
mapper
}
#[cfg(target_arch = "x86_64")]
pub fn init(
hhdm_response: &HhdmResponse,
memory_map_response: &MemoryMapResponse,
) -> OffsetPageTable<'static> {
init_x86_64();
return memory_management_init(hhdm_response, memory_map_response);
}
pub fn idle() -> ! {
loop {
unsafe {
#[cfg(target_arch = "x86_64")]
asm!("hlt");
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
asm!("wfi");
#[cfg(target_arch = "loongarch64")]
asm!("idle 0");
}
}
}

View File

@@ -1,3 +1,3 @@
pub mod serial;
pub mod arch;
#[cfg(target_arch = "x86_64")]
pub mod x86_64;

View File

View File

@@ -1,23 +0,0 @@
use crate::arch::x86_64::{
gdt,
interrupts::{breakpoint_handler, double_fault_handler},
};
use lazy_static::lazy_static;
use x86_64::structures::idt::InterruptDescriptorTable;
lazy_static! {
static ref IDT: InterruptDescriptorTable = {
let mut idt = InterruptDescriptorTable::new();
idt.breakpoint.set_handler_fn(breakpoint_handler);
unsafe {
idt.double_fault
.set_handler_fn(double_fault_handler)
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
}
idt
};
}
pub fn init_idt_x86_64() {
IDT.load();
}

View File

@@ -0,0 +1,16 @@
use crate::arch::x86_64::gdt::load_gdt_x86_64;
use crate::arch::x86_64::interrupts::{PICS, init_idt_x86_64};
use x86_64::instructions::interrupts;
pub fn init_x86_64() {
load_gdt_x86_64();
init_idt_x86_64();
unsafe {
let mut pics = PICS.lock();
pics.initialize();
pics.write_masks(0xFC, 0xFF);
}
interrupts::enable();
}

View File

@@ -1,5 +1,56 @@
use crate::println;
use x86_64::structures::idt::InterruptStackFrame;
use crate::{arch::x86_64::gdt, driver::keyboard::keyboard_interrupt_handler, println};
use lazy_static::lazy_static;
use pic8259::ChainedPics;
use spin::Mutex;
pub use x86_64::instructions::interrupts::without_interrupts;
use x86_64::{
registers::control::Cr2,
structures::idt::{InterruptDescriptorTable, InterruptStackFrame, PageFaultErrorCode},
};
pub const PIC_1_OFFSET: u8 = 32;
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
pub static PICS: Mutex<ChainedPics> =
Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
#[derive(Debug, Clone, Copy)]
#[repr(u8)]
pub enum InterruptIndex {
Timer = PIC_1_OFFSET,
Keyboard, // putting it below increments it by 1, so its offset + 1
}
impl InterruptIndex {
pub 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);
unsafe {
idt.double_fault
.set_handler_fn(double_fault_handler)
.set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX);
}
idt[InterruptIndex::Timer.as_u8()].set_handler_fn(timer_interrupt_handler);
idt.page_fault.set_handler_fn(page_fault_handler);
idt.general_protection_fault.set_handler_fn(gpf_handler);
idt[InterruptIndex::Keyboard.as_u8()].set_handler_fn(keyboard_interrupt_handler);
idt
};
}
pub fn init_idt_x86_64() {
IDT.load();
}
pub extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
@@ -11,3 +62,29 @@ pub extern "x86-interrupt" fn double_fault_handler(
) -> ! {
panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame);
}
pub extern "x86-interrupt" fn page_fault_handler(
stack_frame: InterruptStackFrame,
error_code: PageFaultErrorCode,
) {
panic!(
"EXCEPTION: PAGE FAULT\nAccessed Addresss: {:?}\nError Code: {:?}\n{:#?}",
Cr2::read(),
error_code,
stack_frame
);
}
pub extern "x86-interrupt" fn gpf_handler(stack_frame: InterruptStackFrame, error_code: u64) {
panic!(
"EXCEPTION: GENERAL PROTECTION FAULT\nError Code: {:?}\n{:#?}",
error_code, stack_frame
);
}
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
}
}

View File

@@ -1,4 +1,4 @@
pub mod gdt;
pub mod idt;
pub mod init;
pub mod interrupts;
pub mod paging;

View File

@@ -1 +1,71 @@
use x86_64::{
PhysAddr, VirtAddr,
registers::control::Cr3,
structures::paging::{
FrameAllocator, Mapper, OffsetPageTable, Page, PageTable, PageTableFlags as Flags,
PhysFrame, Size2MiB, Size4KiB,
},
};
use limine::memory_map::{Entry, EntryType};
unsafe fn active_level_4_table(mem_offset: VirtAddr) -> &'static mut PageTable {
let (level_4_table, _) = Cr3::read();
let physical_addr = level_4_table.start_address();
let virtual_addr = mem_offset + physical_addr.as_u64();
let page_table_ptr: *mut PageTable = virtual_addr.as_mut_ptr();
unsafe { &mut *page_table_ptr }
}
pub unsafe fn initialize_paging(physical_memory_offset: VirtAddr) -> OffsetPageTable<'static> {
unsafe {
let level_4_table = active_level_4_table(physical_memory_offset);
OffsetPageTable::new(level_4_table, physical_memory_offset)
}
}
pub struct XunilFrameAllocator<'a> {
next: usize,
memory_map: &'a [&'a Entry],
}
impl<'a> XunilFrameAllocator<'a> {
pub fn new(memory_map: &'a [&'a Entry]) -> Self {
Self {
next: 0,
memory_map,
}
}
fn usable_frames(&self) -> impl Iterator<Item = PhysFrame> {
let regions = self.memory_map.iter();
let usable = regions.filter(|region| region.entry_type == EntryType::USABLE);
let ranges = usable
.map(|usable_region| usable_region.base..usable_region.base + usable_region.length);
let frame_addresses = ranges.flat_map(|r| r.step_by(4096));
frame_addresses
.map(|frame_address| PhysFrame::containing_address(PhysAddr::new(frame_address)))
}
}
unsafe impl<'a> FrameAllocator<Size4KiB> for XunilFrameAllocator<'a> {
fn allocate_frame(&mut self) -> Option<PhysFrame<Size4KiB>> {
let frame = self.usable_frames().nth(self.next);
self.next += 1;
frame
}
}
pub unsafe fn example_mapping(
page: Page<Size2MiB>,
mapper: &mut OffsetPageTable,
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
) {
let frame = PhysFrame::<Size2MiB>::containing_address(PhysAddr::new(0x0000_1234_4000_0000));
let flags = Flags::PRESENT | Flags::WRITABLE;
let map_to_result = unsafe { mapper.map_to(page, frame, flags, frame_allocator) };
map_to_result.expect("map_to failed").flush();
}

View File

@@ -1,6 +1,9 @@
use limine::framebuffer::Framebuffer as LimineFramebuffer;
use spin::Mutex;
#[cfg(target_arch = "x86_64")]
use crate::arch::x86_64::interrupts::without_interrupts;
const MAX_BACKBUFFER_PIXELS: usize = 1920 * 1080;
static mut BACK_BUFFER: [u32; MAX_BACKBUFFER_PIXELS] = [0; MAX_BACKBUFFER_PIXELS];
@@ -62,8 +65,10 @@ pub fn init_framebuffer(raw: &LimineFramebuffer) {
}
pub fn with_framebuffer<F: FnOnce(&mut Framebuffer)>(f: F) {
without_interrupts(|| {
let mut guard = FRAMEBUFFER.lock();
if let Some(fb) = guard.as_mut() {
f(fb);
}
})
}

View File

@@ -1 +1,37 @@
use crate::{
arch::x86_64::interrupts::{InterruptIndex, PICS},
print,
};
use lazy_static::lazy_static;
use pc_keyboard::{DecodedKey, HandleControl, Keyboard, ScancodeSet1, layouts};
use spin::Mutex;
use x86_64::{instructions::port::Port, structures::idt::InterruptStackFrame};
lazy_static! {
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
Mutex::new(Keyboard::new(
ScancodeSet1::new(),
layouts::Us104Key,
HandleControl::Ignore
));
}
pub extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
let mut port = Port::new(0x60);
let scancode: u8 = unsafe { port.read() };
let mut keyboard = KEYBOARD.lock();
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
if let Some(key) = keyboard.process_keyevent(key_event) {
match key {
DecodedKey::Unicode(character) => print!("{}", character),
DecodedKey::RawKey(key) => print!("{:?}", key),
}
}
}
unsafe {
PICS.lock()
.notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
}
}

View File

@@ -1,3 +1,4 @@
pub mod graphics;
pub mod keyboard;
pub mod serial;
pub mod timer;

View File

@@ -4,6 +4,9 @@ use crate::driver::graphics::framebuffer::Framebuffer;
use core::fmt::{self, Write};
use spin::Mutex;
#[cfg(target_arch = "x86_64")]
use crate::arch::x86_64::interrupts::without_interrupts;
pub struct ConsoleWriter<'a> {
pub fb: &'a mut Framebuffer,
pub console: &'a mut SerialConsole,
@@ -70,8 +73,10 @@ pub fn init_serial_console(start_x: usize, start_y: usize) {
}
pub fn with_serial_console<F: FnOnce(&mut SerialConsole)>(f: F) {
without_interrupts(|| {
let mut guard = SERIAL_CONSOLE.lock();
if let Some(fb) = guard.as_mut() {
f(fb);
}
})
}

View File

@@ -2,25 +2,22 @@
#![no_main]
#![feature(abi_x86_interrupt)]
use core::arch::asm;
use core::fmt::Write;
use limine::BaseRevision;
use limine::request::{FramebufferRequest, RequestsEndMarker, RequestsStartMarker};
use limine::request::{
FramebufferRequest, HhdmRequest, MemoryMapRequest, RequestsEndMarker, RequestsStartMarker,
};
pub mod arch;
pub mod driver;
use crate::arch::serial::{ConsoleWriter, init_serial_console, with_serial_console};
#[cfg(target_arch = "x86_64")]
use crate::arch::x86_64::gdt::load_gdt_x86_64;
use crate::arch::arch::{idle, init};
use crate::driver::graphics::base::rgb;
use crate::driver::graphics::framebuffer::{init_framebuffer, with_framebuffer};
use crate::driver::graphics::primitives::{
circle_filled, circle_outline, rectangle_filled, rectangle_outline, triangle_outline,
};
use crate::arch::x86_64::idt::init_idt_x86_64;
use crate::driver::serial::{ConsoleWriter, init_serial_console, with_serial_console};
/// Sets the base revision to the latest revision supported by the crate.
/// See specification for further info.
@@ -34,6 +31,14 @@ static BASE_REVISION: BaseRevision = BaseRevision::new();
#[unsafe(link_section = ".requests")]
static FRAMEBUFFER_REQUEST: FramebufferRequest = FramebufferRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static HHDM_REQUEST: HhdmRequest = HhdmRequest::new();
#[used]
#[unsafe(link_section = ".requests")]
static MEMORY_MAP_REQUEST: MemoryMapRequest = MemoryMapRequest::new();
/// Define the stand and end markers for Limine requests.
#[used]
#[unsafe(link_section = ".requests_start_marker")]
@@ -80,15 +85,25 @@ unsafe extern "C" fn kmain() -> ! {
// removed by the linker.
assert!(BASE_REVISION.is_supported());
#[cfg(target_arch = "x86_64")]
{
load_gdt_x86_64();
init_idt_x86_64();
}
if let Some(framebuffer_response) = FRAMEBUFFER_REQUEST.get_response() {
if let Some(limine_framebuffer) = framebuffer_response.framebuffers().next() {
init_framebuffer(&limine_framebuffer);
// boot_animation();
}
}
if let Some(hhdm_response) = HHDM_REQUEST.get_response() {
if let Some(memory_map_response) = MEMORY_MAP_REQUEST.get_response() {
let mapper = init(hhdm_response, memory_map_response);
} else {
init_serial_console(0, 0);
panic!("Could not get required info from Limine's memory map. ")
}
} else {
init_serial_console(0, 0);
panic!("Could not get required info from the Limine's higher-half direct mapping. ")
}
with_framebuffer(|mut fb| {
let (width, height) = (fb.width.clone(), fb.height.clone());
init_serial_console(0, 0);
@@ -99,8 +114,6 @@ unsafe extern "C" fn kmain() -> ! {
circle_outline(&mut fb, 400, 200, 100.0, rgb(0, 0, 0));
triangle_outline(&mut fb, 100, 400, 200, 400, 150, 600, rgb(0, 0, 0));
});
}
}
idle();
}
@@ -128,16 +141,3 @@ fn rust_panic(_info: &core::panic::PanicInfo) -> ! {
idle();
}
fn idle() -> ! {
loop {
unsafe {
#[cfg(target_arch = "x86_64")]
asm!("hlt");
#[cfg(any(target_arch = "aarch64", target_arch = "riscv64"))]
asm!("wfi");
#[cfg(target_arch = "loongarch64")]
asm!("idle 0");
}
}
}

View File

@@ -1 +0,0 @@

View File

@@ -1 +0,0 @@