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
x86_drivers/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 = "x86_drivers"
version = "0.1.0"

6
x86_drivers/Cargo.toml Normal file
View File

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

37
x86_drivers/src/core.rs Normal file
View 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
View 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
View 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
View 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
View 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
View 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,
}