diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b0f97d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Build artifacts +target/ + +# UEFI boot partition (built output) +esp/ + +# QEMU OVMF variable store (written at runtime) +OVMF_VARS*.fd + +# Cargo config is per-machine (custom linker/target settings) +.cargo/ + +# Editor / IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS junk +.DS_Store +Thumbs.db diff --git a/kernel/Cargo.lock b/kernel/Cargo.lock index 45dc146..88df8e8 100644 --- a/kernel/Cargo.lock +++ b/kernel/Cargo.lock @@ -5,3 +5,15 @@ version = 4 [[package]] name = "kernel" version = "0.1.0" +dependencies = [ + "uefi", + "x86_drivers", +] + +[[package]] +name = "uefi" +version = "0.1.0" + +[[package]] +name = "x86_drivers" +version = "0.1.0" diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index c9211be..72ca503 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -4,3 +4,5 @@ version = "0.1.0" edition = "2024" [dependencies] +uefi = {path = "../uefi"} +x86_drivers = {path = "../x86_drivers"} diff --git a/kernel/run.sh b/kernel/run.sh new file mode 100755 index 0000000..338df7e --- /dev/null +++ b/kernel/run.sh @@ -0,0 +1,10 @@ +mkdir -p esp/EFI/BOOT +cargo b --target x86_64-unknown-uefi --release +cp target/x86_64-unknown-uefi/release/kernel.efi esp/EFI/BOOT/BOOTX64.EFI + +qemu-system-x86_64 \ + -drive if=pflash,format=raw,readonly=on,file=/usr/share/edk2/x64/OVMF_CODE.4m.fd \ + -drive if=pflash,format=raw,file=OVMF_VARS.4m.fd \ + -drive format=raw,file=fat:rw:esp \ + -net none \ + -serial stdio diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs new file mode 100644 index 0000000..44df477 --- /dev/null +++ b/kernel/src/lib.rs @@ -0,0 +1,64 @@ +#![no_std] +#![deny(clippy::perf, clippy::all, clippy::pedantic)] + +use core::{ + mem::MaybeUninit, + sync::atomic::{AtomicBool, Ordering}, +}; + +use uefi::EfiSystemTable; +pub use x86_drivers::prelude::*; +use x86_drivers::uart::Uart1; + +#[macro_export] +macro_rules! print { + ($($tt:tt)+) => { + #[allow(static_mut_refs)] + 'body: { + use core::fmt::Write as _; + if let Some(uart) = $crate::uart_is_init() { + let _ = core::write!(uart, $($tt)*); + break 'body; + } + let table = unsafe { $crate::TABLE.assume_init().as_mut().unwrap_unchecked() }; + let _ = core::write!(table.std_out(), $($tt)*); + } + }; +} + +/// # Panics +/// Panics if the UART `MaybeUninit` pointer is null (should never happen). +#[allow(static_mut_refs)] +pub fn uart_is_init() -> Option<&'static mut Uart> { + unsafe { + UART.0 + .load(Ordering::Relaxed) + .then_some(UART.1.as_mut_ptr().as_mut().expect("uart ptr is non-null")) + } +} + +pub static mut TABLE: MaybeUninit<*mut EfiSystemTable> = MaybeUninit::uninit(); +static mut UART: (AtomicBool, MaybeUninit>) = + (AtomicBool::new(false), MaybeUninit::uninit()); + +#[repr(align(8))] +pub struct Aligned(pub T); + +#[used] +pub static mut MEMORY_MAP_ARENA: Aligned<[u8; 32 * 1_024]> = Aligned([0; _]); + +/// # Panics +/// Panics if UART has already been specified. +#[allow(static_mut_refs)] +pub fn specify_uart(uart: Uart) { + let is_specified = unsafe { UART.0.load(Ordering::Acquire) }; + assert!(!is_specified, "Uart already specified"); + unsafe { UART.1.write(uart) }; + unsafe { UART.0.store(true, Ordering::Release) }; +} + +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + print!("PANIC: {:?}\r\n", info); + loop {} +} diff --git a/kernel/src/main.rs b/kernel/src/main.rs index e7a11a9..f7aae19 100644 --- a/kernel/src/main.rs +++ b/kernel/src/main.rs @@ -1,3 +1,109 @@ -fn main() { - println!("Hello, world!"); +#![no_std] +#![no_main] +#![deny(clippy::perf, clippy::all, clippy::pedantic)] + +use uefi::{EfiMemoryType, EfiSystemTable, ExitBootServicesKey, Handle, UefiStatus}; + +use kernel::print; +use x86_drivers::gdt::{DPL, GdtEntry, GdtPointer, GdtSelector}; +pub use x86_drivers::prelude::*; + +#[unsafe(no_mangle)] +extern "efiapi" fn efi_main(handle: Handle, table: *mut EfiSystemTable) -> UefiStatus { + let peripherals = x86_drivers::core::Core::take(); + + #[allow(static_mut_refs)] + unsafe { + kernel::TABLE.write(table) + }; + let table = unsafe { table.as_mut().expect("efi table ptr is non-null") }; + let res = table.std_out().print("Hello world\r\n"); + if let Err(Some(e)) = res { + return e; + } + + kernel::specify_uart(peripherals.uart1.init()); + + print!("Hello from uart!\r\n"); + + #[allow(static_mut_refs)] + let arena = unsafe { &mut kernel::MEMORY_MAP_ARENA.0 }; + let (_key, map, _version) = table + .boot_services() + .memory_map(arena) + .expect("memory map failed"); + for el in map + .filter_map(|segment| segment.memory_type.interpret().map(|f| (f, segment))) + .filter(|(t, _segment)| *t == EfiMemoryType::ConventionalMemory) + { + print!("Map element {:?}\r\n", el); + } + + let default = ExitBootServicesKey::default(); + print!("Default {:?}", default); + + let boot_services = table.boot_services(); + boot_services + .exit_boot_services(handle, arena) + .expect("exit boot services failed"); + print!("Exited boot services!\r\n"); + + print!("kernel_code: {:#018x}\r\n", GdtEntry::kernel_code().raw()); + print!("kernel_data: {:#018x}\r\n", GdtEntry::kernel_data().raw()); + + print!( + "Initiating GDT to {:?}, CODE_SEL: {:?}, DATA SEL: {:?}", + GDT_TABLE, GDT_KERNEL_CODE_SELECTOR, GDT_KERNEL_DATA_SELECTOR + ); + gdt_init(); + print!("Initiated GDT"); + #[allow(clippy::empty_loop)] + loop {} +} + +static GDT_TABLE: [GdtEntry; 3] = [ + GdtEntry::null(), + GdtEntry::kernel_code(), + GdtEntry::kernel_data(), +]; + +static GDT_KERNEL_CODE_SELECTOR: GdtSelector = GdtSelector::new(DPL::Kernel, &GDT_TABLE, 1); +static GDT_KERNEL_DATA_SELECTOR: GdtSelector = GdtSelector::new(DPL::Kernel, &GDT_TABLE, 2); + +fn gdt_init() { + let ptr = GdtPointer::new(&GDT_TABLE); + print!("GDTR: {:?}\r\n", ptr,); + print!("GDT_TABLE addr: {:#x}\r\n", &GDT_TABLE as *const _ as u64); + unsafe { + core::arch::asm!( + "lgdt [{}]", + in(reg) &ptr, + options(readonly, nostack, preserves_flags) + ); + } + print!("Passed lgdt"); + unsafe { + core::arch::asm!( + "push {code_sel}", + "lea {tmp}, [rip + 2f]", + "push {tmp}", + "retfq", + "2:", + code_sel = const GDT_KERNEL_CODE_SELECTOR.raw() as u64, + tmp = out(reg) _, + ); + } + print!("CS reloaded\r\n"); + + unsafe { + core::arch::asm!( + "mov ds, {data_sel:x}", + "mov es, {data_sel:x}", + "mov fs, {data_sel:x}", + "mov gs, {data_sel:x}", + "mov ss, {data_sel:x}", + data_sel = in(reg) GDT_KERNEL_DATA_SELECTOR.raw(), + ); + } + print!("data segments reloaded\r\n"); } diff --git a/uefi/Cargo.lock b/uefi/Cargo.lock new file mode 100644 index 0000000..a4bbd12 --- /dev/null +++ b/uefi/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "uefi" +version = "0.1.0" diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml new file mode 100644 index 0000000..f83a715 --- /dev/null +++ b/uefi/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "uefi" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/uefi/libstatus.rlib b/uefi/libstatus.rlib new file mode 100644 index 0000000..e37659d Binary files /dev/null and b/uefi/libstatus.rlib differ diff --git a/uefi/src/lib.rs b/uefi/src/lib.rs new file mode 100644 index 0000000..05c16c3 --- /dev/null +++ b/uefi/src/lib.rs @@ -0,0 +1,317 @@ +#![no_std] + +mod memory; +mod status; +pub use memory::EfiMemoryType; +pub use status::UefiStatus; + +use crate::{memory::EfiMemoryDescriptor, status::RawUefiStatus}; + +#[repr(C)] +pub struct EfiTableHeader { + signature: u64, + revision: u32, + header_size: u32, + crc32: u32, + reserved: u32, +} + +#[repr(C)] +pub struct EfiSystemTable { + efi_table_header: EfiTableHeader, + firmware_vendor: *const u16, + firmware_revision: u32, + console_in_handle: Handle, + // TODO: Handle input. + efi_simple_text_input_protocol: *mut core::ffi::c_void, + console_out_handle: Handle, + console_out: *mut SimpleTextOutputProtocol, + standard_error_handle: Handle, + standard_error: *mut SimpleTextOutputProtocol, + runtime_services: *mut core::ffi::c_void, + boot_services: *mut EfiBootServices, + number_of_table_entries: usize, + configuration_table: *mut core::ffi::c_void, +} + +#[repr(C)] +pub struct SimpleTextOutputProtocol { + reset: extern "efiapi" fn(this: *mut Self, extended_verification: bool) -> RawUefiStatus, + output_string: extern "efiapi" fn(this: *mut Self, str: *const u16) -> RawUefiStatus, + test_string: extern "efiapi" fn(this: *mut Self, str: *const u16) -> RawUefiStatus, + query_mode: extern "efiapi" fn( + this: *mut Self, + mode_number: usize, + n_columns: *mut usize, + n_rows: *mut usize, + ) -> RawUefiStatus, + set_mode: extern "efiapi" fn(this: *mut Self, mode_number: usize) -> RawUefiStatus, + set_atribute: extern "efiapi" fn(this: *mut Self, attribute: usize) -> RawUefiStatus, + clear_screen: extern "efiapi" fn(this: *mut Self) -> RawUefiStatus, + set_cursor_position: + extern "efiapi" fn(this: *mut Self, column: usize, row: usize) -> RawUefiStatus, + enable_cursor: extern "efiapi" fn(this: *mut Self, visible: bool) -> RawUefiStatus, + mode: *mut SimpleTextOutputMode, +} + +#[repr(C)] +pub struct SimpleTextOutputMode { + pub max_mode: i32, + pub mode: i32, + pub attribute: i32, + pub cursor_column: i32, + pub cursor_row: i32, + pub cursor_visible: bool, +} + +#[repr(transparent)] +#[derive(Clone, Copy)] +/// Identifier used by UEFI. +pub struct Handle(*mut ()); + +#[repr(transparent)] +#[derive(Debug)] +pub struct ExitBootServicesKey(usize); + +#[repr(C)] +pub struct EfiBootServices { + hdr: EfiTableHeader, + // + // Task Priority Services + // + raise_tpl: *const core::ffi::c_void, + restore_tpl: *const core::ffi::c_void, + // + // Memory Services + // + allocate_pages: *const core::ffi::c_void, + free_pages: *const core::ffi::c_void, + get_memory_map: extern "efiapi" fn( + memory_map_size: *mut usize, // Passed by us as core::mem::size_of(descriptors) returned as size in bytes of + // allocated descriptors. + descriptors: *mut u8, + key: *mut ExitBootServicesKey, // Key used to exit bootloader + descriptor_size: *mut usize, // This ought to be + descriptor_version: *mut u32, + ) -> RawUefiStatus, + allocate_pool: *const core::ffi::c_void, // EFI 1.0 + free_pool: *const core::ffi::c_void, + // + // Event & Timer Services + // + create_event: *const core::ffi::c_void, + set_timer: *const core::ffi::c_void, + wait_for_event: *const core::ffi::c_void, + signal_event: *const core::ffi::c_void, + close_event: *const core::ffi::c_void, + check_event: *const core::ffi::c_void, + // + // Protocol Handler Services + // + install_protocol_interface: *const core::ffi::c_void, + reinstall_protocol_interface: *const core::ffi::c_void, + uninstall_protocol_interface: *const core::ffi::c_void, + handle_protocol: *const core::ffi::c_void, + reserved: *const core::ffi::c_void, + register_protocol_notify: *const core::ffi::c_void, + locate_handle: *const core::ffi::c_void, + locate_device_path: *const core::ffi::c_void, + install_configuration_table: *const core::ffi::c_void, + // + // Image Services + // + load_image: *const core::ffi::c_void, + start_image: *const core::ffi::c_void, + exit: *const core::ffi::c_void, + unload_image: *const core::ffi::c_void, + exit_boot_services: + extern "efiapi" fn(efi_handle: Handle, key: ExitBootServicesKey) -> RawUefiStatus, + // + // Miscellaneous Services + // + get_next_monotonic_count: *const core::ffi::c_void, + stall: *const core::ffi::c_void, + set_watchdog_timer: *const core::ffi::c_void, + // + // DriverSupport Services + // + connect_controller: *const core::ffi::c_void, + disconnect_controller: *const core::ffi::c_void, + // + // Open and Close Protocol Services + // + open_protocol: *const core::ffi::c_void, + close_protocol: *const core::ffi::c_void, + open_protocol_information: *const core::ffi::c_void, + // + // Library Services + // + protocols_per_handle: *const core::ffi::c_void, + locate_handle_buffer: *const core::ffi::c_void, + locate_protocol: *const core::ffi::c_void, + install_multiple_protocol_interfaces: *const core::ffi::c_void, + uninstall_multiple_protocol_interfaces: *const core::ffi::c_void, + // + // 32-bit CRC Services + // + calculate_crc32: *const core::ffi::c_void, + // + // Miscellaneous Services + // + copy_mem: *const core::ffi::c_void, + set_mem: *const core::ffi::c_void, + create_event_ex: *const core::ffi::c_void, // UEFI 2.0+ +} + +impl SimpleTextOutputProtocol { + pub fn print(&mut self, text: &str) -> Result<(), Option> { + let f = self.output_string; + + let data = text.encode_utf16(); + let mut buffer = [0; 1024]; + buffer + .iter_mut() + .zip(data) + .for_each(|(target, val)| *target = val); + + if buffer[buffer.len() - 1] != 0 { + return Err(Some(UefiStatus::BufferTooSmall)); + } + + let Some(val) = f(core::ptr::from_mut(self), buffer.as_ptr()).interpret() else { + return Err(None); + }; + + if val.is_success() { + return Ok(()); + } + + Err(Some(val)) + } +} + +impl ExitBootServicesKey { + pub const fn default() -> Self { + Self(0) + } +} + +pub struct PageIter<'a> { + buffer: &'a [u8], + size: usize, + offset: usize, +} + +impl<'a> PageIter<'a> { + const fn new(buffer: &'a [u8], size: usize) -> Self { + Self { + buffer, + size, + offset: 0, + } + } +} + +impl<'a> Iterator for PageIter<'a> { + type Item = &'a EfiMemoryDescriptor; + + fn next(&mut self) -> Option { + if (self.offset + core::mem::size_of::()) >= self.buffer.len() { + return None; + } + let start = self.offset; + self.offset += self.size; + #[allow(clippy::cast_possible_wrap)] + Some(unsafe { + self.buffer + .as_ptr() + .add(start) + .cast::() + .as_ref() + .unwrap() + }) + } +} + +impl EfiBootServices { + pub fn memory_map<'a, 'b>( + &mut self, + arena: &'a mut [u8], + ) -> Result<(ExitBootServicesKey, PageIter<'b>, u32), Option> + where + 'a: 'b, + { + let mut key = ExitBootServicesKey::default(); + let mut size = 0; + let mut version = 0; + let mut buffer_size = core::mem::size_of_val(arena); + + let code = (self.get_memory_map)( + core::ptr::from_mut(&mut buffer_size), + arena.as_mut_ptr(), + core::ptr::from_mut(&mut key), + core::ptr::from_mut(&mut size), + core::ptr::from_mut(&mut version), + ); + + let Some(code) = code.interpret() else { + return Err(None); + }; + + if !code.is_success() { + return Err(Some(code)); + } + + assert!( + size >= core::mem::size_of::(), + "size {}", + size + ); + + Ok((key, PageIter::new(&arena[..buffer_size], size), version)) + } + + pub fn exit_boot_services( + &mut self, + handle: Handle, + arena: &mut [u8], + ) -> Result<(), Option> { + let (key, ..) = self.memory_map(arena)?; + + let Some(res) = (self.exit_boot_services)(handle, key).interpret() else { + return Err(None); + }; + + if res.is_success() { + return Ok(()); + } + Err(Some(res)) + } +} + +impl EfiSystemTable { + pub const fn std_out(&mut self) -> &mut SimpleTextOutputProtocol { + unsafe { self.console_out.as_mut().unwrap() } + } + + pub const fn std_err(&mut self) -> &mut SimpleTextOutputProtocol { + unsafe { self.standard_error.as_mut().unwrap() } + } + + pub const fn boot_services(&mut self) -> &mut EfiBootServices { + unsafe { self.boot_services.as_mut().unwrap() } + } +} + +impl core::fmt::Write for SimpleTextOutputProtocol { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let _ = self.print(s); + Ok(()) + } +} + +impl Default for ExitBootServicesKey { + fn default() -> Self { + Self::default() + } +} diff --git a/uefi/src/memory.rs b/uefi/src/memory.rs new file mode 100644 index 0000000..0c3b87f --- /dev/null +++ b/uefi/src/memory.rs @@ -0,0 +1,99 @@ +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(u32)] +pub enum EfiMemoryType { + ReservedMemoryType = 0, + LoaderCode = 1, + LoaderData = 2, + BootServicesCode = 3, + BootServicesData = 4, + RuntimeServicesCode = 5, + RuntimeServicesData = 6, + ConventionalMemory = 7, + UnusableMemory = 8, + AcpiReclaimMemory = 9, + AcpiMemoryNvs = 10, + MemoryMappedIo = 11, + MemoryMappedIoPortSpace = 12, + PalCode = 13, + PersistentMemory = 14, + UnacceptedMemoryType = 15, +} + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct RawMemoryType(u32); + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EfiPhysicalAddress(u64); +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EfiVirtualAddress(u64); + +#[repr(C)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct EfiMemoryDescriptor { + pub memory_type: RawMemoryType, + pub physical_start: EfiPhysicalAddress, + pub virtual_start: EfiVirtualAddress, + pub number_of_pages: u64, + pub attribute: u64, +} + +impl RawMemoryType { + #[inline(always)] + pub const fn interpret(&self) -> Option { + EfiMemoryType::try_from_raw(self) + } +} + +impl EfiMemoryType { + #[inline(always)] + pub const fn try_from_raw(raw: &RawMemoryType) -> Option { + Self::from_u32(raw.0) + } + + #[must_use] + #[inline(always)] + pub const fn as_u32(self) -> u32 { + self as u32 + } + + #[must_use] + #[inline(always)] + pub const fn from_u32(value: u32) -> Option { + Some(match value { + 0 => Self::ReservedMemoryType, + 1 => Self::LoaderCode, + 2 => Self::LoaderData, + 3 => Self::BootServicesCode, + 4 => Self::BootServicesData, + 5 => Self::RuntimeServicesCode, + 6 => Self::RuntimeServicesData, + 7 => Self::ConventionalMemory, + 8 => Self::UnusableMemory, + 9 => Self::AcpiReclaimMemory, + 10 => Self::AcpiMemoryNvs, + 11 => Self::MemoryMappedIo, + 12 => Self::MemoryMappedIoPortSpace, + 13 => Self::PalCode, + 14 => Self::PersistentMemory, + 15 => Self::UnacceptedMemoryType, + _ => return None, + }) + } +} + +impl From for u32 { + fn from(memory_type: EfiMemoryType) -> Self { + memory_type.as_u32() + } +} + +impl TryFrom for EfiMemoryType { + type Error = u32; + + fn try_from(value: u32) -> Result { + Self::from_u32(value).ok_or(value) + } +} diff --git a/uefi/src/status.rs b/uefi/src/status.rs new file mode 100644 index 0000000..c8fc671 --- /dev/null +++ b/uefi/src/status.rs @@ -0,0 +1,221 @@ +#![allow( + clippy::module_name_repetitions, + reason = "UefiStatus is the canonical UEFI name" +)] + +/// High bit mask — set on all error codes, clear on warnings and success. +const ERROR: usize = 1 << (usize::BITS - 1); + +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct RawUefiStatus(usize); + +/// UEFI status code (UEFI spec Appendix D). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[repr(usize)] +pub enum UefiStatus { + // ── Success (Table D.2) ────────────────────────────────────────── + /// The operation completed successfully. + Success = 0, + + // ── Warnings (Table D.4, high bit clear) ───────────────────────── + /// String contained characters the device could not render. + WarnUnknownGlyph = 1, + /// Handle closed, but file was not deleted. + WarnDeleteFailure = 2, + /// Handle closed, but data was not flushed properly. + WarnWriteFailure = 3, + /// Buffer was too small; data was truncated. + WarnBufferTooSmall = 4, + /// Data has not been updated within local policy timeframe. + WarnStaleData = 5, + /// Buffer contains UEFI-compliant file system. + WarnFileSystem = 6, + /// Operation will be processed across a system reset. + WarnResetRequired = 7, + + // ── Errors (Table D.3, high bit set) ───────────────────────────── + /// Image failed to load. + LoadError = ERROR | 1, + /// A parameter was incorrect. + InvalidParameter = ERROR | 2, + /// Operation is not supported. + Unsupported = ERROR | 3, + /// Buffer was not the proper size for the request. + BadBufferSize = ERROR | 4, + /// Buffer is not large enough to hold the requested data. + BufferTooSmall = ERROR | 5, + /// No data pending upon return. + NotReady = ERROR | 6, + /// Physical device reported an error. + DeviceError = ERROR | 7, + /// Device cannot be written to. + WriteProtected = ERROR | 8, + /// A resource has run out. + OutOfResources = ERROR | 9, + /// File system inconsistency detected. + VolumeCorrupted = ERROR | 10, + /// No more space on the file system. + VolumeFull = ERROR | 11, + /// Device does not contain any medium. + NoMedia = ERROR | 12, + /// Medium in the device has changed since last access. + MediaChanged = ERROR | 13, + /// Item was not found. + NotFound = ERROR | 14, + /// Access was denied. + AccessDenied = ERROR | 15, + /// Server was not found or did not respond. + NoResponse = ERROR | 16, + /// Mapping to a device does not exist. + NoMapping = ERROR | 17, + /// Timeout expired. + Timeout = ERROR | 18, + /// Protocol has not been started. + NotStarted = ERROR | 19, + /// Protocol has already been started. + AlreadyStarted = ERROR | 20, + /// Operation was aborted. + Aborted = ERROR | 21, + /// ICMP error occurred during network operation. + IcmpError = ERROR | 22, + /// TFTP error occurred during network operation. + TftpError = ERROR | 23, + /// Protocol error occurred during network operation. + ProtocolError = ERROR | 24, + /// Internal version incompatible with caller's requested version. + IncompatibleVersion = ERROR | 25, + /// Operation not performed due to a security violation. + SecurityViolation = ERROR | 26, + /// CRC error detected. + CrcError = ERROR | 27, + /// Beginning or end of media was reached. + EndOfMedia = ERROR | 28, + /// End of file was reached. + EndOfFile = ERROR | 31, + /// Specified language was invalid. + InvalidLanguage = ERROR | 32, + /// Security status of data is unknown or compromised. + CompromisedData = ERROR | 33, + /// Address conflict during address allocation. + IpAddressConflict = ERROR | 34, + /// HTTP error occurred during network operation. + HttpError = ERROR | 35, +} + +impl RawUefiStatus { + pub const fn interpret(&self) -> Option { + UefiStatus::try_from_raw(self) + } +} + +impl UefiStatus { + /// Tries to convert from a raw uefi status. + pub const fn try_from_raw(raw: &RawUefiStatus) -> Option { + Self::from_usize(raw.0) + } + + /// Returns `true` if this status represents an error (high bit set). + #[must_use] + pub const fn is_error(self) -> bool { + self.as_usize() & ERROR != 0 + } + + /// Returns `true` if this status represents a warning (non-zero, high bit clear). + #[must_use] + pub const fn is_warning(self) -> bool { + let v = self.as_usize(); + v != 0 && v & ERROR == 0 + } + + /// Returns `true` if this status is `Success`. + #[must_use] + pub const fn is_success(self) -> bool { + self.as_usize() == 0 + } + + /// Convert to the raw `usize` representation. + #[must_use] + pub const fn as_usize(self) -> usize { + self as usize + } + + /// Convert from a raw `usize`. Returns `None` for unrecognised codes. + #[must_use] + pub const fn from_usize(value: usize) -> Option { + let code = value & !ERROR; + if value & ERROR != 0 { + Self::from_error_code(code) + } else { + Self::from_warning_code(code) + } + } + + const fn from_warning_code(code: usize) -> Option { + Some(match code { + 0 => Self::Success, + 1 => Self::WarnUnknownGlyph, + 2 => Self::WarnDeleteFailure, + 3 => Self::WarnWriteFailure, + 4 => Self::WarnBufferTooSmall, + 5 => Self::WarnStaleData, + 6 => Self::WarnFileSystem, + 7 => Self::WarnResetRequired, + _ => return None, + }) + } + + const fn from_error_code(code: usize) -> Option { + Some(match code { + 1 => Self::LoadError, + 2 => Self::InvalidParameter, + 3 => Self::Unsupported, + 4 => Self::BadBufferSize, + 5 => Self::BufferTooSmall, + 6 => Self::NotReady, + 7 => Self::DeviceError, + 8 => Self::WriteProtected, + 9 => Self::OutOfResources, + 10 => Self::VolumeCorrupted, + 11 => Self::VolumeFull, + 12 => Self::NoMedia, + 13 => Self::MediaChanged, + 14 => Self::NotFound, + 15 => Self::AccessDenied, + 16 => Self::NoResponse, + 17 => Self::NoMapping, + 18 => Self::Timeout, + 19 => Self::NotStarted, + 20 => Self::AlreadyStarted, + 21 => Self::Aborted, + 22 => Self::IcmpError, + 23 => Self::TftpError, + 24 => Self::ProtocolError, + 25 => Self::IncompatibleVersion, + 26 => Self::SecurityViolation, + 27 => Self::CrcError, + 28 => Self::EndOfMedia, + 31 => Self::EndOfFile, + 32 => Self::InvalidLanguage, + 33 => Self::CompromisedData, + 34 => Self::IpAddressConflict, + 35 => Self::HttpError, + _ => return None, + }) + } +} + +impl From for usize { + fn from(status: UefiStatus) -> Self { + status.as_usize() + } +} + +impl TryFrom for UefiStatus { + type Error = usize; + + /// Returns `Err(raw_value)` if the code is not recognised. + fn try_from(value: usize) -> Result { + Self::from_usize(value).ok_or(value) + } +} diff --git a/x86_drivers/Cargo.lock b/x86_drivers/Cargo.lock new file mode 100644 index 0000000..ac428af --- /dev/null +++ b/x86_drivers/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "x86_drivers" +version = "0.1.0" diff --git a/x86_drivers/Cargo.toml b/x86_drivers/Cargo.toml new file mode 100644 index 0000000..a399abf --- /dev/null +++ b/x86_drivers/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "x86_drivers" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/x86_drivers/src/core.rs b/x86_drivers/src/core.rs new file mode 100644 index 0000000..6a84bf7 --- /dev/null +++ b/x86_drivers/src/core.rs @@ -0,0 +1,37 @@ +use core::sync::atomic::{AtomicBool, Ordering}; + +use crate::uart::{Uart1, Uart2, Uart3, Uart4}; + +static TAKEN: AtomicBool = AtomicBool::new(false); + +#[must_use] +pub struct Core { + pub uart1: Uart1, + pub uart2: Uart2, + pub uart3: Uart3, + pub uart4: Uart4, +} + +impl Core { + const fn new() -> Self { + Self { + uart1: Uart1::new(), + uart2: Uart2::new(), + uart3: Uart3::new(), + uart4: Uart4::new(), + } + } + + /// Returns all of the peripherals this core has. + /// + /// # Panics + /// + /// If this has already been called it panics. + pub fn take() -> Self { + assert!( + !TAKEN.swap(true, Ordering::SeqCst), + "Core already initialized" + ); + Self::new() + } +} diff --git a/x86_drivers/src/gdt.rs b/x86_drivers/src/gdt.rs new file mode 100644 index 0000000..19f4718 --- /dev/null +++ b/x86_drivers/src/gdt.rs @@ -0,0 +1,198 @@ +#[repr(transparent)] +#[must_use] +#[derive(Debug)] +pub struct GdtEntry { + inner: u64, +} + +#[allow(unused)] +impl GdtEntry { + const LIMIT_L: u64 = 0; + const LIMIT_H: u64 = 15; + const BASE_L: u64 = 16; + const BASE_H: u64 = 39; + const ACCESS_L: u64 = 40; + const ACCESS_H: u64 = 47; + const FLAGS_L: u64 = 52; + const FLAGS_H: u64 = 55; + + // NOTE: Skipping higher bits since they are zeroed. + + #[inline(always)] + #[allow(clippy::cast_possible_truncation)] + #[must_use] + pub const fn access_code(&self) -> u8 { + self.inner as u8 + } + + #[inline(always)] + pub const fn new(access_code: AccessFlags, flags: Flags) -> Self { + let mut rpr = 0u64; + rpr |= (access_code.to_bits() as u64) << Self::ACCESS_L; + rpr |= (flags.to_bits() as u64) << Self::FLAGS_L; + Self { inner: rpr } + } + + pub const fn raw(&self) -> u64 { + self.inner + } + + pub const fn null() -> Self { + Self { inner: 0 } + } + + pub const fn user_code() -> Self { + Self::new( + AccessFlags { + ring: DPL::User, + data_kind: DataKind::ReadableCodeContained, + }, + Flags::new(true), + ) + } + + pub const fn kernel_code() -> Self { + Self::new( + AccessFlags { + ring: DPL::Kernel, + data_kind: DataKind::ReadableCodeContained, + }, + Flags::new(true), + ) + } + + pub const fn kernel_data() -> Self { + Self::new( + AccessFlags { + ring: DPL::Kernel, + data_kind: DataKind::WriteableDataIncreasing, + }, + Flags::new(false), + ) + } + + pub const fn user_data() -> Self { + Self::new( + AccessFlags { + ring: DPL::User, + data_kind: DataKind::WriteableDataIncreasing, + }, + Flags::new(false), + ) + } +} + +#[repr(u8)] +#[must_use] +#[derive(Debug)] +pub enum DPL { + Kernel = 0, + Ring1 = 1, + Ring2 = 2, + User = 3, +} + +#[repr(u8)] +#[derive(Debug)] +pub enum DataKind { + // Means descriptor == 0, executable = 0, direction = dc, pull to 0 for good measure. + System = 0b000, + // Desc == 1, exec = 0, direction = 1, W = 0 + ProtectedDataDecreasing = 0b1010, + // Desc == 1, exec = 0, direction = 1, W = 1 + WriteableDataDecreasing = 0b1011, + // Desc == 1, exec = 0, direction = 0, W = 0 + ProtectedDataIncreasing = 0b1000, + // Desc == 1, exec = 0, direction = 0, W = 1 + WriteableDataIncreasing = 0b1001, + // Desc == 1, exec = 1, direction = 0, R = 0 + // + // This can only be reached at the appropriated DPL. + ProtectedCodeContained = 0b1100, + // Desc == 1, exec = 1, direction = 0, R = 1 + // + // This can only be reached at the appropriated DPL. + ReadableCodeContained = 0b1101, + // Desc == 1, exec = 1, direction = 1, R = 0 + // + // This can be called from lower priorities, think syscalls. But requires long jumps. + ProtectedCodeExposed = 0b1110, + // Desc == 1, exec = 1, direction = 1, R = 1 + // + // This can be called from lower priorities, think syscalls. But requires long jumps. + ReadableCodeExposed = 0b1111, +} + +#[derive(Debug)] +pub struct AccessFlags { + ring: DPL, + data_kind: DataKind, +} + +impl AccessFlags { + pub const fn new(ring: DPL, data_kind: DataKind) -> Self { + Self { ring, data_kind } + } + + #[must_use] + const fn to_bits(self) -> u8 { + let mut ret = 0b1000_0001; + ret |= (self.ring as u8) << 5; + ret |= (self.data_kind as u8) << 1; + ret + } +} + +// TODO: Add remaining flags. +pub struct Flags { + long_mode: bool, +} + +impl Flags { + pub const fn new(long_mode: bool) -> Self { + Self { long_mode } + } + + const fn to_bits(&self) -> u8 { + 0b0 | ((self.long_mode as u8) << 1) + } +} + +#[repr(transparent)] +#[derive(Debug)] +pub struct GdtSelector { + inner: u16, +} + +impl GdtSelector { + /// Returns a new gdt selector. + /// + /// # Panics + /// + /// This function panics if the index is out of bounds. + pub const fn new(priviledge_level: DPL, table: &[GdtEntry], idx: u16) -> Self { + let _ = table[idx as usize]; // Rust provided length check. + Self { + inner: (idx << 3) | priviledge_level as u16, + } + } + pub const fn raw(&self) -> u16 { + self.inner + } +} + +#[repr(C, packed)] +#[derive(Debug)] +pub struct GdtPointer { + limit: u16, + base: u64, +} + +impl GdtPointer { + pub fn new(table: &[GdtEntry]) -> Self { + Self { + limit: core::mem::size_of_val(table) as u16 - 1, + base: table.as_ptr() as u64, + } + } +} diff --git a/x86_drivers/src/idt.rs b/x86_drivers/src/idt.rs new file mode 100644 index 0000000..29b77ee --- /dev/null +++ b/x86_drivers/src/idt.rs @@ -0,0 +1,56 @@ +use crate::gdt::{DPL, GdtSelector}; + +#[repr(C, packed)] +pub struct InterruptTableDescriptor { + size: u16, + offset: u64, +} + +#[repr(transparent)] +pub struct IdtTableEntry { + inner: u128, +} + +pub type ISR = extern "x86-interrupt" fn(InterruptStackFrame) -> (); + +#[repr(C)] +pub struct InterruptStackFrame { + ip: usize, + cs: usize, + flags: usize, + sp: usize, + ss: usize, +} + +#[repr(u8)] +pub enum Gate { + Interrupt = 0b1110, + Trap = 0b1111, +} + +impl IdtTableEntry { + pub fn new(isr: ISR, selector: GdtSelector, ist: u8, gate: Gate, priority: DPL) -> Self { + let mut inner = 0b0; + let offset = (isr as *const ()) as u64; + + let bytes = (offset >> 32) as u32; + inner |= (bytes as u128) << 64; + + let bytes = (offset as u32) >> 16; + inner |= (bytes as u128) << 48; + + let bytes = offset as u16; + inner |= bytes as u128; + + inner |= (selector.raw() as u128) << 16; + + assert!(ist <= 0b111); + + inner |= (ist as u128) << 32; + inner |= (gate as u128) << 40; + inner |= (priority as u128) << 45; + inner |= 1 << 47; + + Self { inner } + } +} diff --git a/x86_drivers/src/lib.rs b/x86_drivers/src/lib.rs new file mode 100644 index 0000000..259cc58 --- /dev/null +++ b/x86_drivers/src/lib.rs @@ -0,0 +1,42 @@ +#![no_std] +#![feature(abi_x86_interrupt)] +#![deny( + clippy::all, + clippy::perf, + clippy::pedantic, + clippy::nursery, + clippy::complexity, + clippy::correctness, + clippy::style, + clippy::suspicious, + clippy::undocumented_unsafe_blocks, + rustdoc::all, + rust_2018_idioms, + warnings +)] + +#[allow( + missing_docs, + clippy::inline_always, + clippy::missing_safety_doc, + clippy::undocumented_unsafe_blocks, + clippy::unused_self, + clippy::must_use_candidate, + clippy::new_without_default, + clippy::cast_possible_truncation +)] +pub mod uart; + +pub mod gdt; +pub mod idt; + +pub mod core; + +mod private { + pub trait Sealed {} +} + +pub mod prelude { + pub use crate::core::Core; + pub use crate::uart::{InitUart, Uart}; +} diff --git a/x86_drivers/src/uart/mod.rs b/x86_drivers/src/uart/mod.rs new file mode 100644 index 0000000..f335461 --- /dev/null +++ b/x86_drivers/src/uart/mod.rs @@ -0,0 +1,234 @@ +mod pac; + +use core::{fmt, marker::PhantomData}; + +use pac::{ + COM1_BASE, DLH, DLL, FCR, IER, LCR, LSR, LineControlRegister, MCR, P_CLK_FREQ, RBR, THR, + WordLength, inb, outb, +}; + +use crate::uart::pac::{COM2_BASE, COM3_BASE, COM4_BASE, LineStatus}; + +// TODO: wrap all of these in a single core critical section. +pub trait HwUart: crate::private::Sealed { + const BASE: u16; + + #[inline(always)] + fn tx_byte(val: u8) { + unsafe { outb(Self::BASE + THR, val) }; + } + + #[inline(always)] + fn line_status() -> LineStatus { + unsafe { + LineStatus { + bits: inb(Self::BASE + LSR), + } + } + } + + #[inline(always)] + #[allow(unused)] + fn rx_byte() -> u8 { + unsafe { inb(Self::BASE + RBR) } + } + + #[inline(always)] + fn set_baudrate(cfg: &UartConfig) { + let [upper, lower] = cfg.prescaler.to_ne_bytes(); + let mut lcr = Self::read_control_register(); + lcr.enable_baud_config(); + Self::write_control_register(lcr.bits()); + unsafe { outb(Self::BASE + DLL, lower) }; + unsafe { outb(Self::BASE + DLH, upper) }; + let mut lcr = Self::read_control_register(); + lcr.disable_baud_config(); + Self::write_control_register(lcr.bits()); + } + + #[inline(always)] + fn set_word_length(length: WordLength) { + let mut lcr = Self::read_control_register(); + lcr.set_word_length(length); + Self::write_control_register(lcr.bits()); + } + + #[inline(always)] + fn read_control_register() -> LineControlRegister { + unsafe { + LineControlRegister { + bits: inb(Self::BASE + LCR), + } + } + } + + #[inline(always)] + fn write_control_register(data: u8) { + unsafe { outb(Self::BASE + LCR, data) } + } + + #[inline(always)] + fn disable_interrupts() { + unsafe { outb(Self::BASE + IER, 0) }; + } + + #[inline(always)] + fn clear_fifo_and_modem_setting() { + unsafe { + outb(Self::BASE + FCR, 0); + outb(Self::BASE + MCR, 0); + } + } +} + +pub trait InitUart { + fn init(&self) -> Uart { + Uart::new() + } +} + +#[derive(Default)] +pub struct UartConfig { + prescaler: u16, +} + +#[repr(u32)] +pub enum BaudRate { + B115_200 = 115_200, + B57_600 = 57_600, + B38_400 = 38_400, + B19_200 = 19_200, + B9_600 = 9_600, + B1_200 = 1_200, + B300 = 300, +} + +pub struct Uart1 { + _private: PhantomData<()>, +} +pub struct Uart2 { + _private: PhantomData<()>, +} +pub struct Uart3 { + _private: PhantomData<()>, +} +pub struct Uart4 { + _private: PhantomData<()>, +} + +/// 16550 UART driver. +#[must_use] +pub struct Uart { + _t: PhantomData, +} + +#[allow(unused)] +impl UartConfig { + const fn set_baud(self, target_baud: BaudRate) -> UartConfig { + let pres = P_CLK_FREQ / (16 * target_baud as u32); + UartConfig { + prescaler: pres as u16, + } + } + + #[allow(unused)] + const fn set_baud_raw(self, target_baud: u16) -> Option> { + let pres = P_CLK_FREQ / (16 * target_baud as u32); + let real_baud = P_CLK_FREQ / (pres * 16); + if target_baud != real_baud as u16 { + return None; + } + Some(UartConfig { + prescaler: pres as u16, + }) + } +} + +impl Uart { + fn new() -> Self { + let uart = Self { _t: PhantomData }; + uart.init(); + uart + } + + fn init(&self) { + let config = UartConfig::default().set_baud(BaudRate::B115_200); + M::disable_interrupts(); + M::set_baudrate(&config); + M::set_word_length(WordLength::W8); + M::clear_fifo_and_modem_setting(); + } + + fn write_raw(&self, byte: u8) { + M::tx_byte(byte); + } + + fn spin_write_byte(&self, byte: u8) { + while !M::line_status().tx_ready() {} + self.write_raw(byte); + } +} + +impl crate::private::Sealed for Uart1 {} + +impl HwUart for Uart1 { + const BASE: u16 = COM1_BASE; +} +impl crate::private::Sealed for Uart2 {} + +impl HwUart for Uart2 { + const BASE: u16 = COM2_BASE; +} +impl crate::private::Sealed for Uart3 {} + +impl HwUart for Uart3 { + const BASE: u16 = COM3_BASE; +} +impl crate::private::Sealed for Uart4 {} + +impl HwUart for Uart4 { + const BASE: u16 = COM4_BASE; +} + +impl InitUart for M {} + +impl fmt::Write for Uart { + fn write_str(&mut self, s: &str) -> fmt::Result { + for byte in s.bytes() { + if byte == b'\n' { + self.spin_write_byte(b'\r'); + } + self.spin_write_byte(byte); + } + Ok(()) + } +} + +impl Uart1 { + pub(crate) const fn new() -> Self { + Self { + _private: PhantomData, + } + } +} +impl Uart2 { + pub(crate) const fn new() -> Self { + Self { + _private: PhantomData, + } + } +} +impl Uart3 { + pub(crate) const fn new() -> Self { + Self { + _private: PhantomData, + } + } +} +impl Uart4 { + pub(crate) const fn new() -> Self { + Self { + _private: PhantomData, + } + } +} diff --git a/x86_drivers/src/uart/pac.rs b/x86_drivers/src/uart/pac.rs new file mode 100644 index 0000000..8780f5f --- /dev/null +++ b/x86_drivers/src/uart/pac.rs @@ -0,0 +1,106 @@ +pub const COM1_BASE: u16 = 0x3F8; +pub const COM2_BASE: u16 = 0x2F8; // IRQ 3 +pub const COM3_BASE: u16 = 0x3E8; // IRQ 4 (shared with COM1) +pub const COM4_BASE: u16 = 0x2E8; // IRQ 3 (shared with COM2) + +// Write here to pend a transfer. +pub const THR: u16 = 0; +// Holds a byte if there are any. +pub const RBR: u16 = 0; +pub const DLL: u16 = 0; +pub const DLH: u16 = 1; +pub const IER: u16 = 1; +pub const FCR: u16 = 2; +pub const LCR: u16 = 3; +pub const MCR: u16 = 4; +pub const LSR: u16 = 5; + +pub const P_CLK_FREQ: u32 = 1_843_200; + +#[inline] +pub unsafe fn inb(port: u16) -> u8 { + let value: u8; + // SAFETY: port address is valid 16550 register offset from a standard COM base. + unsafe { + core::arch::asm!( + "in al, dx", + out("al") value, + in("dx") port, + options(nomem, nostack, preserves_flags), + ); + } + value +} + +#[inline] +pub unsafe fn outb(port: u16, value: u8) { + // SAFETY: port address is valid 16550 register offset from a standard COM base. + unsafe { + core::arch::asm!( + "out dx, al", + in("dx") port, + in("al") value, + options(nomem, nostack, preserves_flags), + ); + } +} + +#[repr(transparent)] +#[must_use] +pub struct LineStatus { + pub bits: u8, +} + +#[allow(unused)] +impl LineStatus { + const DR: u8 = 0; + const OE: u8 = 1; + const PE: u8 = 2; + const FE: u8 = 3; + const BI: u8 = 4; + const THRE: u8 = 5; + const TEMT: u8 = 6; + const E_RCVR_FIFO: u8 = 7; + + pub const fn rx_ready(&self) -> bool { + (self.bits & (1 << Self::DR)) != 0 + } + + pub const fn tx_ready(&self) -> bool { + (self.bits & (1 << Self::THRE)) != 0 + } +} + +#[repr(transparent)] +#[must_use] +pub struct LineControlRegister { + pub bits: u8, +} + +impl LineControlRegister { + #[inline(always)] + pub const fn bits(&self) -> u8 { + self.bits + } + + pub const fn enable_baud_config(&mut self) { + self.bits |= 1 << 7; + } + + pub const fn disable_baud_config(&mut self) { + self.bits &= !(1 << 7); + } + + pub const fn set_word_length(&mut self, word_length: WordLength) { + const BIT_MASK: u8 = 0b0000_0011u8; + self.bits = (self.bits & !BIT_MASK) | (word_length as u8 & BIT_MASK); + } +} + +#[repr(u8)] +pub enum WordLength { + W8 = 0b11, + W7 = 0b10, + W6 = 0b01, + W5 = 0b00, +}