From aa5ce32dce3fff803f0ede81a1f4be967bd2b199 Mon Sep 17 00:00:00 2001 From: ivajon Date: Wed, 6 May 2026 20:43:00 +0200 Subject: [PATCH] Add simple uart driver, more functionality etc etc etc --- .gitignore | 22 +++ kernel/Cargo.lock | 12 ++ kernel/Cargo.toml | 2 + kernel/run.sh | 10 ++ kernel/src/lib.rs | 64 ++++++++ kernel/src/main.rs | 110 ++++++++++++- uefi/Cargo.lock | 7 + uefi/Cargo.toml | 6 + uefi/libstatus.rlib | Bin 0 -> 60224 bytes uefi/src/lib.rs | 317 ++++++++++++++++++++++++++++++++++++ uefi/src/memory.rs | 99 +++++++++++ uefi/src/status.rs | 221 +++++++++++++++++++++++++ x86_drivers/Cargo.lock | 7 + x86_drivers/Cargo.toml | 6 + x86_drivers/src/core.rs | 37 +++++ x86_drivers/src/gdt.rs | 198 ++++++++++++++++++++++ x86_drivers/src/idt.rs | 56 +++++++ x86_drivers/src/lib.rs | 42 +++++ x86_drivers/src/uart/mod.rs | 234 ++++++++++++++++++++++++++ x86_drivers/src/uart/pac.rs | 106 ++++++++++++ 20 files changed, 1554 insertions(+), 2 deletions(-) create mode 100644 .gitignore create mode 100755 kernel/run.sh create mode 100644 kernel/src/lib.rs create mode 100644 uefi/Cargo.lock create mode 100644 uefi/Cargo.toml create mode 100644 uefi/libstatus.rlib create mode 100644 uefi/src/lib.rs create mode 100644 uefi/src/memory.rs create mode 100644 uefi/src/status.rs create mode 100644 x86_drivers/Cargo.lock create mode 100644 x86_drivers/Cargo.toml create mode 100644 x86_drivers/src/core.rs create mode 100644 x86_drivers/src/gdt.rs create mode 100644 x86_drivers/src/idt.rs create mode 100644 x86_drivers/src/lib.rs create mode 100644 x86_drivers/src/uart/mod.rs create mode 100644 x86_drivers/src/uart/pac.rs 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 0000000000000000000000000000000000000000..e37659dfa855d1a8cc41cce3fd8934c031544ad2 GIT binary patch literal 60224 zcmeFa2YggT_cuOw_hyrj4NC|RLft^9k%U((p2ql!k3at|nQ#^ep1 z7#%$-t}r4gD%F*5k8$O?GhI1DN4fh)CdG{A1RG9tMighI$4nYGHYujSZF3hEgoll{ zJ2J;HKPcLjmE_8wI;j8fB#|^0rK9Xyv!K+7r0V&?!yNgp?4-hi%rg6bmM^m)sh}`5 z)m~8WpX7_P72Kh-Vq1PrW={HlC+wr^ue~ zj!nofjR$N~2gSMbOOpse&BN^-O#OESMp0?#o2VMq7!<`Ni*B`tW3|zEJ2omtmnPSd zBc-brIUIIJTy%F^tS#Ig-966HB{jXUYgpHOid^05)Ny8}bj{DUyKS1se>U!8qU3mw z>K+yq$Ig=LOdm8N-k>NL{xT?{x2Q_#QXv{2tm6AJ+gF%`mr%ji!=D%a29Fvq3_=_t znGt^!@(T;xi0bTnnUkUu2~w@$UAsqi4eOGVnV#WxmRj3K#HNJB#@HOO)`+l(=q};m zT_VCeR2m+smw0jAlDqScE1TjYznb>evm1pWpi6AD-ENPHNlOch=pLIE)xA;}_Id5z zI=lI-H=c=D`g)TU@4lCACqr9QTx@iBOmtdQiY+qE5d*_74WSc%`mx8*cW&73du4lA zeu9|{?J$XpbA-pGIb!Xp(cv)sQGK$69hMrK78@NAlVVE=x5L~w!&Z=ylJ6?csoX#O)GvQObJw;n)pngKZ-#859FZyA z?I~#~_LK;8t0NUSiQwBc-{y3>QY$B|=s7JmZCLMRxkmS&uP9+uLU_16HZsN$8xtLg zP7JdFKpYlnNojVsJ=I;A9&lfx#ThkXV$g~MJyyJsPg$b6N5)2kN87^EA|g|xQ&Nzn zu5_Aa&$i{HJMEQAK90F^=|D>BV;>fMlJr{7Qp%PZ853bkOG{0O#3+ff#Sj>&j`XBV zM`haS>#vp`9aZe=_*neD(otWNd01FjRAfq+-5wVi?r@|McD~4-AK}c*u~&{uZ{5%* z=dQm3CtZK>AM0n$pd8&(9Fg|a7<6Y$Txyt|ddMq1GuxS2c`mqn@h|VcQ1N{$+uge_ z%}pWev{YMEY-ChKY;$c0U#P{J~H%w?{@rJ0iM= zM*#9LM_LrsKHHw1nvq|bc%ZvqK;7bkF-=E=_Uhn%o6RGlBU3SQ!=lo{ZE-2l7{Key zzpbv=U^(`~A9=m5ESJ+=}&{;4M6qtEFOAz24W?aWXm(9TOz;G>^$ zIBB@%O$8}R9h2b_vj)ZNWmpcG*<_d%0Hv3xQ*~ZNCX1Kw9w*OqNY$%@KR=e%n}Ac& z_?)a9jNQS`(%cNF1ECI->Hty?v^z1(;%%AELL8n#X#{P}F1l4`&4IR%BexvqRS7uXe=u2SRQ-Cc!@x(xFmnX4!xY zOch*-5m1egDr!=^t1yQ!9}V$nEl0mpT1pMH=VWq@u~3bbsyd@wsB^9>r+}av55;(? z@P%(%ZY~x@klzdWy;AO-keO|F6}lmv4C!P~F>Z_&t#=ZkNR*1Y{haxPah1{rrA;fV zUy9zLX^^H#sqfI#>|7oGbjZ^+d4j|3kxzwus+9XvkGN7@PCZXHblFlDI5a2Km7QyI zXQnvq_t^6bu=+*Kb77GyEds{bQwwpNl-`r+a>9=F1<)5rz0ZLBRK4ILh>N7S&Y+yM z(GIN3w(R1Ckvn*$K(c z%1l2Yx=^w!lHHU|NQO}|9LWgfWh5gh8HHrD@)43Tl#E3(PPvR^cS`m^@-F3Jvk*Ng z*$c_u%2P=8p=4hq`zeQ!>`%!7NDfp!M{*D)kOrYd=B=1qKA~}JQ_aZq_Sz1?!NtB$7

EalJ`+E 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 s8GQd$>Ti#D{yicQ&EdlhrqAltbD$rZHmbCV4 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, +}