diff --git a/lupos/.cargo/config.toml b/lupos/.cargo/config.toml new file mode 100644 index 0000000..5409245 --- /dev/null +++ b/lupos/.cargo/config.toml @@ -0,0 +1,9 @@ +[unstable] +build-std-features = ["compiler-builtins-mem"] +build-std = ["core", "compiler_builtins", "alloc"] + +[build] +target = "x86_64-lupos.json" + +[target.'cfg(target_os = "none")'] +runner = "bootimage runner" \ No newline at end of file diff --git a/lupos/Cargo.lock b/lupos/Cargo.lock new file mode 100644 index 0000000..ed82de6 --- /dev/null +++ b/lupos/Cargo.lock @@ -0,0 +1,153 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bootloader" +version = "0.9.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "365861702868e2a37b4247aaecc7bd8f4389baec8d025497ad8ba7ff37ee9440" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] + +[[package]] +name = "linked_list_allocator" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549ce1740e46b291953c4340adcd74c59bcf4308f4cac050fd33ba91b7168f4a" +dependencies = [ + "spinning_top", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "lupos" +version = "0.1.0" +dependencies = [ + "bootloader", + "lazy_static", + "linked_list_allocator", + "pc-keyboard", + "pic8259", + "spin", + "uart_16550", + "volatile 0.2.7", + "x86_64", +] + +[[package]] +name = "pc-keyboard" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6f2d937e3b8d63449b01401e2bae4041bc9dd1129c2e3e0d239407cf6635ac" + +[[package]] +name = "pic8259" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb844b5b01db1e0b17938685738f113bfc903846f18932b378bc0eabfa40e194" +dependencies = [ + "x86_64", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "spinning_top" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9eb1a2f4c41445a3a0ff9abc5221c5fcd28e1f13cd7c0397706f9ac938ddb0" +dependencies = [ + "lock_api", +] + +[[package]] +name = "uart_16550" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614ff2a87880d4bd4374722268598a970bbad05ced8bf630439417347254ab2e" +dependencies = [ + "bitflags 1.3.2", + "rustversion", + "x86_64", +] + +[[package]] +name = "volatile" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b06ad3ed06fef1713569d547cdbdb439eafed76341820fb0e0344f29a41945" + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "x86_64" +version = "0.14.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cb6fd45bfeab6a5055c5bffdb08768bd0c069f1d946debe585bbb380a7c062" +dependencies = [ + "bit_field", + "bitflags 2.5.0", + "rustversion", + "volatile 0.4.6", +] diff --git a/lupos/Cargo.toml b/lupos/Cargo.toml new file mode 100644 index 0000000..1059dda --- /dev/null +++ b/lupos/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "lupos" +version = "0.1.0" +edition = "2021" + +[[test]] +name = "should_panic" +harness = false + +[[test]] +name = "stack_overflow" +harness = false + +[dependencies] +bootloader = { version = "0.9", features = ["map_physical_memory"] } +volatile = "0.2.6" +spin = "0.5.2" +x86_64 = "0.14.2" +uart_16550 = "0.2.0" +pic8259 = "0.10.1" +pc-keyboard = "0.5.0" +linked_list_allocator = "0.9.0" + +[dependencies.lazy_static] +version = "1.0" +features = ["spin_no_std"] + + +[package.metadata.bootimage] +test-args = [ + "-device", + "isa-debug-exit,iobase=0xf4,iosize=0x04", + "-serial", + "stdio", + "-display", + "none", +] +test-success-exit-code = 33 # (0x10 << 1) | 1 \ No newline at end of file diff --git a/lupos/src/allocator.rs b/lupos/src/allocator.rs new file mode 100644 index 0000000..c60c787 --- /dev/null +++ b/lupos/src/allocator.rs @@ -0,0 +1,84 @@ + +use alloc::alloc::{GlobalAlloc, Layout}; +use core::ptr::null_mut; +use bump::BumpAllocator; +use x86_64::{ + structures::paging::{ + mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB, + }, + VirtAddr, +}; + + +pub mod bump; +pub mod fixed_size_block; +pub mod linked_list; + +pub const HEAP_START: usize = 0x_4444_4444_0000; +pub const HEAP_SIZE: usize = 100 * 1024; // 100 KiB + +#[global_allocator] +static ALLOCATOR: Locked = Locked::new(BumpAllocator::new()); + +pub fn init_heap( + mapper: &mut impl Mapper, + frame_allocator: &mut impl FrameAllocator, +) -> Result<(), MapToError> { + let page_range = { + let heap_start = VirtAddr::new(HEAP_START as u64); + let heap_end = heap_start + HEAP_SIZE - 1u64; + let heap_start_page = Page::containing_address(heap_start); + let heap_end_page = Page::containing_address(heap_end); + Page::range_inclusive(heap_start_page, heap_end_page) + }; + + for page in page_range { + let frame = frame_allocator + .allocate_frame() + .ok_or(MapToError::FrameAllocationFailed)?; + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + unsafe { mapper.map_to(page, frame, flags, frame_allocator)?.flush() }; + } + + unsafe { + ALLOCATOR.lock().init(HEAP_START, HEAP_SIZE); + } + + Ok(()) +} + +pub struct Dummy; + +unsafe impl GlobalAlloc for Dummy { + unsafe fn alloc(&self, _layout: Layout) -> *mut u8 { + null_mut() + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + panic!("dealloc should be never called") + } +} + +/// A wrapper around spin::Mutex to permit trait implementations. +pub struct Locked { + inner: spin::Mutex, +} + +impl Locked { + pub const fn new(inner: A) -> Self { + Locked { + inner: spin::Mutex::new(inner), + } + } + + pub fn lock(&self) -> spin::MutexGuard { + self.inner.lock() + } +} + +/// Align the given address `addr` upwards to alignment `align`. +/// +/// Requires that `align` is a power of two. +fn align_up(addr: usize, align: usize) -> usize { + (addr + align - 1) & !(align - 1) +} \ No newline at end of file diff --git a/lupos/src/allocator/bump.rs b/lupos/src/allocator/bump.rs new file mode 100644 index 0000000..fbd5e39 --- /dev/null +++ b/lupos/src/allocator/bump.rs @@ -0,0 +1,61 @@ +use super::{align_up, Locked}; +use alloc::alloc::{GlobalAlloc, Layout}; +use core::ptr; + +pub struct BumpAllocator { + heap_start: usize, + heap_end: usize, + next: usize, + allocations: usize, +} + +impl BumpAllocator { + /// Creates a new empty bump allocator. + pub const fn new() -> Self { + BumpAllocator { + heap_start: 0, + heap_end: 0, + next: 0, + allocations: 0, + } + } + + /// Initializes the bump allocator with the given heap bounds. + /// + /// This method is unsafe because the caller must ensure that the given + /// memory range is unused. Also, this method must be called only once. + pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) { + self.heap_start = heap_start; + self.heap_end = heap_start.saturating_add(heap_size); + self.next = heap_start; + } +} + +unsafe impl GlobalAlloc for Locked { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let mut bump = self.lock(); // get a mutable reference + + let alloc_start = align_up(bump.next, layout.align()); + let alloc_end = match alloc_start.checked_add(layout.size()) { + Some(end) => end, + None => return ptr::null_mut(), + }; + + if alloc_end > bump.heap_end { + ptr::null_mut() // out of memory + } else { + bump.next = alloc_end; + bump.allocations += 1; + alloc_start as *mut u8 + } + } + + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) { + let mut bump = self.lock(); // get a mutable reference + + bump.allocations -= 1; + if bump.allocations == 0 { + bump.next = bump.heap_start; + } + } +} \ No newline at end of file diff --git a/lupos/src/allocator/fixed_size_block.rs b/lupos/src/allocator/fixed_size_block.rs new file mode 100644 index 0000000..0c6e132 --- /dev/null +++ b/lupos/src/allocator/fixed_size_block.rs @@ -0,0 +1,103 @@ +use super::Locked; +use alloc::alloc::{GlobalAlloc, Layout}; +use core::{ + mem, + ptr::{self, NonNull}, +}; + +/// The block sizes to use. +/// +/// The sizes must each be power of 2 because they are also used as +/// the block alignment (alignments must be always powers of 2). +const BLOCK_SIZES: &[usize] = &[8, 16, 32, 64, 128, 256, 512, 1024, 2048]; + +/// Choose an appropriate block size for the given layout. +/// +/// Returns an index into the `BLOCK_SIZES` array. +fn list_index(layout: &Layout) -> Option { + let required_block_size = layout.size().max(layout.align()); + BLOCK_SIZES.iter().position(|&s| s >= required_block_size) +} + +struct ListNode { + next: Option<&'static mut ListNode>, +} + +pub struct FixedSizeBlockAllocator { + list_heads: [Option<&'static mut ListNode>; BLOCK_SIZES.len()], + fallback_allocator: linked_list_allocator::Heap, +} + +impl FixedSizeBlockAllocator { + /// Creates an empty FixedSizeBlockAllocator. + pub fn new() -> Self { + const EMPTY: Option<&'static mut ListNode> = None; + FixedSizeBlockAllocator { + list_heads: [EMPTY; BLOCK_SIZES.len()], + fallback_allocator: linked_list_allocator::Heap::empty(), + } + } + + /// Initialize the allocator with the given heap bounds. + /// + /// This function is unsafe because the caller must guarantee that the given + /// heap bounds are valid and that the heap is unused. This method must be + /// called only once. + pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) { + self.fallback_allocator.init(heap_start, heap_size); + } + + /// Allocates using the fallback allocator. + fn fallback_alloc(&mut self, layout: Layout) -> *mut u8 { + match self.fallback_allocator.allocate_first_fit(layout) { + Ok(ptr) => ptr.as_ptr(), + Err(_) => ptr::null_mut(), + } + } +} + +unsafe impl GlobalAlloc for Locked { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let mut allocator = self.lock(); + match list_index(&layout) { + Some(index) => { + match allocator.list_heads[index].take() { + Some(node) => { + allocator.list_heads[index] = node.next.take(); + node as *mut ListNode as *mut u8 + } + None => { + // no block exists in list => allocate new block + let block_size = BLOCK_SIZES[index]; + // only works if all block sizes are a power of 2 + let block_align = block_size; + let layout = Layout::from_size_align(block_size, block_align).unwrap(); + allocator.fallback_alloc(layout) + } + } + } + None => allocator.fallback_alloc(layout), + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + let mut allocator = self.lock(); + match list_index(&layout) { + Some(index) => { + let new_node = ListNode { + next: allocator.list_heads[index].take(), + }; + // verify that block has size and alignment required for storing node + assert!(mem::size_of::() <= BLOCK_SIZES[index]); + assert!(mem::align_of::() <= BLOCK_SIZES[index]); + let new_node_ptr = ptr as *mut ListNode; + new_node_ptr.write(new_node); + allocator.list_heads[index] = Some(&mut *new_node_ptr); + } + None => { + let ptr = NonNull::new(ptr).unwrap(); + allocator.fallback_allocator.deallocate(ptr, layout); + } + } + } +} \ No newline at end of file diff --git a/lupos/src/allocator/linked_list.rs b/lupos/src/allocator/linked_list.rs new file mode 100644 index 0000000..421589b --- /dev/null +++ b/lupos/src/allocator/linked_list.rs @@ -0,0 +1,145 @@ +use super::{align_up, Locked}; +use alloc::alloc::{GlobalAlloc, Layout}; +use core::{mem, ptr}; + +struct ListNode { + size: usize, + next: Option<&'static mut ListNode>, +} +const EMPTY: Option<&'static mut ListNode> = None; +impl ListNode { + const fn new(size: usize) -> Self { + ListNode { size, next: EMPTY } + } + + fn start_addr(&self) -> usize { + self as *const Self as usize + } + + fn end_addr(&self) -> usize { + self.start_addr() + self.size + } +} + +pub struct LinkedListAllocator { + head: ListNode, +} + +impl LinkedListAllocator { + /// Creates an empty LinkedListAllocator. + pub const fn new() -> Self { + Self { + head: ListNode::new(0), + } + } + + /// Initialize the allocator with the given heap bounds. + /// + /// This function is unsafe because the caller must guarantee that the given + /// heap bounds are valid and that the heap is unused. This method must be + /// called only once. + pub unsafe fn init(&mut self, heap_start: usize, heap_size: usize) { + self.add_free_region(heap_start, heap_size); + } + + /// Adds the given memory region to the front of the list. + unsafe fn add_free_region(&mut self, addr: usize, size: usize) { + // ensure that the freed region is capable of holding ListNode + assert_eq!(align_up(addr, mem::align_of::()), addr); + assert!(size >= mem::size_of::()); + + // create a new list node and append it at the start of the list + let mut node = ListNode::new(size); + node.next = self.head.next.take(); + let node_ptr = addr as *mut ListNode; + node_ptr.write(node); + self.head.next = Some(&mut *node_ptr) + } + + /// Looks for a free region with the given size and alignment and removes + /// it from the list. + /// + /// Returns a tuple of the list node and the start address of the allocation. + fn find_region(&mut self, size: usize, align: usize) -> Option<(&'static mut ListNode, usize)> { + // reference to current list node, updated for each iteration + let mut current = &mut self.head; + // look for a large enough memory region in linked list + while let Some(ref mut region) = current.next { + if let Ok(alloc_start) = Self::alloc_from_region(®ion, size, align) { + // region suitable for allocation -> remove node from list + let next = region.next.take(); + let ret = Some((current.next.take().unwrap(), alloc_start)); + current.next = next; + return ret; + } else { + // region not suitable -> continue with next region + current = current.next.as_mut().unwrap(); + } + } + + // no suitable region found + None + } + + /// Try to use the given region for an allocation with given size and alignment. + /// + /// Returns the allocation start address on success. + fn alloc_from_region(region: &ListNode, size: usize, align: usize) -> Result { + let alloc_start = align_up(region.start_addr(), align); + let alloc_end = alloc_start.checked_add(size).ok_or(())?; + + if alloc_end > region.end_addr() { + // region too small + return Err(()); + } + + let excess_size = region.end_addr() - alloc_end; + if excess_size > 0 && excess_size < mem::size_of::() { + // rest of region too small to hold a ListNode (required because the + // allocation splits the region in a used and a free part) + return Err(()); + } + + // region suitable for allocation + Ok(alloc_start) + } + + /// Adjust the given layout so that the resulting allocated memory + /// region is also capable of storing a `ListNode`. + /// + /// Returns the adjusted size and alignment as a (size, align) tuple. + fn size_align(layout: Layout) -> (usize, usize) { + let layout = layout + .align_to(mem::align_of::()) + .expect("adjusting alignment failed") + .pad_to_align(); + let size = layout.size().max(mem::size_of::()); + (size, layout.align()) + } +} + +unsafe impl GlobalAlloc for Locked { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + // perform layout adjustments + let (size, align) = LinkedListAllocator::size_align(layout); + let mut allocator = self.lock(); + + if let Some((region, alloc_start)) = allocator.find_region(size, align) { + let alloc_end = alloc_start.checked_add(size).expect("overflow"); + let excess_size = region.end_addr() - alloc_end; + if excess_size > 0 { + allocator.add_free_region(alloc_end, excess_size); + } + alloc_start as *mut u8 + } else { + ptr::null_mut() + } + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // perform layout adjustments + let (size, _) = LinkedListAllocator::size_align(layout); + + self.lock().add_free_region(ptr as usize, size) + } +} \ No newline at end of file diff --git a/lupos/src/gdt.rs b/lupos/src/gdt.rs new file mode 100644 index 0000000..66d186e --- /dev/null +++ b/lupos/src/gdt.rs @@ -0,0 +1,46 @@ +use x86_64::VirtAddr; +use x86_64::structures::tss::TaskStateSegment; +use lazy_static::lazy_static; + +pub const DOUBLE_FAULT_IST_INDEX: u16 = 0; + +lazy_static! { + static ref TSS: TaskStateSegment = { + let mut tss = TaskStateSegment::new(); + tss.interrupt_stack_table[DOUBLE_FAULT_IST_INDEX as usize] = { + const STACK_SIZE: usize = 4096 * 5; + static mut STACK: [u8; STACK_SIZE] = [0; STACK_SIZE]; + + let stack_start = VirtAddr::from_ptr(unsafe { &STACK }); + let stack_end = stack_start + STACK_SIZE; + stack_end + }; + tss + }; +} +use x86_64::structures::gdt::{GlobalDescriptorTable, Descriptor}; +use x86_64::structures::gdt::SegmentSelector; + +lazy_static! { + static ref GDT: (GlobalDescriptorTable, Selectors) = { + let mut gdt = GlobalDescriptorTable::new(); + let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); + let tss_selector = gdt.add_entry(Descriptor::tss_segment(&TSS)); + (gdt, Selectors { code_selector, tss_selector }) + }; +} + +struct Selectors { + code_selector: SegmentSelector, + tss_selector: SegmentSelector, +} +pub fn init() { + use x86_64::instructions::tables::load_tss; + use x86_64::instructions::segmentation::{CS, Segment}; + + GDT.0.load(); + unsafe { + CS::set_reg(GDT.1.code_selector); + load_tss(GDT.1.tss_selector); + } +} \ No newline at end of file diff --git a/lupos/src/interrupts.rs b/lupos/src/interrupts.rs new file mode 100644 index 0000000..22f793d --- /dev/null +++ b/lupos/src/interrupts.rs @@ -0,0 +1,107 @@ +use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame}; +use crate::println; +use lazy_static::lazy_static; +use crate::keyboard_interrupts; +use keyboard_interrupts::keyboard_intertupt; + +use crate::gdt; +use pic8259::ChainedPics; +use spin; + +pub const PIC_1_OFFSET: u8 = 32; +pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; +#[derive(Debug, Clone, Copy)] +#[repr(u8)] +pub enum InterruptIndex { + Timer = PIC_1_OFFSET, + Keyboard, +} + +impl InterruptIndex { + fn as_u8(self) -> u8 { + self as u8 + } + + fn as_usize(self) -> usize { + usize::from(self.as_u8()) + } +} +pub static PICS: spin::Mutex = + spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); + +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); // new + } + idt[InterruptIndex::Timer.as_usize()] + .set_handler_fn(timer_interrupt_handler); // new + // new + idt[InterruptIndex::Keyboard.as_usize()] + .set_handler_fn(keyboard_interrupt_handler); + idt.page_fault.set_handler_fn(page_fault_handler); + idt + }; +} + +// new +extern "x86-interrupt" fn double_fault_handler( + stack_frame: InterruptStackFrame, _error_code: u64) -> ! +{ + panic!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); +} + + +pub fn init_idt() { + IDT.load(); +} + +extern "x86-interrupt" fn breakpoint_handler( + stack_frame: InterruptStackFrame) +{ + println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +} + +#[test_case] +fn test_breakpoint_exception() { + // invoke a breakpoint exception + x86_64::instructions::interrupts::int3(); +} + + +extern "x86-interrupt" fn timer_interrupt_handler( + _stack_frame: InterruptStackFrame) +{ + unsafe { + PICS.lock() + .notify_end_of_interrupt(InterruptIndex::Timer.as_u8()); + } +} + +extern "x86-interrupt" fn keyboard_interrupt_handler( + _stack_frame: InterruptStackFrame) +{ + keyboard_intertupt(); + unsafe { + PICS.lock() + .notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8()); + } +} + +use x86_64::structures::idt::PageFaultErrorCode; +use crate::hlt_loop; +extern "x86-interrupt" fn page_fault_handler( + stack_frame: InterruptStackFrame, + error_code: PageFaultErrorCode, +) { + use x86_64::registers::control::Cr2; + + println!("EXCEPTION: PAGE FAULT"); + println!("Accessed Address: {:?}", Cr2::read()); + println!("Error Code: {:?}", error_code); + println!("{:#?}", stack_frame); + hlt_loop(); +} \ No newline at end of file diff --git a/lupos/src/keyboard_interrupts.rs b/lupos/src/keyboard_interrupts.rs new file mode 100644 index 0000000..7b3a356 --- /dev/null +++ b/lupos/src/keyboard_interrupts.rs @@ -0,0 +1,52 @@ +use crate::println; +use crate::print; +use crate::vga_buffer::WRITER; +use lazy_static::lazy_static; +use crate::move_cursor_back; +use crate::simple_commands; +use simple_commands::simple_commands_help; +use simple_commands::simple_commands_lupus; + +use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1}; +use spin::Mutex; +use x86_64::instructions::port::Port; +use alloc::string::String; + +pub fn keyboard_intertupt() { + lazy_static! { + static ref KEYBOARD: Mutex> = + Mutex::new(Keyboard::new(layouts::Us104Key, ScancodeSet1, + HandleControl::Ignore) + ); + } + static mut WHOLE_STRING: String = String::new(); + let mut keyboard = KEYBOARD.lock(); + let mut port = Port::new(0x60); + + 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) { + match key { + DecodedKey::Unicode(character) => { + unsafe { + // Handle Backspace (ASCII 0x08) + match character{ + '\x08' => {move_cursor_back!(); WHOLE_STRING.pop();}, + '\n' => {print!("{}", character); check_command_list(WHOLE_STRING.as_str()); WHOLE_STRING.clear();}, + _ => {print!("{}", character); WHOLE_STRING.push(character);}, // Füge das Zeichen zum Puffer hinzu + } + } + } + DecodedKey::RawKey(key) => print!("{:?}", key), + } + } + } +} + +fn check_command_list(command: &str) { + match command{ + "help" => simple_commands_help(), + "lupus" => simple_commands_lupus(), + _ => print!("Invalid Input"), + } +} \ No newline at end of file diff --git a/lupos/src/lib.rs b/lupos/src/lib.rs new file mode 100644 index 0000000..5b81697 --- /dev/null +++ b/lupos/src/lib.rs @@ -0,0 +1,100 @@ +#![no_std] +#![feature(abi_x86_interrupt)] +#![cfg_attr(test, no_main)] +#![feature(custom_test_frameworks)] +#![test_runner(crate::test_runner)] +#![reexport_test_harness_main = "test_main"] + +pub mod serial; +pub mod vga_buffer; +pub mod interrupts; +pub mod gdt; +pub mod memory; +pub mod keyboard_interrupts; +pub mod simple_commands; + +extern crate alloc; + +pub mod allocator; + +use core::panic::PanicInfo; + +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]"); + } +} + +pub fn test_runner(tests: &[&dyn Testable]) { + serial_println!("Running {} tests", tests.len()); + for test in tests { + test.run(); + } + exit_qemu(QemuExitCode::Success); +} + +pub fn test_panic_handler(info: &PanicInfo) -> ! { + serial_println!("[failed]\n"); + serial_println!("Error: {}\n", info); + exit_qemu(QemuExitCode::Failed); + hlt_loop(); +} + +#[cfg(test)] +use bootloader::{entry_point, BootInfo}; + +#[cfg(test)] +entry_point!(test_kernel_main); + +/// Entry point for `cargo test` +#[cfg(test)] +fn test_kernel_main(_boot_info: &'static BootInfo) -> ! { + // like before + init(); + test_main(); + hlt_loop(); +} + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + test_panic_handler(info) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub fn exit_qemu(exit_code: QemuExitCode) { + use x86_64::instructions::port::Port; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } +} + +pub fn init() { + gdt::init(); + interrupts::init_idt(); + unsafe { interrupts::PICS.lock().initialize() }; + x86_64::instructions::interrupts::enable(); +} + +pub fn hlt_loop() -> ! { + loop { + x86_64::instructions::hlt(); + } +} \ No newline at end of file diff --git a/lupos/src/main.rs b/lupos/src/main.rs new file mode 100644 index 0000000..4006e74 --- /dev/null +++ b/lupos/src/main.rs @@ -0,0 +1,68 @@ +#![no_std] +#![no_main] +#![feature(custom_test_frameworks)] +#![feature(const_mut_refs)] +#![test_runner(lupos::test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::panic::PanicInfo; +use lupos::println; +use lupos:: print; +use bootloader::{BootInfo, entry_point}; +use alloc::{boxed::Box, vec, vec::Vec, rc::Rc}; + +extern crate alloc; + +entry_point!(kernel_main); + +fn kernel_main(boot_info: &'static BootInfo) -> ! { + use lupos::memory; + use lupos::allocator; + use x86_64::{structures::paging::Page, VirtAddr}; + use lupos::memory::BootInfoFrameAllocator; + println!("Cave Canem{}", "!"); + + lupos::init(); // new + + let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset); + let mut mapper = unsafe { memory::init(phys_mem_offset) }; + let mut frame_allocator = unsafe { BootInfoFrameAllocator::init(&boot_info.memory_map) }; + + allocator::init_heap(&mut mapper, &mut frame_allocator).expect("heap initialization failed"); + + let page = Page::containing_address(VirtAddr::new(0xdeadbeaf000)); + memory::create_example_mapping(page, &mut mapper, &mut frame_allocator); + + // create a reference counted vector -> will be freed when count reaches 0 + let reference_counted = Rc::new(vec![1, 2, 3]); + let cloned_reference = reference_counted.clone(); + println!( + "current reference count is {}", + Rc::strong_count(&cloned_reference) + ); + core::mem::drop(reference_counted); + println!( + "reference count is {} now", + Rc::strong_count(&cloned_reference) + ); + + #[cfg(test)] + test_main(); + + println!("It did not crash!"); + lupos::hlt_loop(); +} + +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + println!("{}", info); + lupos::hlt_loop(); // new +} + + +#[cfg(test)] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + lupos::test_panic_handler(info) +} \ No newline at end of file diff --git a/lupos/src/memory.rs b/lupos/src/memory.rs new file mode 100644 index 0000000..9066810 --- /dev/null +++ b/lupos/src/memory.rs @@ -0,0 +1,169 @@ +use x86_64::{ + structures::paging::PageTable, + VirtAddr, +}; +use x86_64::{ + PhysAddr, + structures::paging::{Page, PhysFrame, Mapper, Size4KiB, FrameAllocator} +}; + +/// Creates an example mapping for the given page to frame `0xb8000`. +pub fn create_example_mapping( + page: Page, + mapper: &mut OffsetPageTable, + frame_allocator: &mut impl FrameAllocator, +) { + use x86_64::structures::paging::PageTableFlags as Flags; + + let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000)); + let flags = Flags::PRESENT | Flags::WRITABLE; + + let map_to_result = unsafe { + // FIXME: this is not safe, we do it only for testing + mapper.map_to(page, frame, flags, frame_allocator) + }; + map_to_result.expect("map_to failed").flush(); +} +/// Returns a mutable reference to the active level 4 table. +/// +/// This function is unsafe because the caller must guarantee that the +/// complete physical memory is mapped to virtual memory at the passed +/// `physical_memory_offset`. Also, this function must be only called once +/// to avoid aliasing `&mut` references (which is undefined behavior). + +use x86_64::structures::paging::OffsetPageTable; + +/// Initialize a new OffsetPageTable. +/// +/// This function is unsafe because the caller must guarantee that the +/// complete physical memory is mapped to virtual memory at the passed +/// `physical_memory_offset`. Also, this function must be only called once +/// to avoid aliasing `&mut` references (which is undefined behavior). +pub unsafe fn init(physical_memory_offset: VirtAddr) -> OffsetPageTable<'static> { + let level_4_table = active_level_4_table(physical_memory_offset); + OffsetPageTable::new(level_4_table, physical_memory_offset) +} + +unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) + -> &'static mut PageTable +{ + use x86_64::registers::control::Cr3; + + let (level_4_table_frame, _) = Cr3::read(); + + let phys = level_4_table_frame.start_address(); + let virt = physical_memory_offset + phys.as_u64(); + let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); + + &mut *page_table_ptr // unsafe +} + + +/// Translates the given virtual address to the mapped physical address, or +/// `None` if the address is not mapped. +/// +/// This function is unsafe because the caller must guarantee that the +/// complete physical memory is mapped to virtual memory at the passed +/// `physical_memory_offset`. +pub unsafe fn translate_addr(addr: VirtAddr, physical_memory_offset: VirtAddr) + -> Option +{ + translate_addr_inner(addr, physical_memory_offset) +} + +/// Private function that is called by `translate_addr`. +/// +/// This function is safe to limit the scope of `unsafe` because Rust treats +/// the whole body of unsafe functions as an unsafe block. This function must +/// only be reachable through `unsafe fn` from outside of this module. +fn translate_addr_inner(addr: VirtAddr, physical_memory_offset: VirtAddr) + -> Option +{ + use x86_64::structures::paging::page_table::FrameError; + use x86_64::registers::control::Cr3; + + // read the active level 4 frame from the CR3 register + let (level_4_table_frame, _) = Cr3::read(); + + let table_indexes = [ + addr.p4_index(), addr.p3_index(), addr.p2_index(), addr.p1_index() + ]; + let mut frame = level_4_table_frame; + + // traverse the multi-level page table + for &index in &table_indexes { + // convert the frame into a page table reference + let virt = physical_memory_offset + frame.start_address().as_u64(); + let table_ptr: *const PageTable = virt.as_ptr(); + let table = unsafe {&*table_ptr}; + + // read the page table entry and update `frame` + let entry = &table[index]; + frame = match entry.frame() { + Ok(frame) => frame, + Err(FrameError::FrameNotPresent) => return None, + Err(FrameError::HugeFrame) => panic!("huge pages not supported"), + }; + } + + // calculate the physical address by adding the page offset + Some(frame.start_address() + u64::from(addr.page_offset())) +} + +/// A FrameAllocator that always returns `None`. +pub struct EmptyFrameAllocator; + +unsafe impl FrameAllocator for EmptyFrameAllocator { + fn allocate_frame(&mut self) -> Option { + None + } +} + +use bootloader::bootinfo::MemoryMap; + +/// A FrameAllocator that returns usable frames from the bootloader's memory map. +pub struct BootInfoFrameAllocator { + memory_map: &'static MemoryMap, + next: usize, +} + +impl BootInfoFrameAllocator { + /// Create a FrameAllocator from the passed memory map. + /// + /// This function is unsafe because the caller must guarantee that the passed + /// memory map is valid. The main requirement is that all frames that are marked + /// as `USABLE` in it are really unused. + pub unsafe fn init(memory_map: &'static MemoryMap) -> Self { + BootInfoFrameAllocator { + memory_map, + next: 0, + } + } +} + +use bootloader::bootinfo::MemoryRegionType; + +impl BootInfoFrameAllocator { + /// Returns an iterator over the usable frames specified in the memory map. + fn usable_frames(&self) -> impl Iterator { + // get usable regions from memory map + let regions = self.memory_map.iter(); + let usable_regions = regions + .filter(|r| r.region_type == MemoryRegionType::Usable); + // map each region to its address range + let addr_ranges = usable_regions + .map(|r| r.range.start_addr()..r.range.end_addr()); + // transform to an iterator of frame start addresses + let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096)); + // create `PhysFrame` types from the start addresses + frame_addresses.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr))) + } +} + +unsafe impl FrameAllocator for BootInfoFrameAllocator { + fn allocate_frame(&mut self) -> Option { + let frame = self.usable_frames().nth(self.next); + self.next += 1; + frame + } +} \ No newline at end of file diff --git a/lupos/src/serial.rs b/lupos/src/serial.rs new file mode 100644 index 0000000..0605e55 --- /dev/null +++ b/lupos/src/serial.rs @@ -0,0 +1,41 @@ +use uart_16550::SerialPort; +use spin::Mutex; +use lazy_static::lazy_static; + +lazy_static! { + pub static ref SERIAL1: Mutex = { + let mut serial_port = unsafe { SerialPort::new(0x3F8) }; + serial_port.init(); + Mutex::new(serial_port) + }; +} + +#[doc(hidden)] +pub fn _print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; // new + + interrupts::without_interrupts(|| { // new + SERIAL1 + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); + }); +} + +/// Prints to the host through the serial interface. +#[macro_export] +macro_rules! serial_print { + ($($arg:tt)*) => { + $crate::serial::_print(format_args!($($arg)*)); + }; +} + +/// Prints to the host through the serial interface, appending a newline. +#[macro_export] +macro_rules! serial_println { + () => ($crate::serial_print!("\n")); + ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); + ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( + concat!($fmt, "\n"), $($arg)*)); +} \ No newline at end of file diff --git a/lupos/src/simple_commands.rs b/lupos/src/simple_commands.rs new file mode 100644 index 0000000..f58ebf8 --- /dev/null +++ b/lupos/src/simple_commands.rs @@ -0,0 +1,12 @@ +use crate::println; +use crate::print; + +pub fn simple_commands_help() { + println!("List of simple commands:"); + println!(" - help: Shows this table"); + println!(" - lupus: Bark Woof"); +} + +pub fn simple_commands_lupus() { + println!("Wooo00oof!"); +} \ No newline at end of file diff --git a/lupos/src/vga_buffer.rs b/lupos/src/vga_buffer.rs new file mode 100644 index 0000000..2aec611 --- /dev/null +++ b/lupos/src/vga_buffer.rs @@ -0,0 +1,190 @@ +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] + +pub enum Color { + Black = 0, + Blue = 1, + Green = 2, + Cyan = 3, + Red = 4, + Magenta = 5, + Brown = 6, + LightGray = 7, + DarkGray = 8, + LightBlue = 9, + LightGreen = 10, + LightCyan = 11, + LightRed = 12, + Pink = 13, + Yellow = 14, + White = 15, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(transparent)] +struct ColorCode(u8); + +impl ColorCode { + fn new(foreground: Color, background: Color) -> ColorCode { + ColorCode((background as u8) << 4 | (foreground as u8)) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +struct ScreenChar { + ascii_character: u8, + color_code: ColorCode, +} + +const BUFFER_HEIGHT: usize = 25; +const BUFFER_WIDTH: usize = 80; + +pub struct Writer { + column_position: usize, + color_code: ColorCode, + buffer: &'static mut Buffer, +} + +impl Writer { + pub fn write_byte(&mut self, byte: u8) { + match byte { + b'\n' => self.new_line(), + byte=> { + if self.column_position >= BUFFER_WIDTH { + self.new_line(); + } + + let row = BUFFER_HEIGHT - 1; + let col = self.column_position; + + let color_code = self.color_code; + self.buffer.chars[row][col].write(ScreenChar { + ascii_character: byte, + color_code, + }); + self.column_position += 1; + } + } + } + + fn new_line(&mut self) { + for row in 1..BUFFER_HEIGHT { + for col in 0..BUFFER_WIDTH { + let character = self.buffer.chars[row][col].read(); + self.buffer.chars[row - 1][col].write(character); + } + } + self.clear_row(BUFFER_HEIGHT - 1); + self.column_position = 0; + } + + fn clear_row(&mut self, row: usize) { + let blank = ScreenChar { + ascii_character: b' ', + color_code: self.color_code, + }; + for col in 0..BUFFER_WIDTH { + self.buffer.chars[row][col].write(blank); + } + } + pub fn write_string(&mut self, s: &str) { + for byte in s.bytes() { + match byte { + // printable ASCII byte or newline + 0x20..=0x7e | b'\n' => self.write_byte(byte), + // not part of printable ASCII range + _ => self.write_byte(0xfe), + } + + } + } + pub fn move_cursor_back(&mut self) { + if self.column_position > 0 { + self.column_position -= 1; + self.write_byte(b' '); // Überschreibt das Zeichen mit einem Leerzeichen + self.column_position -= 1; // Bewegt den Cursor zurück + } + } + +} + +use alloc::borrow::ToOwned; +use volatile::Volatile; + +struct Buffer { + chars: [[Volatile; BUFFER_WIDTH]; BUFFER_HEIGHT], +} + +use core::borrow::{Borrow, BorrowMut}; +use core::fmt; + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.write_string(s); + Ok(()) + } +} + +use spin::Mutex; +use lazy_static::lazy_static; +lazy_static! { + pub static ref WRITER: Mutex = Mutex::new(Writer { + column_position: 0, + color_code: ColorCode::new(Color::Yellow, Color::Black), + buffer: unsafe { &mut *(0xb8000 as *mut Buffer) }, + }); +} + +#[macro_export] +macro_rules! move_cursor_back { + ($($arg:tt)*) => ($crate::vga_buffer::_move_cursor_back()); +} + +#[macro_export] +macro_rules! print { + ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*))); +} + +#[macro_export] +macro_rules! println { + () => ($crate::print!("\n")); + ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*))); +} + +#[doc(hidden)] +pub fn _move_cursor_back() { + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + WRITER.lock().move_cursor_back(); + }); +} + +#[doc(hidden)] +pub fn _print(args: fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; // new + + interrupts::without_interrupts(|| { // new + WRITER.lock().write_fmt(args).unwrap(); + }); +} + +#[test_case] +fn test_println_output() { + use core::fmt::Write; + use x86_64::instructions::interrupts; + + let s = "Some test string that fits on a single line"; + interrupts::without_interrupts(|| { + let mut writer = WRITER.lock(); + writeln!(writer, "\n{}", s).expect("writeln failed"); + for (i, c) in s.chars().enumerate() { + let screen_char = writer.buffer.chars[BUFFER_HEIGHT - 2][i].read(); + assert_eq!(char::from(screen_char.ascii_character), c); + } + }); +} + diff --git a/lupos/target/.rustc_info.json b/lupos/target/.rustc_info.json new file mode 100644 index 0000000..6c405ca --- /dev/null +++ b/lupos/target/.rustc_info.json @@ -0,0 +1 @@ +{"rustc_fingerprint":9721516820068853826,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/kuro/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\nunix\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.88.0 (6b00bc388 2025-06-23)\nbinary: rustc\ncommit-hash: 6b00bc3880198600130e1cf62b8f8a93494488cc\ncommit-date: 2025-06-23\nhost: x86_64-unknown-linux-gnu\nrelease: 1.88.0\nLLVM version: 20.1.5\n","stderr":""},"17176222793216693274":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.a\n/home/kuro/.rustup/toolchains/stable-x86_64-unknown-linux-gnu\noff\n___\ndebug_assertions\npanic=\"abort\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"none\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"unknown\"\n","stderr":"warning: dropping unsupported crate type `dylib` for target `x86_64-lupos-7114822090336562849`\n\nwarning: dropping unsupported crate type `cdylib` for target `x86_64-lupos-7114822090336562849`\n\nwarning: dropping unsupported crate type `proc-macro` for target `x86_64-lupos-7114822090336562849`\n\nwarning: 3 warnings emitted\n\n"}},"successes":{}} \ No newline at end of file diff --git a/lupos/x86_64-lupos.json b/lupos/x86_64-lupos.json new file mode 100644 index 0000000..79cb3e1 --- /dev/null +++ b/lupos/x86_64-lupos.json @@ -0,0 +1,14 @@ +{ + "llvm-target": "x86_64-unknown-none", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128", + "arch": "x86_64", + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "os": "none", + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "panic-strategy": "abort", + "features": "-mmx,+sse" +} \ No newline at end of file