Add simple uart driver, more functionality etc etc etc

This commit is contained in:
ivajon
2026-05-06 20:43:00 +02:00
parent 0b0acedb2f
commit aa5ce32dce
20 changed files with 1554 additions and 2 deletions

7
uefi/Cargo.lock generated Normal file
View 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
View File

@@ -0,0 +1,6 @@
[package]
name = "uefi"
version = "0.1.0"
edition = "2024"
[dependencies]

BIN
uefi/libstatus.rlib Normal file

Binary file not shown.

317
uefi/src/lib.rs Normal file
View 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
View 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
View 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)
}
}