summaryrefslogtreecommitdiffstats
path: root/rsnix
diff options
context:
space:
mode:
Diffstat (limited to 'rsnix')
-rw-r--r--rsnix/Cargo.toml4
-rw-r--r--rsnix/build.rs70
-rw-r--r--rsnix/src/font.rs5
-rw-r--r--rsnix/src/gop.rs232
-rw-r--r--rsnix/src/interrupts.rs401
-rw-r--r--rsnix/src/lib.rs11
-rw-r--r--rsnix/src/memory.rs196
-rw-r--r--rsnix/src/ps2.rs363
-rw-r--r--rsnix/src/serial.rs118
-rw-r--r--rsnix/src/uefi.rs289
10 files changed, 1689 insertions, 0 deletions
diff --git a/rsnix/Cargo.toml b/rsnix/Cargo.toml
new file mode 100644
index 0000000..d8cd26b
--- /dev/null
+++ b/rsnix/Cargo.toml
@@ -0,0 +1,4 @@
1[package]
2name = "rsnix"
3version.workspace = true
4edition.workspace = true
diff --git a/rsnix/build.rs b/rsnix/build.rs
new file mode 100644
index 0000000..35447e9
--- /dev/null
+++ b/rsnix/build.rs
@@ -0,0 +1,70 @@
1/// Build script — extracts glyph data from a PSF2 font file.
2/// Uses zcat (system gzip) to decompress — zero dependencies.
3use std::env;
4use std::fs;
5use std::path::Path;
6use std::process::{Command, Output};
7
8fn main() -> Result<(), Box<dyn std::error::Error>> {
9 let font_path = "/usr/share/kbd/consolefonts/lat1-16.psfu.gz";
10 println!("cargo::rerun-if-changed={}", font_path);
11
12 // Decompress via zcat (available on any Linux system)
13 let Output { stdout, status, .. } = Command::new("zcat").arg(font_path).output()?;
14 if !status.success() {
15 return Err(format!("zcat exited with {status:?}").into());
16 }
17 let raw = stdout;
18
19 if raw.len() < 32 {
20 return Err("PSF font too small".into());
21 }
22
23 // PSF2 magic: 0x864ab572 (little-endian)
24 let magic = u32::from_le_bytes(raw[..4].try_into()?);
25 if magic != 0x864ab572 {
26 return Err("not a PSF2 font".into());
27 }
28
29 let hdr_size = u32::from_le_bytes(raw[8..12].try_into()?) as usize;
30 let num_glyphs = u32::from_le_bytes(raw[16..20].try_into()?) as usize;
31 let bytes_per_glyph = u32::from_le_bytes(raw[20..24].try_into()?) as usize;
32
33 if num_glyphs != 256 {
34 return Err(format!("expected 256 glyphs, got {num_glyphs}").into());
35 }
36 if bytes_per_glyph != 16 {
37 return Err(format!("expected 16 bytes/glyph, got {bytes_per_glyph}").into());
38 }
39
40 let glyph_data = &raw[hdr_size..hdr_size + num_glyphs * bytes_per_glyph];
41
42 // Write generated Rust source into OUT_DIR
43 let out_dir = env::var("OUT_DIR")?;
44 let dest = Path::new(&out_dir).join("font_data.rs");
45
46 let mut code = String::from(
47 "/// 8×16 bitmap font glyph data (256 glyphs, 16 bytes each).
48#[rustfmt::skip]
49#[allow(clippy::unreadable_literal)]
50pub const GLYPH_8X16: [[u8; 16]; 256] = [",
51 );
52
53 for g in 0..256 {
54 let offset = g * 16;
55 code.push_str("\n [");
56 for row in 0..16 {
57 if row % 4 == 0 {
58 code.push('\n');
59 }
60 let byte = glyph_data[offset + row];
61 code.push_str(&format!(" 0x{byte:02X},"));
62 }
63 code.push_str("\n ],");
64 }
65
66 code.push_str("\n];\n");
67
68 fs::write(&dest, code)?;
69 Ok(())
70}
diff --git a/rsnix/src/font.rs b/rsnix/src/font.rs
new file mode 100644
index 0000000..e188513
--- /dev/null
+++ b/rsnix/src/font.rs
@@ -0,0 +1,5 @@
1/// 8×16 bitmap font.
2///
3/// Glyphs are generated at build time by `build.rs` from the system PSF2 font
4/// (`/usr/share/kbd/consolefonts/lat1-16.psfu.gz`).
5include!(concat!(env!("OUT_DIR"), "/font_data.rs"));
diff --git a/rsnix/src/gop.rs b/rsnix/src/gop.rs
new file mode 100644
index 0000000..9645bb0
--- /dev/null
+++ b/rsnix/src/gop.rs
@@ -0,0 +1,232 @@
1/// GOP framebuffer driver — renders text via a bitmap font.
2use crate::font;
3use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB};
4use core::fmt;
5use core::ptr;
6
7/// Framebuffer state captured before ExitBootServices.
8pub struct Framebuffer {
9 base: *mut u8,
10 #[allow(dead_code)]
11 width: usize,
12 height: usize,
13 pitch: usize, // bytes per scan line
14 bytes_per_pixel: usize,
15 bg: u32, // packed BGRA or RGBA background colour
16 fg: u32, // packed foreground colour
17 cursor_x: usize,
18 cursor_y: usize,
19 rows: usize,
20 cols: usize,
21}
22
23static mut FB: Option<Framebuffer> = None;
24
25/// Initialise the framebuffer from GOP. Call *before* ExitBootServices.
26pub fn init(gop: &mut GraphicsOutputProtocol) {
27 let mode = unsafe { &*gop.mode };
28 let info = unsafe { &*mode.info };
29 let base = mode.framebuffer_base as *mut u8;
30 let width = info.horizontal_resolution as usize;
31 let height = info.vertical_resolution as usize;
32 let pitch = info.pixels_per_scan_line as usize * 4; // 32-bit pixel
33 let bpp = 4;
34
35 // Pick colours based on pixel format
36 let (fg, bg) = match info.pixel_format {
37 PIXEL_BGR => (0x00_FF_FF_FFu32, 0x00_00_00_00u32), // white fg, black bg
38 PIXEL_RGB => (0x00_FF_FF_FFu32, 0x00_00_00_00u32),
39 _ => (0x00_FF_FF_FFu32, 0x00_00_00_00u32),
40 };
41
42 let rows = height / 16;
43 let cols = width / 8;
44
45 let fb = Framebuffer {
46 base,
47 width,
48 height,
49 pitch,
50 bytes_per_pixel: bpp,
51 bg,
52 fg,
53 cursor_x: 0,
54 cursor_y: 0,
55 rows,
56 cols,
57 };
58
59 fb.clear();
60
61 unsafe {
62 FB = Some(fb);
63 }
64}
65
66/// Draw a character at the current cursor position and advance.
67pub fn put_char(c: char) {
68 let fb = unsafe {
69 match FB.as_mut() {
70 Some(f) => f,
71 None => return,
72 }
73 };
74
75 if c == '\x08' {
76 // Backspace - move cursor back and erase
77 if fb.cursor_x > 0 {
78 fb.cursor_x -= 1;
79 } else if fb.cursor_y > 0 {
80 fb.cursor_y -= 1;
81 fb.cursor_x = fb.cols - 1;
82 } else {
83 return;
84 }
85 // Draw glyph 32 (space) to erase — glyph 0 is NUL, not blank
86 fb.draw_glyph(32, fb.cursor_x, fb.cursor_y);
87 return;
88 }
89 if c == '\n' {
90 fb.cursor_x = 0;
91 fb.cursor_y += 1;
92 if fb.cursor_y >= fb.rows {
93 fb.scroll();
94 }
95 return;
96 }
97 if c == '\r' {
98 fb.cursor_x = 0;
99 return;
100 }
101
102 let idx = c as usize;
103 if idx >= 256 {
104 // Unmapped character: draw a space
105 fb.draw_glyph(0, fb.cursor_x, fb.cursor_y);
106 } else {
107 fb.draw_glyph(idx, fb.cursor_x, fb.cursor_y);
108 }
109
110 fb.cursor_x += 1;
111 if fb.cursor_x >= fb.cols {
112 fb.cursor_x = 0;
113 fb.cursor_y += 1;
114 if fb.cursor_y >= fb.rows {
115 fb.scroll();
116 }
117 }
118}
119
120/// Write a string to the framebuffer.
121pub fn write_str(s: &str) {
122 for c in s.chars() {
123 put_char(c);
124 }
125}
126
127/// Clear the entire screen to background colour and reset cursor to (0, 0).
128pub fn clear_screen() {
129 let fb = unsafe {
130 match FB.as_mut() {
131 Some(f) => f,
132 None => return,
133 }
134 };
135 fb.clear();
136 // Reset cursor back to top-left so the next prompt appears at (0, 0)
137 fb.cursor_x = 0;
138 fb.cursor_y = 0;
139}
140
141// ---------------------------------------------------------------------------
142// Framebuffer implementation
143// ---------------------------------------------------------------------------
144impl Framebuffer {
145 fn clear(&self) {
146 let size = self.pitch * self.height;
147 // Fill with background colour (32-bit writes)
148 let mut p = self.base;
149 let end = unsafe { p.add(size) };
150 unsafe {
151 while p < end {
152 ptr::write_volatile(p as *mut u32, self.bg);
153 p = p.add(4);
154 }
155 }
156 }
157
158 fn scroll(&mut self) {
159 // Move everything up one row (16 pixels)
160 let row_bytes = self.pitch * 16;
161 let total = self.pitch * self.height;
162 unsafe {
163 ptr::copy(self.base.add(row_bytes), self.base, total - row_bytes);
164 // Clear last row
165 let last_row = self.base.add(total - row_bytes);
166 for i in 0..(self.pitch * 16 / 4) {
167 ptr::write_volatile(last_row.add(i * 4) as *mut u32, self.bg);
168 }
169 }
170 self.cursor_y -= 1;
171 }
172
173 fn draw_glyph(&self, glyph_idx: usize, col: usize, row: usize) {
174 let glyph = font::GLYPH_8X16;
175 let data = match glyph.get(glyph_idx) {
176 Some(d) => *d,
177 None => return,
178 };
179
180 let x = col * 8;
181 let y = row * 16;
182
183 for cy in 0..16 {
184 let row_bits = data[cy];
185 for cx in 0..8 {
186 if row_bits & (0x80 >> cx) != 0 {
187 // Set foreground pixel
188 let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel;
189 unsafe {
190 ptr::write_volatile(self.base.add(offset) as *mut u32, self.fg);
191 }
192 } else {
193 // Set background pixel
194 let offset = (y + cy) * self.pitch + (x + cx) * self.bytes_per_pixel;
195 unsafe {
196 ptr::write_volatile(self.base.add(offset) as *mut u32, self.bg);
197 }
198 }
199 }
200 }
201 }
202}
203
204// ---------------------------------------------------------------------------
205// fmt::Write wrapper
206// ---------------------------------------------------------------------------
207pub struct GopWriter;
208
209impl fmt::Write for GopWriter {
210 fn write_str(&mut self, s: &str) -> fmt::Result {
211 write_str(s);
212 Ok(())
213 }
214}
215
216/// Write a formatted string to the framebuffer.
217#[macro_export]
218macro_rules! gop_print {
219 ($($arg:tt)*) => {{
220 use core::fmt::Write;
221 let _ = write!(&mut $crate::gop::GopWriter, $($arg)*);
222 }};
223}
224
225#[macro_export]
226macro_rules! gop_println {
227 () => { $crate::gop::write_str("\n"); };
228 ($($arg:tt)*) => {{
229 $crate::gop_print!($($arg)*);
230 $crate::gop::write_str("\n");
231 }};
232}
diff --git a/rsnix/src/interrupts.rs b/rsnix/src/interrupts.rs
new file mode 100644
index 0000000..8f2acd7
--- /dev/null
+++ b/rsnix/src/interrupts.rs
@@ -0,0 +1,401 @@
1// ---------------------------------------------------------------------------
2// Interrupt handling: IDT, PIC remap, ISR dispatch.
3// ---------------------------------------------------------------------------
4use core::arch::asm;
5use core::sync::atomic::{AtomicU16, Ordering};
6
7// ---------------------------------------------------------------------------
8// IDT entry and table
9// ---------------------------------------------------------------------------
10
11/// A single 64-bit IDT entry (16 bytes).
12#[derive(Copy, Clone)]
13#[repr(C, packed)]
14struct IdtEntry {
15 offset_lo: u16,
16 selector: u16,
17 ist: u8,
18 flags: u8,
19 offset_mid: u16,
20 offset_hi: u32,
21 _reserved: u32,
22}
23
24/// Fill one IDT entry at the given offset (in bytes).
25unsafe fn idt_set_entry(idt: *mut u8, vec: u8, handler: u64, selector: u16, flags: u8) {
26 unsafe {
27 let off = (vec as usize) * 16;
28 let p = idt.add(off) as *mut IdtEntry;
29 p.write(IdtEntry {
30 offset_lo: handler as u16,
31 selector,
32 ist: 0,
33 flags: flags | 0x80, // bit 7 = present
34 offset_mid: (handler >> 16) as u16,
35 offset_hi: (handler >> 32) as u32,
36 _reserved: 0,
37 });
38 }
39}
40
41/// The one true IDT (live in .bss, zero-initialised).
42static mut IDT: [u8; 256 * 16] = [0u8; 256 * 16];
43static mut IDTR: [u16; 5] = [0; 5]; // 2-byte limit + 4-byte base (48-bit pseudo-desc)
44
45// ---------------------------------------------------------------------------
46// PIC (8259A)
47// ---------------------------------------------------------------------------
48
49const PIC1_CMD: u16 = 0x20;
50const PIC1_DATA: u16 = 0x21;
51const PIC2_CMD: u16 = 0xA0;
52const PIC2_DATA: u16 = 0xA1;
53
54const PIC_EOI: u8 = 0x20;
55
56const ICW1: u8 = 0x11; // edge, cascade, icw4
57const ICW4: u8 = 0x01; // x86 mode
58
59/// Remap PIC IRQs 0-15 to IDT vectors 32-47, then mask all.
60unsafe fn remap_pic() {
61 unsafe {
62 asm!("out dx, al", in("dx") PIC1_CMD, in("al") ICW1, options(nostack, preserves_flags));
63 asm!("out dx, al", in("dx") PIC2_CMD, in("al") ICW1, options(nostack, preserves_flags));
64 asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x20u8, options(nostack, preserves_flags));
65 asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x28u8, options(nostack, preserves_flags));
66 asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0x04u8, options(nostack, preserves_flags));
67 asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0x02u8, options(nostack, preserves_flags));
68 asm!("out dx, al", in("dx") PIC1_DATA, in("al") ICW4, options(nostack, preserves_flags));
69 asm!("out dx, al", in("dx") PIC2_DATA, in("al") ICW4, options(nostack, preserves_flags));
70 asm!("out dx, al", in("dx") PIC1_DATA, in("al") 0xFFu8, options(nostack, preserves_flags));
71 asm!("out dx, al", in("dx") PIC2_DATA, in("al") 0xFFu8, options(nostack, preserves_flags));
72 }
73}
74
75/// Send End-Of-Interrupt to the master PIC (and slave if needed).
76fn eoi(irq: u8) {
77 unsafe {
78 // Slave IRQs are 8-15, mapped to vectors 40-47.
79 if irq >= 8 {
80 asm!("out dx, al", in("dx") PIC2_CMD, in("al") PIC_EOI,
81 options(nostack, preserves_flags));
82 }
83 asm!("out dx, al", in("dx") PIC1_CMD, in("al") PIC_EOI,
84 options(nostack, preserves_flags));
85 }
86}
87
88// ---------------------------------------------------------------------------
89// PIT timer (channel 0, ~100 Hz)
90// ---------------------------------------------------------------------------
91
92const PIT_CMD: u16 = 0x43;
93const PIT_CH0: u16 = 0x40;
94const PIT_FREQ: u64 = 1_193_182;
95
96/// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish).
97fn pit_set_rate(hz: u64) {
98 let divisor = (PIT_FREQ / hz) as u16;
99 unsafe {
100 // Mode 2, channel 0, lobyte/hibyte, binary
101 asm!("out dx, al", in("dx") PIT_CMD, in("al") 0b00_11_010_0u8,
102 options(nostack, preserves_flags));
103 asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor & 0xFF) as u8,
104 options(nostack, preserves_flags));
105 asm!("out dx, al", in("dx") PIT_CH0, in("al") (divisor >> 8) as u8,
106 options(nostack, preserves_flags));
107 }
108}
109
110// ---------------------------------------------------------------------------
111// Lock-free SPSC ring buffer for keyboard scancodes
112// ---------------------------------------------------------------------------
113
114/// Single-producer (ISR) / single-consumer (main) lock-free ring buffer.
115struct KeyBuf {
116 buf: [core::cell::UnsafeCell<u8>; 256],
117 head: AtomicU16, // next slot to read (written by consumer / main)
118 tail: AtomicU16, // next slot to write (written by producer / ISR)
119}
120
121unsafe impl Sync for KeyBuf {}
122
123impl KeyBuf {
124 const fn new() -> Self {
125 // UnsafeCell requires const initialisation in a const ctx.
126 // Build an array the long way — one cell at a time.
127 const NEW_CELL: core::cell::UnsafeCell<u8> = core::cell::UnsafeCell::new(0);
128 // `[NEW_CELL; 256]` needs Clone, which UnsafeCell doesn't have.
129 // Use a union trick or just mark the whole buffer init with zeroes.
130 // SAFETY: we initialise via the const-fn path in init().
131 Self {
132 buf: [
133 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
134 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
135 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
136 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
137 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
138 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
139 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
140 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
141 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
142 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
143 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
144 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
145 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
146 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
147 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
148 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
149 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
150 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
151 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
152 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
153 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
154 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
155 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
156 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
157 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
158 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
159 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
160 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
161 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
162 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
163 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
164 NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL, NEW_CELL,
165 ],
166 head: AtomicU16::new(0),
167 tail: AtomicU16::new(0),
168 }
169 }
170
171 /// Push one byte — called from ISR (interrupts disabled, single-producer).
172 fn push(&self, byte: u8) {
173 let t = self.tail.load(Ordering::Relaxed);
174 let h = self.head.load(Ordering::Acquire);
175 let next = t.wrapping_add(1);
176 // Full if the distance between head and tail == capacity (256).
177 if next.wrapping_sub(h) > 255 {
178 return; // drop — ring is full
179 }
180 unsafe {
181 *self.buf[t as usize & 255].get() = byte;
182 }
183 // Ensure byte is globally visible before incrementing tail.
184 self.tail.store(next, Ordering::Release);
185 }
186
187 /// Pop one byte — called from main context. Returns None when empty.
188 fn pop(&self) -> Option<u8> {
189 let h = self.head.load(Ordering::Relaxed);
190 let t = self.tail.load(Ordering::Acquire);
191 if h == t {
192 return None;
193 }
194 let byte = unsafe { *self.buf[h as usize & 255].get() };
195 self.head.store(h.wrapping_add(1), Ordering::Release);
196 Some(byte)
197 }
198}
199
200static KEYBUF: KeyBuf = KeyBuf::new();
201
202// ---------------------------------------------------------------------------
203// Interrupt stubs (assembly) and dispatch
204// ---------------------------------------------------------------------------
205
206core::arch::global_asm!(
207 // ISR stubs — push vector number + error code, jump to common.
208 ".globl isr_stub_32",
209 ".p2align 4",
210 "isr_stub_32:",
211 " push 0", // error code (non-exception)
212 " push 32", // vector
213 " jmp isr_common",
214 "",
215 ".globl isr_stub_33",
216 ".p2align 4",
217 "isr_stub_33:",
218 " push 0",
219 " push 33",
220 " jmp isr_common",
221 "",
222 // Catch-all for unexpected interrupts.
223 ".globl isr_stub_spurious",
224 ".p2align 4",
225 "isr_stub_spurious:",
226 " push 0",
227 " push 0xFF",
228 " jmp isr_common",
229 "",
230 // Common handler — save all registers, call into Rust, restore, iretq.
231 "isr_common:",
232 " push rax",
233 " push rcx",
234 " push rdx",
235 " push rbx",
236 " push rbp",
237 " push rsi",
238 " push rdi",
239 " push r8",
240 " push r9",
241 " push r10",
242 " push r11",
243 " push r12",
244 " push r13",
245 " push r14",
246 " push r15",
247 "",
248 " mov rcx, rsp", // first arg (rcx = MS x64 ABI)
249 " call isr_dispatch",
250 "",
251 " pop r15",
252 " pop r14",
253 " pop r13",
254 " pop r12",
255 " pop r11",
256 " pop r10",
257 " pop r9",
258 " pop r8",
259 " pop rdi",
260 " pop rsi",
261 " pop rbp",
262 " pop rbx",
263 " pop rdx",
264 " pop rcx",
265 " pop rax",
266 " add rsp, 16", // pop vector + error code
267 " iretq",
268);
269
270/// Saved register frame (as pushed by `isr_common`).
271#[repr(C)]
272struct IntFrame {
273 r15: u64,
274 r14: u64,
275 r13: u64,
276 r12: u64,
277 r11: u64,
278 r10: u64,
279 r9: u64,
280 r8: u64,
281 rdi: u64,
282 rsi: u64,
283 rbp: u64,
284 rbx: u64,
285 rdx: u64,
286 rcx: u64,
287 rax: u64,
288 vector: u64,
289 error: u64,
290 rip: u64,
291 cs: u64,
292 rflags: u64,
293}
294
295/// Called from the asm common handler — dispatches to the right Rust handler.
296#[no_mangle]
297unsafe extern "C" fn isr_dispatch(frame: &IntFrame) {
298 unsafe {
299 match frame.vector {
300 32 => eoi(0),
301 33 => {
302 let byte: u8;
303 core::arch::asm!("in al, dx", out("al") byte, in("dx") 0x60u16,
304 options(nostack, preserves_flags));
305 // Push scancode to ring buffer (called with interrupts disabled).
306 KEYBUF.push(byte);
307 eoi(1);
308 }
309 _ => {}
310 }
311 }
312}
313
314/// Timer IRQ (PIT channel 0 ~ 100 Hz).
315fn timer_irq() {
316 eoi(0);
317}
318
319/// Keyboard IRQ — read scancode from port 0x60 and push to ring buffer.
320fn keyboard_irq() {
321 let byte: u8;
322 unsafe {
323 asm!("in al, dx", out("al") byte, in("dx") 0x60,
324 options(nostack, preserves_flags));
325 }
326 KEYBUF.push(byte);
327 eoi(1);
328}
329
330// ---------------------------------------------------------------------------
331// Public API
332// ---------------------------------------------------------------------------
333
334/// Read one scancode from the interrupt-driven ring buffer (non-blocking).
335pub fn read_scancode() -> Option<u8> {
336 KEYBUF.pop()
337}
338
339/// Global tick counter (incremented by timer IRQ, ~100 Hz).
340pub static TICKS: AtomicU16 = AtomicU16::new(0);
341
342/// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts.
343pub fn init() {
344 unsafe {
345 // Read the current code segment selector from CS.
346 let cs: u64;
347 asm!("mov {0}, cs", out(reg) cs, options(nostack, preserves_flags));
348 let cs = cs as u16;
349
350 // Build the IDT.
351 let idt_ptr = IDT.as_mut_ptr();
352 let spurious = isr_stub_spurious as *const () as u64;
353
354 // Fill all 256 entries with the spurious handler.
355 for vec in 0..=255u8 {
356 idt_set_entry(idt_ptr, vec, spurious, cs, 0x0Eu8);
357 }
358
359 // Override vectors for interrupt we actually handle.
360 idt_set_entry(idt_ptr, 32, isr_stub_32 as *const () as u64, cs, 0x0Eu8);
361 idt_set_entry(idt_ptr, 33, isr_stub_33 as *const () as u64, cs, 0x0Eu8);
362
363 // Build the IDTR pseudo-descriptor.
364 IDTR = [
365 ((256u16 * 16 - 1)), // limit = 256*16 - 1
366 idt_ptr as u16, // base lo
367 (idt_ptr as u64 >> 16) as u16, // base mid
368 (idt_ptr as u64 >> 32) as u16, // base hi
369 (idt_ptr as u64 >> 48) as u16, // base top
370 ];
371
372 // Load IDT.
373 asm!("lidt [{}]", in(reg) &IDTR, options(nostack, preserves_flags));
374
375 // Remap PIC and mask everything.
376 remap_pic();
377
378 // Set up PIT timer at ~100 Hz.
379 pit_set_rate(100);
380
381 // Unmask timer (IRQ0 → bit 0) and keyboard (IRQ1 → bit 1) on master.
382 let mut mask: u8;
383 asm!("in al, dx", out("al") mask, in("dx") PIC1_DATA,
384 options(nostack, preserves_flags));
385 mask &= !0x03u8;
386 asm!("out dx, al", in("dx") PIC1_DATA, in("al") mask,
387 options(nostack, preserves_flags));
388
389 // Enable interrupts.
390 asm!("sti", options(nostack));
391 }
392}
393
394// ---------------------------------------------------------------------------
395// Extern symbols from global_asm!
396// ---------------------------------------------------------------------------
397extern "C" {
398 fn isr_stub_32();
399 fn isr_stub_33();
400 fn isr_stub_spurious();
401}
diff --git a/rsnix/src/lib.rs b/rsnix/src/lib.rs
new file mode 100644
index 0000000..6c7fb3c
--- /dev/null
+++ b/rsnix/src/lib.rs
@@ -0,0 +1,11 @@
1#![no_std]
2
3extern crate alloc;
4
5pub mod uefi;
6pub mod serial;
7pub mod gop;
8pub mod font;
9pub mod ps2;
10pub mod interrupts;
11pub mod memory;
diff --git a/rsnix/src/memory.rs b/rsnix/src/memory.rs
new file mode 100644
index 0000000..39fd88d
--- /dev/null
+++ b/rsnix/src/memory.rs
@@ -0,0 +1,196 @@
1use crate::uefi::*;
2use core::alloc::{GlobalAlloc, Layout};
3use core::mem;
4use core::ptr;
5
6const MAX_MAP_ENTRIES: usize = 512;
7
8static mut MEMORY_MAP: [MemoryDescriptor; MAX_MAP_ENTRIES] = unsafe { mem::zeroed() };
9static mut MAP_ENTRIES: usize = 0;
10
11/// The map key returned by GetMemoryMap, needed for ExitBootServices.
12static mut MAP_KEY: usize = 0;
13
14/// Static working buffer for GetMemoryMap (64 KB, way more than enough).
15/// Lives in .bss; no pool allocation, so no map-key staleness.
16static mut MAP_WORK_BUF: [u8; 65536] = [0u8; 65536];
17
18// ---------------------------------------------------------------------------
19// Bump allocator — no deallocation, just pointer advancement.
20// Used as Rust's #[global_allocator] once the heap region is set up.
21// ---------------------------------------------------------------------------
22struct BumpAllocator;
23
24unsafe impl Sync for BumpAllocator {}
25
26unsafe impl GlobalAlloc for BumpAllocator {
27 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
28 let align = layout.align().max(16);
29 let size = layout.size();
30 unsafe {
31 let a = (HEAP_CURSOR + align - 1) & !(align - 1);
32 let e = a + size;
33 if e > HEAP_END || e < a {
34 return ptr::null_mut();
35 }
36 HEAP_CURSOR = e;
37 a as *mut u8
38 }
39 }
40
41 unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {
42 // Bump allocator — cannot free individual allocations.
43 // Memory is reclaimed on reboot. Good enough for now.
44 unsafe {
45 core::ptr::read_volatile(&_ptr);
46 }
47 let _ = _layout;
48 }
49}
50
51#[global_allocator]
52static ALLOCATOR: BumpAllocator = BumpAllocator;
53
54/// Heap bump allocator bounds (`static mut` accessed only inside
55/// `GlobalAlloc` impl, which is already `unsafe`).
56static mut HEAP_START: usize = 0;
57static mut HEAP_END: usize = 0;
58static mut HEAP_CURSOR: usize = 0;
59
60/// Capture the UEFI memory map into the static buffer. Uses a pre-allocated
61/// .bss working buffer so no pool allocation (and thus no memory-map change)
62/// occurs between GetMemoryMap and ExitBootServices.
63pub fn capture_memory_map(bs: &BootServices) -> Result<usize, Status> {
64 let mut map_key: usize = 0;
65 let mut desc_size: usize = 0;
66 let mut desc_ver: u32 = 0;
67
68 // Probe with null to get required size + descriptor size
69 let mut buf_size: usize = 0;
70 let status = unsafe {
71 (bs.get_memory_map)(
72 &mut buf_size,
73 ptr::null_mut(),
74 &mut map_key,
75 &mut desc_size,
76 &mut desc_ver,
77 )
78 };
79 if status != EFI_BUFFER_TOO_SMALL {
80 // Fallback: allocate a sensible size
81 buf_size = 64 * 1024;
82 }
83 if desc_size == 0 {
84 desc_size = mem::size_of::<MemoryDescriptor>();
85 }
86
87 // Round up to desc_size alignment
88 let buf_size = buf_size.div_ceil(desc_size) * desc_size;
89
90 // If our static buffer is too small, fall back to pool (rare)
91 if buf_size > unsafe { MAP_WORK_BUF.len() } {
92 return Err(EFI_BUFFER_TOO_SMALL);
93 }
94
95 let mut actual_size = buf_size;
96 let status = unsafe {
97 (bs.get_memory_map)(
98 &mut actual_size,
99 &raw mut MAP_WORK_BUF[0] as *mut MemoryDescriptor,
100 &mut map_key,
101 &mut desc_size,
102 &mut desc_ver,
103 )
104 };
105
106 if status == EFI_BUFFER_TOO_SMALL {
107 // The map grew between the probe and the real call. Try a larger
108 // static buffer (can't grow here without pool, so return error to
109 // let the caller retry if they can allocate pool).
110 // For our 64 KB buffer this should never happen on OVMF.
111 return Err(EFI_BUFFER_TOO_SMALL);
112 }
113 if status != EFI_SUCCESS {
114 return Err(status);
115 }
116
117 // Copy descriptors into our indexed array
118 let count = actual_size / desc_size;
119 let count = count.min(MAX_MAP_ENTRIES);
120 unsafe {
121 for i in 0..count {
122 let p = (&raw const MAP_WORK_BUF[..]) as *const u8;
123 let off = i * desc_size;
124 let src = p.add(off) as *const MemoryDescriptor;
125 MEMORY_MAP[i] = ptr::read(src);
126 }
127 MAP_ENTRIES = count;
128 MAP_KEY = map_key;
129 }
130
131 // Do NOT free anything — the static buffer stays. Map key is still
132 // valid because no pool allocation/free happened after GetMemoryMap.
133
134 init_bump_allocator();
135 Ok(map_key)
136}
137
138/// Retry ExitBootServices with a fresh memory map.
139pub fn refresh_and_exit(bs: &BootServices, image_handle: Handle) -> Result<usize, Status> {
140 let key = capture_memory_map(bs)?;
141 let status = unsafe { (bs.exit_boot_services)(image_handle, key) };
142 if status == EFI_SUCCESS {
143 Ok(key)
144 } else {
145 Err(status)
146 }
147}
148
149fn init_bump_allocator() {
150 unsafe {
151 let mut best_start = 0usize;
152 let mut best_size = 0usize;
153 for i in 0..MAP_ENTRIES {
154 let d = &MEMORY_MAP[i];
155 if d.kind == MEMORY_TYPE_CONVENTIONAL {
156 let sz = d.number_of_pages as usize * 4096;
157 if sz > best_size {
158 best_start = d.physical_start as usize;
159 best_size = sz;
160 }
161 }
162 }
163 if best_size > 2 * 1024 * 1024 {
164 let s = best_start + 2 * 1024 * 1024;
165 let e = best_start + best_size;
166 if s < e {
167 HEAP_START = s;
168 HEAP_END = e;
169 HEAP_CURSOR = s;
170 }
171 }
172 }
173}
174
175#[allow(dead_code)]
176pub fn allocate(size: usize) -> *mut u8 {
177 unsafe {
178 let a = (HEAP_CURSOR + 15) & !15;
179 let e = a + size;
180 if e > HEAP_END || e < a {
181 return ptr::null_mut();
182 }
183 HEAP_CURSOR = e;
184 a as *mut u8
185 }
186}
187
188#[allow(dead_code)]
189pub fn get_map() -> (&'static [MemoryDescriptor], usize) {
190 unsafe {
191 (
192 core::slice::from_raw_parts(&MEMORY_MAP[0], MAP_ENTRIES),
193 MAP_KEY,
194 )
195 }
196}
diff --git a/rsnix/src/ps2.rs b/rsnix/src/ps2.rs
new file mode 100644
index 0000000..b83a4e0
--- /dev/null
+++ b/rsnix/src/ps2.rs
@@ -0,0 +1,363 @@
1/// PS/2 keyboard driver — polling, scancode set 1, US QWERTY layout.
2// Port addresses
3const DATA_PORT: u16 = 0x60;
4const STATUS_PORT: u16 = 0x64;
5
6// Status register bits
7const STAT_OUTPUT_FULL: u8 = 0x01;
8
9// Modifier bit constants
10const MOD_LSHIFT: u8 = 0x01;
11const MOD_RSHIFT: u8 = 0x02;
12const MOD_LCTRL: u8 = 0x04;
13const MOD_RCTRL: u8 = 0x08;
14const MOD_LALT: u8 = 0x10;
15const MOD_RALT: u8 = 0x20;
16const MOD_CAPS: u8 = 0x40;
17
18/// A decoded key event.
19pub enum KeyEvent {
20 Char(char),
21 Enter,
22 Backspace,
23 Tab,
24 Escape,
25 ArrowUp,
26 ArrowDown,
27 ArrowLeft,
28 ArrowRight,
29 Home,
30 End,
31 PageUp,
32 PageDown,
33 Delete,
34 F(u8),
35 Unknown(u8),
36}
37
38// ----- scancode-set-1 translation tables (US QWERTY) -----
39
40/// Normal (unshifted) scancode → ASCII mapping. 0 = unmapped.
41static SCAN_NORMAL: [u8; 128] = {
42 let mut t = [0u8; 128];
43 t[0x02] = b'1';
44 t[0x03] = b'2';
45 t[0x04] = b'3';
46 t[0x05] = b'4';
47 t[0x06] = b'5';
48 t[0x07] = b'6';
49 t[0x08] = b'7';
50 t[0x09] = b'8';
51 t[0x0A] = b'9';
52 t[0x0B] = b'0';
53 t[0x0C] = b'-';
54 t[0x0D] = b'=';
55 t[0x10] = b'q';
56 t[0x11] = b'w';
57 t[0x12] = b'e';
58 t[0x13] = b'r';
59 t[0x14] = b't';
60 t[0x15] = b'y';
61 t[0x16] = b'u';
62 t[0x17] = b'i';
63 t[0x18] = b'o';
64 t[0x19] = b'p';
65 t[0x1A] = b'[';
66 t[0x1B] = b']';
67 t[0x1E] = b'a';
68 t[0x1F] = b's';
69 t[0x20] = b'd';
70 t[0x21] = b'f';
71 t[0x22] = b'g';
72 t[0x23] = b'h';
73 t[0x24] = b'j';
74 t[0x25] = b'k';
75 t[0x26] = b'l';
76 t[0x27] = b';';
77 t[0x28] = b'\'';
78 t[0x29] = b'`';
79 t[0x2B] = b'\\';
80 t[0x2C] = b'z';
81 t[0x2D] = b'x';
82 t[0x2E] = b'c';
83 t[0x2F] = b'v';
84 t[0x30] = b'b';
85 t[0x31] = b'n';
86 t[0x32] = b'm';
87 t[0x33] = b',';
88 t[0x34] = b'.';
89 t[0x35] = b'/';
90 t[0x37] = b'*';
91 t[0x39] = b' ';
92 t
93};
94
95/// Shifted scancode → ASCII.
96static SCAN_SHIFT: [u8; 128] = {
97 let mut t = [0u8; 128];
98 t[0x02] = b'!';
99 t[0x03] = b'@';
100 t[0x04] = b'#';
101 t[0x05] = b'$';
102 t[0x06] = b'%';
103 t[0x07] = b'^';
104 t[0x08] = b'&';
105 t[0x09] = b'*';
106 t[0x0A] = b'(';
107 t[0x0B] = b')';
108 t[0x0C] = b'_';
109 t[0x0D] = b'+';
110 t[0x10] = b'Q';
111 t[0x11] = b'W';
112 t[0x12] = b'E';
113 t[0x13] = b'R';
114 t[0x14] = b'T';
115 t[0x15] = b'Y';
116 t[0x16] = b'U';
117 t[0x17] = b'I';
118 t[0x18] = b'O';
119 t[0x19] = b'P';
120 t[0x1A] = b'{';
121 t[0x1B] = b'}';
122 t[0x1E] = b'A';
123 t[0x1F] = b'S';
124 t[0x20] = b'D';
125 t[0x21] = b'F';
126 t[0x22] = b'G';
127 t[0x23] = b'H';
128 t[0x24] = b'J';
129 t[0x25] = b'K';
130 t[0x26] = b'L';
131 t[0x27] = b':';
132 t[0x28] = b'"';
133 t[0x29] = b'~';
134 t[0x2B] = b'|';
135 t[0x2C] = b'Z';
136 t[0x2D] = b'X';
137 t[0x2E] = b'C';
138 t[0x2F] = b'V';
139 t[0x30] = b'B';
140 t[0x31] = b'N';
141 t[0x32] = b'M';
142 t[0x33] = b'<';
143 t[0x34] = b'>';
144 t[0x35] = b'?';
145 t[0x37] = b'*';
146 t[0x39] = b' ';
147 t
148};
149
150/// Internal keyboard state
151struct KbdState {
152 modifiers: u8,
153 got_e0: bool,
154}
155
156static mut KBD: KbdState = KbdState {
157 modifiers: 0,
158 got_e0: false,
159};
160
161/// Initialise the PS/2 port: drain stale bytes + enable keyboard interrupt.
162pub fn init() {
163 drain_input();
164
165 // Enable the keyboard interrupt on the PS/2 controller.
166 // Read Configuration Byte (command 0x20), set bit 0 (IRQ1 enable),
167 // write back (command 0x60).
168 unsafe {
169 // Tell the keyboard controller we want to read the config byte.
170 // Need to wait for the controller to be ready first.
171 wait_write();
172 core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x20u8,
173 options(nostack, preserves_flags));
174 wait_read();
175 let mut cfg: u8;
176 core::arch::asm!("in al, dx", out("al") cfg, in("dx") 0x60u16,
177 options(nostack, preserves_flags));
178
179 cfg |= 0x01; // enable keyboard interrupt
180
181 // Write config byte back
182 wait_write();
183 core::arch::asm!("out dx, al", in("dx") 0x64u16, in("al") 0x60u8,
184 options(nostack, preserves_flags));
185 wait_write();
186 core::arch::asm!("out dx, al", in("dx") 0x60u16, in("al") cfg,
187 options(nostack, preserves_flags));
188 }
189}
190
191/// Wait until the PS/2 controller is ready to receive a command.
192fn wait_write() {
193 unsafe {
194 loop {
195 let status: u8;
196 core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16,
197 options(nostack, preserves_flags));
198 if status & 0x02 == 0 {
199 // Input buffer empty — controller ready to receive
200 break;
201 }
202 }
203 }
204}
205
206/// Wait until the PS/2 controller has data available to read.
207fn wait_read() {
208 unsafe {
209 loop {
210 let status: u8;
211 core::arch::asm!("in al, dx", out("al") status, in("dx") 0x64u16,
212 options(nostack, preserves_flags));
213 if status & 0x01 != 0 {
214 // Output buffer full — data ready to read
215 break;
216 }
217 }
218 }
219}
220
221fn drain_input() {
222 unsafe {
223 loop {
224 let status: u8;
225 core::arch::asm!("in al, dx", out("al") status, in("dx") STATUS_PORT,
226 options(nostack, preserves_flags));
227 if status & STAT_OUTPUT_FULL == 0 {
228 break;
229 }
230 let _data: u8;
231 core::arch::asm!("in al, dx", out("al") _data, in("dx") DATA_PORT,
232 options(nostack, preserves_flags));
233 }
234 }
235}
236
237/// Poll for a key event. Blocks via `hlt` until a key is pressed.
238/// The keyboard interrupt handler pushes raw scancodes to a ring buffer;
239/// this function pops and decodes them.
240pub fn poll_key() -> KeyEvent {
241 loop {
242 if let Some(byte) = crate::interrupts::read_scancode() {
243 if let Some(ev) = process_byte(byte) {
244 return ev;
245 }
246 } else {
247 unsafe {
248 core::arch::asm!("sti; hlt", options(nostack, preserves_flags));
249 }
250 }
251 }
252}
253
254/// Non-blocking poll — read from the interrupt ring buffer if available.
255pub fn try_poll_key() -> Option<KeyEvent> {
256 let byte = crate::interrupts::read_scancode()?;
257 process_byte(byte)
258}
259
260/// Process a single scancode byte. Returns Some(KeyEvent) when a visible
261/// event happens; returns None for modifier changes, releases, and prefixes.
262fn process_byte(byte: u8) -> Option<KeyEvent> {
263 let state = unsafe { &mut KBD };
264
265 if byte == 0xE0 {
266 state.got_e0 = true;
267 return None;
268 }
269
270 if state.got_e0 {
271 state.got_e0 = false;
272 return process_e0(byte);
273 }
274
275 // Break code (key release) — bit 7 set
276 if byte & 0x80 != 0 {
277 update_modifiers(byte & 0x7F, false, false);
278 return None;
279 }
280
281 // Make code (key press)
282 update_modifiers(byte, true, false);
283
284 match byte {
285 0x01 => Some(KeyEvent::Escape),
286 0x0E => Some(KeyEvent::Backspace),
287 0x0F => Some(KeyEvent::Tab),
288 0x1C => Some(KeyEvent::Enter),
289 0x37 => Some(KeyEvent::Char('*')),
290 0x3B..=0x44 => Some(KeyEvent::F(byte - 0x3B + 1)),
291 _ => {
292 let shifted = (state.modifiers & (MOD_LSHIFT | MOD_RSHIFT)) != 0;
293 // CapsLock inverts case for letters
294 let caps = state.modifiers & MOD_CAPS != 0;
295 let table = if shifted { SCAN_SHIFT } else { SCAN_NORMAL };
296 let mut ch = table[byte as usize];
297 // Handle CapsLock for letters
298 if caps && ch.is_ascii_alphabetic() {
299 // Swap case: if table gives lowercase, make uppercase; etc.
300 // Since normal table has lowercase, shift table has uppercase:
301 ch = if shifted {
302 ch.to_ascii_lowercase()
303 } else {
304 ch.to_ascii_uppercase()
305 };
306 }
307 if ch != 0 {
308 Some(KeyEvent::Char(ch as char))
309 } else {
310 Some(KeyEvent::Unknown(byte))
311 }
312 }
313 }
314}
315
316/// Process an E0-prefixed scancode.
317fn process_e0(byte: u8) -> Option<KeyEvent> {
318 if byte & 0x80 != 0 {
319 update_modifiers(byte & 0x7F, false, true);
320 return None;
321 }
322
323 update_modifiers(byte, true, true);
324
325 match byte {
326 0x48 => Some(KeyEvent::ArrowUp),
327 0x50 => Some(KeyEvent::ArrowDown),
328 0x4B => Some(KeyEvent::ArrowLeft),
329 0x4D => Some(KeyEvent::ArrowRight),
330 0x47 => Some(KeyEvent::Home),
331 0x4F => Some(KeyEvent::End),
332 0x49 => Some(KeyEvent::PageUp),
333 0x51 => Some(KeyEvent::PageDown),
334 0x53 => Some(KeyEvent::Delete),
335 0x35 => Some(KeyEvent::Char('/')),
336 _ => Some(KeyEvent::Unknown(byte)),
337 }
338}
339
340fn update_modifiers(scancode: u8, pressed: bool, e0: bool) {
341 let state = unsafe { &mut KBD };
342 match (scancode, e0) {
343 (0x2A, false) => set_mod(state, MOD_LSHIFT, pressed),
344 (0x36, false) => set_mod(state, MOD_RSHIFT, pressed),
345 (0x1D, false) => set_mod(state, MOD_LCTRL, pressed),
346 (0x1D, true) => set_mod(state, MOD_RCTRL, pressed),
347 (0x38, false) => set_mod(state, MOD_LALT, pressed),
348 (0x38, true) => set_mod(state, MOD_RALT, pressed),
349 (0x3A, false)
350 if pressed => {
351 state.modifiers ^= MOD_CAPS;
352 }
353 _ => {}
354 }
355}
356
357fn set_mod(state: &mut KbdState, bit: u8, pressed: bool) {
358 if pressed {
359 state.modifiers |= bit;
360 } else {
361 state.modifiers &= !bit;
362 }
363}
diff --git a/rsnix/src/serial.rs b/rsnix/src/serial.rs
new file mode 100644
index 0000000..03fa570
--- /dev/null
+++ b/rsnix/src/serial.rs
@@ -0,0 +1,118 @@
1/// UART 16550 serial driver — port-mapped I/O on COM1 (0x3F8)
2use core::fmt;
3
4const COM1: u16 = 0x3F8;
5
6const DATA_REG: u16 = COM1;
7const INTR_ENABLE: u16 = COM1 + 1;
8const FIFO_CONTROL: u16 = COM1 + 2;
9const LINE_CONTROL: u16 = COM1 + 3;
10const MODEM_CONTROL: u16 = COM1 + 4;
11const LINE_STATUS: u16 = COM1 + 5;
12
13const LSR_TX_EMPTY: u8 = 0x20;
14const LSR_DATA_READY: u8 = 0x01;
15
16/// Initialise the UART with 115200 baud, 8N1.
17pub fn init() {
18 unsafe {
19 // Disable interrupts
20 port_out(INTR_ENABLE, 0x00);
21 // Set DLAB=1 (baud rate divisor)
22 port_out(LINE_CONTROL, 0x80);
23 // Divisor = 1 → 115200 baud (with 1.8432 MHz clock)
24 port_out(DATA_REG, 0x01);
25 port_out(INTR_ENABLE, 0x00);
26 // 8-bit, 1 stop, no parity, DLAB=0
27 port_out(LINE_CONTROL, 0x03);
28 // Enable FIFO, clear, 14-byte threshold
29 port_out(FIFO_CONTROL, 0xC7);
30 // RTS/DSR set
31 port_out(MODEM_CONTROL, 0x0B);
32 }
33}
34
35/// Write a single byte to the serial port (blocking poll).
36pub fn put_byte(b: u8) {
37 unsafe {
38 // Wait for transmitter holding register empty
39 while port_in(LINE_STATUS) & LSR_TX_EMPTY == 0 {}
40 port_out(DATA_REG, b);
41 }
42}
43
44/// Read a single byte from the serial port (blocking poll).
45pub fn get_byte() -> u8 {
46 unsafe {
47 while port_in(LINE_STATUS) & LSR_DATA_READY == 0 {}
48 port_in(DATA_REG)
49 }
50}
51
52/// Non-blocking read: returns Some(byte) if data ready, None otherwise.
53pub fn try_get_byte() -> Option<u8> {
54 unsafe {
55 if port_in(LINE_STATUS) & LSR_DATA_READY != 0 {
56 Some(port_in(DATA_REG))
57 } else {
58 None
59 }
60 }
61}
62
63/// Write a string to serial.
64pub fn write_str(s: &str) {
65 for &b in s.as_bytes() {
66 if b == b'\n' {
67 put_byte(b'\r');
68 }
69 put_byte(b);
70 }
71}
72
73// ---------------------------------------------------------------------------
74// fmt::Write implementation
75// ---------------------------------------------------------------------------
76pub struct SerialWriter;
77
78impl fmt::Write for SerialWriter {
79 fn write_str(&mut self, s: &str) -> fmt::Result {
80 write_str(s);
81 Ok(())
82 }
83}
84
85/// Helper macro — prints to serial via format_args!.
86#[macro_export]
87macro_rules! serial_print {
88 ($($arg:tt)*) => {{
89 use core::fmt::Write;
90 let _ = write!(&mut $crate::serial::SerialWriter, $($arg)*);
91 }};
92}
93
94#[macro_export]
95macro_rules! serial_println {
96 () => { $crate::serial::write_str("\r\n"); };
97 ($($arg:tt)*) => {{
98 $crate::serial_print!($($arg)*);
99 $crate::serial::write_str("\r\n");
100 }};
101}
102
103// ---------------------------------------------------------------------------
104// Port I/O helpers
105// ---------------------------------------------------------------------------
106unsafe fn port_in(port: u16) -> u8 {
107 let val: u8;
108 unsafe {
109 core::arch::asm!("in al, dx", out("al") val, in("dx") port, options(nostack, preserves_flags));
110 }
111 val
112}
113
114unsafe fn port_out(port: u16, val: u8) {
115 unsafe {
116 core::arch::asm!("out dx, al", in("al") val, in("dx") port, options(nostack, preserves_flags));
117 }
118}
diff --git a/rsnix/src/uefi.rs b/rsnix/src/uefi.rs
new file mode 100644
index 0000000..2e3ed6b
--- /dev/null
+++ b/rsnix/src/uefi.rs
@@ -0,0 +1,289 @@
1use core::ffi::c_void;
2
3/// Opaque EFI handle
4pub type Handle = *mut c_void;
5
6/// EFI status (0 = success, bit 63 = error code)
7pub type Status = u64;
8
9/// Helper: UEFI error codes always have bit 63 set
10const fn efi_err(n: u64) -> u64 {
11 n | (1u64 << 63)
12}
13
14pub const EFI_SUCCESS: Status = 0;
15pub const EFI_LOAD_ERROR: Status = efi_err(1);
16pub const EFI_INVALID_PARAMETER: Status = efi_err(2);
17#[allow(dead_code)]
18pub const EFI_UNSUPPORTED: Status = efi_err(3);
19pub const EFI_BUFFER_TOO_SMALL: Status = efi_err(5);
20#[allow(dead_code)]
21pub const EFI_NOT_FOUND: Status = efi_err(14);
22pub const EFI_OUT_OF_RESOURCES: Status = efi_err(9);
23
24// ---------------------------------------------------------------------------
25// GUID
26// ---------------------------------------------------------------------------
27#[repr(C)]
28#[derive(Debug, Clone, Copy)]
29pub struct Guid {
30 pub a: u32,
31 pub b: u16,
32 pub c: u16,
33 pub d: [u8; 8],
34}
35
36pub const EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID: Guid = Guid {
37 a: 0x9042a9de,
38 b: 0x23dc,
39 c: 0x4a38,
40 d: [0x96, 0xfb, 0x7a, 0xde, 0xd0, 0x80, 0x51, 0x6a],
41};
42
43// ---------------------------------------------------------------------------
44// Table header
45// ---------------------------------------------------------------------------
46#[repr(C)]
47pub struct TableHeader {
48 pub signature: u64,
49 pub revision: u32,
50 pub header_size: u32,
51 pub crc32: u32,
52 pub reserved: u32,
53}
54
55// ---------------------------------------------------------------------------
56// Simple text output protocol (console output)
57// ---------------------------------------------------------------------------
58#[repr(C)]
59pub struct SimpleTextOutput {
60 _reset: *mut c_void,
61 pub output_string: unsafe extern "efiapi" fn(*mut SimpleTextOutput, *const u16) -> Status,
62 _test_string: *mut c_void,
63 _query_mode: *mut c_void,
64 _set_mode: *mut c_void,
65 _set_attribute: *mut c_void,
66 _clear_screen: *mut c_void,
67 _set_cursor_position: *mut c_void,
68 _enable_cursor: *mut c_void,
69 _mode: *mut c_void,
70}
71
72// ---------------------------------------------------------------------------
73// Simple text input protocol (keyboard input)
74// ---------------------------------------------------------------------------
75#[repr(C)]
76pub struct InputKey {
77 pub scan_code: u16,
78 pub unicode_char: u16,
79}
80
81#[repr(C)]
82pub struct SimpleTextInput {
83 _reset: *mut c_void,
84 pub read_key_stroke: unsafe extern "efiapi" fn(*mut SimpleTextInput, *mut InputKey) -> Status,
85 _wait_for_key: Handle,
86}
87
88// ---------------------------------------------------------------------------
89// Runtime services (stub — we don't use them)
90// ---------------------------------------------------------------------------
91#[repr(C)]
92pub struct RuntimeServices {
93 _hdr: TableHeader,
94 _reserved: [*mut c_void; 30],
95}
96
97// ---------------------------------------------------------------------------
98// Configuration table entry
99// ---------------------------------------------------------------------------
100#[repr(C)]
101pub struct ConfigurationTable {
102 pub vendor_guid: Guid,
103 pub vendor_table: *mut c_void,
104}
105
106// ---------------------------------------------------------------------------
107/// UEFI memory descriptor (from GetMemoryMap)
108// ---------------------------------------------------------------------------
109#[repr(C)]
110pub struct MemoryDescriptor {
111 pub kind: u32,
112 pub pad: u32,
113 pub physical_start: u64,
114 pub virtual_start: u64,
115 pub number_of_pages: u64,
116 pub attribute: u64,
117}
118
119pub const MEMORY_TYPE_CONVENTIONAL: u32 = 7;
120#[allow(dead_code)]
121pub const MEMORY_TYPE_LOADER_DATA: u32 = 6;
122#[allow(dead_code)]
123pub const MEMORY_TYPE_BOOT_SERVICES_CODE: u32 = 3;
124#[allow(dead_code)]
125pub const MEMORY_TYPE_BOOT_SERVICES_DATA: u32 = 4;
126
127// ---------------------------------------------------------------------------
128// Boot services table
129// ---------------------------------------------------------------------------
130pub type EfiGetMemoryMap = unsafe extern "efiapi" fn(
131 *mut usize,
132 *mut MemoryDescriptor,
133 *mut usize,
134 *mut usize,
135 *mut u32,
136) -> Status;
137
138pub type EfiExitBootServices = unsafe extern "efiapi" fn(Handle, usize) -> Status;
139
140pub type EfiHandleProtocol =
141 unsafe extern "efiapi" fn(Handle, *const Guid, *mut *mut c_void) -> Status;
142
143pub type EfiLocateProtocol =
144 unsafe extern "efiapi" fn(*const Guid, *mut c_void, *mut *mut c_void) -> Status;
145
146pub type EfiAllocatePool = unsafe extern "efiapi" fn(u8, usize, *mut *mut c_void) -> Status;
147
148pub type EfiFreePool = unsafe extern "efiapi" fn(*mut c_void) -> Status;
149
150pub type EfiStall = unsafe extern "efiapi" fn(usize) -> Status;
151
152#[repr(C)]
153pub struct BootServices {
154 pub hdr: TableHeader,
155
156 // 0-1: task priority
157 pub raise_tpl: *mut c_void,
158 pub restore_tpl: *mut c_void,
159
160 // 2-3: memory allocation
161 pub allocate_pages: *mut c_void,
162 pub free_pages: *mut c_void,
163
164 // 4: GetMemoryMap
165 pub get_memory_map: EfiGetMemoryMap,
166
167 // 5-6: pool
168 pub allocate_pool: EfiAllocatePool,
169 pub free_pool: EfiFreePool,
170
171 // 7-12: events
172 pub create_event: *mut c_void,
173 pub set_timer: *mut c_void,
174 pub wait_for_event: *mut c_void,
175 pub signal_event: *mut c_void,
176 pub close_event: *mut c_void,
177 pub check_event: *mut c_void,
178
179 // 13-14: protocol install
180 pub install_protocol_interface: *mut c_void,
181 pub reinstall_protocol_interface: *mut c_void,
182 pub uninstall_protocol_interface: *mut c_void,
183
184 // 16: HandleProtocol
185 pub handle_protocol: EfiHandleProtocol,
186
187 pub _reserved: *mut c_void,
188
189 // 18: register protocol notify
190 pub register_protocol_notify: *mut c_void,
191
192 // 19-21: locate
193 pub locate_handle: *mut c_void,
194 pub locate_device_path: *mut c_void,
195 pub install_configuration_table: *mut c_void,
196
197 // 22-23: image loading
198 pub load_image: *mut c_void,
199 pub start_image: *mut c_void,
200
201 // 24-25
202 pub exit: *mut c_void,
203 pub unload_image: *mut c_void,
204
205 // 26: ExitBootServices
206 pub exit_boot_services: EfiExitBootServices,
207
208 // 27-29
209 pub get_next_monotonic_count: *mut c_void,
210 pub stall: EfiStall,
211 pub set_watchdog_timer: *mut c_void,
212
213 // 30-36
214 pub connect_controller: *mut c_void,
215 pub disconnect_controller: *mut c_void,
216 pub open_protocol: *mut c_void,
217 pub close_protocol: *mut c_void,
218 pub open_protocol_information: *mut c_void,
219 pub protocols_per_handle: *mut c_void,
220 pub locate_handle_buffer: *mut c_void,
221
222 // 37: LocateProtocol
223 pub locate_protocol: EfiLocateProtocol,
224
225 // 38-43
226 pub install_multiple_protocol_interfaces: *mut c_void,
227 pub uninstall_multiple_protocol_interfaces: *mut c_void,
228 pub calculate_crc32: *mut c_void,
229 pub copy_mem: *mut c_void,
230 pub set_mem: *mut c_void,
231 pub create_event_ex: *mut c_void,
232}
233
234// ---------------------------------------------------------------------------
235// Graphics Output Protocol (GOP)
236// ---------------------------------------------------------------------------
237#[repr(C)]
238pub struct GopModeInfo {
239 pub version: u32,
240 pub horizontal_resolution: u32,
241 pub vertical_resolution: u32,
242 pub pixel_format: u32, // PixelFormat enum
243 pub red_mask: u32,
244 pub green_mask: u32,
245 pub blue_mask: u32,
246 pub reserved_mask: u32,
247 pub pixels_per_scan_line: u32,
248}
249
250pub const PIXEL_RGB: u32 = 0;
251pub const PIXEL_BGR: u32 = 1;
252
253#[repr(C)]
254pub struct GopMode {
255 pub max_mode: u32,
256 pub mode: u32,
257 pub info: *mut GopModeInfo,
258 pub info_size: usize,
259 pub framebuffer_base: *mut c_void,
260 pub framebuffer_size: usize,
261}
262
263#[repr(C)]
264pub struct GraphicsOutputProtocol {
265 pub query_mode: *mut c_void,
266 pub set_mode: *mut c_void,
267 pub get_current_mode: *mut c_void,
268 pub mode: *mut GopMode,
269}
270
271// ---------------------------------------------------------------------------
272// System table
273// ---------------------------------------------------------------------------
274#[repr(C)]
275pub struct SystemTable {
276 pub hdr: TableHeader,
277 pub firmware_vendor: *const u16,
278 pub firmware_revision: u32,
279 pub console_in_handle: Handle,
280 pub con_in: *mut SimpleTextInput,
281 pub console_out_handle: Handle,
282 pub con_out: *mut SimpleTextOutput,
283 pub stderr_handle: Handle,
284 pub std_err: *mut SimpleTextOutput,
285 pub runtime_services: *mut RuntimeServices,
286 pub boot_services: *mut BootServices,
287 pub number_of_table_entries: usize,
288 pub configuration_table: *const ConfigurationTable,
289}