├── src ├── protocols │ ├── mod.rs │ └── stivale2.rs ├── menu.rs ├── pmm.rs ├── logger.rs ├── main.rs └── config.rs ├── .gitignore ├── .vscode └── settings.json ├── test └── stivale2 │ ├── Cargo.toml │ ├── Makefile │ ├── .cargo │ ├── config.toml │ └── kernel.ld │ ├── x86_64-unknown.json │ ├── Cargo.lock │ └── src │ └── main.rs ├── ion.cfg ├── .cargo └── config.toml ├── README.md ├── Cargo.toml ├── Makefile └── Cargo.lock /src/protocols/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod stivale2; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | build 3 | ovmf 4 | 5 | *.o 6 | *.elf 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[c]": { 3 | "editor.formatOnSave": false 4 | } 5 | } -------------------------------------------------------------------------------- /test/stivale2/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "stivale2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | stivale-boot = "0.2.3" 8 | -------------------------------------------------------------------------------- /ion.cfg: -------------------------------------------------------------------------------- 1 | :Aero 2 | PROTOCOL=stivale2 3 | KERNEL_PATH=boot:///boot/stivale2.elf 4 | 5 | :Arch Linux 6 | PROTOCOL=linux 7 | 8 | :Managarm 9 | :Qword 10 | :SkiftOs 11 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [unstable] 2 | build-std = ["core", "compiler_builtins", "alloc"] 3 | build-std-features = ["compiler-builtins-mem"] 4 | 5 | [build] 6 | target = "x86_64-unknown-uefi" 7 | -------------------------------------------------------------------------------- /test/stivale2/Makefile: -------------------------------------------------------------------------------- 1 | all: stivale2.elf 2 | 3 | stivale2.elf: 4 | @ cargo build 5 | @ cp target/x86_64-unknown/debug/stivale2 ../../build/stivale2.elf 6 | 7 | clean: 8 | @ rm -f ../../build/stivale2.elf 9 | @ cargo clean 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ion 2 | 3 | Ion is a new modern x86_64 UEFI bootloader supporting modern PC features such 4 | as long mode, 5-level paging, and SMP (multicore), to name a few. 5 | 6 | ## Supported Boot Protocols 7 | * stivale2 8 | 9 | ## Supported Partitioning Schemes 10 | * GPT 11 | -------------------------------------------------------------------------------- /test/stivale2/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [unstable] 2 | build-std = ["core", "compiler_builtins", "alloc"] 3 | build-std-features = ["compiler-builtins-mem"] 4 | 5 | [profile.dev] 6 | panic = "abort" 7 | 8 | [profile.release] 9 | panic = "abort" 10 | 11 | [build] 12 | target = "x86_64-unknown.json" 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ion" 3 | version = "0.1.0" 4 | edition = "2018" 5 | 6 | [dependencies] 7 | log = "0.4.14" 8 | spin = "0.9.2" 9 | uart_16550 = "0.2.15" 10 | bit_field = "0.10.1" 11 | xmas-elf = "0.8.0" 12 | raw-cpuid = "10.2.0" 13 | stivale-boot = { path = "../stivale" } 14 | x86_64 = "0.14.4" 15 | 16 | [dependencies.uefi] 17 | version = "0.11.0" 18 | features = ["alloc"] 19 | 20 | [dependencies.font8x8] 21 | version = "0.2.5" 22 | default-features = false 23 | features = ["unicode"] 24 | -------------------------------------------------------------------------------- /test/stivale2/x86_64-unknown.json: -------------------------------------------------------------------------------- 1 | { 2 | "llvm-target": "x86_64-unknown-none", 3 | "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", 4 | "arch": "x86_64", 5 | "target-endian": "little", 6 | "target-pointer-width": "64", 7 | "target-c-int-width": "32", 8 | "os": "none", 9 | "executables": true, 10 | "linker-flavor": "ld.lld", 11 | "linker": "rust-lld", 12 | "panic-strategy": "abort", 13 | "disable-redzone": true, 14 | "pre-link-args": { 15 | "ld.lld": [ 16 | "--gc-sections", 17 | "--script=.cargo/kernel.ld" 18 | ] 19 | }, 20 | "features": "-mmx,-sse,+soft-float" 21 | } -------------------------------------------------------------------------------- /test/stivale2/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bitflags" 7 | version = "1.3.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 10 | 11 | [[package]] 12 | name = "stivale-boot" 13 | version = "0.2.3" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "0f34c9d55480747cc07555801ab9b37c4ec0cd8fbfac111b9ad8a681a3c7cac5" 16 | dependencies = [ 17 | "bitflags", 18 | ] 19 | 20 | [[package]] 21 | name = "stivale2" 22 | version = "0.1.0" 23 | dependencies = [ 24 | "stivale-boot", 25 | ] 26 | -------------------------------------------------------------------------------- /test/stivale2/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | 4 | use core::panic::PanicInfo; 5 | 6 | use stivale_boot::v2::*; 7 | 8 | #[repr(C, align(4096))] 9 | struct P2Align12(T); 10 | 11 | const STACK_SIZE: usize = 4096 * 16; 12 | 13 | /// We need to tell the stivale bootloader where we want our stack to be. 14 | /// We are going to allocate our stack as an uninitialised array in .bss. 15 | static STACK: P2Align12<[u8; STACK_SIZE]> = P2Align12([0; STACK_SIZE]); 16 | 17 | /// The stivale2 specification says we need to define a "header structure". 18 | /// This structure needs to reside in the .stivale2hdr ELF section in order 19 | /// for the bootloader to find it. We use the #[linker_section] and #[used] macros to 20 | /// tell the compiler to put the following structure in said section. 21 | #[link_section = ".stivale2hdr"] 22 | #[no_mangle] 23 | #[used] 24 | static STIVALE_HDR: StivaleHeader = StivaleHeader::new() 25 | .stack(&STACK.0[STACK_SIZE - 4096] as *const u8) 26 | .tags(0x00 as *const ()); 27 | 28 | #[no_mangle] 29 | extern "C" fn _start() -> ! { 30 | loop {} 31 | } 32 | 33 | #[panic_handler] 34 | fn panic(_info: &PanicInfo) -> ! { 35 | loop {} 36 | } 37 | -------------------------------------------------------------------------------- /test/stivale2/.cargo/kernel.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT(elf64-x86-64) 2 | ENTRY(_start) 3 | 4 | PHDRS 5 | { 6 | null PT_NULL FLAGS(0) ; /* Null segment */ 7 | text PT_LOAD FLAGS((1 << 0) | (1 << 2)) ; /* Execute + Read */ 8 | rodata PT_LOAD FLAGS((1 << 2)) ; /* Read only */ 9 | data PT_LOAD FLAGS((1 << 1) | (1 << 2)) ; /* Write + Read */ 10 | } 11 | 12 | /* We want to be placed in the higher half, 2MiB above 0x00 in physical memory */ 13 | KERNEL_OFFSET = 0xFFFFFFFF80200000; 14 | 15 | SECTIONS { 16 | . = KERNEL_OFFSET; 17 | 18 | .text : { 19 | *(.text*) 20 | } :text 21 | 22 | /* Move to the next memory page for .rodata */ 23 | . += 0x1000; 24 | 25 | /* We place the .stivale2hdr section containing the header in its own section, */ 26 | /* and we use the KEEP directive on it to make sure it doesn't get discarded. */ 27 | .stivale2hdr : { 28 | KEEP(*(.stivale2hdr)) 29 | } :rodata 30 | 31 | .rodata : { 32 | *(.rodata*) 33 | } :rodata 34 | 35 | /* Move to the next memory page for .data */ 36 | . += 0x1000; 37 | 38 | .data : { 39 | *(.data*) 40 | } :data 41 | 42 | .bss : { 43 | *(COMMON) 44 | *(.bss*) 45 | } :data 46 | } 47 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: uefi-stivale2-test 2 | .PHONY: clean 3 | .PHONY: ovmf-x64 4 | 5 | # Downloads the latest prebuilt UEFI OVMF binaries for x86_64 into the ovmf 6 | # directory. 7 | ovmf-x64: 8 | @ mkdir -p ovmf 9 | @ test -f ovmf/OVMF-pure-efi.fd \ 10 | || curl --location https://github.com/rust-osdev/ovmf-prebuilt/releases/latest/download/OVMF-pure-efi.fd \ 11 | --output ovmf/OVMF-pure-efi.fd 12 | 13 | @ echo "\033[32;1mOK:\033[0m Downloaded latest OVMF prebuilt binaries..." 14 | 15 | # Builds and runs the stivale 2 test kernel in Qemu using UEFI. 16 | uefi-stivale2-test: 17 | @ test -d build && rm -rf build 18 | @ mkdir build 19 | 20 | @ $(MAKE) ovmf-x64 --no-print-directory 21 | @ $(MAKE) -C test/stivale2 --no-print-directory 22 | @ echo "\033[32;1mOK:\033[0m Built UEFI stivale 2 test kernel..." 23 | 24 | @ cargo build --release 25 | @ dd if=/dev/zero bs=1M count=0 seek=64 of=build/ion.hdd status=none 26 | 27 | @ parted -s build/ion.hdd mklabel gpt 28 | @ parted -s build/ion.hdd mkpart primary 2048s 100% 29 | 30 | @ mkdir build/mnt 31 | 32 | @ sudo losetup -Pf --show build/ion.hdd > loopback_dev 33 | @ sudo mkfs.fat -F 32 `cat loopback_dev`p1 > /dev/null 34 | @ sudo mount `cat loopback_dev`p1 build/mnt 35 | 36 | @ sudo mkdir -p build/mnt/EFI/BOOT 37 | @ sudo mkdir -p build/mnt/boot 38 | 39 | @ sudo cp ./target/x86_64-unknown-uefi/release/ion.efi build/mnt/EFI/BOOT/BOOTX64.EFI 40 | @ sudo cp ./ion.cfg build/mnt/ion.cfg 41 | @ sudo cp ./build/stivale2.elf build/mnt/boot/ 42 | 43 | @ sync 44 | 45 | @ sudo umount build/mnt 46 | @ sudo losetup -d `cat loopback_dev` 47 | 48 | @ rm -rf build/mnt/ loopback_dev 49 | 50 | @ printf '\033[32;1mOK:\033[0m Running UEFI stivale2 test kernel in Qemu...' 51 | @ qemu-system-x86_64 -machine type=q35 -serial stdio -drive format=raw,file=build/ion.hdd \ 52 | -bios ../aero/bundled/ovmf/OVMF-pure-efi.fd \ 53 | -d int \ 54 | -D qemulog.uefi.log \ 55 | --no-reboot 56 | 57 | # Clean up build directory. 58 | clean: 59 | @ cargo clean 60 | @ echo "\033[32;1mOK:\033[0m Cleaned Ion build..." 61 | 62 | @ $(MAKE) -C test/stivale2 clean --no-print-directory 63 | @ echo "\033[32;1mOK:\033[0m Cleaned stivale2 test kernel build..." 64 | 65 | @ rm -rf build/ion.hdd 66 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "bit_field" 7 | version = "0.9.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" 10 | 11 | [[package]] 12 | name = "bit_field" 13 | version = "0.10.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" 16 | 17 | [[package]] 18 | name = "bitflags" 19 | version = "1.2.1" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 28 | 29 | [[package]] 30 | name = "font8x8" 31 | version = "0.2.7" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "e63201c624b8c8883921b1a1accc8916c4fa9dbfb15d122b26e4dde945b86bbf" 34 | 35 | [[package]] 36 | name = "ion" 37 | version = "0.1.0" 38 | dependencies = [ 39 | "bit_field 0.10.1", 40 | "font8x8", 41 | "log", 42 | "raw-cpuid", 43 | "spin", 44 | "stivale-boot", 45 | "uart_16550", 46 | "uefi", 47 | "x86_64", 48 | "xmas-elf", 49 | ] 50 | 51 | [[package]] 52 | name = "lock_api" 53 | version = "0.4.4" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" 56 | dependencies = [ 57 | "scopeguard", 58 | ] 59 | 60 | [[package]] 61 | name = "log" 62 | version = "0.4.14" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" 65 | dependencies = [ 66 | "cfg-if", 67 | ] 68 | 69 | [[package]] 70 | name = "proc-macro2" 71 | version = "1.0.27" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" 74 | dependencies = [ 75 | "unicode-xid", 76 | ] 77 | 78 | [[package]] 79 | name = "quote" 80 | version = "1.0.9" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" 83 | dependencies = [ 84 | "proc-macro2", 85 | ] 86 | 87 | [[package]] 88 | name = "raw-cpuid" 89 | version = "10.2.0" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "929f54e29691d4e6a9cc558479de70db7aa3d98cd6fe7ab86d7507aa2886b9d2" 92 | dependencies = [ 93 | "bitflags", 94 | ] 95 | 96 | [[package]] 97 | name = "scopeguard" 98 | version = "1.1.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 101 | 102 | [[package]] 103 | name = "spin" 104 | version = "0.9.2" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "511254be0c5bcf062b019a6c89c01a664aa359ded62f78aa72c6fc137c0590e5" 107 | dependencies = [ 108 | "lock_api", 109 | ] 110 | 111 | [[package]] 112 | name = "stivale-boot" 113 | version = "0.2.3" 114 | dependencies = [ 115 | "bitflags", 116 | ] 117 | 118 | [[package]] 119 | name = "syn" 120 | version = "1.0.73" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "f71489ff30030d2ae598524f61326b902466f72a0fb1a8564c001cc63425bcc7" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "unicode-xid", 127 | ] 128 | 129 | [[package]] 130 | name = "uart_16550" 131 | version = "0.2.15" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "65ad019480ef5ff8ffe66d6f6a259cd87cf317649481394981db1739d844f374" 134 | dependencies = [ 135 | "bitflags", 136 | "x86_64", 137 | ] 138 | 139 | [[package]] 140 | name = "ucs2" 141 | version = "0.3.2" 142 | source = "registry+https://github.com/rust-lang/crates.io-index" 143 | checksum = "bad643914094137d475641b6bab89462505316ec2ce70907ad20102d28a79ab8" 144 | dependencies = [ 145 | "bit_field 0.10.1", 146 | ] 147 | 148 | [[package]] 149 | name = "uefi" 150 | version = "0.11.0" 151 | source = "registry+https://github.com/rust-lang/crates.io-index" 152 | checksum = "4630a92e80ac72f2b3dedb865dac3cf9e0215ce7e222301f0a37d8e6e3c5dbf4" 153 | dependencies = [ 154 | "bitflags", 155 | "log", 156 | "ucs2", 157 | "uefi-macros", 158 | ] 159 | 160 | [[package]] 161 | name = "uefi-macros" 162 | version = "0.3.3" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "3dcca10ca861f34a320d178f3fdb29ffbf05087fc2c70d2a99860e3329bee1a8" 165 | dependencies = [ 166 | "proc-macro2", 167 | "quote", 168 | "syn", 169 | ] 170 | 171 | [[package]] 172 | name = "unicode-xid" 173 | version = "0.2.2" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" 176 | 177 | [[package]] 178 | name = "volatile" 179 | version = "0.4.4" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "e4c2dbd44eb8b53973357e6e207e370f0c1059990df850aca1eca8947cf464f0" 182 | 183 | [[package]] 184 | name = "x86_64" 185 | version = "0.14.4" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "d95947de37ad0d2d9a4a4dd22e0d042e034e5cbd7ab53edbca0d8035e0a6a64d" 188 | dependencies = [ 189 | "bit_field 0.9.0", 190 | "bitflags", 191 | "volatile", 192 | ] 193 | 194 | [[package]] 195 | name = "xmas-elf" 196 | version = "0.8.0" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "8d29b4d8e7beaceb4e77447ba941a7600d23d0319ab52da0461abea214832d5a" 199 | dependencies = [ 200 | "zero", 201 | ] 202 | 203 | [[package]] 204 | name = "zero" 205 | version = "0.1.2" 206 | source = "registry+https://github.com/rust-lang/crates.io-index" 207 | checksum = "5f1bc8a6b2005884962297587045002d8cfb8dcec9db332f4ca216ddc5de82c5" 208 | -------------------------------------------------------------------------------- /src/menu.rs: -------------------------------------------------------------------------------- 1 | use uefi::prelude::*; 2 | use uefi::proto::console::text::{Input, Key, ScanCode}; 3 | use uefi::table::boot::{EventType, TimerTrigger, Tpl}; 4 | 5 | use crate::config::{self, ConfigurationEntry}; 6 | use crate::logger; 7 | 8 | use crate::config::IonConfig; 9 | use crate::logger::Color; 10 | 11 | use crate::prelude::*; 12 | 13 | /// This function is responsible for sleeping the provided amount of `seconds` and if 14 | /// a special key is pressed in the duration specified, the function will return the keyboard 15 | /// scancode and quit the timer. Else the function will return [`None`]. 16 | pub fn pit_sleep_and_quit_on_keypress( 17 | system_table: &SystemTable, 18 | seconds: usize, 19 | ) -> Option { 20 | unsafe { 21 | // Create a new timer event with the TPL set to callback. 22 | let event = system_table 23 | .boot_services() 24 | .create_event(EventType::TIMER, Tpl::CALLBACK, None) 25 | .expect_success("Failed to create timer event"); 26 | 27 | // Retrieve the input protocol from the boot services, 28 | let input_protocol = system_table 29 | .boot_services() 30 | .locate_protocol::() 31 | .expect_success("Failed to locate input protocol"); 32 | 33 | let key = &mut *input_protocol.get(); // Get the inner cell value 34 | let wait_for_key_event = key.wait_for_key_event(); // Get a reference to the wait for key event 35 | 36 | // Initialize the timer event that we created before and set the amount of seconds requested. 37 | system_table 38 | .boot_services() 39 | .set_timer(event, TimerTrigger::Relative(10000000 * seconds as u64)) 40 | .expect_success("Failed to create timer from event"); 41 | 42 | // Loop until the timer finishes or interrupted by a keyboard interrupt. 43 | loop { 44 | let result = system_table 45 | .boot_services() 46 | .wait_for_event(&mut [event, wait_for_key_event]) 47 | .expect_success("Failed add event in wait queue"); 48 | 49 | // If the result is equal to zero, that means our timer event has finished and return. Since 50 | // we did not retrieve a scancode we return [`None`]. 51 | if result == 0 { 52 | return None; 53 | } 54 | 55 | // Try and read the next keystroke from the input device, if any. 56 | let scancode = key.read_key().expect_success("Failed to read key"); 57 | 58 | // Check if there is any keystore. 59 | if let Some(code) = scancode { 60 | // If the key stroke is classified as special we return the keyboard scancode 61 | // and quit the timer. 62 | if let Key::Special(special) = code { 63 | return Some(special); 64 | } else { 65 | // Else if the key stroke is not classified as special, we will still quit the 66 | // timer as the user might want to access the boot menu. To overcome this issue 67 | // we will return a null scancode. 68 | return Some(ScanCode::NULL); 69 | } 70 | } 71 | } 72 | } 73 | } 74 | 75 | /// Helper function used to print the boot menu tree. 76 | fn print_tree(boot_config: &IonConfig, selected_entry: usize) { 77 | for (i, entry) in boot_config.entries.iter().enumerate() { 78 | if i == selected_entry { 79 | logger::with_fg(Color::new(0xFFAAF), || { 80 | println!("{}", entry.name()); 81 | }) 82 | } else { 83 | println!("{}", entry.name()); 84 | } 85 | } 86 | 87 | logger::flush(); 88 | } 89 | 90 | /// This function is responsible for intializing the boot menu. This function returns the 91 | /// index of the selected boot entry. 92 | pub fn init(system_table: &SystemTable, boot_config: IonConfig) -> ConfigurationEntry { 93 | let mut selected_entry = 0; 94 | let mut done_timeout = false; 95 | 96 | loop { 97 | logger::clear(); 98 | 99 | println!("Ion {} ", env!("CARGO_PKG_VERSION")); 100 | println!("Select entry:\n"); 101 | 102 | print_tree(&boot_config, selected_entry); 103 | 104 | if !done_timeout { 105 | for i in (0..boot_config.timeout()).rev() { 106 | logger::set_cursor_pos(0, logger::display_height() - 24); 107 | logger::set_scroll_lock(true); 108 | 109 | println!( 110 | "Booting automatically in {}, press any key to stop the countdown...", 111 | i 112 | ); 113 | 114 | logger::flush(); 115 | logger::set_scroll_lock(false); 116 | 117 | if pit_sleep_and_quit_on_keypress(system_table, 1).is_some() { 118 | break; 119 | } 120 | } 121 | 122 | done_timeout = true; 123 | continue; 124 | } 125 | 126 | loop { 127 | match config::get_char(system_table) { 128 | Key::Special(code) => match code { 129 | ScanCode::UP => { 130 | selected_entry = selected_entry 131 | .checked_sub(1) 132 | .unwrap_or(boot_config.entries.len() - 1); 133 | 134 | // Breaking out of this loop will cause the parent draw loop to 135 | // continue. 136 | break; 137 | } 138 | 139 | ScanCode::DOWN => { 140 | selected_entry += 1; 141 | 142 | if selected_entry >= boot_config.entries.len() { 143 | selected_entry = 0; 144 | } 145 | 146 | // Breaking out of this loop will cause the parent draw loop to 147 | // continue. 148 | break; 149 | } 150 | 151 | _ => (), 152 | }, 153 | 154 | Key::Printable(c) => { 155 | let c: char = c.into(); 156 | 157 | match c { 158 | // UEFI wierdness the ENTER key returns a carriage return so we have to match 159 | // on that. 160 | '\r' => { 161 | return boot_config.entries[selected_entry].clone(); 162 | } 163 | 164 | _ => (), 165 | } 166 | } 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/pmm.rs: -------------------------------------------------------------------------------- 1 | use uefi::table::boot::{MemoryDescriptor, MemoryType}; 2 | 3 | use x86_64::structures::paging::*; 4 | use x86_64::{PhysAddr, VirtAddr}; 5 | use xmas_elf::program::ProgramHeader; 6 | 7 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 8 | #[non_exhaustive] 9 | #[repr(C)] 10 | pub enum MemoryRegionType { 11 | /// Unused conventional memory, can be used by the kernel. 12 | Usable, 13 | UnknownUefi(u32), 14 | } 15 | 16 | /// Represent a physical memory region. 17 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] 18 | #[repr(C)] 19 | pub struct MemoryRegion { 20 | /// The physical start address of the region. 21 | pub start: u64, 22 | /// The physical end address (exclusive) of the region. 23 | pub end: u64, 24 | /// The memory type of the memory region. 25 | pub kind: MemoryRegionType, 26 | } 27 | 28 | pub trait BootMemoryRegion: Copy + core::fmt::Debug { 29 | /// Returns the physical start address of the region. 30 | fn start(&self) -> PhysAddr; 31 | 32 | /// Returns the size of the region in bytes. 33 | fn len(&self) -> u64; 34 | 35 | /// Returns the type of the region 36 | fn region_type(&self) -> MemoryRegionType; 37 | } 38 | 39 | impl<'a> BootMemoryRegion for MemoryDescriptor { 40 | fn start(&self) -> PhysAddr { 41 | PhysAddr::new(self.phys_start) 42 | } 43 | 44 | fn len(&self) -> u64 { 45 | self.page_count * Size4KiB::SIZE 46 | } 47 | 48 | fn region_type(&self) -> MemoryRegionType { 49 | match self.ty { 50 | MemoryType::CONVENTIONAL => MemoryRegionType::Usable, 51 | other => MemoryRegionType::UnknownUefi(other.0), 52 | } 53 | } 54 | } 55 | 56 | pub struct BootFrameAllocator { 57 | #[allow(unused)] 58 | original: I, 59 | memory_map: I, 60 | current_descriptor: Option, 61 | next_frame: PhysFrame, 62 | } 63 | 64 | impl BootFrameAllocator 65 | where 66 | I: ExactSizeIterator + Clone, 67 | I::Item: BootMemoryRegion, 68 | { 69 | pub fn new(memory_map: I) -> Self { 70 | let start_frame = PhysFrame::containing_address(PhysAddr::new(0x1000)); 71 | 72 | Self { 73 | original: memory_map.clone(), 74 | memory_map, 75 | current_descriptor: None, 76 | next_frame: start_frame, 77 | } 78 | } 79 | 80 | pub fn allocate_frame_from_descriptor(&mut self, descriptor: D) -> Option { 81 | let start_addr = descriptor.start(); 82 | let start_frame = PhysFrame::containing_address(start_addr); 83 | let end_addr = start_addr + descriptor.len(); 84 | let end_frame = PhysFrame::containing_address(end_addr - 1u64); 85 | 86 | // Set next_frame to start_frame if its smaller then next_frame. 87 | if self.next_frame < start_frame { 88 | self.next_frame = start_frame; 89 | } 90 | 91 | if self.next_frame < end_frame { 92 | let frame = self.next_frame; 93 | self.next_frame += 1; 94 | 95 | Some(frame) 96 | } else { 97 | None 98 | } 99 | } 100 | 101 | /// Returns the number of memory regions in the underlying memory map. 102 | /// 103 | /// The function always returns the same value, i.e. the length doesn't 104 | /// change after calls to `allocate_frame`. 105 | pub fn len(&self) -> usize { 106 | self.original.len() 107 | } 108 | 109 | /// Returns the largest detected physical memory address. 110 | /// 111 | /// Useful for creating a mapping for all physical memory. 112 | pub fn max_phys_addr(&self) -> PhysAddr { 113 | self.original 114 | .clone() 115 | .map(|r| r.start() + r.len()) 116 | .max() 117 | .unwrap() 118 | } 119 | } 120 | 121 | unsafe impl FrameAllocator for BootFrameAllocator 122 | where 123 | I: ExactSizeIterator + Clone, 124 | I::Item: BootMemoryRegion, 125 | { 126 | fn allocate_frame(&mut self) -> Option> { 127 | if let Some(current_descriptor) = self.current_descriptor { 128 | match self.allocate_frame_from_descriptor(current_descriptor) { 129 | Some(frame) => return Some(frame), 130 | None => { 131 | self.current_descriptor = None; 132 | } 133 | } 134 | } 135 | 136 | // Find next suitable descriptor 137 | while let Some(descriptor) = self.memory_map.next() { 138 | if descriptor.region_type() != MemoryRegionType::Usable { 139 | continue; 140 | } 141 | 142 | if let Some(frame) = self.allocate_frame_from_descriptor(descriptor) { 143 | self.current_descriptor = Some(descriptor); 144 | return Some(frame); 145 | } 146 | } 147 | 148 | None 149 | } 150 | } 151 | 152 | /// Keeps track of used entries in a level 4 page table. 153 | /// 154 | /// Useful for determining a free virtual memory block, e.g. for mapping additional data. 155 | pub struct UsedLevel4Entries { 156 | entry_state: [bool; 512], // whether an entry is in use by the kernel 157 | } 158 | 159 | impl UsedLevel4Entries { 160 | /// Initializes a new instance from the given ELF program segments. 161 | /// 162 | /// Marks the virtual address range of all segments as used. 163 | pub fn new<'a>(segments: impl Iterator>) -> Self { 164 | let mut used = UsedLevel4Entries { 165 | entry_state: [false; 512], 166 | }; 167 | 168 | used.entry_state[0] = true; // TODO: Can we do this dynamically? 169 | 170 | for segment in segments { 171 | let start_page: Page = Page::containing_address(VirtAddr::new(segment.virtual_addr())); 172 | let end_page: Page = Page::containing_address(VirtAddr::new( 173 | segment.virtual_addr() + segment.mem_size(), 174 | )); 175 | 176 | for p4_index in u64::from(start_page.p4_index())..=u64::from(end_page.p4_index()) { 177 | used.entry_state[p4_index as usize] = true; 178 | } 179 | } 180 | 181 | used 182 | } 183 | 184 | /// Returns a unused level 4 entry and marks it as used. 185 | /// 186 | /// Since this method marks each returned index as used, it can be used multiple times 187 | /// to determine multiple unused virtual memory regions. 188 | pub fn get_free_entry(&mut self) -> PageTableIndex { 189 | let (idx, entry) = self 190 | .entry_state 191 | .iter_mut() 192 | .enumerate() 193 | .find(|(_, &mut entry)| entry == false) 194 | .expect("no usable level 4 entries found"); 195 | 196 | *entry = true; 197 | PageTableIndex::new(idx as u16) 198 | } 199 | 200 | /// Returns the virtual start address of an unused level 4 entry and marks it as used. 201 | /// 202 | /// This is a convenience method around [`get_free_entry`], so all of its docs applies here 203 | /// too. 204 | pub fn get_free_address(&mut self) -> VirtAddr { 205 | Page::from_page_table_indices_1gib(self.get_free_entry(), PageTableIndex::new(0)) 206 | .start_address() 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/logger.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::fmt::Write; 3 | 4 | use font8x8::UnicodeFonts; 5 | 6 | use spin::mutex::SpinMutex; 7 | use spin::Once; 8 | 9 | use bit_field::BitField; 10 | 11 | /// Describes the layout and pixel format of a framebuffer. 12 | #[derive(Debug, Clone, Copy)] 13 | #[repr(C)] 14 | pub struct FrameBufferInfo { 15 | /// The width in pixels. 16 | pub horizontal_resolution: usize, 17 | /// The height in pixels. 18 | pub vertical_resolution: usize, 19 | /// The color format of each pixel. 20 | pub pixel_format: PixelFormat, 21 | /// The number of bits per pixel. 22 | pub bits_per_pixel: usize, 23 | /// Number of pixels between the start of a line and the start of the next. 24 | /// 25 | /// Some framebuffers use additional padding at the end of a line, so this 26 | /// value might be larger than `horizontal_resolution`. It is 27 | /// therefore recommended to use this field for calculating the start address of a line. 28 | pub stride: usize, 29 | } 30 | 31 | /// Color format of pixels in the framebuffer. 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 33 | #[non_exhaustive] 34 | #[repr(C)] 35 | pub enum PixelFormat { 36 | /// One byte red, then one byte green, then one byte blue. 37 | /// 38 | /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] 39 | /// for this. 40 | RGB, 41 | /// One byte blue, then one byte green, then one byte red. 42 | /// 43 | /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] 44 | /// for this. 45 | BGR, 46 | } 47 | 48 | #[repr(transparent)] 49 | #[derive(Debug, Copy, Clone)] 50 | pub struct Color(u32); 51 | 52 | impl Color { 53 | #[inline] 54 | pub fn new(hex: u32) -> Self { 55 | Self(hex) 56 | } 57 | } 58 | 59 | /// The global logger instance used for the `log` crate. 60 | pub static LOGGER: Once = Once::new(); 61 | 62 | /// A [`Logger`] instance protected by a spinlock. 63 | pub struct LockedLogger(SpinMutex); 64 | 65 | impl LockedLogger { 66 | /// Create a new instance that logs to the given framebuffer. 67 | #[inline] 68 | pub fn new( 69 | framebuffer: &'static mut [u8], 70 | backbuffer: &'static mut [u8], 71 | info: FrameBufferInfo, 72 | ) -> Self { 73 | Self(SpinMutex::new(Logger::new(framebuffer, backbuffer, info))) 74 | } 75 | 76 | /// Force-unlocks the logger to prevent a deadlock. 77 | /// 78 | /// ## Saftey 79 | /// This method is not memory safe and should be only used when absolutely necessary. 80 | pub unsafe fn force_unlock(&self) { 81 | self.0.force_unlock() 82 | } 83 | } 84 | 85 | impl log::Log for LockedLogger { 86 | #[inline] 87 | fn enabled(&self, _metadata: &log::Metadata) -> bool { 88 | true 89 | } 90 | 91 | #[inline] 92 | fn log(&self, record: &log::Record) { 93 | let mut logger = self.0.lock(); 94 | writeln!(logger, "{}: {}", record.level(), record.args()).unwrap(); 95 | } 96 | 97 | #[inline] 98 | fn flush(&self) {} 99 | } 100 | 101 | struct Logger { 102 | framebuffer: &'static mut [u8], 103 | backbuffer: &'static mut [u8], 104 | 105 | info: FrameBufferInfo, 106 | 107 | x_pos: usize, 108 | y_pos: usize, 109 | 110 | scroll_lock: bool, 111 | 112 | fg: Color, 113 | bg: Color, 114 | } 115 | 116 | impl Logger { 117 | #[inline] 118 | fn new( 119 | framebuffer: &'static mut [u8], 120 | backbuffer: &'static mut [u8], 121 | info: FrameBufferInfo, 122 | ) -> Self { 123 | Self { 124 | framebuffer, 125 | backbuffer, 126 | 127 | info, 128 | 129 | x_pos: 0x00, 130 | y_pos: 0x00, 131 | 132 | scroll_lock: false, 133 | fg: Color::new(u32::MAX), 134 | bg: Color::new(u32::MIN), 135 | } 136 | } 137 | 138 | fn write_char(&mut self, c: char) { 139 | match c { 140 | '\n' => self.new_line(), 141 | '\r' => self.carriage_return(), 142 | _ => { 143 | if self.x_pos >= self.width() { 144 | self.new_line(); 145 | } 146 | 147 | if self.y_pos >= (self.height() - 16) { 148 | self.clear(); 149 | } 150 | 151 | let rendered = font8x8::BASIC_FONTS 152 | .get(c) 153 | .expect("Character not found in basic font"); 154 | 155 | self.write_rendered_char(rendered); 156 | } 157 | } 158 | } 159 | 160 | fn write_rendered_char(&mut self, rendered: [u8; 8]) { 161 | for (y, byte) in rendered.iter().enumerate() { 162 | for (x, bit) in (0..8).enumerate() { 163 | let draw = *byte & (1 << bit) == 0; 164 | self.write_pixel( 165 | self.x_pos + x, 166 | self.y_pos + y, 167 | if draw { self.bg } else { self.fg }, 168 | ); 169 | } 170 | } 171 | 172 | self.x_pos += 8; 173 | } 174 | 175 | fn write_pixel(&mut self, x: usize, y: usize, color: Color) { 176 | let pixel_offset = y * self.info.stride + x; 177 | let color = [ 178 | (color.0.get_bits(0..8) & 255) as u8, 179 | (color.0.get_bits(8..16) & 255) as u8, 180 | (color.0.get_bits(16..24) & 255) as u8, 181 | (color.0.get_bits(24..32) & 255) as u8, 182 | ]; 183 | 184 | let bits_per_pixel = self.info.bits_per_pixel; 185 | let byte_offset = pixel_offset * bits_per_pixel; 186 | 187 | self.backbuffer[byte_offset..(byte_offset + bits_per_pixel)] 188 | .copy_from_slice(&color[..bits_per_pixel]); 189 | } 190 | 191 | #[inline] 192 | fn clear(&mut self) { 193 | self.x_pos = 0; 194 | self.y_pos = 0; 195 | 196 | self.backbuffer.fill(0x00) 197 | } 198 | 199 | #[inline] 200 | fn width(&self) -> usize { 201 | self.info.horizontal_resolution 202 | } 203 | 204 | #[inline] 205 | fn height(&self) -> usize { 206 | self.info.vertical_resolution 207 | } 208 | 209 | #[inline] 210 | fn carriage_return(&mut self) { 211 | self.x_pos = 0; 212 | } 213 | 214 | #[inline] 215 | fn new_line(&mut self) { 216 | if !self.scroll_lock { 217 | self.y_pos += 16; 218 | } 219 | 220 | self.carriage_return(); 221 | } 222 | 223 | fn flush(&mut self) { 224 | // SAFETY: life is ment to be unsafe 225 | unsafe { 226 | self.backbuffer 227 | .as_ptr() 228 | .copy_to_nonoverlapping(self.framebuffer.as_mut_ptr(), self.framebuffer.len()); 229 | } 230 | } 231 | } 232 | 233 | impl fmt::Write for Logger { 234 | fn write_str(&mut self, s: &str) -> fmt::Result { 235 | for c in s.chars() { 236 | self.write_char(c) 237 | } 238 | 239 | Ok(()) 240 | } 241 | } 242 | 243 | /// This function is responsible for initializing the global logger 244 | /// instance. 245 | pub fn init(framebuffer: &'static mut [u8], backbuffer: &'static mut [u8], info: FrameBufferInfo) { 246 | let logger = LOGGER.call_once(move || LockedLogger::new(framebuffer, backbuffer, info)); 247 | 248 | log::set_logger(logger).expect("Logger already set"); 249 | log::set_max_level(log::LevelFilter::Trace); 250 | } 251 | 252 | #[macro_export] 253 | macro_rules! print { 254 | ($($arg:tt)*) => ($crate::logger::_print(format_args!($($arg)*))); 255 | } 256 | 257 | #[macro_export] 258 | macro_rules! println { 259 | () => ($crate::prelude::print!("\n")); 260 | ($($arg:tt)*) => ($crate::prelude::print!("{}\n", format_args!($($arg)*))); 261 | } 262 | 263 | /// This function is responsible for clearing the screen. 264 | pub fn clear() { 265 | LOGGER.get().map(|l| l.0.lock().clear()); 266 | } 267 | 268 | pub fn set_cursor_pos(x: usize, y: usize) { 269 | LOGGER.get().map(|l| { 270 | l.0.lock().x_pos = x; 271 | l.0.lock().y_pos = y; 272 | }); 273 | } 274 | 275 | pub fn with_fg(color: Color, f: F) 276 | where 277 | F: FnOnce(), 278 | { 279 | LOGGER.get().map(|l| { 280 | let mut lock = l.0.lock(); 281 | let old = lock.fg; 282 | lock.fg = color; 283 | core::mem::drop(lock); 284 | f(); 285 | let mut lock = l.0.lock(); 286 | lock.fg = old; 287 | }); 288 | } 289 | 290 | pub fn flush() { 291 | LOGGER.get().map(|l| l.0.lock().flush()); 292 | } 293 | pub fn display_height() -> usize { 294 | LOGGER.get().map(|l| l.0.lock().height()).unwrap() 295 | } 296 | 297 | pub fn set_scroll_lock(lock: bool) { 298 | LOGGER.get().map(|l| l.0.lock().scroll_lock = lock); 299 | } 300 | 301 | #[doc(hidden)] 302 | pub fn _print(args: fmt::Arguments) { 303 | LOGGER.get().map(|l| l.0.lock().write_fmt(args)); 304 | } 305 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![feature( 2 | abi_efiapi, 3 | custom_test_frameworks, 4 | asm, 5 | panic_info_message, 6 | alloc_error_handler, 7 | maybe_uninit_slice 8 | )] 9 | #![test_runner(crate::test_runner)] 10 | #![no_std] 11 | #![no_main] 12 | 13 | extern crate alloc; 14 | 15 | use uefi::prelude::*; 16 | use uefi::proto::console::gop::GraphicsOutput; 17 | use uefi::proto::loaded_image::LoadedImage; 18 | use uefi::proto::media::file::{Directory, File, FileAttribute, FileInfo, FileMode, RegularFile}; 19 | use uefi::proto::media::fs::SimpleFileSystem; 20 | use uefi::table::boot::{AllocateType, MemoryDescriptor, MemoryType}; 21 | use x86_64::registers::control::{Cr3, Cr3Flags}; 22 | use x86_64::structures::paging::*; 23 | use x86_64::VirtAddr; 24 | 25 | use core::mem; 26 | use core::panic::PanicInfo; 27 | 28 | mod config; 29 | mod logger; 30 | mod menu; 31 | mod pmm; 32 | mod protocols; 33 | mod prelude { 34 | pub use crate::{print, println}; 35 | } 36 | 37 | #[alloc_error_handler] 38 | fn alloc_error_handler(layout: core::alloc::Layout) -> ! { 39 | panic!("oom {:?}", layout) 40 | } 41 | 42 | pub struct BootPageTables { 43 | /// Provides access to the page tables of the bootloader address space. 44 | pub bootloader: OffsetPageTable<'static>, 45 | /// Provides access to the page tables of the kernel address space (not active). 46 | pub kernel: OffsetPageTable<'static>, 47 | /// The physical frame where the level 4 page table of the kernel address space is stored. 48 | pub kernel_level_4_frame: PhysFrame, 49 | } 50 | 51 | /// Helper function to create and load the bootloader's page table and 52 | /// create a new page table for the kernel itself. 53 | fn setup_boot_paging(frame_allocator: &mut impl FrameAllocator) -> BootPageTables { 54 | // NOTE: UEFI identity-maps all memory, so the offset between physical 55 | // and virtual addresses is 0. 56 | let off = VirtAddr::zero(); 57 | 58 | let boot_page_table = { 59 | let old_table = { 60 | let (frame, _) = Cr3::read(); 61 | 62 | let ptr: *const PageTable = (off + frame.start_address().as_u64()).as_ptr(); 63 | 64 | unsafe { &*ptr } 65 | }; 66 | 67 | let new_frame = frame_allocator 68 | .allocate_frame() 69 | .expect("mm: failed to allocate frame for new level 4 boot table"); 70 | 71 | let new_table: &mut PageTable = { 72 | let ptr: *mut PageTable = (off + new_frame.start_address().as_u64()).as_mut_ptr(); 73 | 74 | unsafe { 75 | // Create a new empty, fresh page table. 76 | ptr.write(PageTable::new()); 77 | &mut *ptr 78 | } 79 | }; 80 | 81 | // Copy the first entry (we don't need to access more than 512 GiB; also, some UEFI 82 | // implementations seem to create an level 4 table entry 0 in all slots) 83 | new_table[0] = old_table[0].clone(); 84 | 85 | // The first level 4 table entry is now identical, so we can just load the new one. 86 | unsafe { 87 | Cr3::write(new_frame, Cr3Flags::empty()); 88 | OffsetPageTable::new(&mut *new_table, off) 89 | } 90 | }; 91 | 92 | // Now we will create a page table for the kernel itself. 93 | let (kernel_page_table, kernel_level_4_frame) = { 94 | // get an unused frame for new level 4 page table 95 | let frame: PhysFrame = frame_allocator 96 | .allocate_frame() 97 | .expect("mm: no unused frames"); 98 | 99 | log::info!("new page table at: {:#?}", &frame); 100 | 101 | // 1. Get the corresponding virtual address. 102 | let addr = off + frame.start_address().as_u64(); 103 | 104 | // 2. Initialize a new page table. 105 | let ptr = addr.as_mut_ptr(); 106 | unsafe { *ptr = PageTable::new() }; 107 | let level_4_table = unsafe { &mut *ptr }; 108 | 109 | (unsafe { OffsetPageTable::new(level_4_table, off) }, frame) 110 | }; 111 | 112 | BootPageTables { 113 | bootloader: boot_page_table, 114 | kernel: kernel_page_table, 115 | kernel_level_4_frame, 116 | } 117 | } 118 | 119 | /// This function is responsible for initializing the logger for Ion and 120 | /// returns the physical address of the framebuffer. 121 | fn init_logger(system_table: &SystemTable) { 122 | let gop = system_table 123 | .boot_services() 124 | .locate_protocol::() 125 | .expect_success("failed to locate GOP"); 126 | 127 | let gop = unsafe { &mut *gop.get() }; 128 | let mode_info = gop.current_mode_info(); 129 | let (horizontal_resolution, vertical_resolution) = mode_info.resolution(); 130 | 131 | let mut framebuffer = gop.frame_buffer(); 132 | 133 | let backbuffer = unsafe { 134 | let ptr = system_table 135 | .boot_services() 136 | .allocate_pool(MemoryType::LOADER_DATA, framebuffer.size()) 137 | .expect_success("could not allocate memory"); 138 | 139 | // SAFETY: The provided pointer by allocate_pool is guaranteed to be 140 | // valid. 141 | core::slice::from_raw_parts_mut(ptr, framebuffer.size()) 142 | }; 143 | 144 | let slice = 145 | unsafe { core::slice::from_raw_parts_mut(framebuffer.as_mut_ptr(), framebuffer.size()) }; 146 | 147 | let info = logger::FrameBufferInfo { 148 | horizontal_resolution, 149 | vertical_resolution, 150 | pixel_format: match mode_info.pixel_format() { 151 | uefi::proto::console::gop::PixelFormat::Rgb => logger::PixelFormat::RGB, 152 | uefi::proto::console::gop::PixelFormat::Bgr => logger::PixelFormat::BGR, 153 | _ => unimplemented!(), 154 | }, 155 | bits_per_pixel: 4, 156 | stride: mode_info.stride(), 157 | }; 158 | 159 | logger::init(slice, backbuffer, info) 160 | } 161 | 162 | fn prepare_kernel( 163 | system_table: &SystemTable, 164 | root: &mut Directory, 165 | entry: &config::ConfigurationEntry, 166 | ) -> &'static [u8] { 167 | let parsed_uri = config::parse_uri(entry.path()).expect("stivale2: failed to parse the URI"); 168 | let uri = config::handle_uri_redirect(&parsed_uri, root); 169 | 170 | assert_ne!(entry.path().len(), 0, "stivale2: KERNEL_PATH not specified"); 171 | 172 | let kernel_path = entry.path(); 173 | let file_completion = uri 174 | .open(parsed_uri.path(), FileMode::Read, FileAttribute::empty()) 175 | .expect_success("stivale2: failed to open kernel file. Is its path correct?"); 176 | 177 | log::debug!("stivale2: loading kernel {}...\n", kernel_path); 178 | 179 | let mut cfg_file_handle = unsafe { RegularFile::new(file_completion) }; 180 | 181 | let mut info_buf = [0; 0x100]; 182 | let cfg_info = cfg_file_handle 183 | .get_info::(&mut info_buf) 184 | .unwrap_success(); 185 | 186 | let pages = cfg_info.file_size() as usize / 0x1000 + 1; 187 | let mem_start = system_table 188 | .boot_services() 189 | .allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, pages) 190 | .unwrap_success(); 191 | 192 | let buf = unsafe { core::slice::from_raw_parts_mut(mem_start as *mut u8, pages * 0x1000) }; 193 | let len = cfg_file_handle.read(buf).unwrap_success(); 194 | 195 | buf[..len].as_ref() 196 | } 197 | 198 | #[entry] 199 | fn efi_main(image_handle: Handle, system_table: SystemTable) -> Status { 200 | system_table 201 | .stdout() 202 | .clear() 203 | .expect_success("failed to clear system stdout"); 204 | 205 | init_logger(&system_table); 206 | 207 | let boot_services = system_table.boot_services(); 208 | 209 | unsafe { 210 | // SAFETY: We invoke exit_boot_services in alloc when we are done with the 211 | // boot services. 212 | uefi::alloc::init(boot_services); 213 | } 214 | 215 | // Query the handle for the loaded image protocol. 216 | let loaded_image = system_table 217 | .boot_services() 218 | .handle_protocol::(image_handle) 219 | .expect_success("failed to retrieve loaded image protocokl"); 220 | let loaded_image = unsafe { &*loaded_image.get() }; // Get the inner cell value 221 | 222 | // Query the handle for the simple file system protocol. 223 | let filesystem = system_table 224 | .boot_services() 225 | .handle_protocol::(loaded_image.device()) 226 | .expect_success("failed to retrieve simple file system to read disk"); 227 | let filesystem = unsafe { &mut *filesystem.get() }; // Get the inner cell value 228 | 229 | // Open the root directory of the simple file system volume. 230 | let mut root = filesystem 231 | .open_volume() 232 | .expect_success("failed to open volume"); 233 | 234 | let ion_config = config::load(&system_table, &mut root); // Load the config and store it in a local variable. 235 | let selected_entry = menu::init(&system_table, ion_config); 236 | 237 | // We have to load the kernel before we exit the boot services since we rely on the 238 | // simple file system boot services protocol to read the kernel from the disk into 239 | // memory. 240 | let kernel = prepare_kernel(&system_table, &mut root, &selected_entry); 241 | 242 | let mmap_storage = { 243 | let max_mmap_size = 244 | system_table.boot_services().memory_map_size() + 8 * mem::size_of::(); 245 | 246 | let ptr = system_table 247 | .boot_services() 248 | .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size) 249 | .expect_success("dispatch: failed to allocate pool for memory map"); 250 | 251 | unsafe { core::slice::from_raw_parts_mut(ptr, max_mmap_size) } 252 | }; 253 | 254 | uefi::alloc::exit_boot_services(); 255 | 256 | let (_, mmap) = system_table 257 | .exit_boot_services(image_handle, mmap_storage) 258 | .expect_success("ion: failed to exit the boot services"); 259 | 260 | logger::clear(); 261 | logger::flush(); 262 | 263 | let mut allocator = pmm::BootFrameAllocator::new(mmap.copied()); 264 | let mut offset_tables = setup_boot_paging(&mut allocator); 265 | 266 | match selected_entry.protocol() { 267 | config::BootProtocol::Stivale2 => { 268 | protocols::stivale2::boot(&mut offset_tables, &mut allocator, kernel) 269 | } 270 | 271 | config::BootProtocol::Stivale => todo!(), 272 | config::BootProtocol::Multiboot => todo!(), 273 | config::BootProtocol::Multiboot2 => todo!(), 274 | config::BootProtocol::Linux => todo!(), 275 | } 276 | 277 | loop {} 278 | } 279 | 280 | #[panic_handler] 281 | extern "C" fn rust_begin_unwind(info: &PanicInfo) -> ! { 282 | unsafe { 283 | logger::LOGGER.get().map(|l| l.force_unlock()); 284 | } 285 | 286 | let deafult_panic = &format_args!(""); 287 | let panic_message = info.message().unwrap_or(deafult_panic); 288 | 289 | log::error!("cpu '0' panicked at '{}'", panic_message); 290 | 291 | if let Some(panic_location) = info.location() { 292 | log::error!("{}", panic_location); 293 | } 294 | 295 | logger::flush(); 296 | 297 | unsafe { 298 | asm!("cli"); 299 | 300 | loop { 301 | asm!("hlt"); 302 | } 303 | } 304 | } 305 | 306 | #[cfg(test)] 307 | fn test_runner(tests: &[&dyn Fn()]) { 308 | for test in tests { 309 | test(); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use alloc::string::String; 2 | use alloc::vec::Vec; 3 | use uefi::prelude::*; 4 | use uefi::proto::console::text::{Input, Key}; 5 | use uefi::proto::media::file::{Directory, File, FileAttribute, FileInfo, FileMode, RegularFile}; 6 | use uefi::table::boot::{AllocateType, MemoryType}; 7 | 8 | use crate::prelude::*; 9 | 10 | const CONFIG_PATHS: &[&str] = &["boot\\ion.cfg", "ion.cfg"]; 11 | 12 | #[derive(Debug, Clone, Copy)] 13 | pub enum BootProtocol { 14 | Stivale2, 15 | Stivale, 16 | Multiboot, 17 | Multiboot2, 18 | Linux, 19 | } 20 | 21 | #[derive(Clone)] 22 | pub struct ConfigurationEntry { 23 | protocol: BootProtocol, 24 | path: &'static str, 25 | name: &'static str, 26 | command_line: &'static str, 27 | } 28 | 29 | impl ConfigurationEntry { 30 | /// Returns the path of the kernel in the config entry. 31 | #[inline] 32 | pub fn path(&self) -> &'static str { 33 | self.path 34 | } 35 | 36 | /// Returns the boot protocol of the kernel in the config entry. 37 | #[inline] 38 | pub fn protocol(&self) -> BootProtocol { 39 | self.protocol 40 | } 41 | 42 | /// Returns the name of the kernel in the config entry. 43 | #[inline] 44 | pub fn name(&self) -> &'static str { 45 | self.name 46 | } 47 | } 48 | 49 | #[derive(Debug)] 50 | struct BootConfigutation { 51 | timeout: usize, 52 | } 53 | 54 | pub struct IonConfig { 55 | boot: BootConfigutation, 56 | pub entries: alloc::vec::Vec, 57 | } 58 | 59 | impl IonConfig { 60 | /// Returns the timout in seconds for the boot menu. 61 | pub fn timeout(&self) -> usize { 62 | self.boot.timeout 63 | } 64 | } 65 | 66 | /// This function is responsible for wating for a keystroke event and returns the respective 67 | /// key code for that keystroke. 68 | pub fn get_char(system_table: &SystemTable) -> Key { 69 | unsafe { 70 | // Retrieve the input protocol from the boot services, 71 | let input_protocol = system_table 72 | .boot_services() 73 | .locate_protocol::() 74 | .expect_success("Failed to locate input protocol"); 75 | 76 | let key = &mut *input_protocol.get(); // Get the inner cell value 77 | let wait_for_key_event = key.wait_for_key_event(); // Get a reference to the wait for key event 78 | 79 | // Loop until there is a keyboard event 80 | loop { 81 | system_table 82 | .boot_services() 83 | .wait_for_event(&mut [wait_for_key_event]) 84 | .expect_success("Failed add event in wait queue"); 85 | 86 | // Try and read the next keystroke from the input device, if any. 87 | let scancode = key.read_key().expect_success("Failed to read key"); 88 | 89 | if let Some(code) = scancode { 90 | return code; 91 | } 92 | } 93 | } 94 | } 95 | 96 | pub struct Uri { 97 | resource: String, 98 | partition: Option, 99 | path: String, 100 | } 101 | 102 | impl Uri { 103 | /// Returns the path component of the URI. 104 | pub fn path(&self) -> &str { 105 | &self.path 106 | } 107 | } 108 | 109 | #[derive(Debug, Clone, Copy)] 110 | pub enum UriParseError { 111 | /// Missing the resource part of the URI. (e.g. `:///root/path`) 112 | MissingResource, 113 | /// Invalid syntax for the URI. (e.g. `://`, `:^)` or invalid partition number.) 114 | InvalidSyntax, 115 | /// Invalid partition number or the provided partition number is out of bounds 116 | /// `0..256`. 117 | InvalidPartition, 118 | } 119 | 120 | pub fn handle_uri_redirect<'a>(parsed_uri: &Uri, root: &'a mut Directory) -> &'a mut Directory { 121 | match parsed_uri.resource.as_ref() { 122 | "boot" => { 123 | if parsed_uri.partition.is_some() { 124 | unimplemented!() 125 | } else { 126 | // The user has not provided a partition number, so we will 127 | // use the root directory of the boot partition instead. 128 | root 129 | } 130 | } 131 | 132 | "hdd" => unimplemented!(), 133 | "odd" => unimplemented!(), 134 | "guid" => unimplemented!(), 135 | "uuid" => unimplemented!(), 136 | 137 | "bios" => { 138 | panic!( 139 | "bios:// resource is no longer supported. Checkout CONFIG.md for hdd:// and odd://" 140 | ) 141 | } 142 | 143 | resource => panic!("unsupported resource type: {}", resource), 144 | } 145 | } 146 | 147 | /// Helper function to parse the path URI. A URI takes the form of: 148 | /// `resource:///root/path`. This function will return false if the URI is 149 | /// not valid. 150 | pub fn parse_uri(uri: &'static str) -> Result { 151 | // 1. Seperate the domain from the URI. 152 | let uri = uri.split(':').collect::>(); 153 | 154 | // ERROR: missing the resource 155 | if uri.len() == 0 { 156 | return Err(UriParseError::MissingResource); 157 | } 158 | 159 | let resource = uri[0]; 160 | let root = uri[1]; 161 | 162 | // ERROR: missing the double backslashes after the resource. 163 | if root.len() < 3 || &root[0..2] != "//" { 164 | return Err(UriParseError::InvalidSyntax); 165 | } 166 | 167 | let root = root[2..].split("/").collect::>(); 168 | 169 | // ERROR: Missing the root partition number (or a backslash indicating 170 | // that we have to use the boot partition) and the root directory itself and 171 | // the path. 172 | if root.len() < 3 { 173 | return Err(UriParseError::InvalidSyntax); 174 | } 175 | 176 | let partition = match root[0] { 177 | "" => None, 178 | n => Some( 179 | n.parse::() 180 | .or(Err(UriParseError::InvalidPartition))?, 181 | ), 182 | }; 183 | 184 | // 2. Convert the provided path to a UEFI path. Since UEFI paths use 185 | // windows type of forward slashes as the path seperator and the URI 186 | // uses backslashes instead. 187 | let path = root[1..].join("\\"); 188 | 189 | Ok(Uri { 190 | resource: String::from(resource), 191 | partition, 192 | path, 193 | }) 194 | } 195 | 196 | /// This function is responsible for loading and parsing the config file for Ion. 197 | pub fn load(system_table: &SystemTable, root: &mut Directory) -> IonConfig { 198 | let mut configuration_file = None; 199 | 200 | // Go through each possible config path and initialize the configuration_file 201 | // variable if file exists. 202 | for filename in CONFIG_PATHS { 203 | let file_completion = root.open(filename, FileMode::Read, FileAttribute::empty()); 204 | 205 | // Check if the file read operation completed with success. 206 | if let Ok(handle) = file_completion { 207 | configuration_file = Some(handle.expect("file read exited with warnings")); 208 | break; // Avoid to re-assign the file handle again. 209 | } 210 | } 211 | 212 | let configuration_file = if let Some(config) = configuration_file { 213 | config 214 | } else { 215 | println!("Configuration file not found.\n"); 216 | 217 | println!("For information on the format of Ion config entries, consult CONFIG.md in"); 218 | println!("the root of the Ion source repository.\n"); 219 | 220 | println!("Press a key to enter an editor session and manually define a config entry..."); 221 | let _ = get_char(system_table); 222 | 223 | // TODO: Print a friendly message that the configuration file does not exist and add a built-in 224 | // terminal way to create the config file on the fly. 225 | unreachable!() 226 | }; 227 | 228 | let mut cfg_file_handle = unsafe { RegularFile::new(configuration_file) }; 229 | 230 | let mut info_buf = [0; 0x100]; 231 | let cfg_info = cfg_file_handle 232 | .get_info::(&mut info_buf) 233 | .unwrap_success(); 234 | 235 | let pages = cfg_info.file_size() as usize / 0x1000 + 1; 236 | let mem_start = system_table 237 | .boot_services() 238 | .allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, pages) 239 | .unwrap_success(); 240 | 241 | let buf = unsafe { core::slice::from_raw_parts_mut(mem_start as *mut u8, pages * 0x1000) }; 242 | let len = cfg_file_handle.read(buf).unwrap_success(); 243 | 244 | let buf = buf[..len].as_ref(); 245 | let configuration_str = core::str::from_utf8(buf).expect("invalid UTF-8 in configuration file"); 246 | 247 | let mut boot_config = BootConfigutation { 248 | // We set the default time out to 5 seconds. 249 | timeout: 5, 250 | }; 251 | 252 | let mut entries = alloc::vec::Vec::new(); 253 | 254 | // Create the menu tree. 255 | for line in configuration_str.split("\n") { 256 | let mut line_chars = line.chars(); 257 | 258 | if let Some(':') = line_chars.nth(0) { 259 | // In this case we got a new entry. 260 | let config = ConfigurationEntry { 261 | // We use stivale 2 as the default boot protocol. 262 | protocol: BootProtocol::Stivale2, 263 | // We have already skipped the colon using line_chars.nth(0) above so the rest 264 | // of the line will be the kernel's name. 265 | name: line_chars.as_str(), 266 | // By default we will set the kernel command line to an empty string. 267 | command_line: "", 268 | // By default we will set the kernel path to an empty string. 269 | path: "", 270 | }; 271 | 272 | entries.push(config); 273 | } else if let Some(current_entry) = entries.last_mut() { 274 | // Else in this case we are defining the local keys. 275 | if let Some(key_idx) = line.find("=") { 276 | let mut local_chars = line.chars(); 277 | local_chars.nth(key_idx); // Skip the key 278 | 279 | let value = local_chars.as_str(); // Left with the value 280 | 281 | if line.starts_with("PROTOCOL=") 282 | || line.starts_with("KERNEL_PROTOCOL=") 283 | || line.starts_with("PROTO=") 284 | { 285 | let protocol = match value { 286 | "stivale2" => BootProtocol::Stivale2, 287 | "stivale1" => BootProtocol::Stivale, 288 | "stivale" => BootProtocol::Stivale, 289 | 290 | "multiboot" => BootProtocol::Multiboot, 291 | "multiboot1" => BootProtocol::Multiboot, 292 | "multiboot2" => BootProtocol::Multiboot2, 293 | 294 | "linux" => BootProtocol::Linux, 295 | 296 | _ => panic!("Invalid boot protocol"), 297 | }; 298 | 299 | current_entry.protocol = protocol; 300 | } else if line.starts_with("CMDLINE=") || line.starts_with("KERNEL_CMDLINE=") { 301 | current_entry.command_line = value; 302 | } else if line.starts_with("PATH=") || line.starts_with("KERNEL_PATH=") { 303 | current_entry.path = value; 304 | 305 | // TODO: Do not just expect the user to give the correct kernel path and verify 306 | // and parse the URI specified by the user. We will leave it as it is right now. 307 | } 308 | } 309 | } else { 310 | // In this case we got a global key. 311 | if let Some(key_idx) = line.find("=") { 312 | let mut local_chars = line.chars(); 313 | local_chars.nth(key_idx); // Skip the key 314 | 315 | let value = local_chars.as_str(); // Left with the value 316 | 317 | if line.starts_with("TIMEOUT") { 318 | let timeout = 319 | value 320 | .parse::() 321 | .unwrap_or_else(|_| if value.eq("no") { 0 } else { 5 }); 322 | 323 | boot_config.timeout = timeout; 324 | } 325 | } 326 | } 327 | } 328 | 329 | cfg_file_handle.close(); 330 | 331 | IonConfig { 332 | boot: boot_config, 333 | entries, 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /src/protocols/stivale2.rs: -------------------------------------------------------------------------------- 1 | use core::mem::MaybeUninit; 2 | 3 | use crate::logger; 4 | use crate::pmm::BootFrameAllocator; 5 | use crate::pmm::BootMemoryRegion; 6 | use crate::pmm::MemoryRegion; 7 | use crate::pmm::UsedLevel4Entries; 8 | use crate::BootPageTables; 9 | 10 | use raw_cpuid::CpuId; 11 | use stivale_boot::v2::*; 12 | 13 | use x86_64::align_up; 14 | use x86_64::registers::control::Cr0; 15 | use x86_64::registers::control::Cr0Flags; 16 | use x86_64::registers::model_specific::Efer; 17 | use x86_64::registers::model_specific::EferFlags; 18 | use x86_64::structures::paging::*; 19 | use x86_64::PhysAddr; 20 | use x86_64::VirtAddr; 21 | 22 | use x86_64::structures::paging::mapper::MapToError; 23 | use xmas_elf::program::ProgramHeader; 24 | 25 | fn handle_bss_segment( 26 | segment: &ProgramHeader, 27 | segment_flags: PageTableFlags, 28 | kernel_offset: PhysAddr, 29 | page_table: &mut OffsetPageTable, 30 | frame_allocator: &mut impl FrameAllocator, 31 | ) -> Result<(), MapToError> { 32 | let virt_start_addr = VirtAddr::new(segment.virtual_addr()); 33 | let phys_start_addr = kernel_offset + segment.offset(); 34 | let mem_size = segment.mem_size(); 35 | let file_size = segment.file_size(); 36 | 37 | // Calculate virual memory region that must be zeroed 38 | let zero_start = virt_start_addr + file_size; 39 | let zero_end = virt_start_addr + mem_size; 40 | 41 | // A type alias that helps in efficiently clearing a page 42 | type PageArray = [u64; Size4KiB::SIZE as usize / 8]; 43 | const ZERO_ARRAY: PageArray = [0; Size4KiB::SIZE as usize / 8]; 44 | 45 | // In some cases, `zero_start` might not be page-aligned. This requires some 46 | // special treatment because we can't safely zero a frame of the original file. 47 | let data_bytes_before_zero = zero_start.as_u64() & 0xfff; 48 | if data_bytes_before_zero != 0 { 49 | /* 50 | * The last non-bss frame of the segment consists partly of data and partly of bss 51 | * memory, which must be zeroed. Unfortunately, the file representation might have 52 | * reused the part of the frame that should be zeroed to store the next segment. This 53 | * means that we can't simply overwrite that part with zeroes, as we might overwrite 54 | * other data this way. 55 | * 56 | * Example: 57 | * 58 | * XXXXXXXXXXXXXXX000000YYYYYYY000ZZZZZZZZZZZ virtual memory (XYZ are data) 59 | * |·············| /·····/ /·········/ 60 | * |·············| ___/·····/ /·········/ 61 | * |·············|/·····/‾‾‾ /·········/ 62 | * |·············||·····|/·̅·̅·̅·̅·̅·····/‾‾‾‾ 63 | * XXXXXXXXXXXXXXXYYYYYYYZZZZZZZZZZZ file memory (zeros are not saved) 64 | * ' ' ' ' ' 65 | * The areas filled with dots (`·`) indicate a mapping between virtual and file 66 | * memory. We see that the data regions `X`, `Y`, `Z` have a valid mapping, while 67 | * the regions that are initialized with 0 have not. 68 | * 69 | * The ticks (`'`) below the file memory line indicate the start of a new frame. We 70 | * see that the last frames of the `X` and `Y` regions in the file are followed 71 | * by the bytes of the next region. So we can't zero these parts of the frame 72 | * because they are needed by other memory regions. 73 | * 74 | * To solve this problem, we need to allocate a new frame for the last segment page 75 | * and copy all data content of the original frame over. Afterwards, we can zero 76 | * the remaining part of the frame since the frame is no longer shared with other 77 | * segments now. 78 | */ 79 | 80 | // Calculate the frame where the last segment page is mapped 81 | let orig_frame: PhysFrame = 82 | PhysFrame::containing_address(phys_start_addr + file_size - 1u64); 83 | 84 | // Allocate a new frame to replace `orig_frame` 85 | let new_frame = frame_allocator.allocate_frame().unwrap(); 86 | 87 | // Zero new frame, utilizing that it's identity-mapped 88 | { 89 | let new_frame_ptr = new_frame.start_address().as_u64() as *mut PageArray; 90 | unsafe { new_frame_ptr.write(ZERO_ARRAY) }; 91 | } 92 | 93 | // Copy the data bytes from orig_frame to new_frame 94 | { 95 | log::info!("Copy contents"); 96 | let orig_bytes_ptr = orig_frame.start_address().as_u64() as *mut u8; 97 | let new_bytes_ptr = new_frame.start_address().as_u64() as *mut u8; 98 | 99 | for offset in 0..(data_bytes_before_zero as isize) { 100 | unsafe { 101 | let orig_byte = orig_bytes_ptr.offset(offset).read(); 102 | new_bytes_ptr.offset(offset).write(orig_byte); 103 | } 104 | } 105 | } 106 | 107 | // Remap last page from orig_frame to `new_frame` 108 | log::info!("Remap last page"); 109 | 110 | let last_page = Page::containing_address(virt_start_addr + file_size - 1u64); 111 | 112 | // SAFETY: We operate on an inactive page table, so we don't need to flush our changes 113 | page_table.unmap(last_page.clone()).unwrap().1.ignore(); 114 | 115 | let flusher = 116 | unsafe { page_table.map_to(last_page, new_frame, segment_flags, frame_allocator) }?; 117 | 118 | // SAFETY: We operate on an inactive page table, so we don't need to flush our changes 119 | flusher.ignore(); 120 | } 121 | 122 | // Map additional frames for `.bss` memory that is not present in source file 123 | let start_page: Page = 124 | Page::containing_address(VirtAddr::new(align_up(zero_start.as_u64(), Size4KiB::SIZE))); 125 | let end_page = Page::containing_address(zero_end); 126 | 127 | for page in Page::range_inclusive(start_page, end_page) { 128 | let frame = frame_allocator.allocate_frame().unwrap(); 129 | 130 | // Zero frame, utilizing identity-mapping 131 | let frame_ptr = frame.start_address().as_u64() as *mut PageArray; 132 | unsafe { frame_ptr.write(ZERO_ARRAY) }; 133 | 134 | let flusher = unsafe { page_table.map_to(page, frame, segment_flags, frame_allocator)? }; 135 | 136 | // SAFETY: We operate on an inactive page table, so we don't need to flush our changes 137 | flusher.ignore(); 138 | } 139 | 140 | Ok(()) 141 | } 142 | 143 | fn handle_load_segment( 144 | segment: ProgramHeader, 145 | kernel_offset: PhysAddr, 146 | page_table: &mut OffsetPageTable, 147 | frame_allocator: &mut impl FrameAllocator, 148 | ) -> Result<(), MapToError> { 149 | let phys_start_addr = kernel_offset + segment.offset(); 150 | let start_frame: PhysFrame = PhysFrame::containing_address(phys_start_addr); 151 | let end_frame: PhysFrame = 152 | PhysFrame::containing_address(phys_start_addr + segment.file_size() - 1u64); 153 | 154 | let virt_start_addr = VirtAddr::new(segment.virtual_addr()); 155 | let start_page: Page = Page::containing_address(virt_start_addr); 156 | 157 | let mut segment_flags = PageTableFlags::PRESENT; 158 | 159 | if !segment.flags().is_execute() { 160 | segment_flags |= PageTableFlags::NO_EXECUTE; 161 | } 162 | 163 | if segment.flags().is_write() { 164 | segment_flags |= PageTableFlags::WRITABLE; 165 | } 166 | 167 | // Map all frames of the segment at the desired virtual address. 168 | for frame in PhysFrame::range_inclusive(start_frame, end_frame) { 169 | let offset = frame - start_frame; 170 | let page = start_page + offset; 171 | 172 | let flusher = unsafe { page_table.map_to(page, frame, segment_flags, frame_allocator) }?; 173 | // We operate on an inactive page table, so there's no need to flush anything :^) 174 | flusher.ignore(); 175 | } 176 | 177 | if segment.mem_size() > segment.file_size() { 178 | handle_bss_segment( 179 | &segment, 180 | segment_flags, 181 | kernel_offset, 182 | page_table, 183 | frame_allocator, 184 | )?; 185 | } 186 | 187 | Ok(()) 188 | } 189 | 190 | fn allocate_boot_info_tag( 191 | page_tables: &mut BootPageTables, 192 | frame_allocator: &mut BootFrameAllocator, 193 | useable_entries: &mut UsedLevel4Entries, 194 | value: T, 195 | ) -> &'static mut T 196 | where 197 | I: ExactSizeIterator + Clone, 198 | D: BootMemoryRegion, 199 | { 200 | let addr = useable_entries.get_free_address(); 201 | let addr_end = addr + core::mem::size_of::(); 202 | 203 | let memory_map_regions_addr = addr_end.align_up(core::mem::align_of::() as u64); 204 | let regions = frame_allocator.len() + 1; // one region might be split into used/unused 205 | let memory_map_regions_end = 206 | memory_map_regions_addr + regions * core::mem::size_of::(); 207 | 208 | let start_page = Page::containing_address(addr); 209 | let end_page = Page::containing_address(memory_map_regions_end - 1u64); 210 | for page in Page::range_inclusive(start_page, end_page) { 211 | let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; 212 | let frame = frame_allocator 213 | .allocate_frame() 214 | .expect("frame allocation for boot info failed"); 215 | 216 | unsafe { 217 | page_tables 218 | .kernel 219 | .map_to(page, frame, flags, frame_allocator) 220 | } 221 | .unwrap() 222 | .flush(); 223 | 224 | // We need to be able to access it too. 225 | unsafe { 226 | page_tables 227 | .bootloader 228 | .map_to(page, frame, flags, frame_allocator) 229 | } 230 | .unwrap() 231 | .flush(); 232 | } 233 | 234 | let boot_info: &'static mut MaybeUninit = unsafe { &mut *addr.as_mut_ptr() }; 235 | boot_info.write(value) 236 | } 237 | 238 | pub fn boot( 239 | page_tables: &mut BootPageTables, 240 | frame_allocator: &mut BootFrameAllocator, 241 | kernel: &'static [u8], 242 | ) where 243 | I: ExactSizeIterator + Clone, 244 | D: BootMemoryRegion, 245 | { 246 | let kernel_offset = unsafe { PhysAddr::new_unsafe(&kernel[0] as *const u8 as u64) }; 247 | assert!( 248 | kernel_offset.is_aligned(Size4KiB::SIZE), 249 | "stivale2: loaded kernel ELF file is not sufficiently aligned" 250 | ); 251 | 252 | let elf = xmas_elf::ElfFile::new(kernel).expect("stivale2: invalid ELF file"); 253 | 254 | let stivale2_hdr; 255 | let is_32_bit = false; 256 | 257 | enable_nxe_bit(); 258 | enable_write_protect_bit(); 259 | 260 | match elf.header.pt2.machine().as_machine() { 261 | xmas_elf::header::Machine::X86_64 => { 262 | // 1. Check if the CPU actually supports long mode. 263 | let long_mode_supported = CpuId::new() 264 | .get_extended_processor_and_feature_identifiers() 265 | .map_or(false, |info| info.has_64bit_mode()); 266 | 267 | if !long_mode_supported { 268 | panic!("stivale2: CPU does not support 64-bit mode.") 269 | } 270 | 271 | xmas_elf::header::sanity_check(&elf).expect("stivale2: failed ELF sanity check"); 272 | 273 | // 2. Get the stivale2 header section. 274 | let header = elf 275 | .find_section_by_name(".stivale2hdr") 276 | .expect("stivale2: section .stivale2hdr not found"); 277 | 278 | if header.size() < core::mem::size_of::() as u64 { 279 | panic!("stivale2: section .stivale2hdr is smaller than size of the struct."); 280 | } else if header.size() > core::mem::size_of::() as u64 { 281 | panic!("stivale2: section .stivale2hdr is larger than size of the struct."); 282 | } 283 | 284 | // SAFETY: The size of the section is checked above and the address provided is valid and 285 | // mapped. 286 | stivale2_hdr = unsafe { &*(header.raw_data(&elf).as_ptr() as *const StivaleHeader) }; 287 | 288 | log::info!("stivale2: 64-bit kernel detected"); 289 | 290 | // 3. Load the kernel. 291 | for p_header in elf.program_iter() { 292 | xmas_elf::program::sanity_check(p_header, &elf) 293 | .expect("stivale2: failed ELF program header sanity check"); 294 | 295 | match p_header 296 | .get_type() 297 | .expect("stivale2: failed to get ELF program heade type") 298 | { 299 | xmas_elf::program::Type::Load => handle_load_segment( 300 | p_header, 301 | kernel_offset, 302 | &mut page_tables.kernel, 303 | frame_allocator, 304 | ) 305 | .unwrap(), 306 | _ => {} 307 | } 308 | } 309 | } 310 | 311 | machine => panic!("stivale2: unsupported architecture {:?}", machine), 312 | }; 313 | 314 | if (stivale2_hdr.get_flags() & (1 << 1)) == 1 && is_32_bit { 315 | panic!("stivale2: higher half header flag not supported in 32-bit mode"); 316 | } 317 | 318 | // The stivale2 specs says the stack has to be 16-byte aligned. 319 | if (stivale2_hdr.get_stack() as u64 & (16 - 1)) != 0 { 320 | panic!("stivale2: requested stack is not 16-byte aligned"); 321 | } 322 | 323 | // It also says the stack cannot be NULL for 32-bit kernels 324 | if is_32_bit && stivale2_hdr.get_stack() as u64 == 0 { 325 | panic!("stivale2: the stack cannot be 0 for 32-bit kernels"); 326 | } 327 | 328 | // Identity-map context switch function, so that we don't get an immediate pagefault 329 | // after switching the active page table. 330 | let context_switch_function = PhysAddr::new(context_switch as *const () as u64); 331 | let context_switch_function_start_frame: PhysFrame = 332 | PhysFrame::containing_address(context_switch_function); 333 | 334 | for frame in PhysFrame::range_inclusive( 335 | context_switch_function_start_frame, 336 | context_switch_function_start_frame + 1, 337 | ) { 338 | unsafe { 339 | page_tables 340 | .kernel 341 | .identity_map(frame, PageTableFlags::PRESENT, frame_allocator) 342 | } 343 | .unwrap() 344 | .flush(); 345 | } 346 | 347 | logger::flush(); 348 | 349 | let mut useable_entries = UsedLevel4Entries::new(elf.program_iter()); 350 | 351 | let offset = useable_entries.get_free_address(); 352 | let start_frame = PhysFrame::containing_address(PhysAddr::new(0)); 353 | let max_phys = frame_allocator.max_phys_addr(); 354 | let end_frame: PhysFrame = PhysFrame::containing_address(max_phys - 1u64); 355 | 356 | for frame in PhysFrame::range_inclusive(start_frame, end_frame) { 357 | let page = Page::containing_address(offset + frame.start_address().as_u64()); 358 | let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; 359 | 360 | unsafe { 361 | page_tables 362 | .kernel 363 | .map_to(page, frame, flags, frame_allocator) 364 | } 365 | .unwrap() 366 | .flush(); 367 | } 368 | 369 | // Now we have to prepare the stivale struct that we will pass as an argument 370 | // in RDI to the kernel's entry point function. 371 | let stivale_struct = allocate_boot_info_tag( 372 | page_tables, 373 | frame_allocator, 374 | &mut useable_entries, 375 | StivaleStruct::new(), 376 | ); 377 | 378 | stivale_struct.set_bootloader_brand("Ion"); 379 | stivale_struct.set_bootloader_version(env!("CARGO_PKG_VERSION")); 380 | 381 | let switch_context = SwitchContext { 382 | page_table: page_tables.kernel_level_4_frame, 383 | stack_top: VirtAddr::new(stivale2_hdr.get_stack() as u64), 384 | entry_point: VirtAddr::new(elf.header.pt2.entry_point()), 385 | stivale_struct, 386 | }; 387 | 388 | // SAFTEY: The stack and the kernel entry point are checked above. 389 | unsafe { 390 | context_switch(switch_context); 391 | } 392 | } 393 | 394 | struct SwitchContext { 395 | page_table: PhysFrame, 396 | stack_top: VirtAddr, 397 | entry_point: VirtAddr, 398 | stivale_struct: &'static StivaleStruct, 399 | } 400 | 401 | unsafe fn context_switch(context: SwitchContext) -> ! { 402 | asm!( 403 | "mov cr3, {}; mov rsp, {}; push 0; jmp {}", 404 | in(reg) context.page_table.start_address().as_u64(), 405 | in(reg) context.stack_top.as_u64(), 406 | in(reg) context.entry_point.as_u64(), 407 | in("rdi") context.stivale_struct as *const _ as usize, 408 | ); 409 | 410 | unreachable!() 411 | } 412 | 413 | fn enable_nxe_bit() { 414 | unsafe { Efer::update(|efer| *efer |= EferFlags::NO_EXECUTE_ENABLE) } 415 | } 416 | 417 | fn enable_write_protect_bit() { 418 | unsafe { Cr0::update(|cr0| *cr0 |= Cr0Flags::WRITE_PROTECT) }; 419 | } 420 | --------------------------------------------------------------------------------