diff options
| author | salmonik <salmonik@tuta.io> | 2026-07-31 02:58:31 +0200 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-07-31 02:58:31 +0200 |
| commit | 6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b (patch) | |
| tree | ed59a5bd59a94b3a731b28c68c507f18afc44b0b | |
| parent | auto-version all strings from Cargo.toml via env!("CARGO_PKG_VERSION") (diff) | |
| download | rs-dos-6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b.tar.gz rs-dos-6a5c32fa6ae8ec34e3e5b44d7e5a78ded8fe729b.zip | |
restructure: rsnix kernel + shell crate + rsdos entry point
Architecture:
rs-dos/ (workspace root, was raisernics/)
├── rsnix/ — kernel lib crate (uefi, serial, gop, ps2, interrupts, memory)
├── shell/ — shell lib crate (prompt, commands, line input)
├── rsdos/ — binary crate (efi_main, init, panic handler)
└── rsboot/ — future git submodule
No functional changes — boots to shell prompt identically.
Version auto-pulled from Cargo.toml via env!("CARGO_PKG_VERSION").
| -rw-r--r-- | Cargo.lock | 17 | ||||
| -rw-r--r-- | Cargo.toml | 13 | ||||
| -rw-r--r-- | Makefile | 22 | ||||
| -rw-r--r-- | rsdos/Cargo.toml | 8 | ||||
| -rw-r--r-- | rsdos/src/main.rs | 138 | ||||
| -rw-r--r-- | rsnix/Cargo.toml | 4 | ||||
| -rw-r--r-- | rsnix/build.rs (renamed from build.rs) | 0 | ||||
| -rw-r--r-- | rsnix/src/font.rs (renamed from src/font.rs) | 0 | ||||
| -rw-r--r-- | rsnix/src/gop.rs (renamed from src/gop.rs) | 0 | ||||
| -rw-r--r-- | rsnix/src/interrupts.rs (renamed from src/interrupts.rs) | 4 | ||||
| -rw-r--r-- | rsnix/src/lib.rs | 11 | ||||
| -rw-r--r-- | rsnix/src/memory.rs (renamed from src/memory.rs) | 2 | ||||
| -rw-r--r-- | rsnix/src/ps2.rs (renamed from src/ps2.rs) | 5 | ||||
| -rw-r--r-- | rsnix/src/serial.rs (renamed from src/serial.rs) | 0 | ||||
| -rw-r--r-- | rsnix/src/uefi.rs (renamed from src/uefi.rs) | 0 | ||||
| -rw-r--r-- | shell/Cargo.toml | 7 | ||||
| -rw-r--r-- | shell/src/lib.rs (renamed from src/shell.rs) | 42 | ||||
| -rw-r--r-- | src/main.rs | 186 |
18 files changed, 229 insertions, 230 deletions
| @@ -3,5 +3,20 @@ | |||
| 3 | version = 4 | 3 | version = 4 |
| 4 | 4 | ||
| 5 | [[package]] | 5 | [[package]] |
| 6 | name = "raisernics" | 6 | name = "rsdos" |
| 7 | version = "0.2.0" | 7 | version = "0.2.0" |
| 8 | dependencies = [ | ||
| 9 | "rsnix", | ||
| 10 | "shell", | ||
| 11 | ] | ||
| 12 | |||
| 13 | [[package]] | ||
| 14 | name = "rsnix" | ||
| 15 | version = "0.2.0" | ||
| 16 | |||
| 17 | [[package]] | ||
| 18 | name = "shell" | ||
| 19 | version = "0.2.0" | ||
| 20 | dependencies = [ | ||
| 21 | "rsnix", | ||
| 22 | ] | ||
| @@ -1,10 +1,7 @@ | |||
| 1 | [package] | 1 | [workspace] |
| 2 | name = "raisernics" | 2 | resolver = "2" |
| 3 | members = ["rsnix", "shell", "rsdos"] | ||
| 4 | |||
| 5 | [workspace.package] | ||
| 3 | version = "0.2.0" | 6 | version = "0.2.0" |
| 4 | edition = "2021" | 7 | edition = "2021" |
| 5 | |||
| 6 | [profile.release] | ||
| 7 | lto = true | ||
| 8 | codegen-units = 1 | ||
| 9 | strip = "symbols" | ||
| 10 | opt-level = "z" | ||
| @@ -4,10 +4,10 @@ export TARGET=x86_64-unknown-uefi | |||
| 4 | export PROFILE=debug | 4 | export PROFILE=debug |
| 5 | export OUT_DIR=target/$(TARGET)/$(PROFILE) | 5 | export OUT_DIR=target/$(TARGET)/$(PROFILE) |
| 6 | export ESP_DIR=esp | 6 | export ESP_DIR=esp |
| 7 | export KERNEL=$(OUT_DIR)/raisernics.efi | 7 | export KERNEL=$(OUT_DIR)/rsdos.efi |
| 8 | export BZIMAGE=$(ESP_DIR)/bzImage | 8 | export BZIMAGE=$(ESP_DIR)/bzImage |
| 9 | export INITRD=$(ESP_DIR)/initramfs.cpio.gz | 9 | export INITRD=$(ESP_DIR)/initramfs.cpio.gz |
| 10 | export BOOTLOADER=../rng/rsbl/../build/bootloader/BOOTX64.EFI | 10 | export BOOTLOADER=rsboot/rsboot.efi |
| 11 | export OVMF=/usr/share/edk2/x64/OVMF.4m.fd | 11 | export OVMF=/usr/share/edk2/x64/OVMF.4m.fd |
| 12 | export BOOT_EFI=$(ESP_DIR)/EFI/BOOT/BOOTX64.EFI | 12 | export BOOT_EFI=$(ESP_DIR)/EFI/BOOT/BOOTX64.EFI |
| 13 | 13 | ||
| @@ -15,7 +15,7 @@ export BOOT_EFI=$(ESP_DIR)/EFI/BOOT/BOOTX64.EFI | |||
| 15 | 15 | ||
| 16 | # ---- Build --------------------------------------------------------------- | 16 | # ---- Build --------------------------------------------------------------- |
| 17 | 17 | ||
| 18 | all: $(BZIMAGE) $(INITRD) $(BOOT_EFI) | 18 | all: $(BZIMAGE) $(INITRD) |
| 19 | 19 | ||
| 20 | release: PROFILE=release | 20 | release: PROFILE=release |
| 21 | release: export PROFILE=release | 21 | release: export PROFILE=release |
| @@ -26,16 +26,12 @@ release: all | |||
| 26 | # shellcheck disable=SC2034 | 26 | # shellcheck disable=SC2034 |
| 27 | CARGO_FLAGS= | 27 | CARGO_FLAGS= |
| 28 | 28 | ||
| 29 | $(OUT_DIR)/raisernics.efi: $(shell find src -name '*.rs') Cargo.toml build.rs | 29 | $(OUT_DIR)/rsdos.efi: $(shell find rsnix shell rsdos -name '*.rs') Cargo.toml rsnix/Cargo.toml shell/Cargo.toml rsdos/Cargo.toml |
| 30 | cargo build --target $(TARGET) $(CARGO_FLAGS) | 30 | cargo build --package rsdos --target $(TARGET) $(CARGO_FLAGS) |
| 31 | 31 | ||
| 32 | $(BZIMAGE): $(KERNEL) | $(ESP_DIR) | 32 | $(BZIMAGE): $(KERNEL) | $(ESP_DIR) |
| 33 | cp "$<" "$@" | 33 | cp "$<" "$@" |
| 34 | 34 | ||
| 35 | $(BOOT_EFI): $(BOOTLOADER) | $(ESP_DIR) | ||
| 36 | mkdir -p $(ESP_DIR)/EFI/BOOT | ||
| 37 | cp "$<" "$@" | ||
| 38 | |||
| 39 | $(ESP_DIR): | 35 | $(ESP_DIR): |
| 40 | mkdir -p $(ESP_DIR) | 36 | mkdir -p $(ESP_DIR) |
| 41 | 37 | ||
| @@ -95,7 +91,7 @@ run-image: all | |||
| 95 | # ---- Disk image ---------------------------------------------------------- | 91 | # ---- Disk image ---------------------------------------------------------- |
| 96 | 92 | ||
| 97 | image: all | 93 | image: all |
| 98 | ./tools/boot-image.sh raisernics.img | 94 | ./tools/boot-image.sh rs-dos.img |
| 99 | 95 | ||
| 100 | # ---- Utilities ----------------------------------------------------------- | 96 | # ---- Utilities ----------------------------------------------------------- |
| 101 | 97 | ||
| @@ -104,14 +100,14 @@ fmt: | |||
| 104 | 100 | ||
| 105 | clean: | 101 | clean: |
| 106 | cargo clean | 102 | cargo clean |
| 107 | rm -rf $(ESP_DIR) raisernics.img | 103 | rm -rf $(ESP_DIR) rs-dos.img |
| 108 | 104 | ||
| 109 | help: | 105 | help: |
| 110 | @echo "raisernics — Make targets" | 106 | @echo "rs-dos — Make targets" |
| 111 | @echo "" | 107 | @echo "" |
| 112 | @echo " make build kernel + ESP (default)" | 108 | @echo " make build kernel + ESP (default)" |
| 113 | @echo " make release build with --release (smaller binary)" | 109 | @echo " make release build with --release (smaller binary)" |
| 114 | @echo " make image create bootable USB image (raisernics.img)" | 110 | @echo " make image create bootable USB image (rs-dos.img)" |
| 115 | @echo " make run QEMU serial console (no graphics)" | 111 | @echo " make run QEMU serial console (no graphics)" |
| 116 | @echo " make run-gui QEMU with graphical window (GOP/PS2)" | 112 | @echo " make run-gui QEMU with graphical window (GOP/PS2)" |
| 117 | @echo " make debug QEMU + GDB stub on :1234" | 113 | @echo " make debug QEMU + GDB stub on :1234" |
diff --git a/rsdos/Cargo.toml b/rsdos/Cargo.toml new file mode 100644 index 0000000..5bf83fe --- /dev/null +++ b/rsdos/Cargo.toml | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | [package] | ||
| 2 | name = "rsdos" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | rsnix = { path = "../rsnix" } | ||
| 8 | shell = { path = "../shell" } | ||
diff --git a/rsdos/src/main.rs b/rsdos/src/main.rs new file mode 100644 index 0000000..543556b --- /dev/null +++ b/rsdos/src/main.rs | |||
| @@ -0,0 +1,138 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![allow(static_mut_refs, unsafe_code)] | ||
| 4 | |||
| 5 | use core::panic::PanicInfo; | ||
| 6 | use rsnix::uefi::*; | ||
| 7 | |||
| 8 | // --------------------------------------------------------------------------- | ||
| 9 | // Panic handler | ||
| 10 | // --------------------------------------------------------------------------- | ||
| 11 | #[panic_handler] | ||
| 12 | fn panic(info: &PanicInfo) -> ! { | ||
| 13 | use core::fmt::Write; | ||
| 14 | rsnix::serial::write_str("\r\n*** PANIC ***\r\n"); | ||
| 15 | let _ = write!(&mut rsnix::serial::SerialWriter, "{}\r\n", info.message()); | ||
| 16 | if let Some(loc) = info.location() { | ||
| 17 | let _ = write!( | ||
| 18 | &mut rsnix::serial::SerialWriter, | ||
| 19 | " at {}:{}\r\n", | ||
| 20 | loc.file(), | ||
| 21 | loc.line() | ||
| 22 | ); | ||
| 23 | } | ||
| 24 | loop { | ||
| 25 | unsafe { | ||
| 26 | core::arch::asm!("cli; hlt"); | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
| 30 | |||
| 31 | // --------------------------------------------------------------------------- | ||
| 32 | // EFI entry point | ||
| 33 | // --------------------------------------------------------------------------- | ||
| 34 | #[no_mangle] | ||
| 35 | pub extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemTable) -> ! { | ||
| 36 | let st = unsafe { &mut *system_table }; | ||
| 37 | let bs = unsafe { &mut *st.boot_services }; | ||
| 38 | let con_out = unsafe { &mut *st.con_out }; | ||
| 39 | |||
| 40 | // ---- Phase 1: UEFI boot services alive ---- | ||
| 41 | |||
| 42 | // Print banner via UEFI console | ||
| 43 | unsafe { | ||
| 44 | let s = concat!("rsnix v", env!("CARGO_PKG_VERSION"), " booting..\n"); | ||
| 45 | let bytes = s.as_bytes(); | ||
| 46 | let mut buf: [u16; 48] = [0; 48]; | ||
| 47 | for (i, &b) in bytes.iter().enumerate().take(47) { | ||
| 48 | buf[i] = b as u16; | ||
| 49 | } | ||
| 50 | (con_out.output_string)(con_out, buf.as_ptr()); | ||
| 51 | } | ||
| 52 | |||
| 53 | rsnix::serial::init(); | ||
| 54 | rsnix::serial_println!("=== rsnix boot ==="); | ||
| 55 | |||
| 56 | // Stash the memory map | ||
| 57 | let map_key = match rsnix::memory::capture_memory_map(bs) { | ||
| 58 | Ok(k) => k, | ||
| 59 | Err(e) => { | ||
| 60 | rsnix::serial_println!("mem: capture_memory_map failed 0x{:x}", e); | ||
| 61 | 0 | ||
| 62 | } | ||
| 63 | }; | ||
| 64 | |||
| 65 | // Locate GOP (Graphics Output Protocol) | ||
| 66 | let mut gop_interface: *mut core::ffi::c_void = core::ptr::null_mut(); | ||
| 67 | let gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; | ||
| 68 | let gop_result = unsafe { | ||
| 69 | (bs.locate_protocol)( | ||
| 70 | &gop_guid as *const Guid, | ||
| 71 | core::ptr::null_mut(), | ||
| 72 | &mut gop_interface, | ||
| 73 | ) | ||
| 74 | }; | ||
| 75 | if gop_result == EFI_SUCCESS { | ||
| 76 | let gop = unsafe { &mut *(gop_interface as *mut GraphicsOutputProtocol) }; | ||
| 77 | rsnix::serial_println!("gop: framebuffer found"); | ||
| 78 | rsnix::gop::init(gop); | ||
| 79 | } else { | ||
| 80 | rsnix::serial_println!("gop: not found (0x{:x})", gop_result); | ||
| 81 | } | ||
| 82 | |||
| 83 | // ExitBootServices — we now own the machine | ||
| 84 | if unsafe { (bs.exit_boot_services)(image_handle, map_key) } != EFI_SUCCESS { | ||
| 85 | rsnix::serial_println!("WARN: ExitBootServices failed, retrying..."); | ||
| 86 | match rsnix::memory::refresh_and_exit(bs, image_handle) { | ||
| 87 | Ok(_) => {} | ||
| 88 | Err(e) => { | ||
| 89 | rsnix::serial_println!("FATAL: cannot ExitBootServices (0x{:x})", e); | ||
| 90 | loop { | ||
| 91 | unsafe { | ||
| 92 | core::arch::asm!("cli; hlt"); | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | } | ||
| 98 | |||
| 99 | // ---- Phase 2: Native mode ---- | ||
| 100 | rsnix::serial::init(); | ||
| 101 | rsnix::serial_println!("serial: COM1 115200 8N1"); | ||
| 102 | rsnix::ps2::init(); | ||
| 103 | rsnix::serial_println!("ps2: keyboard initialized"); | ||
| 104 | rsnix::interrupts::init(); | ||
| 105 | rsnix::serial_println!("intr: IDT loaded, PIC remapped, timer ~100 Hz"); | ||
| 106 | |||
| 107 | dump_memory_summary(); | ||
| 108 | |||
| 109 | rsnix::serial_println!("boot: entering shell"); | ||
| 110 | shell::run(); | ||
| 111 | } | ||
| 112 | |||
| 113 | // --------------------------------------------------------------------------- | ||
| 114 | // Print a memory summary from the stashed memory map | ||
| 115 | // --------------------------------------------------------------------------- | ||
| 116 | fn dump_memory_summary() { | ||
| 117 | let (map, key) = rsnix::memory::get_map(); | ||
| 118 | let mut conventional_4k = 0u64; | ||
| 119 | let mut addr_space_4k = 0u64; | ||
| 120 | for desc in map.iter() { | ||
| 121 | let pages = desc.number_of_pages; | ||
| 122 | addr_space_4k += pages; | ||
| 123 | if desc.kind == 7 { | ||
| 124 | conventional_4k += pages; | ||
| 125 | } | ||
| 126 | } | ||
| 127 | rsnix::serial_println!( | ||
| 128 | "mem: {} entries, key=0x{:x}, usable: {} KB ({} MB)", | ||
| 129 | map.len(), | ||
| 130 | key, | ||
| 131 | conventional_4k * 4, | ||
| 132 | (conventional_4k * 4) / 1024, | ||
| 133 | ); | ||
| 134 | rsnix::serial_println!( | ||
| 135 | "mem: address space: {} MB (incl. PCIe MMIO)", | ||
| 136 | (addr_space_4k * 4) / 1024, | ||
| 137 | ); | ||
| 138 | } | ||
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] | ||
| 2 | name = "rsnix" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
diff --git a/build.rs b/rsnix/build.rs index 35447e9..35447e9 100644 --- a/build.rs +++ b/rsnix/build.rs | |||
diff --git a/src/font.rs b/rsnix/src/font.rs index e188513..e188513 100644 --- a/src/font.rs +++ b/rsnix/src/font.rs | |||
diff --git a/src/gop.rs b/rsnix/src/gop.rs index 9645bb0..9645bb0 100644 --- a/src/gop.rs +++ b/rsnix/src/gop.rs | |||
diff --git a/src/interrupts.rs b/rsnix/src/interrupts.rs index 90d5bbb..8f2acd7 100644 --- a/src/interrupts.rs +++ b/rsnix/src/interrupts.rs | |||
| @@ -174,7 +174,7 @@ impl KeyBuf { | |||
| 174 | let h = self.head.load(Ordering::Acquire); | 174 | let h = self.head.load(Ordering::Acquire); |
| 175 | let next = t.wrapping_add(1); | 175 | let next = t.wrapping_add(1); |
| 176 | // Full if the distance between head and tail == capacity (256). | 176 | // Full if the distance between head and tail == capacity (256). |
| 177 | if next.wrapping_sub(h) as u16 > 255 { | 177 | if next.wrapping_sub(h) > 255 { |
| 178 | return; // drop — ring is full | 178 | return; // drop — ring is full |
| 179 | } | 179 | } |
| 180 | unsafe { | 180 | unsafe { |
| @@ -362,7 +362,7 @@ pub fn init() { | |||
| 362 | 362 | ||
| 363 | // Build the IDTR pseudo-descriptor. | 363 | // Build the IDTR pseudo-descriptor. |
| 364 | IDTR = [ | 364 | IDTR = [ |
| 365 | (256u16 * 16 - 1) as u16, // limit = 256*16 - 1 | 365 | ((256u16 * 16 - 1)), // limit = 256*16 - 1 |
| 366 | idt_ptr as u16, // base lo | 366 | idt_ptr as u16, // base lo |
| 367 | (idt_ptr as u64 >> 16) as u16, // base mid | 367 | (idt_ptr as u64 >> 16) as u16, // base mid |
| 368 | (idt_ptr as u64 >> 32) as u16, // base hi | 368 | (idt_ptr as u64 >> 32) as u16, // base hi |
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/src/memory.rs b/rsnix/src/memory.rs index d223f69..39fd88d 100644 --- a/src/memory.rs +++ b/rsnix/src/memory.rs | |||
| @@ -85,7 +85,7 @@ pub fn capture_memory_map(bs: &BootServices) -> Result<usize, Status> { | |||
| 85 | } | 85 | } |
| 86 | 86 | ||
| 87 | // Round up to desc_size alignment | 87 | // Round up to desc_size alignment |
| 88 | let buf_size = (buf_size + desc_size - 1) / desc_size * desc_size; | 88 | let buf_size = buf_size.div_ceil(desc_size) * desc_size; |
| 89 | 89 | ||
| 90 | // If our static buffer is too small, fall back to pool (rare) | 90 | // If our static buffer is too small, fall back to pool (rare) |
| 91 | if buf_size > unsafe { MAP_WORK_BUF.len() } { | 91 | if buf_size > unsafe { MAP_WORK_BUF.len() } { |
diff --git a/src/ps2.rs b/rsnix/src/ps2.rs index 4ea8b7a..b83a4e0 100644 --- a/src/ps2.rs +++ b/rsnix/src/ps2.rs | |||
| @@ -346,11 +346,10 @@ fn update_modifiers(scancode: u8, pressed: bool, e0: bool) { | |||
| 346 | (0x1D, true) => set_mod(state, MOD_RCTRL, pressed), | 346 | (0x1D, true) => set_mod(state, MOD_RCTRL, pressed), |
| 347 | (0x38, false) => set_mod(state, MOD_LALT, pressed), | 347 | (0x38, false) => set_mod(state, MOD_LALT, pressed), |
| 348 | (0x38, true) => set_mod(state, MOD_RALT, pressed), | 348 | (0x38, true) => set_mod(state, MOD_RALT, pressed), |
| 349 | (0x3A, false) => { | 349 | (0x3A, false) |
| 350 | if pressed { | 350 | if pressed => { |
| 351 | state.modifiers ^= MOD_CAPS; | 351 | state.modifiers ^= MOD_CAPS; |
| 352 | } | 352 | } |
| 353 | } | ||
| 354 | _ => {} | 353 | _ => {} |
| 355 | } | 354 | } |
| 356 | } | 355 | } |
diff --git a/src/serial.rs b/rsnix/src/serial.rs index 03fa570..03fa570 100644 --- a/src/serial.rs +++ b/rsnix/src/serial.rs | |||
diff --git a/src/uefi.rs b/rsnix/src/uefi.rs index 2e3ed6b..2e3ed6b 100644 --- a/src/uefi.rs +++ b/rsnix/src/uefi.rs | |||
diff --git a/shell/Cargo.toml b/shell/Cargo.toml new file mode 100644 index 0000000..9fd10bc --- /dev/null +++ b/shell/Cargo.toml | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | [package] | ||
| 2 | name = "shell" | ||
| 3 | version.workspace = true | ||
| 4 | edition.workspace = true | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | rsnix = { path = "../rsnix" } | ||
diff --git a/src/shell.rs b/shell/src/lib.rs index 8e4cb62..bdd79c6 100644 --- a/src/shell.rs +++ b/shell/src/lib.rs | |||
| @@ -1,14 +1,24 @@ | |||
| 1 | #![no_std] | ||
| 2 | |||
| 3 | extern crate alloc; | ||
| 4 | |||
| 1 | /// Minimal command shell — reads from PS/2 keyboard, writes to GOP + serial. | 5 | /// Minimal command shell — reads from PS/2 keyboard, writes to GOP + serial. |
| 2 | use crate::ps2::{self, KeyEvent}; | 6 | use rsnix::ps2::{self, KeyEvent}; |
| 3 | use crate::{gop_print, gop_println, serial_print, serial_println}; | 7 | use rsnix::{gop_print, gop_println, serial_print, serial_println}; |
| 4 | 8 | ||
| 5 | /// Maximum input line length. | 9 | /// Maximum input line length. |
| 6 | const LINE_BUF_SIZE: usize = 256; | 10 | const LINE_BUF_SIZE: usize = 256; |
| 7 | 11 | ||
| 8 | /// Run the shell forever. | 12 | /// Run the shell forever. |
| 9 | pub fn run() -> ! { | 13 | pub fn run() -> ! { |
| 10 | gop_println!("raisernics v{} — type 'help' for commands", env!("CARGO_PKG_VERSION")); | 14 | gop_println!( |
| 11 | serial_println!("========== raisernics v{} ==========", env!("CARGO_PKG_VERSION")); | 15 | "rs-dos v{} — type 'help' for commands", |
| 16 | env!("CARGO_PKG_VERSION") | ||
| 17 | ); | ||
| 18 | serial_println!( | ||
| 19 | "========== rs-dos v{} ==========", | ||
| 20 | env!("CARGO_PKG_VERSION") | ||
| 21 | ); | ||
| 12 | 22 | ||
| 13 | loop { | 23 | loop { |
| 14 | // Print prompt | 24 | // Print prompt |
| @@ -53,32 +63,32 @@ fn read_line() -> heapless_str::String<LINE_BUF_SIZE> { | |||
| 53 | loop { | 63 | loop { |
| 54 | match ps2::poll_key() { | 64 | match ps2::poll_key() { |
| 55 | KeyEvent::Enter => { | 65 | KeyEvent::Enter => { |
| 56 | crate::gop::put_char('\n'); | 66 | rsnix::gop::put_char('\n'); |
| 57 | crate::serial::write_str("\r\n"); | 67 | rsnix::serial::write_str("\r\n"); |
| 58 | return buf; | 68 | return buf; |
| 59 | } | 69 | } |
| 60 | KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => { | 70 | KeyEvent::Char(c) if c.is_ascii_graphic() || c == ' ' => { |
| 61 | if buf.push(c).is_ok() { | 71 | if buf.push(c).is_ok() { |
| 62 | crate::gop::put_char(c); | 72 | rsnix::gop::put_char(c); |
| 63 | // Echo to serial too | 73 | // Echo to serial too |
| 64 | let mut s = [0u8; 4]; | 74 | let mut s = [0u8; 4]; |
| 65 | let s = c.encode_utf8(&mut s); | 75 | let s = c.encode_utf8(&mut s); |
| 66 | crate::serial::write_str(s); | 76 | rsnix::serial::write_str(s); |
| 67 | } | 77 | } |
| 68 | } | 78 | } |
| 69 | KeyEvent::Backspace => { | 79 | KeyEvent::Backspace => { |
| 70 | if buf.pop().is_some() { | 80 | if buf.pop().is_some() { |
| 71 | // GOP backspace: move cursor back and erase | 81 | // GOP backspace: move cursor back and erase |
| 72 | crate::gop::put_char('\x08'); | 82 | rsnix::gop::put_char('\x08'); |
| 73 | // Serial backspace: send BS SP BS (standard terminal protocol) | 83 | // Serial backspace: send BS SP BS (standard terminal protocol) |
| 74 | crate::serial::write_str("\x08 \x08"); | 84 | rsnix::serial::write_str("\x08 \x08"); |
| 75 | } | 85 | } |
| 76 | } | 86 | } |
| 77 | KeyEvent::Tab => { | 87 | KeyEvent::Tab => { |
| 78 | for _ in 0..4 { | 88 | for _ in 0..4 { |
| 79 | if buf.push(' ').is_ok() { | 89 | if buf.push(' ').is_ok() { |
| 80 | crate::gop::put_char(' '); | 90 | rsnix::gop::put_char(' '); |
| 81 | crate::serial::write_str(" "); | 91 | rsnix::serial::write_str(" "); |
| 82 | } | 92 | } |
| 83 | } | 93 | } |
| 84 | } | 94 | } |
| @@ -120,14 +130,14 @@ fn cmd_echo(args: &str) { | |||
| 120 | } | 130 | } |
| 121 | 131 | ||
| 122 | fn cmd_clear() { | 132 | fn cmd_clear() { |
| 123 | crate::gop::clear_screen(); | 133 | rsnix::gop::clear_screen(); |
| 124 | } | 134 | } |
| 125 | 135 | ||
| 126 | fn cmd_info() { | 136 | fn cmd_info() { |
| 127 | // Test that the global allocator works: build a String on the heap | 137 | // Test that the global allocator works: build a String on the heap |
| 128 | let mut s = alloc::string::String::from("raisernics v"); | 138 | let mut s = alloc::string::String::from("rs-dos v"); |
| 129 | s.push_str(env!("CARGO_PKG_VERSION")); | 139 | s.push_str(env!("CARGO_PKG_VERSION")); |
| 130 | s.push_str(" — RNG custom kernel"); | 140 | s.push_str(" — rsnix kernel"); |
| 131 | gop_println!("{}", s); | 141 | gop_println!("{}", s); |
| 132 | gop_println!("arch: x86_64, freestanding, no userspace"); | 142 | gop_println!("arch: x86_64, freestanding, no userspace"); |
| 133 | serial_println!("{}", s); | 143 | serial_println!("{}", s); |
| @@ -136,7 +146,7 @@ fn cmd_info() { | |||
| 136 | } | 146 | } |
| 137 | 147 | ||
| 138 | fn cmd_mem() { | 148 | fn cmd_mem() { |
| 139 | let (map, key) = crate::memory::get_map(); | 149 | let (map, key) = rsnix::memory::get_map(); |
| 140 | 150 | ||
| 141 | let mut total_kb = 0u64; | 151 | let mut total_kb = 0u64; |
| 142 | let mut conventional_kb = 0u64; | 152 | let mut conventional_kb = 0u64; |
diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9a901f5..0000000 --- a/src/main.rs +++ /dev/null | |||
| @@ -1,186 +0,0 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![allow(static_mut_refs, unsafe_code, dead_code, unused_doc_comments)] | ||
| 4 | |||
| 5 | // SAFETY: single-core kernel, no preemption — static mut access is sound | ||
| 6 | // Toy kernel: dead code and unused items during active development | ||
| 7 | |||
| 8 | extern crate alloc; | ||
| 9 | |||
| 10 | mod font; | ||
| 11 | mod gop; | ||
| 12 | mod interrupts; | ||
| 13 | mod memory; | ||
| 14 | mod ps2; | ||
| 15 | mod serial; | ||
| 16 | mod shell; | ||
| 17 | mod uefi; | ||
| 18 | |||
| 19 | use core::panic::PanicInfo; | ||
| 20 | use uefi::*; | ||
| 21 | |||
| 22 | // --------------------------------------------------------------------------- | ||
| 23 | // Panic handler | ||
| 24 | // --------------------------------------------------------------------------- | ||
| 25 | #[panic_handler] | ||
| 26 | fn panic(info: &PanicInfo) -> ! { | ||
| 27 | use core::fmt::Write; | ||
| 28 | serial::write_str("\r\n*** PANIC ***\r\n"); | ||
| 29 | let _ = write!(&mut serial::SerialWriter, "{}\r\n", info.message()); | ||
| 30 | if let Some(loc) = info.location() { | ||
| 31 | let _ = write!( | ||
| 32 | &mut serial::SerialWriter, | ||
| 33 | " at {}:{}\r\n", | ||
| 34 | loc.file(), | ||
| 35 | loc.line() | ||
| 36 | ); | ||
| 37 | } | ||
| 38 | loop { | ||
| 39 | unsafe { | ||
| 40 | core::arch::asm!("cli; hlt"); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | // --------------------------------------------------------------------------- | ||
| 46 | // EFI entry point | ||
| 47 | // --------------------------------------------------------------------------- | ||
| 48 | #[no_mangle] | ||
| 49 | pub extern "efiapi" fn efi_main(image_handle: Handle, system_table: *mut SystemTable) -> ! { | ||
| 50 | let st = unsafe { &mut *system_table }; | ||
| 51 | let bs = unsafe { &mut *st.boot_services }; | ||
| 52 | let con_out = unsafe { &mut *st.con_out }; | ||
| 53 | |||
| 54 | // ---- Phase 1: UEFI boot services alive ---- | ||
| 55 | |||
| 56 | // Print banner via UEFI console | ||
| 57 | unsafe { | ||
| 58 | let banner: [u16; 28] = [ | ||
| 59 | 'r' as u16, 'a' as u16, 'i' as u16, 's' as u16, 'e' as u16, 'r' as u16, 'n' as u16, | ||
| 60 | 'i' as u16, 'c' as u16, 's' as u16, ' ' as u16, 'v' as u16, '0' as u16, '.' as u16, | ||
| 61 | '1' as u16, ' ' as u16, '-' as u16, ' ' as u16, 'b' as u16, 'o' as u16, 'o' as u16, | ||
| 62 | 't' as u16, 'i' as u16, 'n' as u16, 'g' as u16, '.' as u16, '.' as u16, 0x0A, | ||
| 63 | ]; | ||
| 64 | (con_out.output_string)(con_out, banner.as_ptr()); | ||
| 65 | } | ||
| 66 | |||
| 67 | // Init serial early so we can debug-print before EBS | ||
| 68 | serial::init(); | ||
| 69 | |||
| 70 | serial_println!("=== raisernics boot ==="); | ||
| 71 | |||
| 72 | // Stash the memory map | ||
| 73 | let map_key = memory::capture_memory_map(bs); | ||
| 74 | let map_key = match map_key { | ||
| 75 | Ok(k) => k, | ||
| 76 | Err(e) => { | ||
| 77 | serial_println!("mem: capture_memory_map failed 0x{:x}", e); | ||
| 78 | 0 | ||
| 79 | } | ||
| 80 | }; | ||
| 81 | |||
| 82 | // Locate GOP (Graphics Output Protocol) | ||
| 83 | let mut gop_interface: *mut core::ffi::c_void = core::ptr::null_mut(); | ||
| 84 | let gop_guid = EFI_GRAPHICS_OUTPUT_PROTOCOL_GUID; | ||
| 85 | let gop_result = unsafe { | ||
| 86 | (bs.locate_protocol)( | ||
| 87 | &gop_guid as *const Guid, | ||
| 88 | core::ptr::null_mut(), | ||
| 89 | &mut gop_interface, | ||
| 90 | ) | ||
| 91 | }; | ||
| 92 | if gop_result == EFI_SUCCESS { | ||
| 93 | let gop = unsafe { &mut *(gop_interface as *mut GraphicsOutputProtocol) }; | ||
| 94 | serial_println!("gop: framebuffer found"); | ||
| 95 | gop::init(gop); | ||
| 96 | } else { | ||
| 97 | serial_println!("gop: not found (0x{:x})", gop_result); | ||
| 98 | } | ||
| 99 | |||
| 100 | // ExitBootServices — we now own the machine | ||
| 101 | let ebs_result = unsafe { (bs.exit_boot_services)(image_handle, map_key) }; | ||
| 102 | if ebs_result != EFI_SUCCESS { | ||
| 103 | // Memory map may have changed; try again with fresh map | ||
| 104 | // (This is the standard UEFI EBS retry dance.) | ||
| 105 | serial_println!( | ||
| 106 | "WARN: ExitBootServices failed (0x{:x}), retrying...", | ||
| 107 | ebs_result | ||
| 108 | ); | ||
| 109 | let _map_key = match memory::refresh_and_exit(bs, image_handle) { | ||
| 110 | Ok(k) => k, | ||
| 111 | Err(e) => { | ||
| 112 | serial_println!("FATAL: cannot ExitBootServices (0x{:x})", e); | ||
| 113 | loop { | ||
| 114 | unsafe { | ||
| 115 | core::arch::asm!("cli; hlt"); | ||
| 116 | } | ||
| 117 | } | ||
| 118 | } | ||
| 119 | }; | ||
| 120 | } | ||
| 121 | |||
| 122 | // ---- Phase 2: Native mode ---- | ||
| 123 | |||
| 124 | serial::init(); | ||
| 125 | serial_println!("serial: COM1 115200 8N1"); | ||
| 126 | |||
| 127 | // PS/2 keyboard | ||
| 128 | ps2::init(); | ||
| 129 | serial_println!("ps2: keyboard initialized"); | ||
| 130 | |||
| 131 | // Interrupts — IDT, PIC, PIT timer, keyboard IRQ handler | ||
| 132 | interrupts::init(); | ||
| 133 | serial_println!("intr: IDT loaded, PIC remapped, timer ~100 Hz"); | ||
| 134 | |||
| 135 | // Memory: the bump allocator was set up during capture_memory_map. | ||
| 136 | // `alloc::Vec`, `alloc::String`, `format!` etc. are now usable. | ||
| 137 | |||
| 138 | // Print memory summary | ||
| 139 | dump_memory_summary(); | ||
| 140 | |||
| 141 | serial_println!("boot: entering shell"); | ||
| 142 | shell::run(); | ||
| 143 | } | ||
| 144 | |||
| 145 | // --------------------------------------------------------------------------- | ||
| 146 | // Print a memory summary from the stashed memory map | ||
| 147 | // --------------------------------------------------------------------------- | ||
| 148 | fn dump_memory_summary() { | ||
| 149 | let (map, key) = memory::get_map(); | ||
| 150 | let mut conventional_4k = 0u64; | ||
| 151 | let mut addr_space_4k = 0u64; | ||
| 152 | for desc in map.iter() { | ||
| 153 | let pages = desc.number_of_pages; | ||
| 154 | addr_space_4k += pages; | ||
| 155 | if desc.kind == 7 { | ||
| 156 | conventional_4k += pages; | ||
| 157 | } | ||
| 158 | } | ||
| 159 | serial_println!( | ||
| 160 | "mem: {} entries, key=0x{:x}, usable: {} KB ({} MB)", | ||
| 161 | map.len(), | ||
| 162 | key, | ||
| 163 | conventional_4k * 4, | ||
| 164 | (conventional_4k * 4) / 1024, | ||
| 165 | ); | ||
| 166 | serial_println!( | ||
| 167 | "mem: address space: {} MB (incl. PCIe MMIO)", | ||
| 168 | (addr_space_4k * 4) / 1024, | ||
| 169 | ); | ||
| 170 | } | ||
| 171 | |||
| 172 | // --------------------------------------------------------------------------- | ||
| 173 | // Helper: write a panic-proof string via UEFI console (used before serial) | ||
| 174 | // --------------------------------------------------------------------------- | ||
| 175 | #[allow(dead_code)] | ||
| 176 | unsafe fn uefi_puts(con_out: *mut SimpleTextOutput, s: &str) { | ||
| 177 | // Simple ASCII → UEFI-CHAR16 conversion (stack buffer, panic-safe) | ||
| 178 | let mut buf: [u16; 128] = [0; 128]; | ||
| 179 | let len = s.len().min(127); | ||
| 180 | for (i, &b) in s.as_bytes()[..len].iter().enumerate() { | ||
| 181 | buf[i] = b as u16; | ||
| 182 | } | ||
| 183 | buf[len] = 0x0A; // LF | ||
| 184 | let out = unsafe { &mut *con_out }; | ||
| 185 | (out.output_string)(out, buf.as_ptr()); | ||
| 186 | } | ||
