diff options
Diffstat (limited to 'rsnix/src')
| -rw-r--r-- | rsnix/src/font.rs | 5 | ||||
| -rw-r--r-- | rsnix/src/gop.rs | 232 | ||||
| -rw-r--r-- | rsnix/src/interrupts.rs | 401 | ||||
| -rw-r--r-- | rsnix/src/lib.rs | 11 | ||||
| -rw-r--r-- | rsnix/src/memory.rs | 196 | ||||
| -rw-r--r-- | rsnix/src/ps2.rs | 363 | ||||
| -rw-r--r-- | rsnix/src/serial.rs | 118 | ||||
| -rw-r--r-- | rsnix/src/uefi.rs | 289 |
8 files changed, 1615 insertions, 0 deletions
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`). | ||
| 5 | include!(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. | ||
| 2 | use crate::font; | ||
| 3 | use crate::uefi::{GraphicsOutputProtocol, PIXEL_BGR, PIXEL_RGB}; | ||
| 4 | use core::fmt; | ||
| 5 | use core::ptr; | ||
| 6 | |||
| 7 | /// Framebuffer state captured before ExitBootServices. | ||
| 8 | pub 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 | |||
| 23 | static mut FB: Option<Framebuffer> = None; | ||
| 24 | |||
| 25 | /// Initialise the framebuffer from GOP. Call *before* ExitBootServices. | ||
| 26 | pub 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. | ||
| 67 | pub 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. | ||
| 121 | pub 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). | ||
| 128 | pub 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 | // --------------------------------------------------------------------------- | ||
| 144 | impl 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 | // --------------------------------------------------------------------------- | ||
| 207 | pub struct GopWriter; | ||
| 208 | |||
| 209 | impl 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] | ||
| 218 | macro_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] | ||
| 226 | macro_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 | // --------------------------------------------------------------------------- | ||
| 4 | use core::arch::asm; | ||
| 5 | use 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)] | ||
| 14 | struct 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). | ||
| 25 | unsafe 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). | ||
| 42 | static mut IDT: [u8; 256 * 16] = [0u8; 256 * 16]; | ||
| 43 | static mut IDTR: [u16; 5] = [0; 5]; // 2-byte limit + 4-byte base (48-bit pseudo-desc) | ||
| 44 | |||
| 45 | // --------------------------------------------------------------------------- | ||
| 46 | // PIC (8259A) | ||
| 47 | // --------------------------------------------------------------------------- | ||
| 48 | |||
| 49 | const PIC1_CMD: u16 = 0x20; | ||
| 50 | const PIC1_DATA: u16 = 0x21; | ||
| 51 | const PIC2_CMD: u16 = 0xA0; | ||
| 52 | const PIC2_DATA: u16 = 0xA1; | ||
| 53 | |||
| 54 | const PIC_EOI: u8 = 0x20; | ||
| 55 | |||
| 56 | const ICW1: u8 = 0x11; // edge, cascade, icw4 | ||
| 57 | const ICW4: u8 = 0x01; // x86 mode | ||
| 58 | |||
| 59 | /// Remap PIC IRQs 0-15 to IDT vectors 32-47, then mask all. | ||
| 60 | unsafe 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). | ||
| 76 | fn 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 | |||
| 92 | const PIT_CMD: u16 = 0x43; | ||
| 93 | const PIT_CH0: u16 = 0x40; | ||
| 94 | const PIT_FREQ: u64 = 1_193_182; | ||
| 95 | |||
| 96 | /// Set up PIT channel 0 in mode 2 (rate generator) at `hz` (1–1000ish). | ||
| 97 | fn 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. | ||
| 115 | struct 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 | |||
| 121 | unsafe impl Sync for KeyBuf {} | ||
| 122 | |||
| 123 | impl 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 | |||
| 200 | static KEYBUF: KeyBuf = KeyBuf::new(); | ||
| 201 | |||
| 202 | // --------------------------------------------------------------------------- | ||
| 203 | // Interrupt stubs (assembly) and dispatch | ||
| 204 | // --------------------------------------------------------------------------- | ||
| 205 | |||
| 206 | core::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)] | ||
| 272 | struct 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] | ||
| 297 | unsafe 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). | ||
| 315 | fn timer_irq() { | ||
| 316 | eoi(0); | ||
| 317 | } | ||
| 318 | |||
| 319 | /// Keyboard IRQ — read scancode from port 0x60 and push to ring buffer. | ||
| 320 | fn 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). | ||
| 335 | pub fn read_scancode() -> Option<u8> { | ||
| 336 | KEYBUF.pop() | ||
| 337 | } | ||
| 338 | |||
| 339 | /// Global tick counter (incremented by timer IRQ, ~100 Hz). | ||
| 340 | pub static TICKS: AtomicU16 = AtomicU16::new(0); | ||
| 341 | |||
| 342 | /// Initialise the IDT, remap the PIC, set up the PIT, and enable interrupts. | ||
| 343 | pub 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 | // --------------------------------------------------------------------------- | ||
| 397 | extern "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 | |||
| 3 | extern crate alloc; | ||
| 4 | |||
| 5 | pub mod uefi; | ||
| 6 | pub mod serial; | ||
| 7 | pub mod gop; | ||
| 8 | pub mod font; | ||
| 9 | pub mod ps2; | ||
| 10 | pub mod interrupts; | ||
| 11 | pub 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 @@ | |||
| 1 | use crate::uefi::*; | ||
| 2 | use core::alloc::{GlobalAlloc, Layout}; | ||
| 3 | use core::mem; | ||
| 4 | use core::ptr; | ||
| 5 | |||
| 6 | const MAX_MAP_ENTRIES: usize = 512; | ||
| 7 | |||
| 8 | static mut MEMORY_MAP: [MemoryDescriptor; MAX_MAP_ENTRIES] = unsafe { mem::zeroed() }; | ||
| 9 | static mut MAP_ENTRIES: usize = 0; | ||
| 10 | |||
| 11 | /// The map key returned by GetMemoryMap, needed for ExitBootServices. | ||
| 12 | static 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. | ||
| 16 | static 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 | // --------------------------------------------------------------------------- | ||
| 22 | struct BumpAllocator; | ||
| 23 | |||
| 24 | unsafe impl Sync for BumpAllocator {} | ||
| 25 | |||
| 26 | unsafe 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] | ||
| 52 | static ALLOCATOR: BumpAllocator = BumpAllocator; | ||
| 53 | |||
| 54 | /// Heap bump allocator bounds (`static mut` accessed only inside | ||
| 55 | /// `GlobalAlloc` impl, which is already `unsafe`). | ||
| 56 | static mut HEAP_START: usize = 0; | ||
| 57 | static mut HEAP_END: usize = 0; | ||
| 58 | static 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. | ||
| 63 | pub 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. | ||
| 139 | pub 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 | |||
| 149 | fn 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)] | ||
| 176 | pub 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)] | ||
| 189 | pub 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 | ||
| 3 | const DATA_PORT: u16 = 0x60; | ||
| 4 | const STATUS_PORT: u16 = 0x64; | ||
| 5 | |||
| 6 | // Status register bits | ||
| 7 | const STAT_OUTPUT_FULL: u8 = 0x01; | ||
| 8 | |||
| 9 | // Modifier bit constants | ||
| 10 | const MOD_LSHIFT: u8 = 0x01; | ||
| 11 | const MOD_RSHIFT: u8 = 0x02; | ||
| 12 | const MOD_LCTRL: u8 = 0x04; | ||
| 13 | const MOD_RCTRL: u8 = 0x08; | ||
| 14 | const MOD_LALT: u8 = 0x10; | ||
| 15 | const MOD_RALT: u8 = 0x20; | ||
| 16 | const MOD_CAPS: u8 = 0x40; | ||
| 17 | |||
| 18 | /// A decoded key event. | ||
| 19 | pub 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. | ||
| 41 | static 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. | ||
| 96 | static 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 | ||
| 151 | struct KbdState { | ||
| 152 | modifiers: u8, | ||
| 153 | got_e0: bool, | ||
| 154 | } | ||
| 155 | |||
| 156 | static 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. | ||
| 162 | pub 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. | ||
| 192 | fn 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. | ||
| 207 | fn 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 | |||
| 221 | fn 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. | ||
| 240 | pub 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. | ||
| 255 | pub 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. | ||
| 262 | fn 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. | ||
| 317 | fn 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 | |||
| 340 | fn 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 | |||
| 357 | fn 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) | ||
| 2 | use core::fmt; | ||
| 3 | |||
| 4 | const COM1: u16 = 0x3F8; | ||
| 5 | |||
| 6 | const DATA_REG: u16 = COM1; | ||
| 7 | const INTR_ENABLE: u16 = COM1 + 1; | ||
| 8 | const FIFO_CONTROL: u16 = COM1 + 2; | ||
| 9 | const LINE_CONTROL: u16 = COM1 + 3; | ||
| 10 | const MODEM_CONTROL: u16 = COM1 + 4; | ||
| 11 | const LINE_STATUS: u16 = COM1 + 5; | ||
| 12 | |||
| 13 | const LSR_TX_EMPTY: u8 = 0x20; | ||
| 14 | const LSR_DATA_READY: u8 = 0x01; | ||
| 15 | |||
| 16 | /// Initialise the UART with 115200 baud, 8N1. | ||
| 17 | pub 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). | ||
| 36 | pub 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). | ||
| 45 | pub 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. | ||
| 53 | pub 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. | ||
| 64 | pub 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 | // --------------------------------------------------------------------------- | ||
| 76 | pub struct SerialWriter; | ||
| 77 | |||
| 78 | impl 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] | ||
| 87 | macro_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] | ||
| 95 | macro_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 | // --------------------------------------------------------------------------- | ||
| 106 | unsafe 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 | |||
| 114 | unsafe 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 @@ | |||
| 1 | use core::ffi::c_void; | ||
| 2 | |||
| 3 | /// Opaque EFI handle | ||
| 4 | pub type Handle = *mut c_void; | ||
| 5 | |||
| 6 | /// EFI status (0 = success, bit 63 = error code) | ||
| 7 | pub type Status = u64; | ||
| 8 | |||
| 9 | /// Helper: UEFI error codes always have bit 63 set | ||
| 10 | const fn efi_err(n: u64) -> u64 { | ||
| 11 | n | (1u64 << 63) | ||
| 12 | } | ||
| 13 | |||
| 14 | pub const EFI_SUCCESS: Status = 0; | ||
| 15 | pub const EFI_LOAD_ERROR: Status = efi_err(1); | ||
| 16 | pub const EFI_INVALID_PARAMETER: Status = efi_err(2); | ||
| 17 | #[allow(dead_code)] | ||
| 18 | pub const EFI_UNSUPPORTED: Status = efi_err(3); | ||
| 19 | pub const EFI_BUFFER_TOO_SMALL: Status = efi_err(5); | ||
| 20 | #[allow(dead_code)] | ||
| 21 | pub const EFI_NOT_FOUND: Status = efi_err(14); | ||
| 22 | pub const EFI_OUT_OF_RESOURCES: Status = efi_err(9); | ||
| 23 | |||
| 24 | // --------------------------------------------------------------------------- | ||
| 25 | // GUID | ||
| 26 | // --------------------------------------------------------------------------- | ||
| 27 | #[repr(C)] | ||
| 28 | #[derive(Debug, Clone, Copy)] | ||
| 29 | pub struct Guid { | ||
| 30 | pub a: u32, | ||
| 31 | pub b: u16, | ||
| 32 | pub c: u16, | ||
| 33 | pub d: [u8; 8], | ||
| 34 | } | ||
| 35 | |||
| 36 | pub 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)] | ||
| 47 | pub 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)] | ||
| 59 | pub 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)] | ||
| 76 | pub struct InputKey { | ||
| 77 | pub scan_code: u16, | ||
| 78 | pub unicode_char: u16, | ||
| 79 | } | ||
| 80 | |||
| 81 | #[repr(C)] | ||
| 82 | pub 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)] | ||
| 92 | pub struct RuntimeServices { | ||
| 93 | _hdr: TableHeader, | ||
| 94 | _reserved: [*mut c_void; 30], | ||
| 95 | } | ||
| 96 | |||
| 97 | // --------------------------------------------------------------------------- | ||
| 98 | // Configuration table entry | ||
| 99 | // --------------------------------------------------------------------------- | ||
| 100 | #[repr(C)] | ||
| 101 | pub 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)] | ||
| 110 | pub 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 | |||
| 119 | pub const MEMORY_TYPE_CONVENTIONAL: u32 = 7; | ||
| 120 | #[allow(dead_code)] | ||
| 121 | pub const MEMORY_TYPE_LOADER_DATA: u32 = 6; | ||
| 122 | #[allow(dead_code)] | ||
| 123 | pub const MEMORY_TYPE_BOOT_SERVICES_CODE: u32 = 3; | ||
| 124 | #[allow(dead_code)] | ||
| 125 | pub const MEMORY_TYPE_BOOT_SERVICES_DATA: u32 = 4; | ||
| 126 | |||
| 127 | // --------------------------------------------------------------------------- | ||
| 128 | // Boot services table | ||
| 129 | // --------------------------------------------------------------------------- | ||
| 130 | pub type EfiGetMemoryMap = unsafe extern "efiapi" fn( | ||
| 131 | *mut usize, | ||
| 132 | *mut MemoryDescriptor, | ||
| 133 | *mut usize, | ||
| 134 | *mut usize, | ||
| 135 | *mut u32, | ||
| 136 | ) -> Status; | ||
| 137 | |||
| 138 | pub type EfiExitBootServices = unsafe extern "efiapi" fn(Handle, usize) -> Status; | ||
| 139 | |||
| 140 | pub type EfiHandleProtocol = | ||
| 141 | unsafe extern "efiapi" fn(Handle, *const Guid, *mut *mut c_void) -> Status; | ||
| 142 | |||
| 143 | pub type EfiLocateProtocol = | ||
| 144 | unsafe extern "efiapi" fn(*const Guid, *mut c_void, *mut *mut c_void) -> Status; | ||
| 145 | |||
| 146 | pub type EfiAllocatePool = unsafe extern "efiapi" fn(u8, usize, *mut *mut c_void) -> Status; | ||
| 147 | |||
| 148 | pub type EfiFreePool = unsafe extern "efiapi" fn(*mut c_void) -> Status; | ||
| 149 | |||
| 150 | pub type EfiStall = unsafe extern "efiapi" fn(usize) -> Status; | ||
| 151 | |||
| 152 | #[repr(C)] | ||
| 153 | pub 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)] | ||
| 238 | pub 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 | |||
| 250 | pub const PIXEL_RGB: u32 = 0; | ||
| 251 | pub const PIXEL_BGR: u32 = 1; | ||
| 252 | |||
| 253 | #[repr(C)] | ||
| 254 | pub 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)] | ||
| 264 | pub 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)] | ||
| 275 | pub 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 | } | ||
