Add simple uart driver, more functionality etc etc etc
This commit is contained in:
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
@@ -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
|
||||
12
kernel/Cargo.lock
generated
12
kernel/Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -4,3 +4,5 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
uefi = {path = "../uefi"}
|
||||
x86_drivers = {path = "../x86_drivers"}
|
||||
|
||||
10
kernel/run.sh
Executable file
10
kernel/run.sh
Executable file
@@ -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
|
||||
64
kernel/src/lib.rs
Normal file
64
kernel/src/lib.rs
Normal file
@@ -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<Uart1>> {
|
||||
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<Uart<Uart1>>) =
|
||||
(AtomicBool::new(false), MaybeUninit::uninit());
|
||||
|
||||
#[repr(align(8))]
|
||||
pub struct Aligned<T>(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<Uart1>) {
|
||||
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 {}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
7
uefi/Cargo.lock
generated
Normal file
7
uefi/Cargo.lock
generated
Normal file
@@ -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"
|
||||
6
uefi/Cargo.toml
Normal file
6
uefi/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "uefi"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
BIN
uefi/libstatus.rlib
Normal file
BIN
uefi/libstatus.rlib
Normal file
Binary file not shown.
317
uefi/src/lib.rs
Normal file
317
uefi/src/lib.rs
Normal file
@@ -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<UefiStatus>> {
|
||||
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<Self::Item> {
|
||||
if (self.offset + core::mem::size_of::<EfiMemoryDescriptor>()) >= 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::<EfiMemoryDescriptor>()
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl EfiBootServices {
|
||||
pub fn memory_map<'a, 'b>(
|
||||
&mut self,
|
||||
arena: &'a mut [u8],
|
||||
) -> Result<(ExitBootServicesKey, PageIter<'b>, u32), Option<UefiStatus>>
|
||||
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::<EfiMemoryDescriptor>(),
|
||||
"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<UefiStatus>> {
|
||||
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()
|
||||
}
|
||||
}
|
||||
99
uefi/src/memory.rs
Normal file
99
uefi/src/memory.rs
Normal file
@@ -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> {
|
||||
EfiMemoryType::try_from_raw(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl EfiMemoryType {
|
||||
#[inline(always)]
|
||||
pub const fn try_from_raw(raw: &RawMemoryType) -> Option<Self> {
|
||||
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<Self> {
|
||||
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<EfiMemoryType> for u32 {
|
||||
fn from(memory_type: EfiMemoryType) -> Self {
|
||||
memory_type.as_u32()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<u32> for EfiMemoryType {
|
||||
type Error = u32;
|
||||
|
||||
fn try_from(value: u32) -> Result<Self, Self::Error> {
|
||||
Self::from_u32(value).ok_or(value)
|
||||
}
|
||||
}
|
||||
221
uefi/src/status.rs
Normal file
221
uefi/src/status.rs
Normal file
@@ -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> {
|
||||
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> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<UefiStatus> for usize {
|
||||
fn from(status: UefiStatus) -> Self {
|
||||
status.as_usize()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<usize> for UefiStatus {
|
||||
type Error = usize;
|
||||
|
||||
/// Returns `Err(raw_value)` if the code is not recognised.
|
||||
fn try_from(value: usize) -> Result<Self, Self::Error> {
|
||||
Self::from_usize(value).ok_or(value)
|
||||
}
|
||||
}
|
||||
7
x86_drivers/Cargo.lock
generated
Normal file
7
x86_drivers/Cargo.lock
generated
Normal file
@@ -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"
|
||||
6
x86_drivers/Cargo.toml
Normal file
6
x86_drivers/Cargo.toml
Normal file
@@ -0,0 +1,6 @@
|
||||
[package]
|
||||
name = "x86_drivers"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
37
x86_drivers/src/core.rs
Normal file
37
x86_drivers/src/core.rs
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
198
x86_drivers/src/gdt.rs
Normal file
198
x86_drivers/src/gdt.rs
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
56
x86_drivers/src/idt.rs
Normal file
56
x86_drivers/src/idt.rs
Normal file
@@ -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 }
|
||||
}
|
||||
}
|
||||
42
x86_drivers/src/lib.rs
Normal file
42
x86_drivers/src/lib.rs
Normal file
@@ -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};
|
||||
}
|
||||
234
x86_drivers/src/uart/mod.rs
Normal file
234
x86_drivers/src/uart/mod.rs
Normal file
@@ -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<true>) {
|
||||
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<M: HwUart> {
|
||||
fn init(&self) -> Uart<M> {
|
||||
Uart::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UartConfig<const BAUD_SET: bool> {
|
||||
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<M: HwUart> {
|
||||
_t: PhantomData<M>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl UartConfig<false> {
|
||||
const fn set_baud(self, target_baud: BaudRate) -> UartConfig<true> {
|
||||
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<UartConfig<true>> {
|
||||
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<M: HwUart> Uart<M> {
|
||||
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<M: HwUart> InitUart<M> for M {}
|
||||
|
||||
impl<M: HwUart> fmt::Write for Uart<M> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
106
x86_drivers/src/uart/pac.rs
Normal file
106
x86_drivers/src/uart/pac.rs
Normal file
@@ -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,
|
||||
}
|
||||
Reference in New Issue
Block a user