From 15ce0953510af592e4d588f4414fbf904d2aab40 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 13 Dec 2019 16:26:10 +0100 Subject: [PATCH 001/226] Create a 16-bit 'Hello World' --- real_mode/.cargo/config | 2 ++ real_mode/.gitignore | 2 ++ real_mode/Cargo.lock | 5 +++ real_mode/Cargo.toml | 12 +++++++ real_mode/README.md | 35 +++++++++++++++++++ real_mode/linker.ld | 20 +++++++++++ real_mode/src/boot.s | 29 ++++++++++++++++ real_mode/src/main.rs | 38 +++++++++++++++++++++ real_mode/test.bin | Bin 0 -> 524 bytes real_mode/x86_64-bootloader-real-mode.json | 25 ++++++++++++++ 10 files changed, 168 insertions(+) create mode 100644 real_mode/.cargo/config create mode 100644 real_mode/.gitignore create mode 100644 real_mode/Cargo.lock create mode 100644 real_mode/Cargo.toml create mode 100644 real_mode/README.md create mode 100644 real_mode/linker.ld create mode 100644 real_mode/src/boot.s create mode 100644 real_mode/src/main.rs create mode 100755 real_mode/test.bin create mode 100644 real_mode/x86_64-bootloader-real-mode.json diff --git a/real_mode/.cargo/config b/real_mode/.cargo/config new file mode 100644 index 00000000..c8d53d24 --- /dev/null +++ b/real_mode/.cargo/config @@ -0,0 +1,2 @@ +[build] +target = "x86_64-bootloader-real-mode.json" diff --git a/real_mode/.gitignore b/real_mode/.gitignore new file mode 100644 index 00000000..eccd7b4a --- /dev/null +++ b/real_mode/.gitignore @@ -0,0 +1,2 @@ +/target/ +**/*.rs.bk diff --git a/real_mode/Cargo.lock b/real_mode/Cargo.lock new file mode 100644 index 00000000..edbd9875 --- /dev/null +++ b/real_mode/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "real_mode" +version = "0.1.0" diff --git a/real_mode/Cargo.toml b/real_mode/Cargo.toml new file mode 100644 index 00000000..4003a0c5 --- /dev/null +++ b/real_mode/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "real_mode" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[profile.release] +opt-level = "z" diff --git a/real_mode/README.md b/real_mode/README.md new file mode 100644 index 00000000..c77db965 --- /dev/null +++ b/real_mode/README.md @@ -0,0 +1,35 @@ +# 16-bit Rust (Experiment) + +This is an experiment to translate the 16-bit code of the bootloader from assembly to Rust. + +## Building + +To build the project, use cargo-xbuild: + +``` +cargo xbuild --release +``` + +The BIOS only loads the first 512 bytes of our executable into memory, so the amount of code that this binary can contain is very limited. This is also the reason why this can only be built in release mode. + +If the code does not fit into 512 bytes, the linker will throw the following error: + +> rust-lld: error: linker.ld:16: unable to move location counter backward for: .bootloader + +## Creating a Disk Image + +The output of `cargo xbuild` is an ELF binary, which can't be loaded directly by the BIOS. To boot our project, we must therefore convert it into a flat binary first. This works with the following `objcopy` command: + +``` +objcopy -I elf32-i386 -O binary target/x86_64-bootloader-real-mode/release/real_mode image.bin +``` + +This creates a file named `image.bin` in the root folder of the project, which is a bootable disk image. + +## Running it in QEMU + +To run the disk image in QEMU, execute the following command: + +``` +qemu-system-x86_64 -drive format=raw,file=image.bin +``` diff --git a/real_mode/linker.ld b/real_mode/linker.ld new file mode 100644 index 00000000..56a70991 --- /dev/null +++ b/real_mode/linker.ld @@ -0,0 +1,20 @@ +ENTRY(_start) + +SECTIONS { + . = 0x500; + _stack_start = .; + . = 0x7c00; + _stack_end = .; + + .bootloader : + { + *(.boot-first-stage) + *(.text .text.*) + *(.rodata .rodata.*) + *(.data .data.*) + *(.got) + . = 0x7c00 + 510; + SHORT(0xaa55) /* magic number for bootable disk */ + } + _bootloader_end = .; +} diff --git a/real_mode/src/boot.s b/real_mode/src/boot.s new file mode 100644 index 00000000..0d216c24 --- /dev/null +++ b/real_mode/src/boot.s @@ -0,0 +1,29 @@ +.section .boot-first-stage, "awx" +.global _start +.intel_syntax noprefix +.code16 + +# This stage initializes the stack, enables the A20 line, loads the rest of +# the bootloader from disk, and jumps to stage_2. + +_start: + # zero segment registers + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # clear the direction flag (e.g. go forward in memory when using + # instructions like lodsb) + cld + + # initialize stack + mov sp, 0x7c00 + + call rust_main + +spin: + hlt + jmp spin diff --git a/real_mode/src/main.rs b/real_mode/src/main.rs new file mode 100644 index 00000000..99d7af1b --- /dev/null +++ b/real_mode/src/main.rs @@ -0,0 +1,38 @@ +#![feature(asm, global_asm)] +#![no_std] +#![no_main] + +use core::panic::PanicInfo; + +global_asm!(include_str!("boot.s")); + +#[no_mangle] +pub extern "C" fn rust_main() { + println(b"Hello from Rust!"); + panic!() +} + +fn println(s: &[u8]) { + print(s); + print_char(b'\n'); +} + +fn print(s: &[u8]) { + for &c in s { + print_char(c); + } +} + +fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10" :: "{ax}"(ax) :: "intel" ); + } +} + +#[panic_handler] +pub fn panic(_info: &PanicInfo) -> ! { + println(b"PANIC!"); + loop {} +} + diff --git a/real_mode/test.bin b/real_mode/test.bin new file mode 100755 index 0000000000000000000000000000000000000000..42274b0fd141100aeedd53635d176e57aa75574e GIT binary patch literal 524 zcmXp!(08NnK;MPF2YoO8>|v-$d%?`W!0_eu-?ZSg7eE$6T69|D;ron0#tUU2H$APl zdB^|%|ABP(93Y(*ke0=l7MS*;7^JrO2oFCuiq54#ogh6uAhlTnK;mwBwE}K*d0pHYaj0q-C+BMFGVFff}0=MHqmN26~VU=Zz Date: Sat, 14 Dec 2019 11:56:42 +0100 Subject: [PATCH 002/226] Remove panic! call since it leads to a too large binary --- real_mode/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/real_mode/src/main.rs b/real_mode/src/main.rs index 99d7af1b..73b3f986 100644 --- a/real_mode/src/main.rs +++ b/real_mode/src/main.rs @@ -9,7 +9,6 @@ global_asm!(include_str!("boot.s")); #[no_mangle] pub extern "C" fn rust_main() { println(b"Hello from Rust!"); - panic!() } fn println(s: &[u8]) { From fd1b5bef9a242d517a7006e28cb1877f37ae1b15 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Dec 2019 14:41:37 +0100 Subject: [PATCH 003/226] Enable lto and debug info --- real_mode/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/real_mode/Cargo.toml b/real_mode/Cargo.toml index 4003a0c5..e1513932 100644 --- a/real_mode/Cargo.toml +++ b/real_mode/Cargo.toml @@ -10,3 +10,5 @@ edition = "2018" [profile.release] opt-level = "z" +lto = true +debug = true From 37ff5209e19a0e18080029de773ff7a1a065b676 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Dec 2019 14:43:00 +0100 Subject: [PATCH 004/226] Split first stage into multiple output sections --- real_mode/linker.ld | 17 ++++++++++++++--- real_mode/src/boot.s | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/real_mode/linker.ld b/real_mode/linker.ld index 56a70991..ec42525f 100644 --- a/real_mode/linker.ld +++ b/real_mode/linker.ld @@ -6,14 +6,25 @@ SECTIONS { . = 0x7c00; _stack_end = .; - .bootloader : + _bootloader_start = .; + .boot : + { + *(.boot) + } + .text : { - *(.boot-first-stage) *(.text .text.*) + } + .data : + { *(.rodata .rodata.*) *(.data .data.*) *(.got) - . = 0x7c00 + 510; + } + + . = 0x7c00 + 510; + .magic_number : + { SHORT(0xaa55) /* magic number for bootable disk */ } _bootloader_end = .; diff --git a/real_mode/src/boot.s b/real_mode/src/boot.s index 0d216c24..c290689b 100644 --- a/real_mode/src/boot.s +++ b/real_mode/src/boot.s @@ -1,4 +1,4 @@ -.section .boot-first-stage, "awx" +.section .boot, "awx" .global _start .intel_syntax noprefix .code16 From 9ca6de1225f6c8d13f571ec1383c5e6ff46ba253 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Dec 2019 14:43:42 +0100 Subject: [PATCH 005/226] Perform more work in boot.s --- real_mode/src/boot.s | 80 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/real_mode/src/boot.s b/real_mode/src/boot.s index c290689b..b1031ac6 100644 --- a/real_mode/src/boot.s +++ b/real_mode/src/boot.s @@ -22,8 +22,88 @@ _start: # initialize stack mov sp, 0x7c00 +enable_a20: + # enable A20-Line via IO-Port 92, might not work on all motherboards + in al, 0x92 + test al, 2 + jnz enable_a20_after + or al, 2 + and al, 0xFE + out 0x92, al +enable_a20_after: + +enter_protected_mode: + # clear interrupts + cli + push ds + push es + + lgdt [gdt32info] + + mov eax, cr0 + or al, 1 # set protected mode bit + mov cr0, eax + + jmp protected_mode # tell 386/486 to not crash + +protected_mode: + mov bx, 0x10 + mov ds, bx # set data segment + mov es, bx # set extra segment + + and al, 0xfe # clear protected mode bit + mov cr0, eax + +unreal_mode: + pop es # get back old extra segment + pop ds # get back old data segment + sti + + # back to real mode, but internal data segment register is still loaded + # with gdt segment -> we can access the full 4GiB of memory + + mov bx, 0x0f01 # attrib/char of smiley + mov eax, 0xb8f00 # note 32 bit offset + mov word ptr ds:[eax], bx + +check_int13h_extensions: + mov ah, 0x41 + mov bx, 0x55aa + # dl contains drive number + int 0x13 + jc no_int13h_extensions + +rust: + push dx # pass disk number as argument call rust_main spin: hlt jmp spin + +gdt32info: + .word gdt32_end - gdt32 - 1 # last byte in table + .word gdt32 # start of table + +gdt32: + # entry 0 is always unused + .quad 0 +codedesc: + .byte 0xff + .byte 0xff + .byte 0 + .byte 0 + .byte 0 + .byte 0x9a + .byte 0xcf + .byte 0 +datadesc: + .byte 0xff + .byte 0xff + .byte 0 + .byte 0 + .byte 0 + .byte 0x92 + .byte 0xcf + .byte 0 +gdt32_end: From 1b95736454d35561ebd37ed0e87ae71f104764e7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Dec 2019 14:44:11 +0100 Subject: [PATCH 006/226] Set relocation model to static --- real_mode/x86_64-bootloader-real-mode.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/real_mode/x86_64-bootloader-real-mode.json b/real_mode/x86_64-bootloader-real-mode.json index 644fb167..82c01f59 100644 --- a/real_mode/x86_64-bootloader-real-mode.json +++ b/real_mode/x86_64-bootloader-real-mode.json @@ -15,11 +15,11 @@ "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, - "relro-level": "full", "target-c-int-width": "32", "target-pointer-width": "32", "target-endian": "little", "panic-strategy": "abort", "os": "none", - "vendor": "unknown" + "vendor": "unknown", + "relocation_model": "static" } \ No newline at end of file From 4d746040f2498ba2d3a49d38adfb96d28cc8a15b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Dec 2019 14:44:50 +0100 Subject: [PATCH 007/226] Load a second stage from disk in Rust --- real_mode/linker.ld | 9 ++++- real_mode/src/dap.rs | 36 ++++++++++++++++++ real_mode/src/main.rs | 73 ++++++++++++++++++++++++++++++++++-- real_mode/src/second_stage.s | 8 ++++ 4 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 real_mode/src/dap.rs create mode 100644 real_mode/src/second_stage.s diff --git a/real_mode/linker.ld b/real_mode/linker.ld index ec42525f..bf92af7d 100644 --- a/real_mode/linker.ld +++ b/real_mode/linker.ld @@ -27,5 +27,12 @@ SECTIONS { { SHORT(0xaa55) /* magic number for bootable disk */ } - _bootloader_end = .; + + _second_stage_start = .; + .second_stage : + { + *(.second_stage) + } + . = ALIGN(512); + _second_stage_end = .; } diff --git a/real_mode/src/dap.rs b/real_mode/src/dap.rs new file mode 100644 index 00000000..cc7711f5 --- /dev/null +++ b/real_mode/src/dap.rs @@ -0,0 +1,36 @@ +#[repr(packed)] +pub struct DiskAddressPacket { + /// Size of the DAP structure + packet_size: u8, + /// always zero + zero: u8, + /// Number of sectors to transfer + number_of_sectors: u16, + /// Offset to memory buffer + offset: u16, + /// Segment of memory buffer + segment: u16, + /// Start logical block address + start_lba: u64, +} + +impl DiskAddressPacket { + pub fn new(memory_buffer_start: u16, file_offset: u64, bytes: u32) -> Self { + Self { + packet_size: 0x10, + zero: 0, + number_of_sectors: (bytes / 512) as u16, + offset: memory_buffer_start, + segment: 0, + start_lba: file_offset / 512, + } + } + + pub unsafe fn perform_load(&self, disk_number: u16) { + let self_addr = self as *const Self as u16; + asm!(" + int 0x13 + jc dap_load_failed + " :: "{si}"(self_addr), "{ax}"(0x4200), "{dx}"(disk_number) :: "intel"); + } +} diff --git a/real_mode/src/main.rs b/real_mode/src/main.rs index 73b3f986..3a52cd9c 100644 --- a/real_mode/src/main.rs +++ b/real_mode/src/main.rs @@ -4,13 +4,57 @@ use core::panic::PanicInfo; +mod dap; + global_asm!(include_str!("boot.s")); +global_asm!(include_str!("second_stage.s")); + +extern "C" { + fn second_stage() -> u32; +} + +#[allow(improper_ctypes)] +extern "C" { + static _bootloader_start: (); + static _second_stage_start: (); + static _second_stage_end: (); +} #[no_mangle] -pub extern "C" fn rust_main() { - println(b"Hello from Rust!"); +pub extern "C" fn rust_main(disk_number: u16) { + load_second_stage(disk_number); + + let val = unsafe { second_stage() }; + if val == 12345 { + println(b"match"); + } else { + println(b"no match"); + } } +fn bootloader_start() -> usize { + unsafe { &_bootloader_start as *const _ as usize } +} + +fn second_stage_start() -> usize { + unsafe { &_second_stage_start as *const _ as usize } +} + +fn second_stage_end() -> usize { + unsafe { &_second_stage_end as *const _ as usize } +} + +fn load_second_stage(disk_number: u16) { + use dap::DiskAddressPacket; + + let file_offset = (second_stage_start() - bootloader_start()) as u64; + let size = (second_stage_end() - second_stage_start()) as u32; + + let dap = DiskAddressPacket::new(second_stage_start() as u16, file_offset, size); + unsafe { dap.perform_load(disk_number) } +} + +#[inline(never)] fn println(s: &[u8]) { print(s); print_char(b'\n'); @@ -29,9 +73,32 @@ fn print_char(c: u8) { } } +#[no_mangle] +pub extern "C" fn dap_load_failed() -> ! { + println(b"ERROR: DAP load failed"); + loop { + hlt() + } +} + +#[no_mangle] +pub extern "C" fn no_int13h_extensions() -> ! { + println(b"ERROR: No int13h extensions"); + loop { + hlt() + } +} + #[panic_handler] pub fn panic(_info: &PanicInfo) -> ! { println(b"PANIC!"); - loop {} + loop { + hlt() + } } +fn hlt() { + unsafe { + asm!("hlt":::: "intel","volatile"); + } +} diff --git a/real_mode/src/second_stage.s b/real_mode/src/second_stage.s new file mode 100644 index 00000000..e7765e18 --- /dev/null +++ b/real_mode/src/second_stage.s @@ -0,0 +1,8 @@ +.section .second_stage, "awx" +.global second_stage +.intel_syntax noprefix +.code16 + +second_stage: + mov eax, 12345 + ret From 2f3bd08b826aa118abdd83816c76c8dbe6b559c3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 20 Dec 2019 14:45:40 +0100 Subject: [PATCH 008/226] Ignore test.bin --- real_mode/.gitignore | 2 ++ real_mode/test.bin | Bin 524 -> 0 bytes 2 files changed, 2 insertions(+) delete mode 100755 real_mode/test.bin diff --git a/real_mode/.gitignore b/real_mode/.gitignore index eccd7b4a..bae8924d 100644 --- a/real_mode/.gitignore +++ b/real_mode/.gitignore @@ -1,2 +1,4 @@ /target/ **/*.rs.bk +/test.bin + diff --git a/real_mode/test.bin b/real_mode/test.bin deleted file mode 100755 index 42274b0fd141100aeedd53635d176e57aa75574e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 524 zcmXp!(08NnK;MPF2YoO8>|v-$d%?`W!0_eu-?ZSg7eE$6T69|D;ron0#tUU2H$APl zdB^|%|ABP(93Y(*ke0=l7MS*;7^JrO2oFCuiq54#ogh6uAhlTnK;mwBwE}K*d0pHYaj0q-C+BMFGVFff}0=MHqmN26~VU=Zz Date: Fri, 20 Dec 2019 18:16:39 +0100 Subject: [PATCH 009/226] wip --- real_mode/Cargo.toml | 3 +- real_mode/build.rs | 45 ++++++++ real_mode/first_stage/.gitignore | 1 + real_mode/first_stage/Cargo.lock | 5 + real_mode/first_stage/Cargo.toml | 20 ++++ real_mode/{ => first_stage}/src/boot.s | 15 ++- real_mode/{ => first_stage}/src/dap.rs | 0 real_mode/first_stage/src/lib.rs | 68 ++++++++++++ real_mode/linker.ld | 38 +++++-- real_mode/second_stage/.gitignore | 1 + real_mode/second_stage/Cargo.lock | 6 ++ real_mode/second_stage/Cargo.toml | 20 ++++ real_mode/second_stage/src/lib.rs | 45 ++++++++ .../{ => second_stage}/src/second_stage.s | 2 +- real_mode/src/main.rs | 100 +----------------- real_mode/tmp-libreal_mode.a | 0 real_mode/x86_64-bootloader-real-mode.json | 3 +- 17 files changed, 264 insertions(+), 108 deletions(-) create mode 100644 real_mode/build.rs create mode 100644 real_mode/first_stage/.gitignore create mode 100644 real_mode/first_stage/Cargo.lock create mode 100644 real_mode/first_stage/Cargo.toml rename real_mode/{ => first_stage}/src/boot.s (89%) rename real_mode/{ => first_stage}/src/dap.rs (100%) create mode 100644 real_mode/first_stage/src/lib.rs create mode 100644 real_mode/second_stage/.gitignore create mode 100644 real_mode/second_stage/Cargo.lock create mode 100644 real_mode/second_stage/Cargo.toml create mode 100644 real_mode/second_stage/src/lib.rs rename real_mode/{ => second_stage}/src/second_stage.s (85%) create mode 100644 real_mode/tmp-libreal_mode.a diff --git a/real_mode/Cargo.toml b/real_mode/Cargo.toml index e1513932..5a155ee9 100644 --- a/real_mode/Cargo.toml +++ b/real_mode/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] [profile.release] -opt-level = "z" +opt-level = "s" lto = true debug = true +codegen-units = 1 diff --git a/real_mode/build.rs b/real_mode/build.rs new file mode 100644 index 00000000..2193f99b --- /dev/null +++ b/real_mode/build.rs @@ -0,0 +1,45 @@ +use std::process::Command; +use std::env; +use std::fs::{self, File}; +use std::path::Path; + +fn main() { + let out_dir = env::var("OUT_DIR").unwrap(); + + // first stage + let mut cmd = Command::new("cargo"); + cmd.arg("xbuild").arg("--release"); + cmd.arg("--manifest-path=first_stage/Cargo.toml"); + cmd.arg("-Z").arg("unstable-options"); + cmd.arg("--out-dir").arg(&out_dir); + let status = cmd.status().unwrap(); + assert!(status.success()); + + // second stage + let mut cmd = Command::new("cargo"); + cmd.arg("xbuild").arg("--release"); + cmd.arg("--manifest-path=second_stage/Cargo.toml"); + cmd.arg("-Z").arg("unstable-options"); + cmd.arg("--out-dir").arg(&out_dir); + let status = cmd.status().unwrap(); + assert!(status.success()); + + let concat_script = Path::new(&out_dir).join("concat.mri"); + fs::write(&concat_script, " + create libreal_mode.a + addlib libfirst_stage.a + addlib libsecond_stage.a + save + end + ").unwrap(); + + // concat archives + let mut cmd = Command::new("ar"); + cmd.arg("-M").stdin(File::open(concat_script).unwrap()); + cmd.current_dir(&out_dir); + let status = cmd.status().unwrap(); + assert!(status.success()); + + println!("cargo:rustc-link-search=native={}", out_dir); + println!("cargo:rustc-link-lib=static=real_mode"); +} \ No newline at end of file diff --git a/real_mode/first_stage/.gitignore b/real_mode/first_stage/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/real_mode/first_stage/.gitignore @@ -0,0 +1 @@ +/target diff --git a/real_mode/first_stage/Cargo.lock b/real_mode/first_stage/Cargo.lock new file mode 100644 index 00000000..5574ace3 --- /dev/null +++ b/real_mode/first_stage/Cargo.lock @@ -0,0 +1,5 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "first_stage" +version = "0.1.0" diff --git a/real_mode/first_stage/Cargo.toml b/real_mode/first_stage/Cargo.toml new file mode 100644 index 00000000..0a4d965e --- /dev/null +++ b/real_mode/first_stage/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "first_stage" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "first_stage" +crate-type = ["staticlib"] + +[dependencies] + + +[profile.release] +opt-level = "s" +lto = true +codegen-units = 1 +debug = true diff --git a/real_mode/src/boot.s b/real_mode/first_stage/src/boot.s similarity index 89% rename from real_mode/src/boot.s rename to real_mode/first_stage/src/boot.s index b1031ac6..50163ffb 100644 --- a/real_mode/src/boot.s +++ b/real_mode/first_stage/src/boot.s @@ -74,8 +74,19 @@ check_int13h_extensions: jc no_int13h_extensions rust: - push dx # pass disk number as argument - call rust_main + # push arguments +arg_0: + push dx # disk number + lea eax, _bootloader_start +arg_1: + push eax + lea eax, _second_stage_end +arg_2: + push eax + lea eax, _second_stage_start +arg_3: + push eax + call first_stage spin: hlt diff --git a/real_mode/src/dap.rs b/real_mode/first_stage/src/dap.rs similarity index 100% rename from real_mode/src/dap.rs rename to real_mode/first_stage/src/dap.rs diff --git a/real_mode/first_stage/src/lib.rs b/real_mode/first_stage/src/lib.rs new file mode 100644 index 00000000..45b96a8e --- /dev/null +++ b/real_mode/first_stage/src/lib.rs @@ -0,0 +1,68 @@ +#![feature(asm, global_asm)] +#![no_std] + +global_asm!(include_str!("boot.s")); + +mod dap; + +extern "C" { + fn second_stage(disk_number: u16); +} + +/// Test function +#[no_mangle] +pub extern "C" fn first_stage(second_stage_start: u32, second_stage_end: u32, bootloader_start: u32, disk_number: u16) { + load_second_stage(second_stage_start, second_stage_end, bootloader_start, disk_number); + unsafe { second_stage(disk_number); } +} + +fn load_second_stage(second_stage_start: u32, second_stage_end: u32, bootloader_start: u32, disk_number: u16) { + use dap::DiskAddressPacket; + + let file_offset = (second_stage_start - bootloader_start) as u64; + let size = (second_stage_end - second_stage_start) as u32; + + let dap = DiskAddressPacket::new(second_stage_start as u16, file_offset, size); + unsafe { dap.perform_load(disk_number) } +} + +#[no_mangle] +pub extern fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10" :: "{ax}"(ax), "{bx}"(0) :: "intel" ); + } +} + +#[no_mangle] +pub extern "C" fn dap_load_failed() -> ! { + err(b'1'); +} + +#[no_mangle] +pub extern "C" fn no_int13h_extensions() -> ! { + err(b'2'); +} + +#[cold] +fn err(code: u8) -> ! { + for &c in b"Err:" { + print_char(c); + } + print_char(code); + loop { + hlt() + } +} + +fn hlt() { + unsafe { + asm!("hlt":::: "intel","volatile"); + } +} + +#[panic_handler] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + err(b'P'); +} + diff --git a/real_mode/linker.ld b/real_mode/linker.ld index bf92af7d..04189f49 100644 --- a/real_mode/linker.ld +++ b/real_mode/linker.ld @@ -11,15 +11,15 @@ SECTIONS { { *(.boot) } - .text : + .first_stage_text : { - *(.text .text.*) + *first_stage*(.text .text.*) } - .data : + .first_stage_data : { - *(.rodata .rodata.*) - *(.data .data.*) - *(.got) + *first_stage*(.rodata .rodata.*) + *first_stage*(.data .data.*) + *first_stage*(.got .got.*) } . = 0x7c00 + 510; @@ -29,7 +29,31 @@ SECTIONS { } _second_stage_start = .; - .second_stage : + + .second_stage_text : + { + *second_stage*(.text .text.*) + } + .second_stage_data : + { + *second_stage*(.rodata .rodata.*) + *second_stage*(.data .data.*) + *second_stage*(.got .got.*) + } + + .text : + { + *(.text .text.*) + } + + .data : + { + *(.rodata .rodata.*) + *(.data .data.*) + *(.got .got.*) + } + + .asm : { *(.second_stage) } diff --git a/real_mode/second_stage/.gitignore b/real_mode/second_stage/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/real_mode/second_stage/.gitignore @@ -0,0 +1 @@ +/target diff --git a/real_mode/second_stage/Cargo.lock b/real_mode/second_stage/Cargo.lock new file mode 100644 index 00000000..78182a41 --- /dev/null +++ b/real_mode/second_stage/Cargo.lock @@ -0,0 +1,6 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "second_stage" +version = "0.1.0" + diff --git a/real_mode/second_stage/Cargo.toml b/real_mode/second_stage/Cargo.toml new file mode 100644 index 00000000..62ace3ba --- /dev/null +++ b/real_mode/second_stage/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "second_stage" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "second_stage" +crate-type = ["staticlib"] + +[dependencies] + + +[profile.release] +opt-level = "s" +lto = true +codegen-units = 1 +debug = true diff --git a/real_mode/second_stage/src/lib.rs b/real_mode/second_stage/src/lib.rs new file mode 100644 index 00000000..ec78a355 --- /dev/null +++ b/real_mode/second_stage/src/lib.rs @@ -0,0 +1,45 @@ +#![feature(asm, global_asm)] +#![no_std] + +global_asm!(include_str!("second_stage.s")); + +extern "C" { + fn print_char(c: u8); + fn second_stage_asm() -> u32; +} + +#[no_mangle] +pub extern "C" fn second_stage(_disk_number: u16) { + let val = unsafe { second_stage_asm() }; + if val == 12345 { + println(b"match"); + } else { + println(b"no match"); + } +} + +#[panic_handler] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + println(b"PANIC!"); + loop { + hlt() + } +} + +fn hlt() { + unsafe { + asm!("hlt":::: "intel","volatile"); + } +} + +#[inline(never)] +fn println(s: &[u8]) { + print(s); + unsafe { print_char(b'\n') }; +} + +fn print(s: &[u8]) { + for &c in s { + unsafe { print_char(c) }; + } +} diff --git a/real_mode/src/second_stage.s b/real_mode/second_stage/src/second_stage.s similarity index 85% rename from real_mode/src/second_stage.s rename to real_mode/second_stage/src/second_stage.s index e7765e18..200b3b08 100644 --- a/real_mode/src/second_stage.s +++ b/real_mode/second_stage/src/second_stage.s @@ -3,6 +3,6 @@ .intel_syntax noprefix .code16 -second_stage: +second_stage_asm: mov eax, 12345 ret diff --git a/real_mode/src/main.rs b/real_mode/src/main.rs index 3a52cd9c..39944883 100644 --- a/real_mode/src/main.rs +++ b/real_mode/src/main.rs @@ -1,104 +1,12 @@ -#![feature(asm, global_asm)] #![no_std] #![no_main] -use core::panic::PanicInfo; - -mod dap; - -global_asm!(include_str!("boot.s")); -global_asm!(include_str!("second_stage.s")); - -extern "C" { - fn second_stage() -> u32; -} - -#[allow(improper_ctypes)] -extern "C" { - static _bootloader_start: (); - static _second_stage_start: (); - static _second_stage_end: (); -} - -#[no_mangle] -pub extern "C" fn rust_main(disk_number: u16) { - load_second_stage(disk_number); - - let val = unsafe { second_stage() }; - if val == 12345 { - println(b"match"); - } else { - println(b"no match"); - } -} - -fn bootloader_start() -> usize { - unsafe { &_bootloader_start as *const _ as usize } -} - -fn second_stage_start() -> usize { - unsafe { &_second_stage_start as *const _ as usize } -} - -fn second_stage_end() -> usize { - unsafe { &_second_stage_end as *const _ as usize } -} - -fn load_second_stage(disk_number: u16) { - use dap::DiskAddressPacket; - - let file_offset = (second_stage_start() - bootloader_start()) as u64; - let size = (second_stage_end() - second_stage_start()) as u32; - - let dap = DiskAddressPacket::new(second_stage_start() as u16, file_offset, size); - unsafe { dap.perform_load(disk_number) } -} - -#[inline(never)] -fn println(s: &[u8]) { - print(s); - print_char(b'\n'); -} - -fn print(s: &[u8]) { - for &c in s { - print_char(c); - } -} - -fn print_char(c: u8) { - let ax = u16::from(c) | 0x0e00; - unsafe { - asm!("int 0x10" :: "{ax}"(ax) :: "intel" ); - } -} - -#[no_mangle] -pub extern "C" fn dap_load_failed() -> ! { - println(b"ERROR: DAP load failed"); - loop { - hlt() - } -} - #[no_mangle] -pub extern "C" fn no_int13h_extensions() -> ! { - println(b"ERROR: No int13h extensions"); - loop { - hlt() - } +pub extern "C" fn rust_main() -> u32 { + 54321 } #[panic_handler] -pub fn panic(_info: &PanicInfo) -> ! { - println(b"PANIC!"); - loop { - hlt() - } -} - -fn hlt() { - unsafe { - asm!("hlt":::: "intel","volatile"); - } +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + loop{} } diff --git a/real_mode/tmp-libreal_mode.a b/real_mode/tmp-libreal_mode.a new file mode 100644 index 00000000..e69de29b diff --git a/real_mode/x86_64-bootloader-real-mode.json b/real_mode/x86_64-bootloader-real-mode.json index 82c01f59..9a60d2c0 100644 --- a/real_mode/x86_64-bootloader-real-mode.json +++ b/real_mode/x86_64-bootloader-real-mode.json @@ -21,5 +21,6 @@ "panic-strategy": "abort", "os": "none", "vendor": "unknown", - "relocation_model": "static" + "relocation_model": "static", + "eliminate_frame_pointer": true } \ No newline at end of file From 34fc6d01b71d8a1ea8a04502356a5a7375d8d5af Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 29 Dec 2019 21:00:35 +0100 Subject: [PATCH 010/226] Use localized symbols --- real_mode/Cargo.lock | 9 ++++++++ real_mode/Cargo.toml | 3 +++ real_mode/build.rs | 53 ++++++++++++++++++++++---------------------- real_mode/linker.ld | 2 +- 4 files changed, 39 insertions(+), 28 deletions(-) diff --git a/real_mode/Cargo.lock b/real_mode/Cargo.lock index edbd9875..59226f01 100644 --- a/real_mode/Cargo.lock +++ b/real_mode/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" + [[package]] name = "real_mode" version = "0.1.0" +dependencies = [ + "llvm-tools", +] diff --git a/real_mode/Cargo.toml b/real_mode/Cargo.toml index 5a155ee9..0845d3bf 100644 --- a/real_mode/Cargo.toml +++ b/real_mode/Cargo.toml @@ -8,6 +8,9 @@ edition = "2018" [dependencies] +[build-dependencies] +llvm-tools = "0.1.1" + [profile.release] opt-level = "s" lto = true diff --git a/real_mode/build.rs b/real_mode/build.rs index 2193f99b..9e217fce 100644 --- a/real_mode/build.rs +++ b/real_mode/build.rs @@ -1,45 +1,44 @@ use std::process::Command; use std::env; -use std::fs::{self, File}; use std::path::Path; +use llvm_tools::{LlvmTools, exe}; fn main() { let out_dir = env::var("OUT_DIR").unwrap(); + let llvm_tools = LlvmTools::new().expect("LLVM tools not found"); + let objcopy = llvm_tools.tool(&exe("llvm-objcopy")).expect("llvm-objcopy not found"); - // first stage - let mut cmd = Command::new("cargo"); - cmd.arg("xbuild").arg("--release"); - cmd.arg("--manifest-path=first_stage/Cargo.toml"); - cmd.arg("-Z").arg("unstable-options"); - cmd.arg("--out-dir").arg(&out_dir); - let status = cmd.status().unwrap(); - assert!(status.success()); - - // second stage + build_subproject(Path::new("first_stage"), &["_start", "print_char"], &out_dir, &objcopy); + build_subproject(Path::new("second_stage"), &["second_stage"], &out_dir, &objcopy); +} + +fn build_subproject(dir: &Path, global_symbols: &[&str], out_dir: &str, objcopy: &Path) { + let dir_name = dir.file_name().unwrap().to_str().unwrap(); + let manifest_path = dir.join("Cargo.toml"); + let out_path = Path::new(&out_dir); + assert!(global_symbols.len() > 0, "must have at least one global symbol"); + + // build let mut cmd = Command::new("cargo"); cmd.arg("xbuild").arg("--release"); - cmd.arg("--manifest-path=second_stage/Cargo.toml"); + cmd.arg(format!("--manifest-path={}", manifest_path.display())); cmd.arg("-Z").arg("unstable-options"); cmd.arg("--out-dir").arg(&out_dir); + cmd.arg("--target-dir").arg("target"); + cmd.env("XBUILD_SYSROOT_PATH", format!("target/{}-sysroot", dir_name)); let status = cmd.status().unwrap(); assert!(status.success()); - let concat_script = Path::new(&out_dir).join("concat.mri"); - fs::write(&concat_script, " - create libreal_mode.a - addlib libfirst_stage.a - addlib libsecond_stage.a - save - end - ").unwrap(); - - // concat archives - let mut cmd = Command::new("ar"); - cmd.arg("-M").stdin(File::open(concat_script).unwrap()); - cmd.current_dir(&out_dir); + // localize symbols + let mut cmd = Command::new(objcopy); + for symbol in global_symbols { + cmd.arg("-G").arg(symbol); + } + cmd.arg(out_path.join(format!("lib{}.a", dir_name))); let status = cmd.status().unwrap(); assert!(status.success()); - + + // emit linker flags println!("cargo:rustc-link-search=native={}", out_dir); - println!("cargo:rustc-link-lib=static=real_mode"); + println!("cargo:rustc-link-lib=static={}", dir_name); } \ No newline at end of file diff --git a/real_mode/linker.ld b/real_mode/linker.ld index 04189f49..9197da5d 100644 --- a/real_mode/linker.ld +++ b/real_mode/linker.ld @@ -9,7 +9,7 @@ SECTIONS { _bootloader_start = .; .boot : { - *(.boot) + *first_stage*(.boot) } .first_stage_text : { From 8e9f565d98fe97ab0d55dbe9d9ed3fec81085eed Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 29 Dec 2019 21:39:15 +0100 Subject: [PATCH 011/226] Try to link as ELF64 --- real_mode/.cargo/config | 2 +- real_mode/Cargo.lock | 14 +++++----- real_mode/Cargo.toml | 2 +- real_mode/build.rs | 16 +++++++++--- real_mode/first_stage/x86_64-target.json | 26 +++++++++++++++++++ real_mode/linker.ld | 12 ++++----- .../{second_stage => real_mode}/.gitignore | 0 .../{second_stage => real_mode}/Cargo.lock | 2 +- .../{second_stage => real_mode}/Cargo.toml | 4 +-- .../{second_stage => real_mode}/src/lib.rs | 0 .../src/second_stage.s | 0 real_mode/real_mode/x86_64-target.json | 26 +++++++++++++++++++ real_mode/x86_64-bootloader.json | 21 +++++++++++++++ 13 files changed, 104 insertions(+), 21 deletions(-) create mode 100644 real_mode/first_stage/x86_64-target.json rename real_mode/{second_stage => real_mode}/.gitignore (100%) rename real_mode/{second_stage => real_mode}/Cargo.lock (84%) rename real_mode/{second_stage => real_mode}/Cargo.toml (87%) rename real_mode/{second_stage => real_mode}/src/lib.rs (100%) rename real_mode/{second_stage => real_mode}/src/second_stage.s (100%) create mode 100644 real_mode/real_mode/x86_64-target.json create mode 100644 real_mode/x86_64-bootloader.json diff --git a/real_mode/.cargo/config b/real_mode/.cargo/config index c8d53d24..fb871b1b 100644 --- a/real_mode/.cargo/config +++ b/real_mode/.cargo/config @@ -1,2 +1,2 @@ [build] -target = "x86_64-bootloader-real-mode.json" +target = "x86_64-bootloader.json" diff --git a/real_mode/Cargo.lock b/real_mode/Cargo.lock index 59226f01..fd402fe0 100644 --- a/real_mode/Cargo.lock +++ b/real_mode/Cargo.lock @@ -1,14 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "llvm-tools" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" - -[[package]] -name = "real_mode" +name = "bootloader" version = "0.1.0" dependencies = [ "llvm-tools", ] + +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" diff --git a/real_mode/Cargo.toml b/real_mode/Cargo.toml index 0845d3bf..da95f3ea 100644 --- a/real_mode/Cargo.toml +++ b/real_mode/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "real_mode" +name = "bootloader" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2018" diff --git a/real_mode/build.rs b/real_mode/build.rs index 9e217fce..e8f6c27b 100644 --- a/real_mode/build.rs +++ b/real_mode/build.rs @@ -9,7 +9,7 @@ fn main() { let objcopy = llvm_tools.tool(&exe("llvm-objcopy")).expect("llvm-objcopy not found"); build_subproject(Path::new("first_stage"), &["_start", "print_char"], &out_dir, &objcopy); - build_subproject(Path::new("second_stage"), &["second_stage"], &out_dir, &objcopy); + build_subproject(Path::new("real_mode"), &["second_stage"], &out_dir, &objcopy); } fn build_subproject(dir: &Path, global_symbols: &[&str], out_dir: &str, objcopy: &Path) { @@ -21,11 +21,14 @@ fn build_subproject(dir: &Path, global_symbols: &[&str], out_dir: &str, objcopy: // build let mut cmd = Command::new("cargo"); cmd.arg("xbuild").arg("--release"); + cmd.arg("--verbose"); cmd.arg(format!("--manifest-path={}", manifest_path.display())); + cmd.arg(format!("--target={}", dir.join("x86_64-target.json").display())); cmd.arg("-Z").arg("unstable-options"); cmd.arg("--out-dir").arg(&out_dir); - cmd.arg("--target-dir").arg("target"); - cmd.env("XBUILD_SYSROOT_PATH", format!("target/{}-sysroot", dir_name)); + cmd.arg("--target-dir").arg(out_path.join("target").join(dir_name)); + cmd.env_remove("RUSTFLAGS"); + cmd.env("XBUILD_SYSROOT_PATH", out_path.join("target").join(dir_name).join("sysroot")); let status = cmd.status().unwrap(); assert!(status.success()); @@ -37,6 +40,13 @@ fn build_subproject(dir: &Path, global_symbols: &[&str], out_dir: &str, objcopy: cmd.arg(out_path.join(format!("lib{}.a", dir_name))); let status = cmd.status().unwrap(); assert!(status.success()); + + // convert to ELF64 + let mut cmd = Command::new(objcopy); + cmd.arg("-I").arg("elf32-i386").arg("-O").arg("elf64-x86-64"); + cmd.arg(out_path.join(format!("lib{}.a", dir_name))); + let status = cmd.status().unwrap(); + assert!(status.success()); // emit linker flags println!("cargo:rustc-link-search=native={}", out_dir); diff --git a/real_mode/first_stage/x86_64-target.json b/real_mode/first_stage/x86_64-target.json new file mode 100644 index 00000000..9a60d2c0 --- /dev/null +++ b/real_mode/first_stage/x86_64-target.json @@ -0,0 +1,26 @@ +{ + "arch": "x86", + "cpu": "i386", + "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", + "dynamic-linking": false, + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "llvm-target": "i386-unknown-none-code16", + "pre-link-args": { + "ld.lld": [ + "-Tlinker.ld" + ] + }, + "max-atomic-width": 64, + "position-independent-executables": false, + "disable-redzone": true, + "target-c-int-width": "32", + "target-pointer-width": "32", + "target-endian": "little", + "panic-strategy": "abort", + "os": "none", + "vendor": "unknown", + "relocation_model": "static", + "eliminate_frame_pointer": true +} \ No newline at end of file diff --git a/real_mode/linker.ld b/real_mode/linker.ld index 9197da5d..5fbea44d 100644 --- a/real_mode/linker.ld +++ b/real_mode/linker.ld @@ -30,15 +30,15 @@ SECTIONS { _second_stage_start = .; - .second_stage_text : + .real_mode_text : { - *second_stage*(.text .text.*) + *real_mode*(.text .text.*) } - .second_stage_data : + .real_mode_data : { - *second_stage*(.rodata .rodata.*) - *second_stage*(.data .data.*) - *second_stage*(.got .got.*) + *real_mode*(.rodata .rodata.*) + *real_mode*(.data .data.*) + *real_mode*(.got .got.*) } .text : diff --git a/real_mode/second_stage/.gitignore b/real_mode/real_mode/.gitignore similarity index 100% rename from real_mode/second_stage/.gitignore rename to real_mode/real_mode/.gitignore diff --git a/real_mode/second_stage/Cargo.lock b/real_mode/real_mode/Cargo.lock similarity index 84% rename from real_mode/second_stage/Cargo.lock rename to real_mode/real_mode/Cargo.lock index 78182a41..f7dc4c93 100644 --- a/real_mode/second_stage/Cargo.lock +++ b/real_mode/real_mode/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "second_stage" +name = "real_mode" version = "0.1.0" diff --git a/real_mode/second_stage/Cargo.toml b/real_mode/real_mode/Cargo.toml similarity index 87% rename from real_mode/second_stage/Cargo.toml rename to real_mode/real_mode/Cargo.toml index 62ace3ba..f798ceb8 100644 --- a/real_mode/second_stage/Cargo.toml +++ b/real_mode/real_mode/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "second_stage" +name = "real_mode" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2018" @@ -7,7 +7,7 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] -name = "second_stage" +name = "real_mode" crate-type = ["staticlib"] [dependencies] diff --git a/real_mode/second_stage/src/lib.rs b/real_mode/real_mode/src/lib.rs similarity index 100% rename from real_mode/second_stage/src/lib.rs rename to real_mode/real_mode/src/lib.rs diff --git a/real_mode/second_stage/src/second_stage.s b/real_mode/real_mode/src/second_stage.s similarity index 100% rename from real_mode/second_stage/src/second_stage.s rename to real_mode/real_mode/src/second_stage.s diff --git a/real_mode/real_mode/x86_64-target.json b/real_mode/real_mode/x86_64-target.json new file mode 100644 index 00000000..9a60d2c0 --- /dev/null +++ b/real_mode/real_mode/x86_64-target.json @@ -0,0 +1,26 @@ +{ + "arch": "x86", + "cpu": "i386", + "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", + "dynamic-linking": false, + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "llvm-target": "i386-unknown-none-code16", + "pre-link-args": { + "ld.lld": [ + "-Tlinker.ld" + ] + }, + "max-atomic-width": 64, + "position-independent-executables": false, + "disable-redzone": true, + "target-c-int-width": "32", + "target-pointer-width": "32", + "target-endian": "little", + "panic-strategy": "abort", + "os": "none", + "vendor": "unknown", + "relocation_model": "static", + "eliminate_frame_pointer": true +} \ No newline at end of file diff --git a/real_mode/x86_64-bootloader.json b/real_mode/x86_64-bootloader.json new file mode 100644 index 00000000..074ea1bc --- /dev/null +++ b/real_mode/x86_64-bootloader.json @@ -0,0 +1,21 @@ +{ + "llvm-target": "x86_64-unknown-none-gnu", + "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "pre-link-args": { + "ld.lld": [ + "--script=linker.ld" + ] + }, + "target-endian": "little", + "target-pointer-width": "64", + "target-c-int-width": "32", + "arch": "x86_64", + "os": "none", + "features": "-mmx,-sse,+soft-float", + "disable-redzone": true, + "panic-strategy": "abort", + "executables": true, + "relocation_model": "static" +} From 88af6688cbe225f4a6877422464c52c995171c32 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 30 Dec 2019 00:43:28 +0100 Subject: [PATCH 012/226] Some renaming --- real_mode/.cargo/config | 2 +- real_mode/build.rs | 13 +++++++----- .../{x86_64-target.json => x86-16bit.json} | 5 ----- .../{x86_64-target.json => x86-16bit.json} | 5 ----- real_mode/tmp-libreal_mode.a | 0 ...otloader-real-mode.json => x86-32bit.json} | 10 ++++----- real_mode/x86_64-bootloader.json | 21 ------------------- 7 files changed, 14 insertions(+), 42 deletions(-) rename real_mode/first_stage/{x86_64-target.json => x86-16bit.json} (90%) rename real_mode/real_mode/{x86_64-target.json => x86-16bit.json} (90%) delete mode 100644 real_mode/tmp-libreal_mode.a rename real_mode/{x86_64-bootloader-real-mode.json => x86-32bit.json} (84%) delete mode 100644 real_mode/x86_64-bootloader.json diff --git a/real_mode/.cargo/config b/real_mode/.cargo/config index fb871b1b..1f97af0f 100644 --- a/real_mode/.cargo/config +++ b/real_mode/.cargo/config @@ -1,2 +1,2 @@ [build] -target = "x86_64-bootloader.json" +target = "x86-32bit.json" diff --git a/real_mode/build.rs b/real_mode/build.rs index e8f6c27b..0617a56a 100644 --- a/real_mode/build.rs +++ b/real_mode/build.rs @@ -8,11 +8,11 @@ fn main() { let llvm_tools = LlvmTools::new().expect("LLVM tools not found"); let objcopy = llvm_tools.tool(&exe("llvm-objcopy")).expect("llvm-objcopy not found"); - build_subproject(Path::new("first_stage"), &["_start", "print_char"], &out_dir, &objcopy); - build_subproject(Path::new("real_mode"), &["second_stage"], &out_dir, &objcopy); + build_subproject(Path::new("first_stage"), &["_start", "print_char"], "x86-16bit.json", &out_dir, &objcopy); + build_subproject(Path::new("real_mode"), &["second_stage"], "x86-16bit.json", &out_dir, &objcopy); } -fn build_subproject(dir: &Path, global_symbols: &[&str], out_dir: &str, objcopy: &Path) { +fn build_subproject(dir: &Path, global_symbols: &[&str], target: &str, out_dir: &str, objcopy: &Path) { let dir_name = dir.file_name().unwrap().to_str().unwrap(); let manifest_path = dir.join("Cargo.toml"); let out_path = Path::new(&out_dir); @@ -23,7 +23,7 @@ fn build_subproject(dir: &Path, global_symbols: &[&str], out_dir: &str, objcopy: cmd.arg("xbuild").arg("--release"); cmd.arg("--verbose"); cmd.arg(format!("--manifest-path={}", manifest_path.display())); - cmd.arg(format!("--target={}", dir.join("x86_64-target.json").display())); + cmd.arg(format!("--target={}", dir.join(target).display())); cmd.arg("-Z").arg("unstable-options"); cmd.arg("--out-dir").arg(&out_dir); cmd.arg("--target-dir").arg(out_path.join("target").join(dir_name)); @@ -41,12 +41,15 @@ fn build_subproject(dir: &Path, global_symbols: &[&str], out_dir: &str, objcopy: let status = cmd.status().unwrap(); assert!(status.success()); + /* + // FIXME: it seems like this messes up relocations // convert to ELF64 let mut cmd = Command::new(objcopy); cmd.arg("-I").arg("elf32-i386").arg("-O").arg("elf64-x86-64"); - cmd.arg(out_path.join(format!("lib{}.a", dir_name))); + cmd.arg(out_path.join(format!("lib{}.a", dir_name))); let status = cmd.status().unwrap(); assert!(status.success()); + */ // emit linker flags println!("cargo:rustc-link-search=native={}", out_dir); diff --git a/real_mode/first_stage/x86_64-target.json b/real_mode/first_stage/x86-16bit.json similarity index 90% rename from real_mode/first_stage/x86_64-target.json rename to real_mode/first_stage/x86-16bit.json index 9a60d2c0..35bd2784 100644 --- a/real_mode/first_stage/x86_64-target.json +++ b/real_mode/first_stage/x86-16bit.json @@ -7,11 +7,6 @@ "linker-flavor": "ld.lld", "linker": "rust-lld", "llvm-target": "i386-unknown-none-code16", - "pre-link-args": { - "ld.lld": [ - "-Tlinker.ld" - ] - }, "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, diff --git a/real_mode/real_mode/x86_64-target.json b/real_mode/real_mode/x86-16bit.json similarity index 90% rename from real_mode/real_mode/x86_64-target.json rename to real_mode/real_mode/x86-16bit.json index 9a60d2c0..35bd2784 100644 --- a/real_mode/real_mode/x86_64-target.json +++ b/real_mode/real_mode/x86-16bit.json @@ -7,11 +7,6 @@ "linker-flavor": "ld.lld", "linker": "rust-lld", "llvm-target": "i386-unknown-none-code16", - "pre-link-args": { - "ld.lld": [ - "-Tlinker.ld" - ] - }, "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, diff --git a/real_mode/tmp-libreal_mode.a b/real_mode/tmp-libreal_mode.a deleted file mode 100644 index e69de29b..00000000 diff --git a/real_mode/x86_64-bootloader-real-mode.json b/real_mode/x86-32bit.json similarity index 84% rename from real_mode/x86_64-bootloader-real-mode.json rename to real_mode/x86-32bit.json index 9a60d2c0..7a2d0eaa 100644 --- a/real_mode/x86_64-bootloader-real-mode.json +++ b/real_mode/x86-32bit.json @@ -6,12 +6,12 @@ "executables": true, "linker-flavor": "ld.lld", "linker": "rust-lld", - "llvm-target": "i386-unknown-none-code16", "pre-link-args": { - "ld.lld": [ - "-Tlinker.ld" - ] - }, + "ld.lld": [ + "--script=linker.ld" + ] + }, + "llvm-target": "i386-unknown-none", "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, diff --git a/real_mode/x86_64-bootloader.json b/real_mode/x86_64-bootloader.json deleted file mode 100644 index 074ea1bc..00000000 --- a/real_mode/x86_64-bootloader.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none-gnu", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "pre-link-args": { - "ld.lld": [ - "--script=linker.ld" - ] - }, - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "arch": "x86_64", - "os": "none", - "features": "-mmx,-sse,+soft-float", - "disable-redzone": true, - "panic-strategy": "abort", - "executables": true, - "relocation_model": "static" -} From c183314da1b6669bb043b2ea5612ff0b7e9e53f6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 24 Apr 2020 11:43:54 +0200 Subject: [PATCH 013/226] Fix build instructions in readme --- real_mode/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/real_mode/README.md b/real_mode/README.md index c77db965..29a85726 100644 --- a/real_mode/README.md +++ b/real_mode/README.md @@ -21,7 +21,7 @@ If the code does not fit into 512 bytes, the linker will throw the following err The output of `cargo xbuild` is an ELF binary, which can't be loaded directly by the BIOS. To boot our project, we must therefore convert it into a flat binary first. This works with the following `objcopy` command: ``` -objcopy -I elf32-i386 -O binary target/x86_64-bootloader-real-mode/release/real_mode image.bin +objcopy -I elf32-i386 -O binary target/x86-32bit/release/bootloader image.bin ``` This creates a file named `image.bin` in the root folder of the project, which is a bootable disk image. From 382388e185970129fa3f1ebc4be34d6ea93c12f6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 24 Apr 2020 13:33:17 +0200 Subject: [PATCH 014/226] Use `-Zbuildstd --- real_mode/.cargo/config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/real_mode/.cargo/config b/real_mode/.cargo/config index 1f97af0f..168bd90a 100644 --- a/real_mode/.cargo/config +++ b/real_mode/.cargo/config @@ -1,2 +1,5 @@ [build] target = "x86-32bit.json" + +[alias] +xbuild = "build -Zbuild-std=core" From bd12d2c0b3a0b57acf60daca4630a368b391354d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 24 Apr 2020 13:34:05 +0200 Subject: [PATCH 015/226] Create output section of .eh_frame to prevent automatic placing The section should be unused, but we still keep it to be safe. --- real_mode/linker.ld | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/real_mode/linker.ld b/real_mode/linker.ld index 5fbea44d..64f42333 100644 --- a/real_mode/linker.ld +++ b/real_mode/linker.ld @@ -53,6 +53,12 @@ SECTIONS { *(.got .got.*) } + .eh_frame : + { + _EH_FRAME = .; + *(.eh_frame) + } + .asm : { *(.second_stage) From b3df5e8debad2cfd9d0cad5c4b3914568ec613c7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 2 Dec 2021 19:41:16 +0100 Subject: [PATCH 016/226] Create new `bootloader_api` crate that puts config in ELF section --- Cargo.lock | 53 ++++++ Cargo.toml | 1 + api/Cargo.toml | 11 ++ api/build.rs | 114 +++++++++++++ api/src/config.rs | 400 ++++++++++++++++++++++++++++++++++++++++++++++ api/src/info.rs | 308 +++++++++++++++++++++++++++++++++++ api/src/lib.rs | 54 +++++++ 7 files changed, 941 insertions(+) create mode 100644 api/Cargo.toml create mode 100644 api/build.rs create mode 100644 api/src/config.rs create mode 100644 api/src/info.rs create mode 100644 api/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 287e4e8f..9173d509 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -92,6 +92,13 @@ dependencies = [ "json", ] +[[package]] +name = "bootloader_api" +version = "0.1.0" +dependencies = [ + "rand", +] + [[package]] name = "build_const" version = "0.2.2" @@ -272,6 +279,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "ppv-lite86" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" + [[package]] name = "proc-macro2" version = "1.0.27" @@ -290,6 +303,46 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "rsdp" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 874cd738..0c33f665 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ build = "build.rs" [workspace] members = [ + "api", "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", diff --git a/api/Cargo.toml b/api/Cargo.toml new file mode 100644 index 00000000..904a5455 --- /dev/null +++ b/api/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bootloader_api" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[dev-dependencies] +rand = "0.8.4" diff --git a/api/build.rs b/api/build.rs new file mode 100644 index 00000000..84dd35c4 --- /dev/null +++ b/api/build.rs @@ -0,0 +1,114 @@ +use std::{env, fs, path::Path}; + +// #![feature(split_array)] + +// use config::{BootloaderConfig, Version}; + +// fn main() { +// let BootloaderConfig { +// version: +// Version { +// version_major, +// version_minor, +// version_patch, +// pre_release, +// }, +// mappings, +// kernel_stack_size, +// frame_buffer, +// } = BootloaderConfig::default(); + +// let version_major = version_major.to_le_bytes(); +// let version_minor = version_minor.to_le_bytes(); +// let version_patch = version_patch.to_le_bytes(); +// let pre_release = [pre_release as u8]; + +// let version_fields = [ +// ("version_major", version_major.len()), +// ("version_minor", version_minor.len()), +// ("version_patch", version_patch.len()), +// ("pre_release", pre_release.len()), +// ]; +// let version_len = version_fields.iter().map(|(_, l)| l).sum::(); + +// let kernel_stack_size = kernel_stack_size.to_le_bytes(); +// let fields = [ +// ("Self::UUID", BootloaderConfig::UUID.len()), +// ("version", version_len), +// ("kernel_stack_size", kernel_stack_size.len()), +// ("mappings", mappings_len), +// ("frame_buffer", frame_buffer_len), +// ]; + +// let total_len = fields.iter().map(|(_, l)| l).sum::(); + +// let x = format!( +// " +// impl Version {{ +// pub SERIALIZED_LEN: usize = {}; +// pub const fn serialize(&self) -> [u8; Self::SERIALIZED_LEN] {{ +// {} +// }} +// }} + +// impl BootloaderConfig {{ +// pub SERIALIZED_LEN: usize = {}; +// pub const fn serialize(&self) -> [u8; Self::SERIALIZED_LEN] {{ +// [] +// }} +// }} +// ", +// version_len, total_len +// ); + +// panic!("{}", x); +// } + +// #[path = "src/config.rs"] +// mod config; + +fn main() { + let out_dir = env::var_os("OUT_DIR").unwrap(); + let dest_path = Path::new(&out_dir).join("concat.rs"); + + let combinations = [ + (1, 8), + (1, 9), + (2, 1), + (2, 2), + (4, 3), + (16, 7), + (23, 8), + (31, 9), + (40, 9), + (49, 9), + (58, 10), + (68, 10), + (78, 9), + (87, 9), + ]; + + let mut code = String::new(); + for (i, j) in combinations { + + code += &format!( + "pub const fn concat_{i}_{j}(a: [u8; {i}], b: [u8; {j}]) -> [u8; {i} + {j}] {{ + [{a}, {b}] + }}", + i = i, + j = j, + a = (0..i) + .map(|idx| format!("a[{}]", idx)) + .collect::>() + .join(","), + b = (0..j) + .map(|idx| format!("b[{}]", idx)) + .collect::>() + .join(","), + ); + + } + + fs::write(&dest_path, code).unwrap(); + println!("cargo:rerun-if-changed=build.rs"); +} diff --git a/api/src/config.rs b/api/src/config.rs new file mode 100644 index 00000000..06cdc3c0 --- /dev/null +++ b/api/src/config.rs @@ -0,0 +1,400 @@ +use crate::concat::*; + +#[derive(Debug, PartialEq, Eq)] +#[non_exhaustive] +pub struct BootloaderConfig { + pub(crate) version: Version, + + pub mappings: Mappings, + + pub kernel_stack_size: u64, + + pub frame_buffer: FrameBuffer, +} + +impl BootloaderConfig { + pub(crate) const UUID: [u8; 16] = [ + 0x74, 0x3C, 0xA9, 0x61, 0x09, 0x36, 0x46, 0xA0, 0xBB, 0x55, 0x5C, 0x15, 0x89, 0x15, 0x25, + 0x3D, + ]; + pub const SERIALIZED_LEN: usize = 96; + + pub const fn new_default() -> Self { + Self { + kernel_stack_size: 80 * 1024, + version: Version::new_default(), + mappings: Mappings::new_default(), + frame_buffer: FrameBuffer::new_default(), + } + } + + pub const fn serialize(&self) -> [u8; Self::SERIALIZED_LEN] { + let Self { + version, + mappings, + kernel_stack_size, + frame_buffer, + } = self; + let Version { + version_major, + version_minor, + version_patch, + pre_release, + } = version; + let Mappings { + kernel_stack, + boot_info, + framebuffer, + physical_memory, + page_table_recursive, + } = mappings; + let FrameBuffer { + minimum_framebuffer_height, + minimum_framebuffer_width, + } = frame_buffer; + + let version = { + let one = concat_2_2(version_major.to_le_bytes(), version_minor.to_le_bytes()); + let two = concat_2_1(version_patch.to_le_bytes(), [*pre_release as u8]); + concat_4_3(one, two) + }; + let buf = concat_16_7(Self::UUID, version); + let buf = concat_23_8(buf, kernel_stack_size.to_le_bytes()); + + let buf = concat_31_9(buf, kernel_stack.serialize()); + let buf = concat_40_9(buf, boot_info.serialize()); + let buf = concat_49_9(buf, framebuffer.serialize()); + + let buf = concat_58_10( + buf, + match physical_memory { + Option::None => [0; 10], + Option::Some(m) => concat_1_9([1], m.serialize()), + }, + ); + let buf = concat_68_10( + buf, + match page_table_recursive { + Option::None => [0; 10], + Option::Some(m) => concat_1_9([1], m.serialize()), + }, + ); + let buf = concat_78_9( + buf, + match minimum_framebuffer_height { + Option::None => [0; 9], + Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), + }, + ); + let buf = concat_87_9( + buf, + match minimum_framebuffer_width { + Option::None => [0; 9], + Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), + }, + ); + + buf + } + + pub fn deserialize(serialized: &[u8]) -> Result { + if serialized.len() != Self::SERIALIZED_LEN { + return Err(()); + } + + let s = serialized; + + let (uuid, s) = s.split_array_ref(); + if uuid != &Self::UUID { + return Err(()); + } + + let (version, s) = { + let (&major, s) = s.split_array_ref(); + let (&minor, s) = s.split_array_ref(); + let (&patch, s) = s.split_array_ref(); + let (&pre, s) = s.split_array_ref(); + let pre = match pre { + [0] => false, + [1] => true, + _ => return Err(()), + }; + + let version = Version { + version_major: u16::from_le_bytes(major), + version_minor: u16::from_le_bytes(minor), + version_patch: u16::from_le_bytes(patch), + pre_release: pre, + }; + (version, s) + }; + + let (&kernel_stack_size, s) = s.split_array_ref(); + + let (mappings, s) = { + let (&kernel_stack, s) = s.split_array_ref(); + let (&boot_info, s) = s.split_array_ref(); + let (&framebuffer, s) = s.split_array_ref(); + let (&physical_memory_some, s) = s.split_array_ref(); + let (&physical_memory, s) = s.split_array_ref(); + let (&page_table_recursive_some, s) = s.split_array_ref(); + let (&page_table_recursive, s) = s.split_array_ref(); + + let mappings = Mappings { + kernel_stack: Mapping::deserialize(&kernel_stack)?, + boot_info: Mapping::deserialize(&boot_info)?, + framebuffer: Mapping::deserialize(&framebuffer)?, + physical_memory: match physical_memory_some { + [0] if physical_memory == [0; 9] => Option::None, + [1] => Option::Some(Mapping::deserialize(&physical_memory)?), + _ => return Err(()), + }, + page_table_recursive: match page_table_recursive_some { + [0] if page_table_recursive == [0; 9] => Option::None, + [1] => Option::Some(Mapping::deserialize(&page_table_recursive)?), + _ => return Err(()), + }, + }; + (mappings, s) + }; + + let (frame_buffer, s) = { + let (&min_framebuffer_height_some, s) = s.split_array_ref(); + let (&min_framebuffer_height, s) = s.split_array_ref(); + let (&min_framebuffer_width_some, s) = s.split_array_ref(); + let (&min_framebuffer_width, s) = s.split_array_ref(); + + let frame_buffer = FrameBuffer { + minimum_framebuffer_height: match min_framebuffer_height_some { + [0] if min_framebuffer_height == [0; 8] => Option::None, + [1] => Option::Some(u64::from_le_bytes(min_framebuffer_height)), + _ => return Err(()), + }, + minimum_framebuffer_width: match min_framebuffer_width_some { + [0] if min_framebuffer_width == [0; 8] => Option::None, + [1] => Option::Some(u64::from_le_bytes(min_framebuffer_width)), + _ => return Err(()), + }, + }; + (frame_buffer, s) + }; + + if !s.is_empty() { + return Err(()); + } + + Ok(Self { + version, + kernel_stack_size: u64::from_le_bytes(kernel_stack_size), + mappings, + frame_buffer, + }) + } + + #[cfg(test)] + fn random() -> Self { + Self { + version: Version::random(), + mappings: Mappings::random(), + kernel_stack_size: rand::random(), + frame_buffer: FrameBuffer::random(), + } + } +} + +impl Default for BootloaderConfig { + fn default() -> Self { + Self::new_default() + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct Version { + /// Bootloader version (major). + version_major: u16, + /// Bootloader version (minor). + version_minor: u16, + /// Bootloader version (patch). + version_patch: u16, + /// Whether the bootloader version is a pre-release. + /// + /// We can't store the full prerelease string of the version number since it could be + /// arbitrarily long. + pre_release: bool, +} + +impl Version { + const fn new_default() -> Self { + Self { + // todo: generate these from build script + version_major: 0, + version_minor: 0, + version_patch: 0, + pre_release: true, + } + } + + #[cfg(test)] + fn random() -> Version { + Self { + version_major: rand::random(), + version_minor: rand::random(), + version_patch: rand::random(), + pre_release: rand::random(), + } + } +} + +impl Default for Version { + fn default() -> Self { + Self::new_default() + } +} + +#[derive(Debug, Default, PartialEq, Eq)] +#[non_exhaustive] +pub struct Mappings { + pub kernel_stack: Mapping, + pub boot_info: Mapping, + pub framebuffer: Mapping, + pub physical_memory: Option, + pub page_table_recursive: Option, +} + +impl Mappings { + pub const fn new_default() -> Self { + Self { + kernel_stack: Mapping::new_default(), + boot_info: Mapping::new_default(), + framebuffer: Mapping::new_default(), + physical_memory: Option::None, + page_table_recursive: Option::None, + } + } + + #[cfg(test)] + fn random() -> Mappings { + let phys = rand::random(); + let recursive = rand::random(); + Self { + kernel_stack: Mapping::random(), + boot_info: Mapping::random(), + framebuffer: Mapping::random(), + physical_memory: if phys { + Option::Some(Mapping::random()) + } else { + Option::None + }, + page_table_recursive: if recursive { + Option::Some(Mapping::random()) + } else { + Option::None + }, + } + } +} + +#[derive(Debug, Default, PartialEq, Eq)] +#[non_exhaustive] +pub struct FrameBuffer { + pub minimum_framebuffer_height: Option, + pub minimum_framebuffer_width: Option, +} + +impl FrameBuffer { + pub const fn new_default() -> Self { + Self { + minimum_framebuffer_height: Option::None, + minimum_framebuffer_width: Option::None, + } + } + + #[cfg(test)] + fn random() -> FrameBuffer { + Self { + minimum_framebuffer_height: if rand::random() { + Option::Some(rand::random()) + } else { + Option::None + }, + minimum_framebuffer_width: if rand::random() { + Option::Some(rand::random()) + } else { + Option::None + }, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Mapping { + Dynamic, + FixedAddress(u64), +} + +impl Mapping { + pub const fn new_default() -> Self { + Self::Dynamic + } + + #[cfg(test)] + fn random() -> Mapping { + let fixed = rand::random(); + if fixed { + Self::Dynamic + } else { + Self::FixedAddress(rand::random()) + } + } + + const fn serialize(&self) -> [u8; 9] { + match self { + Mapping::Dynamic => [0; 9], + Mapping::FixedAddress(addr) => concat_1_8([1], addr.to_le_bytes()), + } + } + + pub fn deserialize(serialized: &[u8; 9]) -> Result { + let (&variant, s) = serialized.split_array_ref(); + let (&addr, s) = s.split_array_ref(); + if !s.is_empty() { + return Err(()); + } + + match variant { + [0] if addr == [0; 8] => Ok(Mapping::Dynamic), + [1] => Ok(Mapping::FixedAddress(u64::from_le_bytes(addr))), + _ => Err(()), + } + } +} + +impl Default for Mapping { + fn default() -> Self { + Self::new_default() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn mapping_serde() { + for _ in 0..1000 { + let config = Mapping::random(); + assert_eq!(Mapping::deserialize(&config.serialize()), Ok(config)); + } + } + + #[test] + fn config_serde() { + for _ in 0..1000 { + let config = BootloaderConfig::random(); + assert_eq!( + BootloaderConfig::deserialize(&config.serialize()), + Ok(config) + ); + } + } +} diff --git a/api/src/info.rs b/api/src/info.rs new file mode 100644 index 00000000..adb97bc6 --- /dev/null +++ b/api/src/info.rs @@ -0,0 +1,308 @@ +use core::{ops, slice}; + +/// This structure represents the information that the bootloader passes to the kernel. +/// +/// The information is passed as an argument to the entry point. The entry point function must +/// have the following signature: +/// +/// ``` +/// # use bootloader::BootInfo; +/// # type _SIGNATURE = +/// extern "C" fn(boot_info: &'static mut BootInfo) -> !; +/// ``` +/// +/// Note that no type checking occurs for the entry point function, so be careful to +/// use the correct argument types. To ensure that the entry point function has the correct +/// signature, use the [`entry_point`] macro. +#[derive(Debug)] +#[repr(C)] +#[non_exhaustive] +pub struct BootInfo { + /// Bootloader version (major). + pub version_major: u16, + /// Bootloader version (minor). + pub version_minor: u16, + /// Bootloader version (patch). + pub version_patch: u16, + /// Whether the bootloader version is a pre-release. + /// + /// We can't store the full prerelease string of the version number since it could be + /// arbitrarily long. + pub pre_release: bool, + /// A map of the physical memory regions of the underlying machine. + /// + /// The bootloader queries this information from the BIOS/UEFI firmware and translates this + /// information to Rust types. It also marks any memory regions that the bootloader uses in + /// the memory map before passing it to the kernel. Regions marked as usable can be freely + /// used by the kernel. + pub memory_regions: MemoryRegions, + /// Information about the framebuffer for screen output if available. + pub framebuffer: Optional, + /// The virtual address at which the mapping of the physical memory starts. + /// + /// Physical addresses can be converted to virtual addresses by adding this offset to them. + /// + /// The mapping of the physical memory allows to access arbitrary physical frames. Accessing + /// frames that are also mapped at other virtual addresses can easily break memory safety and + /// cause undefined behavior. Only frames reported as `USABLE` by the memory map in the `BootInfo` + /// can be safely accessed. + /// + /// Only available if the `map-physical-memory` config option is enabled. + pub physical_memory_offset: Optional, + /// The virtual address of the recursively mapped level 4 page table. + /// + /// Only available if the `map-page-table-recursively` config option is enabled. + pub recursive_index: Optional, + /// The address of the `RSDP` data structure, which can be use to find the ACPI tables. + /// + /// This field is `None` if no `RSDP` was found (for BIOS) or reported (for UEFI). + pub rsdp_addr: Optional, + /// The thread local storage (TLS) template of the kernel executable, if present. + pub tls_template: Optional, +} + +/// FFI-safe slice of [`MemoryRegion`] structs, semantically equivalent to +/// `&'static mut [MemoryRegion]`. +/// +/// This type implements the [`Deref`][core::ops::Deref] and [`DerefMut`][core::ops::DerefMut] +/// traits, so it can be used like a `&mut [MemoryRegion]` slice. It also implements [`From`] +/// and [`Into`] for easy conversions from and to `&'static mut [MemoryRegion]`. +#[derive(Debug)] +#[repr(C)] +pub struct MemoryRegions { + pub(crate) ptr: *mut MemoryRegion, + pub(crate) len: usize, +} + +impl ops::Deref for MemoryRegions { + type Target = [MemoryRegion]; + + fn deref(&self) -> &Self::Target { + unsafe { slice::from_raw_parts(self.ptr, self.len) } + } +} + +impl ops::DerefMut for MemoryRegions { + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } + } +} + +impl From<&'static mut [MemoryRegion]> for MemoryRegions { + fn from(regions: &'static mut [MemoryRegion]) -> Self { + MemoryRegions { + ptr: regions.as_mut_ptr(), + len: regions.len(), + } + } +} + +impl From for &'static mut [MemoryRegion] { + fn from(regions: MemoryRegions) -> &'static mut [MemoryRegion] { + unsafe { slice::from_raw_parts_mut(regions.ptr, regions.len) } + } +} + +/// Represent a physical memory region. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(C)] +pub struct MemoryRegion { + /// The physical start address of the region. + pub start: u64, + /// The physical end address (exclusive) of the region. + pub end: u64, + /// The memory type of the memory region. + /// + /// Only [`Usable`][MemoryRegionKind::Usable] regions can be freely used. + pub kind: MemoryRegionKind, +} + +impl MemoryRegion { + /// Creates a new empty memory region (with length 0). + pub const fn empty() -> Self { + MemoryRegion { + start: 0, + end: 0, + kind: MemoryRegionKind::Bootloader, + } + } +} + +/// Represents the different types of memory. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[non_exhaustive] +#[repr(C)] +pub enum MemoryRegionKind { + /// Unused conventional memory, can be used by the kernel. + Usable, + /// Memory mappings created by the bootloader, including the kernel and boot info mappings. + /// + /// This memory should _not_ be used by the kernel. + Bootloader, + /// An unknown memory region reported by the UEFI firmware. + /// + /// This should only be used if the UEFI memory type is known as usable. + UnknownUefi(u32), + /// An unknown memory region reported by the BIOS firmware. + /// + /// This should only be used if the BIOS memory type is known as usable. + UnknownBios(u32), +} + +/// A pixel-based framebuffer that controls the screen output. +#[derive(Debug)] +#[repr(C)] +pub struct FrameBuffer { + pub(crate) buffer_start: u64, + pub(crate) buffer_byte_len: usize, + pub(crate) info: FrameBufferInfo, +} + +impl FrameBuffer { + /// Returns the raw bytes of the framebuffer as slice. + pub fn buffer(&self) -> &[u8] { + unsafe { self.create_buffer() } + } + + /// Returns the raw bytes of the framebuffer as mutable slice. + pub fn buffer_mut(&mut self) -> &mut [u8] { + unsafe { self.create_buffer() } + } + + unsafe fn create_buffer<'a>(&self) -> &'a mut [u8] { + unsafe { slice::from_raw_parts_mut(self.buffer_start as *mut u8, self.buffer_byte_len) } + } + + /// Returns layout and pixel format information of the framebuffer. + pub fn info(&self) -> FrameBufferInfo { + self.info + } +} + +/// Describes the layout and pixel format of a framebuffer. +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct FrameBufferInfo { + /// The total size in bytes. + pub byte_len: usize, + /// The width in pixels. + pub horizontal_resolution: usize, + /// The height in pixels. + pub vertical_resolution: usize, + /// The color format of each pixel. + pub pixel_format: PixelFormat, + /// The number of bytes per pixel. + pub bytes_per_pixel: usize, + /// Number of pixels between the start of a line and the start of the next. + /// + /// Some framebuffers use additional padding at the end of a line, so this + /// value might be larger than `horizontal_resolution`. It is + /// therefore recommended to use this field for calculating the start address of a line. + pub stride: usize, +} + +/// Color format of pixels in the framebuffer. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[non_exhaustive] +#[repr(C)] +pub enum PixelFormat { + /// One byte red, then one byte green, then one byte blue. + /// + /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] + /// for this. + RGB, + /// One byte blue, then one byte green, then one byte red. + /// + /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] + /// for this. + BGR, + /// A single byte, representing the grayscale value. + /// + /// Length might be larger than 1, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] + /// for this. + U8, +} + +/// Information about the thread local storage (TLS) template. +/// +/// This template can be used to set up thread local storage for threads. For +/// each thread, a new memory location of size `mem_size` must be initialized. +/// Then the first `file_size` bytes of this template needs to be copied to the +/// location. The additional `mem_size - file_size` bytes must be initialized with +/// zero. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct TlsTemplate { + /// The virtual start address of the thread local storage template. + pub start_addr: u64, + /// The number of data bytes in the template. + /// + /// Corresponds to the length of the `.tdata` section. + pub file_size: u64, + /// The total number of bytes that the TLS segment should have in memory. + /// + /// Corresponds to the combined length of the `.tdata` and `.tbss` sections. + pub mem_size: u64, +} + +/// FFI-safe variant of [`Option`]. +/// +/// Implements the [`From`] and [`Into`] traits for easy conversion to and from [`Option`]. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(C)] +pub enum Optional { + /// Some value `T` + Some(T), + /// No value + None, +} + +impl Optional { + /// Converts the `Optional` to an [`Option`]. + pub fn into_option(self) -> Option { + self.into() + } + + /// Converts from `&Optional` to `Option<&T>`. + /// + /// For convenience, this method directly performs the conversion to the standard + /// [`Option`] type. + pub const fn as_ref(&self) -> Option<&T> { + match self { + Self::Some(x) => Some(x), + Self::None => None, + } + } + + /// Converts from `&mut Optional` to `Option<&mut T>`. + /// + /// For convenience, this method directly performs the conversion to the standard + /// [`Option`] type. + pub fn as_mut(&mut self) -> Option<&mut T> { + match self { + Self::Some(x) => Some(x), + Self::None => None, + } + } +} + +impl From> for Optional { + fn from(v: Option) -> Self { + match v { + Some(v) => Optional::Some(v), + None => Optional::None, + } + } +} + +impl From> for Option { + fn from(optional: Optional) -> Option { + match optional { + Optional::Some(v) => Some(v), + Optional::None => None, + } + } +} + +/// Check that bootinfo is FFI-safe +extern "C" fn _assert_ffi(_boot_info: BootInfo) {} diff --git a/api/src/lib.rs b/api/src/lib.rs new file mode 100644 index 00000000..f94cf28f --- /dev/null +++ b/api/src/lib.rs @@ -0,0 +1,54 @@ +#![feature(asm)] +#![feature(split_array)] + +#![deny(unsafe_op_in_unsafe_fn)] + +#![cfg_attr(not(test), no_std)] + +pub use config::BootloaderConfig; + +pub mod config; +pub mod info; + +mod concat { + include!(concat!(env!("OUT_DIR"), "/concat.rs")); +} + +/// Defines the entry point function. +/// +/// The function must have the signature `fn(&'static mut BootInfo) -> !`. +/// +/// This macro just creates a function named `_start`, which the linker will use as the entry +/// point. The advantage of using this macro instead of providing an own `_start` function is +/// that the macro ensures that the function and argument types are correct. +#[macro_export] +macro_rules! entry_point { + ($path:path) => { + entry_point!($path, config = &crate::BootloaderConfig::new_default()); + }; + ($path:path, config = $config:expr) => { + #[link_section = ".bootloader-config"] + pub static __BOOTLOADER_CONFIG: [u8; $crate::BootloaderConfig::SERIALIZED_LEN] = { + // validate the type + let config: &$crate::BootloaderConfig = $config; + config.serialize() + }; + + #[export_name = "_start"] + pub extern "C" fn __impl_start(boot_info: &'static mut $crate::info::BootInfo) -> ! { + // validate the signature of the program entry point + let f: fn(&'static mut $crate::info::BootInfo) -> ! = $path; + + // ensure that the config is used so that the linker keeps it + $crate::__force_use(&__BOOTLOADER_CONFIG); + + f(boot_info) + } + }; +} + +#[doc(hidden)] +pub fn __force_use(slice: &[u8]) { + let force_use = &slice as *const _ as usize; + unsafe { asm!("add {0}, 0", in(reg) force_use, options(nomem, nostack)) }; +} From 01ab004a090b984be7e2f43a3b5bb46fc6e0fea8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 2 Dec 2021 20:27:16 +0100 Subject: [PATCH 017/226] Reexport `BootInfo` struct at top level --- api/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index f94cf28f..7bd19071 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -5,7 +5,7 @@ #![cfg_attr(not(test), no_std)] -pub use config::BootloaderConfig; +pub use self::{config::BootloaderConfig, info::BootInfo}; pub mod config; pub mod info; From e52684431f9e1d30900c5a13125ca631741c8d04 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 2 Dec 2021 20:28:01 +0100 Subject: [PATCH 018/226] Run cargo fmt --- api/build.rs | 28 +++++++++++++--------------- api/src/lib.rs | 2 -- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/api/build.rs b/api/build.rs index 84dd35c4..4911aa60 100644 --- a/api/build.rs +++ b/api/build.rs @@ -90,23 +90,21 @@ fn main() { let mut code = String::new(); for (i, j) in combinations { - - code += &format!( - "pub const fn concat_{i}_{j}(a: [u8; {i}], b: [u8; {j}]) -> [u8; {i} + {j}] {{ + code += &format!( + "pub const fn concat_{i}_{j}(a: [u8; {i}], b: [u8; {j}]) -> [u8; {i} + {j}] {{ [{a}, {b}] }}", - i = i, - j = j, - a = (0..i) - .map(|idx| format!("a[{}]", idx)) - .collect::>() - .join(","), - b = (0..j) - .map(|idx| format!("b[{}]", idx)) - .collect::>() - .join(","), - ); - + i = i, + j = j, + a = (0..i) + .map(|idx| format!("a[{}]", idx)) + .collect::>() + .join(","), + b = (0..j) + .map(|idx| format!("b[{}]", idx)) + .collect::>() + .join(","), + ); } fs::write(&dest_path, code).unwrap(); diff --git a/api/src/lib.rs b/api/src/lib.rs index 7bd19071..7d57dfe0 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,8 +1,6 @@ #![feature(asm)] #![feature(split_array)] - #![deny(unsafe_op_in_unsafe_fn)] - #![cfg_attr(not(test), no_std)] pub use self::{config::BootloaderConfig, info::BootInfo}; From 846105fbe8bb5712dbfb222d8a7892babe6e6ad4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 11:59:39 +0100 Subject: [PATCH 019/226] Remove commented-out build.rs code --- api/build.rs | 67 ---------------------------------------------------- 1 file changed, 67 deletions(-) diff --git a/api/build.rs b/api/build.rs index 4911aa60..ec2e3b70 100644 --- a/api/build.rs +++ b/api/build.rs @@ -1,72 +1,5 @@ use std::{env, fs, path::Path}; -// #![feature(split_array)] - -// use config::{BootloaderConfig, Version}; - -// fn main() { -// let BootloaderConfig { -// version: -// Version { -// version_major, -// version_minor, -// version_patch, -// pre_release, -// }, -// mappings, -// kernel_stack_size, -// frame_buffer, -// } = BootloaderConfig::default(); - -// let version_major = version_major.to_le_bytes(); -// let version_minor = version_minor.to_le_bytes(); -// let version_patch = version_patch.to_le_bytes(); -// let pre_release = [pre_release as u8]; - -// let version_fields = [ -// ("version_major", version_major.len()), -// ("version_minor", version_minor.len()), -// ("version_patch", version_patch.len()), -// ("pre_release", pre_release.len()), -// ]; -// let version_len = version_fields.iter().map(|(_, l)| l).sum::(); - -// let kernel_stack_size = kernel_stack_size.to_le_bytes(); -// let fields = [ -// ("Self::UUID", BootloaderConfig::UUID.len()), -// ("version", version_len), -// ("kernel_stack_size", kernel_stack_size.len()), -// ("mappings", mappings_len), -// ("frame_buffer", frame_buffer_len), -// ]; - -// let total_len = fields.iter().map(|(_, l)| l).sum::(); - -// let x = format!( -// " -// impl Version {{ -// pub SERIALIZED_LEN: usize = {}; -// pub const fn serialize(&self) -> [u8; Self::SERIALIZED_LEN] {{ -// {} -// }} -// }} - -// impl BootloaderConfig {{ -// pub SERIALIZED_LEN: usize = {}; -// pub const fn serialize(&self) -> [u8; Self::SERIALIZED_LEN] {{ -// [] -// }} -// }} -// ", -// version_len, total_len -// ); - -// panic!("{}", x); -// } - -// #[path = "src/config.rs"] -// mod config; - fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); let dest_path = Path::new(&out_dir).join("concat.rs"); From b49f0189ef38f181718887ebe579bafd6fb5280c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 12:01:19 +0100 Subject: [PATCH 020/226] Replace unstable `split_array` feature with custom function --- api/src/config.rs | 48 ++++++++++++++++++++++++++++------------------- api/src/lib.rs | 1 - 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/api/src/config.rs b/api/src/config.rs index 06cdc3c0..e852ae59 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -104,16 +104,16 @@ impl BootloaderConfig { let s = serialized; - let (uuid, s) = s.split_array_ref(); + let (uuid, s) = split_array_ref(s); if uuid != &Self::UUID { return Err(()); } let (version, s) = { - let (&major, s) = s.split_array_ref(); - let (&minor, s) = s.split_array_ref(); - let (&patch, s) = s.split_array_ref(); - let (&pre, s) = s.split_array_ref(); + let (&major, s) = split_array_ref(s); + let (&minor, s) = split_array_ref(s); + let (&patch, s) = split_array_ref(s); + let (&pre, s) = split_array_ref(s); let pre = match pre { [0] => false, [1] => true, @@ -129,16 +129,16 @@ impl BootloaderConfig { (version, s) }; - let (&kernel_stack_size, s) = s.split_array_ref(); + let (&kernel_stack_size, s) = split_array_ref(s); let (mappings, s) = { - let (&kernel_stack, s) = s.split_array_ref(); - let (&boot_info, s) = s.split_array_ref(); - let (&framebuffer, s) = s.split_array_ref(); - let (&physical_memory_some, s) = s.split_array_ref(); - let (&physical_memory, s) = s.split_array_ref(); - let (&page_table_recursive_some, s) = s.split_array_ref(); - let (&page_table_recursive, s) = s.split_array_ref(); + let (&kernel_stack, s) = split_array_ref(s); + let (&boot_info, s) = split_array_ref(s); + let (&framebuffer, s) = split_array_ref(s); + let (&physical_memory_some, s) = split_array_ref(s); + let (&physical_memory, s) = split_array_ref(s); + let (&page_table_recursive_some, s) = split_array_ref(s); + let (&page_table_recursive, s) = split_array_ref(s); let mappings = Mappings { kernel_stack: Mapping::deserialize(&kernel_stack)?, @@ -159,10 +159,10 @@ impl BootloaderConfig { }; let (frame_buffer, s) = { - let (&min_framebuffer_height_some, s) = s.split_array_ref(); - let (&min_framebuffer_height, s) = s.split_array_ref(); - let (&min_framebuffer_width_some, s) = s.split_array_ref(); - let (&min_framebuffer_width, s) = s.split_array_ref(); + let (&min_framebuffer_height_some, s) = split_array_ref(s); + let (&min_framebuffer_height, s) = split_array_ref(s); + let (&min_framebuffer_width_some, s) = split_array_ref(s); + let (&min_framebuffer_width, s) = split_array_ref(s); let frame_buffer = FrameBuffer { minimum_framebuffer_height: match min_framebuffer_height_some { @@ -355,8 +355,8 @@ impl Mapping { } pub fn deserialize(serialized: &[u8; 9]) -> Result { - let (&variant, s) = serialized.split_array_ref(); - let (&addr, s) = s.split_array_ref(); + let (&variant, s) = split_array_ref(serialized); + let (&addr, s) = split_array_ref(s); if !s.is_empty() { return Err(()); } @@ -375,6 +375,16 @@ impl Default for Mapping { } } +/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 +/// +/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, +/// see https://github.com/rust-lang/rust/issues/90091 +fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { + let (a, b) = slice.split_at(N); + // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) + unsafe { (&*(a.as_ptr() as *const [T; N]), b) } +} + #[cfg(test)] mod tests { use super::*; diff --git a/api/src/lib.rs b/api/src/lib.rs index 7d57dfe0..8f38a409 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,5 +1,4 @@ #![feature(asm)] -#![feature(split_array)] #![deny(unsafe_op_in_unsafe_fn)] #![cfg_attr(not(test), no_std)] From 275e991355d019bad0354e55eaea95ba7e5cbb02 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 12:02:18 +0100 Subject: [PATCH 021/226] Fix import in doc test --- api/src/info.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/info.rs b/api/src/info.rs index adb97bc6..15c647d0 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -6,7 +6,7 @@ use core::{ops, slice}; /// have the following signature: /// /// ``` -/// # use bootloader::BootInfo; +/// # use bootloader_api::BootInfo; /// # type _SIGNATURE = /// extern "C" fn(boot_info: &'static mut BootInfo) -> !; /// ``` From 14a42c6a48ebb77be7a3a8ca1ccae827a244fba6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 12:02:45 +0100 Subject: [PATCH 022/226] The `asm` macro was stabilized as `core::arch::asm` --- api/src/lib.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 8f38a409..52374d08 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(asm)] #![deny(unsafe_op_in_unsafe_fn)] #![cfg_attr(not(test), no_std)] @@ -47,5 +46,5 @@ macro_rules! entry_point { #[doc(hidden)] pub fn __force_use(slice: &[u8]) { let force_use = &slice as *const _ as usize; - unsafe { asm!("add {0}, 0", in(reg) force_use, options(nomem, nostack)) }; + unsafe { core::arch::asm!("add {0}, 0", in(reg) force_use, options(nomem, nostack)) }; } From 731d6247312529e03865c7f37f570961686106c8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 12:02:57 +0100 Subject: [PATCH 023/226] Add rustfmt/clippy to rust-toolchain.toml --- rust-toolchain | 1 - rust-toolchain.toml | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) delete mode 100644 rust-toolchain create mode 100644 rust-toolchain.toml diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index 07ade694..00000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..8e275b74 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "nightly" +components = ["rustfmt", "clippy"] From 07781c839e67d56969fbc2b763e9dae6558f885b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:13:40 +0100 Subject: [PATCH 024/226] Improve docs --- api/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/api/src/lib.rs b/api/src/lib.rs index 52374d08..bc92dd9b 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -3,6 +3,7 @@ pub use self::{config::BootloaderConfig, info::BootInfo}; +/// Contains the boot information struct sent by the bootloader to the kernel on startup. pub mod config; pub mod info; From 5cd1abf2cbbd12b759c1b91657e4cb07ac7bda73 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:13:54 +0100 Subject: [PATCH 025/226] Fix imports in macro --- api/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index bc92dd9b..1a744d37 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -21,7 +21,7 @@ mod concat { #[macro_export] macro_rules! entry_point { ($path:path) => { - entry_point!($path, config = &crate::BootloaderConfig::new_default()); + entry_point!($path, config = &$crate::BootloaderConfig::new_default()); }; ($path:path, config = $config:expr) => { #[link_section = ".bootloader-config"] @@ -32,9 +32,9 @@ macro_rules! entry_point { }; #[export_name = "_start"] - pub extern "C" fn __impl_start(boot_info: &'static mut $crate::info::BootInfo) -> ! { + pub extern "C" fn __impl_start(boot_info: &'static mut $crate::BootInfo) -> ! { // validate the signature of the program entry point - let f: fn(&'static mut $crate::info::BootInfo) -> ! = $path; + let f: fn(&'static mut $crate::BootInfo) -> ! = $path; // ensure that the config is used so that the linker keeps it $crate::__force_use(&__BOOTLOADER_CONFIG); From e2612b4b4922360e66a8ea9892d1f18e576dcf64 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:15:00 +0100 Subject: [PATCH 026/226] Remove stabilized asm and global_asm features --- src/bin/bios.rs | 2 -- src/bin/uefi.rs | 1 - src/lib.rs | 1 - 3 files changed, 4 deletions(-) diff --git a/src/bin/bios.rs b/src/bin/bios.rs index 8f2eaedf..0c5e9404 100644 --- a/src/bin/bios.rs +++ b/src/bin/bios.rs @@ -1,6 +1,4 @@ #![feature(lang_items)] -#![feature(global_asm)] -#![feature(asm)] #![no_std] #![no_main] diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs index dc5223f6..d81ebfd2 100644 --- a/src/bin/uefi.rs +++ b/src/bin/uefi.rs @@ -1,7 +1,6 @@ #![no_std] #![no_main] #![feature(abi_efiapi)] -#![feature(asm)] #![feature(maybe_uninit_extra)] #![deny(unsafe_op_in_unsafe_fn)] diff --git a/src/lib.rs b/src/lib.rs index ba237106..d30fb4cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -66,7 +66,6 @@ for all possible configuration options. */ #![cfg_attr(not(feature = "builder"), no_std)] -#![feature(asm)] #![feature(maybe_uninit_extra)] #![feature(maybe_uninit_slice)] #![deny(unsafe_op_in_unsafe_fn)] From adb6fee06315647c54682b07c5cc0abfc25f0e54 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:15:50 +0100 Subject: [PATCH 027/226] Use `bootloader_api::BootInfo` struct in `bootloader` --- src/boot_info.rs | 308 ----------------------------------------------- src/lib.rs | 3 - 2 files changed, 311 deletions(-) delete mode 100644 src/boot_info.rs diff --git a/src/boot_info.rs b/src/boot_info.rs deleted file mode 100644 index adb97bc6..00000000 --- a/src/boot_info.rs +++ /dev/null @@ -1,308 +0,0 @@ -use core::{ops, slice}; - -/// This structure represents the information that the bootloader passes to the kernel. -/// -/// The information is passed as an argument to the entry point. The entry point function must -/// have the following signature: -/// -/// ``` -/// # use bootloader::BootInfo; -/// # type _SIGNATURE = -/// extern "C" fn(boot_info: &'static mut BootInfo) -> !; -/// ``` -/// -/// Note that no type checking occurs for the entry point function, so be careful to -/// use the correct argument types. To ensure that the entry point function has the correct -/// signature, use the [`entry_point`] macro. -#[derive(Debug)] -#[repr(C)] -#[non_exhaustive] -pub struct BootInfo { - /// Bootloader version (major). - pub version_major: u16, - /// Bootloader version (minor). - pub version_minor: u16, - /// Bootloader version (patch). - pub version_patch: u16, - /// Whether the bootloader version is a pre-release. - /// - /// We can't store the full prerelease string of the version number since it could be - /// arbitrarily long. - pub pre_release: bool, - /// A map of the physical memory regions of the underlying machine. - /// - /// The bootloader queries this information from the BIOS/UEFI firmware and translates this - /// information to Rust types. It also marks any memory regions that the bootloader uses in - /// the memory map before passing it to the kernel. Regions marked as usable can be freely - /// used by the kernel. - pub memory_regions: MemoryRegions, - /// Information about the framebuffer for screen output if available. - pub framebuffer: Optional, - /// The virtual address at which the mapping of the physical memory starts. - /// - /// Physical addresses can be converted to virtual addresses by adding this offset to them. - /// - /// The mapping of the physical memory allows to access arbitrary physical frames. Accessing - /// frames that are also mapped at other virtual addresses can easily break memory safety and - /// cause undefined behavior. Only frames reported as `USABLE` by the memory map in the `BootInfo` - /// can be safely accessed. - /// - /// Only available if the `map-physical-memory` config option is enabled. - pub physical_memory_offset: Optional, - /// The virtual address of the recursively mapped level 4 page table. - /// - /// Only available if the `map-page-table-recursively` config option is enabled. - pub recursive_index: Optional, - /// The address of the `RSDP` data structure, which can be use to find the ACPI tables. - /// - /// This field is `None` if no `RSDP` was found (for BIOS) or reported (for UEFI). - pub rsdp_addr: Optional, - /// The thread local storage (TLS) template of the kernel executable, if present. - pub tls_template: Optional, -} - -/// FFI-safe slice of [`MemoryRegion`] structs, semantically equivalent to -/// `&'static mut [MemoryRegion]`. -/// -/// This type implements the [`Deref`][core::ops::Deref] and [`DerefMut`][core::ops::DerefMut] -/// traits, so it can be used like a `&mut [MemoryRegion]` slice. It also implements [`From`] -/// and [`Into`] for easy conversions from and to `&'static mut [MemoryRegion]`. -#[derive(Debug)] -#[repr(C)] -pub struct MemoryRegions { - pub(crate) ptr: *mut MemoryRegion, - pub(crate) len: usize, -} - -impl ops::Deref for MemoryRegions { - type Target = [MemoryRegion]; - - fn deref(&self) -> &Self::Target { - unsafe { slice::from_raw_parts(self.ptr, self.len) } - } -} - -impl ops::DerefMut for MemoryRegions { - fn deref_mut(&mut self) -> &mut Self::Target { - unsafe { slice::from_raw_parts_mut(self.ptr, self.len) } - } -} - -impl From<&'static mut [MemoryRegion]> for MemoryRegions { - fn from(regions: &'static mut [MemoryRegion]) -> Self { - MemoryRegions { - ptr: regions.as_mut_ptr(), - len: regions.len(), - } - } -} - -impl From for &'static mut [MemoryRegion] { - fn from(regions: MemoryRegions) -> &'static mut [MemoryRegion] { - unsafe { slice::from_raw_parts_mut(regions.ptr, regions.len) } - } -} - -/// Represent a physical memory region. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[repr(C)] -pub struct MemoryRegion { - /// The physical start address of the region. - pub start: u64, - /// The physical end address (exclusive) of the region. - pub end: u64, - /// The memory type of the memory region. - /// - /// Only [`Usable`][MemoryRegionKind::Usable] regions can be freely used. - pub kind: MemoryRegionKind, -} - -impl MemoryRegion { - /// Creates a new empty memory region (with length 0). - pub const fn empty() -> Self { - MemoryRegion { - start: 0, - end: 0, - kind: MemoryRegionKind::Bootloader, - } - } -} - -/// Represents the different types of memory. -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -#[non_exhaustive] -#[repr(C)] -pub enum MemoryRegionKind { - /// Unused conventional memory, can be used by the kernel. - Usable, - /// Memory mappings created by the bootloader, including the kernel and boot info mappings. - /// - /// This memory should _not_ be used by the kernel. - Bootloader, - /// An unknown memory region reported by the UEFI firmware. - /// - /// This should only be used if the UEFI memory type is known as usable. - UnknownUefi(u32), - /// An unknown memory region reported by the BIOS firmware. - /// - /// This should only be used if the BIOS memory type is known as usable. - UnknownBios(u32), -} - -/// A pixel-based framebuffer that controls the screen output. -#[derive(Debug)] -#[repr(C)] -pub struct FrameBuffer { - pub(crate) buffer_start: u64, - pub(crate) buffer_byte_len: usize, - pub(crate) info: FrameBufferInfo, -} - -impl FrameBuffer { - /// Returns the raw bytes of the framebuffer as slice. - pub fn buffer(&self) -> &[u8] { - unsafe { self.create_buffer() } - } - - /// Returns the raw bytes of the framebuffer as mutable slice. - pub fn buffer_mut(&mut self) -> &mut [u8] { - unsafe { self.create_buffer() } - } - - unsafe fn create_buffer<'a>(&self) -> &'a mut [u8] { - unsafe { slice::from_raw_parts_mut(self.buffer_start as *mut u8, self.buffer_byte_len) } - } - - /// Returns layout and pixel format information of the framebuffer. - pub fn info(&self) -> FrameBufferInfo { - self.info - } -} - -/// Describes the layout and pixel format of a framebuffer. -#[derive(Debug, Clone, Copy)] -#[repr(C)] -pub struct FrameBufferInfo { - /// The total size in bytes. - pub byte_len: usize, - /// The width in pixels. - pub horizontal_resolution: usize, - /// The height in pixels. - pub vertical_resolution: usize, - /// The color format of each pixel. - pub pixel_format: PixelFormat, - /// The number of bytes per pixel. - pub bytes_per_pixel: usize, - /// Number of pixels between the start of a line and the start of the next. - /// - /// Some framebuffers use additional padding at the end of a line, so this - /// value might be larger than `horizontal_resolution`. It is - /// therefore recommended to use this field for calculating the start address of a line. - pub stride: usize, -} - -/// Color format of pixels in the framebuffer. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[non_exhaustive] -#[repr(C)] -pub enum PixelFormat { - /// One byte red, then one byte green, then one byte blue. - /// - /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] - /// for this. - RGB, - /// One byte blue, then one byte green, then one byte red. - /// - /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] - /// for this. - BGR, - /// A single byte, representing the grayscale value. - /// - /// Length might be larger than 1, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] - /// for this. - U8, -} - -/// Information about the thread local storage (TLS) template. -/// -/// This template can be used to set up thread local storage for threads. For -/// each thread, a new memory location of size `mem_size` must be initialized. -/// Then the first `file_size` bytes of this template needs to be copied to the -/// location. The additional `mem_size - file_size` bytes must be initialized with -/// zero. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(C)] -pub struct TlsTemplate { - /// The virtual start address of the thread local storage template. - pub start_addr: u64, - /// The number of data bytes in the template. - /// - /// Corresponds to the length of the `.tdata` section. - pub file_size: u64, - /// The total number of bytes that the TLS segment should have in memory. - /// - /// Corresponds to the combined length of the `.tdata` and `.tbss` sections. - pub mem_size: u64, -} - -/// FFI-safe variant of [`Option`]. -/// -/// Implements the [`From`] and [`Into`] traits for easy conversion to and from [`Option`]. -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -#[repr(C)] -pub enum Optional { - /// Some value `T` - Some(T), - /// No value - None, -} - -impl Optional { - /// Converts the `Optional` to an [`Option`]. - pub fn into_option(self) -> Option { - self.into() - } - - /// Converts from `&Optional` to `Option<&T>`. - /// - /// For convenience, this method directly performs the conversion to the standard - /// [`Option`] type. - pub const fn as_ref(&self) -> Option<&T> { - match self { - Self::Some(x) => Some(x), - Self::None => None, - } - } - - /// Converts from `&mut Optional` to `Option<&mut T>`. - /// - /// For convenience, this method directly performs the conversion to the standard - /// [`Option`] type. - pub fn as_mut(&mut self) -> Option<&mut T> { - match self { - Self::Some(x) => Some(x), - Self::None => None, - } - } -} - -impl From> for Optional { - fn from(v: Option) -> Self { - match v { - Some(v) => Optional::Some(v), - None => Optional::None, - } - } -} - -impl From> for Option { - fn from(optional: Optional) -> Option { - match optional { - Optional::Some(v) => Some(v), - Optional::None => None, - } - } -} - -/// Check that bootinfo is FFI-safe -extern "C" fn _assert_ffi(_boot_info: BootInfo) {} diff --git a/src/lib.rs b/src/lib.rs index d30fb4cc..6e1b502d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,14 +71,11 @@ for all possible configuration options. #![deny(unsafe_op_in_unsafe_fn)] #![warn(missing_docs)] -pub use crate::boot_info::BootInfo; pub use crate::config::Config; /// Configuration options for the bootloader. mod config; -/// Contains the boot information struct sent by the bootloader to the kernel on startup. -pub mod boot_info; /// Contains the actual bootloader implementation ("bootloader as a binary"). /// From 8cb6d1a8ddacd8d8ec035fa354f4ce2756431292 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:16:41 +0100 Subject: [PATCH 028/226] Start migrating tests and examples to bootloader_api (doesn't work yet) --- Cargo.lock | 6 +++--- examples/basic/Cargo.toml | 2 +- examples/basic/src/main.rs | 2 +- examples/test_framework/Cargo.toml | 2 +- examples/test_framework/src/main.rs | 2 +- tests/runner/src/main.rs | 2 +- tests/test_kernels/default_settings/Cargo.toml | 2 +- tests/test_kernels/default_settings/src/bin/basic_boot.rs | 2 +- .../default_settings/src/bin/check_boot_info.rs | 2 +- tests/test_kernels/default_settings/src/bin/should_panic.rs | 2 +- tests/test_kernels/higher_half/Cargo.toml | 2 +- tests/test_kernels/higher_half/src/bin/basic_boot.rs | 2 +- tests/test_kernels/higher_half/src/bin/check_boot_info.rs | 2 +- tests/test_kernels/higher_half/src/bin/should_panic.rs | 2 +- .../test_kernels/higher_half/src/bin/verify_higher_half.rs | 2 +- tests/test_kernels/map_phys_mem/Cargo.toml | 2 +- tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs | 2 +- tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs | 2 +- 18 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9173d509..02a89f5b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -410,7 +410,7 @@ dependencies = [ name = "test_kernel_default_settings" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", "uart_16550", "x86_64", ] @@ -419,7 +419,7 @@ dependencies = [ name = "test_kernel_higher_half" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", "uart_16550", "x86_64", ] @@ -428,7 +428,7 @@ dependencies = [ name = "test_kernel_map_phys_mem" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", "uart_16550", "x86_64", ] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml index 2c3cc202..a29f8951 100644 --- a/examples/basic/Cargo.toml +++ b/examples/basic/Cargo.toml @@ -11,4 +11,4 @@ members = [ ] [dependencies] -bootloader = { path = "../.." } # replace this with a version number +bootloader_api = { path = "../../api" } # replace this with a version number diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs index afd8fe87..9ac8d38a 100644 --- a/examples/basic/src/main.rs +++ b/examples/basic/src/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; entry_point!(kernel_main); diff --git a/examples/test_framework/Cargo.toml b/examples/test_framework/Cargo.toml index 808adb68..01bbd691 100644 --- a/examples/test_framework/Cargo.toml +++ b/examples/test_framework/Cargo.toml @@ -11,7 +11,7 @@ members = [ ] [dependencies] -bootloader = { path = "../.." } # replace this with a version number +bootloader_api = { path = "../../api" } # replace this with a version number x86_64 = "0.14.7" uart_16550 = "0.2.14" spin = { version = "0.9.0", features = ["lazy"] } diff --git a/examples/test_framework/src/main.rs b/examples/test_framework/src/main.rs index a367e983..deaa75c2 100644 --- a/examples/test_framework/src/main.rs +++ b/examples/test_framework/src/main.rs @@ -4,7 +4,7 @@ #![test_runner(test_runner)] #![reexport_test_harness_main = "test_main"] -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; mod serial; diff --git a/tests/runner/src/main.rs b/tests/runner/src/main.rs index a083ce15..cdad97b8 100644 --- a/tests/runner/src/main.rs +++ b/tests/runner/src/main.rs @@ -1,7 +1,7 @@ use std::{ io::Write, path::{Path, PathBuf}, - process::{Command, Stdio}, + process::Command, }; const QEMU_ARGS: &[&str] = &[ diff --git a/tests/test_kernels/default_settings/Cargo.toml b/tests/test_kernels/default_settings/Cargo.toml index 0410f326..72229755 100644 --- a/tests/test_kernels/default_settings/Cargo.toml +++ b/tests/test_kernels/default_settings/Cargo.toml @@ -5,6 +5,6 @@ authors = ["Philipp Oppermann "] edition = "2018" [dependencies] -bootloader = { path = "../../.." } +bootloader_api = { path = "../../../api" } x86_64 = { version = "0.14.7", default-features = false, features = ["instructions", "inline_asm"] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/default_settings/src/bin/basic_boot.rs b/tests/test_kernels/default_settings/src/bin/basic_boot.rs index eef2bb3a..a31d9528 100644 --- a/tests/test_kernels/default_settings/src/bin/basic_boot.rs +++ b/tests/test_kernels/default_settings/src/bin/basic_boot.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_default_settings::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs index fab65b53..0b7770a8 100644 --- a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs +++ b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{boot_info::PixelFormat, entry_point, BootInfo}; +use bootloader_api::{info::PixelFormat, entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_default_settings::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/default_settings/src/bin/should_panic.rs b/tests/test_kernels/default_settings/src/bin/should_panic.rs index 48546907..b79c7ecd 100644 --- a/tests/test_kernels/default_settings/src/bin/should_panic.rs +++ b/tests/test_kernels/default_settings/src/bin/should_panic.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_default_settings::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/higher_half/Cargo.toml b/tests/test_kernels/higher_half/Cargo.toml index ba9ce580..7e12ba2f 100644 --- a/tests/test_kernels/higher_half/Cargo.toml +++ b/tests/test_kernels/higher_half/Cargo.toml @@ -5,6 +5,6 @@ authors = ["Philipp Oppermann "] edition = "2018" [dependencies] -bootloader = { path = "../../.." } +bootloader_api = { path = "../../../api" } x86_64 = { version = "0.14.7", default-features = false, features = ["instructions", "inline_asm"] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/higher_half/src/bin/basic_boot.rs b/tests/test_kernels/higher_half/src/bin/basic_boot.rs index b812a9ac..0d21957f 100644 --- a/tests/test_kernels/higher_half/src/bin/basic_boot.rs +++ b/tests/test_kernels/higher_half/src/bin/basic_boot.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs index 474f37c4..d0996f4b 100644 --- a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{boot_info::PixelFormat, entry_point, BootInfo}; +use bootloader_api::{boot_info::PixelFormat, entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/higher_half/src/bin/should_panic.rs b/tests/test_kernels/higher_half/src/bin/should_panic.rs index ea3bae75..0aaafcb7 100644 --- a/tests/test_kernels/higher_half/src/bin/should_panic.rs +++ b/tests/test_kernels/higher_half/src/bin/should_panic.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs index f081b99a..44ec289f 100644 --- a/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs +++ b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/map_phys_mem/Cargo.toml b/tests/test_kernels/map_phys_mem/Cargo.toml index e34621a2..ea4f81ec 100644 --- a/tests/test_kernels/map_phys_mem/Cargo.toml +++ b/tests/test_kernels/map_phys_mem/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Philipp Oppermann "] edition = "2018" [target.'cfg(target_arch = "x86_64")'.dependencies] -bootloader = { path = "../../.." } +bootloader_api = { path = "../../../api" } x86_64 = { version = "0.14.7", default-features = false, features = ["instructions", "inline_asm"] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs b/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs index eec06f36..dc41b19c 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode}; diff --git a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs index 361c4fb4..f10e270b 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{boot_info::PixelFormat, entry_point, BootInfo}; +use bootloader_api::{boot_info::PixelFormat, entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode}; From d0f974988bcc4859ed6d6833a66e83059b5caffe Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:20:01 +0100 Subject: [PATCH 029/226] Rename GitHub actions CI workflow file The VSCode yaml plugin mistakes build.yml as a hammerkit yaml file. --- .github/workflows/{build.yml => ci.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{build.yml => ci.yml} (100%) diff --git a/.github/workflows/build.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflows/build.yml rename to .github/workflows/ci.yml From cfa19ad3710267cf799914a4bf43febe56c1b3a7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:21:33 +0100 Subject: [PATCH 030/226] Add rust-src and llvm-tools-preview components to rust-toolchain file --- rust-toolchain.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 8e275b74..3d8f3dee 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,3 @@ [toolchain] channel = "nightly" -components = ["rustfmt", "clippy"] +components = ["rustfmt", "clippy", "rust-src", "llvm-tools-preview"] From aae7f7d948d1636c3b44907eb7ddb39ba234cbce Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:22:02 +0100 Subject: [PATCH 031/226] Remove toolchain install action from CI Rustup is already installed on GitHub Actions. --- .github/workflows/ci.yml | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ea807d32..359969bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,6 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - name: "Run `cargo check`" uses: actions-rs/cargo@v1 with: @@ -36,11 +32,6 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - components: rust-src, llvm-tools-preview # install QEMU - name: Install QEMU (Linux) @@ -80,11 +71,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - components: rustfmt - name: Run `cargo fmt --all -- --check` uses: actions-rs/cargo@v1 with: @@ -96,11 +82,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - with: - profile: minimal - override: true - components: clippy - name: Run `cargo clippy` uses: actions-rs/cargo@v1 with: From 577aa9c7b9feec8f0626b30125252869d7fe51ce Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:22:26 +0100 Subject: [PATCH 032/226] Run cargo fmt --- src/lib.rs | 1 - tests/test_kernels/default_settings/src/bin/check_boot_info.rs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 6e1b502d..68d83c01 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,6 @@ pub use crate::config::Config; /// Configuration options for the bootloader. mod config; - /// Contains the actual bootloader implementation ("bootloader as a binary"). /// /// Useful for reusing part of the bootloader implementation for other crates. diff --git a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs index 0b7770a8..3d46987c 100644 --- a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs +++ b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader_api::{info::PixelFormat, entry_point, BootInfo}; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; use core::panic::PanicInfo; use test_kernel_default_settings::{exit_qemu, QemuExitCode}; From 611ac54087f25e6c3193a5451b255ad1df227193 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 13:23:15 +0100 Subject: [PATCH 033/226] Enable format on save on VSCode --- .vscode/settings.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 309b3c40..c66bf6ba 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,5 +2,6 @@ "rust-analyzer.checkOnSave.allTargets": false, "rust-analyzer.checkOnSave.extraArgs": [ "-Zbuild-std=core,alloc" - ] -} \ No newline at end of file + ], + "editor.formatOnSave": true +} From c9adc1d118335a5372da956f42d66248ca03e473 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 14:17:31 +0100 Subject: [PATCH 034/226] Document entry point macro and fix macro import --- api/src/lib.rs | 81 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 1a744d37..55c17816 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -18,10 +18,89 @@ mod concat { /// This macro just creates a function named `_start`, which the linker will use as the entry /// point. The advantage of using this macro instead of providing an own `_start` function is /// that the macro ensures that the function and argument types are correct. +/// +/// ## Configuration +/// +/// This macro supports an optional second parameter to configure how the bootloader should +/// boot the kernel. The second parameter needs to be given as `config = ...` and be of type +/// [`&BootloaderConfig`](crate::BootloaderConfig). If not given, the configuration defaults to +/// [`BootloaderConfig::new_default`](crate::BootloaderConfig::new_default). +/// +/// ## Examples +/// +/// - With default configuration: +/// +/// ```no_run +/// #![no_std] +/// #![no_main] +/// # #![feature(lang_items)] +/// +/// bootloader_api::entry_point!(main); +/// +/// fn main(bootinfo: &'static mut bootloader_api::BootInfo) -> ! { +/// loop {} +/// } +/// +/// #[panic_handler] +/// fn panic(_info: &core::panic::PanicInfo) -> ! { +/// loop {} +/// } +/// +/// # #[lang = "eh_personality"] fn eh_personality() {} // not needed when disabling unwinding +/// ``` +/// +/// The name of the entry point function does not matter. For example, instead of `main`, we +/// could also name it `fn my_entry_point(...) -> !`. We would then need to specify +/// `entry_point!(my_entry_point)` of course. +/// +/// - With custom configuration: +/// +/// ```no_run +/// #![no_std] +/// #![no_main] +/// # #![feature(lang_items)] +/// +/// use bootloader_api::{entry_point, BootloaderConfig}; +/// +/// pub static BOOTLOADER_CONFIG: BootloaderConfig = { +/// let mut config = BootloaderConfig::new_default(); +/// config.frame_buffer.minimum_framebuffer_height = Some(720); +/// config +/// }; +/// +/// entry_point!(main, config = &BOOTLOADER_CONFIG); +/// +/// fn main(bootinfo: &'static mut bootloader_api::BootInfo) -> ! { +/// loop {} +/// } +/// +/// #[panic_handler] +/// fn panic(_info: &core::panic::PanicInfo) -> ! { +/// loop {} +/// } +/// +/// # #[lang = "eh_personality"] fn eh_personality() {} // not needed when disabling unwinding +/// ``` +/// +/// ## Implementation Notes +/// +/// - **Start function:** The `entry_point` macro generates a small wrapper function named +/// `_start` (without name mangling) that becomes the actual entry point function of the +/// executable. This function doesn't do anything itself, it just calls into the function +/// that is provided as macro argument. The purpose of this function is to use the correct +/// ABI and parameter types required by this crate. A user-provided `_start` function could +/// silently become incompatible on dependency updates since the Rust compiler cannot +/// check the signature of custom entry point functions. +/// - **Configuration:** Behind the scenes, the configuration struct is serialized using +/// [`BootloaderConfig::serialize`](crate::BootloaderConfig::serialize). The resulting byte +/// array is then stored as a static variable annotated with +/// `#[link_section = ".bootloader-config"]`, which instructs the Rust compiler to store it +/// in a special section of the resulting ELF executable. From there, the bootloader will +/// automatically read it when loading the kernel. #[macro_export] macro_rules! entry_point { ($path:path) => { - entry_point!($path, config = &$crate::BootloaderConfig::new_default()); + $crate::entry_point!($path, config = &$crate::BootloaderConfig::new_default()); }; ($path:path, config = $config:expr) => { #[link_section = ".bootloader-config"] From c8cc7bbcd923de89ede97f070590faba2af9fcca Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 14:17:43 +0100 Subject: [PATCH 035/226] Remove old entry point macro --- src/lib.rs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 68d83c01..7bb824ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,23 +102,3 @@ compile_error!( not(feature = "builder") ))] compile_error!("This crate only supports the x86_64 architecture."); - -/// Defines the entry point function. -/// -/// The function must have the signature `fn(&'static mut BootInfo) -> !`. -/// -/// This macro just creates a function named `_start`, which the linker will use as the entry -/// point. The advantage of using this macro instead of providing an own `_start` function is -/// that the macro ensures that the function and argument types are correct. -#[macro_export] -macro_rules! entry_point { - ($path:path) => { - #[export_name = "_start"] - pub extern "C" fn __impl_start(boot_info: &'static mut $crate::boot_info::BootInfo) -> ! { - // validate the signature of the program entry point - let f: fn(&'static mut $crate::boot_info::BootInfo) -> ! = $path; - - f(boot_info) - } - }; -} From 8c7e951b8fcd96ea9d9bf902e0a44dc8d3c58d99 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 23 Dec 2021 14:40:12 +0100 Subject: [PATCH 036/226] Fully document the api crate --- api/src/config.rs | 92 +++++++++++++++++++++++++++++++++++++++++------ api/src/lib.rs | 9 +++-- 2 files changed, 89 insertions(+), 12 deletions(-) diff --git a/api/src/config.rs b/api/src/config.rs index e852ae59..3f1242e6 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -1,14 +1,31 @@ use crate::concat::*; +/// Allows configuring the bootloader behavior. +/// +/// TODO: describe use together with `entry_point` macro +/// TODO: example #[derive(Debug, PartialEq, Eq)] #[non_exhaustive] pub struct BootloaderConfig { - pub(crate) version: Version, + /// The version of the bootloader API. + /// + /// Automatically generated from the crate version. Checked on deserialization to + /// ensure that the kernel and bootloader use the same API version, i.e. the same config + /// and boot info format. + pub(crate) version: ApiVersion, + /// Configuration for (optional) page table mappings created by the bootloader. pub mappings: Mappings, + /// The size of the stack that the bootloader should allocate for the kernel (in bytes). + /// + /// The bootloader starts the kernel with a valid stack pointer. This setting defines + /// the stack size that the bootloader should allocate and map. The stack is created + /// with a guard page, so a stack overflow will lead to a page fault. pub kernel_stack_size: u64, + /// Configuration for the frame buffer that can be used by the kernel to display pixels + /// on the screen. pub frame_buffer: FrameBuffer, } @@ -17,17 +34,27 @@ impl BootloaderConfig { 0x74, 0x3C, 0xA9, 0x61, 0x09, 0x36, 0x46, 0xA0, 0xBB, 0x55, 0x5C, 0x15, 0x89, 0x15, 0x25, 0x3D, ]; + #[doc(hidden)] pub const SERIALIZED_LEN: usize = 96; + /// Creates a new default configuration with the following values: + /// + /// - `kernel_stack_size`: 80kiB + /// - `mappings`: See [`Mappings::new_default()`] + /// - `frame_buffer`: See [`FrameBuffer::new_default()`] pub const fn new_default() -> Self { Self { kernel_stack_size: 80 * 1024, - version: Version::new_default(), + version: ApiVersion::new_default(), mappings: Mappings::new_default(), frame_buffer: FrameBuffer::new_default(), } } + /// Serializes the configuration to a byte array. + /// + /// This is used by the [`crate::entry_point`] macro to store the configuration in a + /// dedicated section in the resulting ELF file. pub const fn serialize(&self) -> [u8; Self::SERIALIZED_LEN] { let Self { version, @@ -35,7 +62,7 @@ impl BootloaderConfig { kernel_stack_size, frame_buffer, } = self; - let Version { + let ApiVersion { version_major, version_minor, version_patch, @@ -97,6 +124,12 @@ impl BootloaderConfig { buf } + /// Tries to deserialize a config byte array that was created using [`Self::serialize`]. + /// + /// This is used by the bootloader to deserialize the configuration given in the kernel's + /// ELF file. + /// + /// TODO: return error enum pub fn deserialize(serialized: &[u8]) -> Result { if serialized.len() != Self::SERIALIZED_LEN { return Err(()); @@ -120,7 +153,7 @@ impl BootloaderConfig { _ => return Err(()), }; - let version = Version { + let version = ApiVersion { version_major: u16::from_le_bytes(major), version_minor: u16::from_le_bytes(minor), version_patch: u16::from_le_bytes(patch), @@ -129,6 +162,8 @@ impl BootloaderConfig { (version, s) }; + // TODO check version against this crate version -> error if they're different + let (&kernel_stack_size, s) = split_array_ref(s); let (mappings, s) = { @@ -194,7 +229,7 @@ impl BootloaderConfig { #[cfg(test)] fn random() -> Self { Self { - version: Version::random(), + version: ApiVersion::random(), mappings: Mappings::random(), kernel_stack_size: rand::random(), frame_buffer: FrameBuffer::random(), @@ -209,7 +244,7 @@ impl Default for BootloaderConfig { } #[derive(Debug, PartialEq, Eq)] -pub struct Version { +pub(crate) struct ApiVersion { /// Bootloader version (major). version_major: u16, /// Bootloader version (minor). @@ -223,7 +258,7 @@ pub struct Version { pre_release: bool, } -impl Version { +impl ApiVersion { const fn new_default() -> Self { Self { // todo: generate these from build script @@ -235,7 +270,7 @@ impl Version { } #[cfg(test)] - fn random() -> Version { + fn random() -> ApiVersion { Self { version_major: rand::random(), version_minor: rand::random(), @@ -245,23 +280,40 @@ impl Version { } } -impl Default for Version { +impl Default for ApiVersion { fn default() -> Self { Self::new_default() } } +/// Allows to configure the virtual memory mappings created by the bootloader. #[derive(Debug, Default, PartialEq, Eq)] #[non_exhaustive] pub struct Mappings { + /// Configures how the kernel stack should be mapped. pub kernel_stack: Mapping, + /// Specifies where the [`crate::BootInfo`] struct should be placed in virtual memory. pub boot_info: Mapping, + /// Specifies the mapping of the frame buffer memory region. pub framebuffer: Mapping, + /// The bootloader supports to map the whole physical memory into the virtual address + /// space at some offset. This is useful for accessing and modifying the page tables set + /// up by the bootloader. + /// + /// Defaults to `None`, i.e. no mapping of the physical memory. pub physical_memory: Option, + /// As an alternative to mapping the whole physical memory (see [`Self::physical_memory`]), + /// the bootloader also has support for setting up a + /// [recursive level 4 page table](https://os.phil-opp.com/paging-implementation/#recursive-page-tables). + /// + /// Defaults to `None`, i.e. no recursive mapping. pub page_table_recursive: Option, } impl Mappings { + /// Creates a new mapping configuration with dynamic mapping for kernel, boot info and + /// frame buffer. Neither physical memory mapping nor recursive page table creation are + /// enabled. pub const fn new_default() -> Self { Self { kernel_stack: Mapping::new_default(), @@ -294,14 +346,22 @@ impl Mappings { } } +/// Configuration for the frame buffer used for graphical output. #[derive(Debug, Default, PartialEq, Eq)] #[non_exhaustive] pub struct FrameBuffer { + /// Instructs the bootloader to set up a framebuffer format that has at least the given height. + /// + /// If this is not possible, the bootloader will fall back to a smaller format. pub minimum_framebuffer_height: Option, + /// Instructs the bootloader to set up a framebuffer format that has at least the given width. + /// + /// If this is not possible, the bootloader will fall back to a smaller format. pub minimum_framebuffer_width: Option, } impl FrameBuffer { + /// Creates a default configuration without any requirements. pub const fn new_default() -> Self { Self { minimum_framebuffer_height: Option::None, @@ -326,13 +386,25 @@ impl FrameBuffer { } } +/// Specifies how the bootloader should map a memory region into the virtual address space. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum Mapping { + /// Look for an unused virtual memory region at runtime. Dynamic, + /// Try to map the region at the given virtual address. + /// + /// The given virtual address must be page-aligned. + /// + /// This setting can lead to runtime boot errors if the given address is not aligned, + /// already in use, or invalid for other reasons. FixedAddress(u64), } impl Mapping { + /// Creates a new [`Mapping::Dynamic`]. + /// + /// This function has identical results as [`Default::default`], the only difference is + /// that this is a `const` function. pub const fn new_default() -> Self { Self::Dynamic } @@ -354,7 +426,7 @@ impl Mapping { } } - pub fn deserialize(serialized: &[u8; 9]) -> Result { + fn deserialize(serialized: &[u8; 9]) -> Result { let (&variant, s) = split_array_ref(serialized); let (&addr, s) = split_array_ref(s); if !s.is_empty() { diff --git a/api/src/lib.rs b/api/src/lib.rs index 55c17816..078f7c5e 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,10 +1,15 @@ -#![deny(unsafe_op_in_unsafe_fn)] +//! Provides the interface to make kernels compatible with the +//! [**`bootloader`**](https://docs.rs/bootloader/latest/bootloader/) crate. + #![cfg_attr(not(test), no_std)] +#![deny(unsafe_op_in_unsafe_fn)] +#![warn(missing_docs)] pub use self::{config::BootloaderConfig, info::BootInfo}; -/// Contains the boot information struct sent by the bootloader to the kernel on startup. +/// Allows to configure the system environment set up by the bootloader. pub mod config; +/// Contains the boot information struct sent by the bootloader to the kernel on startup. pub mod info; mod concat { From c2674d64f25435cf6a8801bfaeb834146a51bd11 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:41:17 +0100 Subject: [PATCH 037/226] Set up custom build command for rust-analyzer --- .vscode/settings.json | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c66bf6ba..2a201078 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,7 +1,22 @@ { "rust-analyzer.checkOnSave.allTargets": false, "rust-analyzer.checkOnSave.extraArgs": [ - "-Zbuild-std=core,alloc" + "-Zbuild-std=core,alloc", ], - "editor.formatOnSave": true + "rust-analyzer.cargo.target": "x86_64-unknown-uefi", + "editor.formatOnSave": true, + "rust-analyzer.cargo.features": [ + "uefi_bin" + ], + "rust-analyzer.checkOnSave.enable": true, + "rust-analyzer.checkOnSave.overrideCommand": [ + "cargo", + "c", + "--features", + "uefi_bin", + "--target", + "x86_64-unknown-uefi", + "-Zbuild-std=core", + "--message-format=json" + ] } From 4e18af72cad5b353edd7286de7fbcec032e85a35 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:42:16 +0100 Subject: [PATCH 038/226] Remove bootloader build script The plan is to do the config parsing and kernel loading dynamically at runtime. --- Cargo.toml | 1 - build.rs | 483 ----------------------------------------------------- 2 files changed, 484 deletions(-) delete mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 0c33f665..236536ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,6 @@ license = "MIT/Apache-2.0" description = "An experimental x86_64 bootloader that works on both BIOS and UEFI systems." repository = "https://github.com/rust-osdev/bootloader" edition = "2018" -build = "build.rs" [workspace] members = [ diff --git a/build.rs b/build.rs deleted file mode 100644 index c86cb670..00000000 --- a/build.rs +++ /dev/null @@ -1,483 +0,0 @@ -#[cfg(not(feature = "binary"))] -fn main() {} - -#[cfg(feature = "binary")] -fn main() { - binary::main(); -} - -#[cfg(feature = "binary")] -mod binary { - use quote::quote; - use std::convert::TryInto; - - pub fn main() { - use llvm_tools_build as llvm_tools; - use std::{ - env, - fs::{self, File}, - io::Write, - path::{Path, PathBuf}, - process::{self, Command}, - }; - use toml::Value; - - let target = env::var("TARGET").expect("TARGET not set"); - let (firmware, expected_target) = if cfg!(feature = "uefi_bin") { - ("UEFI", "x86_64-unknown-uefi") - } else if cfg!(feature = "bios_bin") { - ("BIOS", "x86_64-bootloader") - } else { - panic!( - "Either the `uefi_bin` or `bios_bin` feature must be enabled when \ - the `binary` feature is enabled" - ); - }; - if Path::new(&target) - .file_stem() - .expect("target has no file stem") - != expected_target - { - panic!( - "The {} bootloader must be compiled for the `{}` target.", - firmware, expected_target, - ); - } - - let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR not set")); - let kernel = PathBuf::from(match env::var("KERNEL") { - Ok(kernel) => kernel, - Err(_) => { - eprintln!( - "The KERNEL environment variable must be set for building the bootloader.\n\n\ - Please use the `cargo builder` command for building." - ); - process::exit(1); - } - }); - let kernel_file_name = kernel - .file_name() - .expect("KERNEL has no valid file name") - .to_str() - .expect("kernel file name not valid utf8"); - - // check that the kernel file exists - assert!( - kernel.exists(), - "KERNEL does not exist: {}", - kernel.display() - ); - - // get access to llvm tools shipped in the llvm-tools-preview rustup component - let llvm_tools = match llvm_tools::LlvmTools::new() { - Ok(tools) => tools, - Err(llvm_tools::Error::NotFound) => { - eprintln!("Error: llvm-tools not found"); - eprintln!("Maybe the rustup component `llvm-tools-preview` is missing?"); - eprintln!(" Install it through: `rustup component add llvm-tools-preview`"); - process::exit(1); - } - Err(err) => { - eprintln!("Failed to retrieve llvm-tools component: {:?}", err); - process::exit(1); - } - }; - - // check that kernel executable has code in it - let llvm_size = llvm_tools - .tool(&llvm_tools::exe("llvm-size")) - .expect("llvm-size not found in llvm-tools"); - let mut cmd = Command::new(llvm_size); - cmd.arg(&kernel); - let output = cmd.output().expect("failed to run llvm-size"); - let output_str = String::from_utf8_lossy(&output.stdout); - let second_line_opt = output_str.lines().skip(1).next(); - let second_line = second_line_opt.expect(&format!( - "unexpected llvm-size line output:\n{}", - output_str - )); - let text_size_opt = second_line.split_ascii_whitespace().next(); - let text_size = - text_size_opt.expect(&format!("unexpected llvm-size output:\n{}", output_str)); - if text_size == "0" { - panic!("Kernel executable has an empty text section. Perhaps the entry point was set incorrectly?\n\n\ - Kernel executable at `{}`\n", kernel.display()); - } - - // strip debug symbols from kernel for faster loading - let stripped_kernel_file_name = format!("kernel_stripped-{}", kernel_file_name); - let stripped_kernel = out_dir.join(&stripped_kernel_file_name); - let objcopy = llvm_tools - .tool(&llvm_tools::exe("llvm-objcopy")) - .expect("llvm-objcopy not found in llvm-tools"); - let mut cmd = Command::new(&objcopy); - cmd.arg("--strip-debug"); - cmd.arg(&kernel); - cmd.arg(&stripped_kernel); - let exit_status = cmd - .status() - .expect("failed to run objcopy to strip debug symbols"); - if !exit_status.success() { - eprintln!("Error: Stripping debug symbols failed"); - process::exit(1); - } - - if cfg!(feature = "uefi_bin") { - // write file for including kernel in binary - let file_path = out_dir.join("kernel_info.rs"); - let mut file = File::create(file_path).expect("failed to create kernel_info.rs"); - let kernel_size = fs::metadata(&stripped_kernel) - .expect("Failed to read file metadata of stripped kernel") - .len(); - file.write_all( - format!( - "const KERNEL_SIZE: usize = {}; const KERNEL_BYTES: [u8; KERNEL_SIZE] = *include_bytes!(r\"{}\");", - kernel_size, - stripped_kernel.display(), - ) - .as_bytes(), - ) - .expect("write to kernel_info.rs failed"); - } - - if cfg!(feature = "bios_bin") { - // wrap the kernel executable as binary in a new ELF file - let stripped_kernel_file_name_replaced = stripped_kernel_file_name - .replace('-', "_") - .replace('.', "_"); - let kernel_bin = out_dir.join(format!("kernel_bin-{}.o", kernel_file_name)); - let kernel_archive = out_dir.join(format!("libkernel_bin-{}.a", kernel_file_name)); - let mut cmd = Command::new(&objcopy); - cmd.arg("-I").arg("binary"); - cmd.arg("-O").arg("elf64-x86-64"); - cmd.arg("--binary-architecture=i386:x86-64"); - cmd.arg("--rename-section").arg(".data=.kernel"); - cmd.arg("--redefine-sym").arg(format!( - "_binary_{}_start=_kernel_start_addr", - stripped_kernel_file_name_replaced - )); - cmd.arg("--redefine-sym").arg(format!( - "_binary_{}_end=_kernel_end_addr", - stripped_kernel_file_name_replaced - )); - cmd.arg("--redefine-sym").arg(format!( - "_binary_{}_size=_kernel_size", - stripped_kernel_file_name_replaced - )); - cmd.current_dir(&out_dir); - cmd.arg(&stripped_kernel_file_name); - cmd.arg(&kernel_bin); - let exit_status = cmd.status().expect("failed to run objcopy"); - if !exit_status.success() { - eprintln!("Error: Running objcopy failed"); - process::exit(1); - } - - // create an archive for linking - let ar = llvm_tools - .tool(&llvm_tools::exe("llvm-ar")) - .unwrap_or_else(|| { - eprintln!("Failed to retrieve llvm-ar component"); - eprint!("This component is available since nightly-2019-03-29,"); - eprintln!("so try updating your toolchain if you're using an older nightly"); - process::exit(1); - }); - let mut cmd = Command::new(ar); - cmd.arg("crs"); - cmd.arg(&kernel_archive); - cmd.arg(&kernel_bin); - let exit_status = cmd.status().expect("failed to run ar"); - if !exit_status.success() { - eprintln!("Error: Running ar failed"); - process::exit(1); - } - - // pass link arguments to rustc - println!("cargo:rustc-link-search=native={}", out_dir.display()); - println!( - "cargo:rustc-link-lib=static=kernel_bin-{}", - kernel_file_name - ); - } - - // Parse configuration from the kernel's Cargo.toml - let mut config = None; - let config_stream = match env::var("KERNEL_MANIFEST") { - Err(env::VarError::NotPresent) => { - panic!("The KERNEL_MANIFEST environment variable must be set for building the bootloader.\n\n\ - Please use `cargo builder` for building."); - } - Err(env::VarError::NotUnicode(_)) => { - panic!("The KERNEL_MANIFEST environment variable contains invalid unicode") - } - Ok(path) - if Path::new(&path).file_name().and_then(|s| s.to_str()) != Some("Cargo.toml") => - { - let err = format!( - "The given `--kernel-manifest` path `{}` does not \ - point to a `Cargo.toml`", - path, - ); - quote! { compile_error!(#err) } - } - Ok(path) if !Path::new(&path).exists() => { - let err = format!( - "The given `--kernel-manifest` path `{}` does not exist.", - path - ); - quote! { - compile_error!(#err) - } - } - Ok(path) => { - println!("cargo:rerun-if-changed={}", path); - - let contents = fs::read_to_string(&path).expect(&format!( - "failed to read kernel manifest file (path: {})", - path - )); - - let manifest = contents - .parse::() - .expect("failed to parse kernel's Cargo.toml"); - - if manifest - .get("dependencies") - .and_then(|d| d.get("bootloader")) - .or_else(|| { - manifest - .get("target") - .and_then(|table| table.get(r#"cfg(target_arch = "x86_64")"#)) - .and_then(|table| table.get("dependencies")) - .and_then(|table| table.get("bootloader")) - }) - .is_some() - { - // it seems to be the correct Cargo.toml - let config_table = manifest - .get("package") - .and_then(|table| table.get("metadata")) - .and_then(|table| table.get("bootloader")) - .cloned() - .unwrap_or_else(|| toml::Value::Table(toml::map::Map::new())); - - let result = config_table.try_into::(); - match result { - Ok(p_config) => { - let stream = quote! { #p_config }; - config = Some(p_config); - stream - } - Err(err) => { - let err = format!( - "failed to parse bootloader config in {}:\n\n{}", - path, - err.to_string() - ); - quote! { - compile_error!(#err) - } - } - } - } else { - let err = format!( - "no bootloader dependency in {}\n\n The \ - `--kernel-manifest` path should point to the `Cargo.toml` \ - of the kernel.", - path - ); - quote! { - compile_error!(#err) - } - } - } - }; - let config = config; - - // Write config to file - let file_path = out_dir.join("bootloader_config.rs"); - let mut file = File::create(file_path).expect("failed to create config file"); - file.write_all( - quote::quote! { - /// Module containing the user-supplied configuration. - /// Public so that `bin/uefi.rs` can read framebuffer configuration. - pub mod parsed_config { - use crate::config::Config; - /// The parsed configuration given by the user. - pub const CONFIG: Config = #config_stream; - } - } - .to_string() - .as_bytes(), - ) - .expect("writing config failed"); - - // Write VESA framebuffer configuration - let file_path = out_dir.join("vesa_config.s"); - let mut file = File::create(file_path).expect("failed to create vesa config file"); - file.write_fmt(format_args!( - "vesa_minx: .2byte {}\n\ - vesa_miny: .2byte {}", - config - .as_ref() - .map(|c| c.minimum_framebuffer_width) - .flatten() - .unwrap_or(640), - config - .as_ref() - .map(|c| c.minimum_framebuffer_height) - .flatten() - .unwrap_or(480) - )) - .expect("writing config failed"); - - println!("cargo:rerun-if-env-changed=KERNEL"); - println!("cargo:rerun-if-env-changed=KERNEL_MANIFEST"); - println!("cargo:rerun-if-changed={}", kernel.display()); - println!("cargo:rerun-if-changed=build.rs"); - } - - fn val_true() -> bool { - true - } - - /// Must be always identical with the struct in `src/config.rs` - /// - /// This copy is needed because we can't derive Deserialize in the `src/config.rs` - /// module itself, since cargo currently unifies dependencies (the `toml` crate enables - /// serde's standard feature). Also, it allows to separate the parsing special cases - /// such as `AlignedAddress` more cleanly. - #[derive(Debug, serde::Deserialize)] - #[serde(rename_all = "kebab-case", deny_unknown_fields)] - struct ParsedConfig { - #[serde(default)] - pub map_physical_memory: bool, - #[serde(default)] - pub map_page_table_recursively: bool, - #[serde(default = "val_true")] - pub map_framebuffer: bool, - pub kernel_stack_size: Option, - pub physical_memory_offset: Option, - pub recursive_index: Option, - pub kernel_stack_address: Option, - pub boot_info_address: Option, - pub framebuffer_address: Option, - pub minimum_framebuffer_height: Option, - pub minimum_framebuffer_width: Option, - } - - /// Convert to tokens suitable for initializing the `Config` struct. - impl quote::ToTokens for ParsedConfig { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - fn optional(value: Option) -> proc_macro2::TokenStream { - value.map(|v| quote!(Some(#v))).unwrap_or(quote!(None)) - } - - let map_physical_memory = self.map_physical_memory; - let map_page_table_recursively = self.map_page_table_recursively; - let map_framebuffer = self.map_framebuffer; - let kernel_stack_size = optional(self.kernel_stack_size); - let physical_memory_offset = optional(self.physical_memory_offset); - let recursive_index = optional(self.recursive_index); - let kernel_stack_address = optional(self.kernel_stack_address); - let boot_info_address = optional(self.boot_info_address); - let framebuffer_address = optional(self.framebuffer_address); - let minimum_framebuffer_height = optional(self.minimum_framebuffer_height); - let minimum_framebuffer_width = optional(self.minimum_framebuffer_width); - - tokens.extend(quote! { Config { - map_physical_memory: #map_physical_memory, - map_page_table_recursively: #map_page_table_recursively, - map_framebuffer: #map_framebuffer, - kernel_stack_size: #kernel_stack_size, - physical_memory_offset: #physical_memory_offset, - recursive_index: #recursive_index, - kernel_stack_address: #kernel_stack_address, - boot_info_address: #boot_info_address, - framebuffer_address: #framebuffer_address, - minimum_framebuffer_height: #minimum_framebuffer_height, - minimum_framebuffer_width: #minimum_framebuffer_width - }}); - } - } - - #[derive(Debug, Clone, Copy)] - struct AlignedAddress(u64); - - impl quote::ToTokens for AlignedAddress { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - self.0.to_tokens(tokens); - } - } - - impl<'de> serde::Deserialize<'de> for AlignedAddress { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_str(AlignedAddressVisitor) - } - } - - /// Helper struct for implementing the `optional_version_deserialize` function. - struct AlignedAddressVisitor; - - impl serde::de::Visitor<'_> for AlignedAddressVisitor { - type Value = AlignedAddress; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - write!( - formatter, - "a page-aligned memory address, either as integer or as decimal or hexadecimal \ - string (e.g. \"0xffff0000\"); large addresses must be given as string because \ - TOML does not support unsigned 64-bit integers" - ) - } - - fn visit_u64(self, num: u64) -> Result - where - E: serde::de::Error, - { - if num % 0x1000 == 0 { - Ok(AlignedAddress(num)) - } else { - Err(serde::de::Error::custom(format!( - "address {:#x} is not page aligned", - num - ))) - } - } - - fn visit_i64(self, num: i64) -> Result - where - E: serde::de::Error, - { - let unsigned: u64 = num - .try_into() - .map_err(|_| serde::de::Error::custom(format!("address {} is negative", num)))?; - self.visit_u64(unsigned) - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - // ignore any `_` (used for digit grouping) - let value = &value.replace('_', ""); - - let num = if value.starts_with("0x") { - u64::from_str_radix(&value[2..], 16) - } else { - u64::from_str_radix(&value, 10) - } - .map_err(|_err| { - serde::de::Error::custom(format!( - "string \"{}\" is not a valid memory address", - value - )) - })?; - - self.visit_u64(num) - } - } -} From 6c43cffbd70d06f18b7eadccfca2c91c6169fbf6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:42:57 +0100 Subject: [PATCH 039/226] Format Cargo.toml --- Cargo.toml | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 236536ef..bec1941d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,10 +15,7 @@ members = [ "tests/test_kernels/map_phys_mem", "tests/test_kernels/higher_half", ] -exclude = [ - "examples/basic", - "examples/test_framework", -] +exclude = ["examples/basic", "examples/test_framework"] [[bin]] name = "builder" @@ -34,7 +31,10 @@ required-features = ["uefi_bin"] [dependencies] xmas-elf = { version = "0.6.2", optional = true } -x86_64 = { version = "0.14.7", optional = true, default-features = false, features = ["instructions", "inline_asm"] } +x86_64 = { version = "0.14.7", optional = true, default-features = false, features = [ + "instructions", + "inline_asm", +] } usize_conversions = { version = "0.2.0", optional = true } bit_field = { version = "0.10.0", optional = true } log = { version = "0.4.8", optional = true } @@ -60,18 +60,37 @@ optional = true [build-dependencies] llvm-tools-build = { version = "0.1", optional = true, package = "llvm-tools" } toml = { version = "0.5.1", optional = true } -serde = { version = "1.0", features = ["derive"], optional = true} -quote = { version = "1.0", optional = true} -proc-macro2 = { version = "1.0", optional = true} +serde = { version = "1.0", features = ["derive"], optional = true } +quote = { version = "1.0", optional = true } +proc-macro2 = { version = "1.0", optional = true } [features] default = [] -builder = ["argh", "thiserror", "displaydoc", "anyhow", "llvm-tools", "json", "fatfs", "gpt"] +builder = [ + "argh", + "thiserror", + "displaydoc", + "anyhow", + "llvm-tools", + "json", + "fatfs", + "gpt", +] bios_bin = ["binary", "rsdp"] uefi_bin = ["binary", "uefi"] binary = [ - "llvm-tools-build", "x86_64", "toml", "xmas-elf", "usize_conversions", "log", "conquer-once", - "spinning_top", "serde", "font8x8", "quote", "proc-macro2", + "llvm-tools-build", + "x86_64", + "toml", + "xmas-elf", + "usize_conversions", + "log", + "conquer-once", + "spinning_top", + "serde", + "font8x8", + "quote", + "proc-macro2", ] [profile.dev] @@ -89,6 +108,6 @@ default-target = "x86_64-unknown-linux-gnu" [package.metadata.release] no-dev-version = true pre-release-replacements = [ - { file="Changelog.md", search="# Unreleased", replace="# Unreleased\n\n# {{version}} – {{date}}", exactly=1 }, + { file = "Changelog.md", search = "# Unreleased", replace = "# Unreleased\n\n# {{version}} – {{date}}", exactly = 1 }, ] pre-release-commit-message = "Release version {{version}}" From 8f8b5acb684c170c0c1ef36f82fc272bcda16f1f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:45:21 +0100 Subject: [PATCH 040/226] Remove compile-time config and kernel byte array completely --- src/bin/uefi.rs | 5 --- src/binary/mod.rs | 14 -------- src/config.rs | 90 ----------------------------------------------- src/lib.rs | 5 --- 4 files changed, 114 deletions(-) delete mode 100644 src/config.rs diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs index d81ebfd2..97fbe7c0 100644 --- a/src/bin/uefi.rs +++ b/src/bin/uefi.rs @@ -4,11 +4,6 @@ #![feature(maybe_uninit_extra)] #![deny(unsafe_op_in_unsafe_fn)] -// Defines the constants `KERNEL_BYTES` (array of `u8`) and `KERNEL_SIZE` (`usize`). -include!(concat!(env!("OUT_DIR"), "/kernel_info.rs")); - -static KERNEL: PageAligned<[u8; KERNEL_SIZE]> = PageAligned(KERNEL_BYTES); - #[repr(align(4096))] struct PageAligned(T); diff --git a/src/binary/mod.rs b/src/binary/mod.rs index 9a415ecf..f5d1bbc6 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -8,7 +8,6 @@ use core::{ slice, }; use level_4_entries::UsedLevel4Entries; -use parsed_config::CONFIG; use usize_conversions::FromUsize; use x86_64::{ structures::paging::{ @@ -35,19 +34,6 @@ pub mod load_kernel; /// Provides a logger type that logs output as text to pixel-based framebuffers. pub mod logger; -// Contains the parsed configuration table from the kernel's Cargo.toml. -// -// The layout of the file is the following: -// -// ``` -// mod parsed_config { -// pub const CONFIG: Config = Config { … }; -// } -// ``` -// -// The module file is created by the build script. -include!(concat!(env!("OUT_DIR"), "/bootloader_config.rs")); - const PAGE_SIZE: u64 = 4096; /// Initialize a text-based logger using the given pixel-based framebuffer as output. diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 4f6ee1a4..00000000 --- a/src/config.rs +++ /dev/null @@ -1,90 +0,0 @@ -/// Allows configuring the bootloader behavior. -/// -/// To control these, use a `[package.metadata.bootloader]` table in the `Cargo.toml` of -/// your kernel. The naming convention for all config fields is `kebab-case`, otherwise the -/// config keys correspond to the field names of this struct (i.e. just replace `_` with `-`). -/// Unknown config keys lead to an error. -/// -/// ## Example -/// -/// To map the complete physical memory starting at virtual address `0x0000_4000_0000_0000`, add -/// the following to your kernel's `Cargo.toml`: -/// -/// ```toml -/// [package.metadata.bootloader] -/// map-physical-memory = true -/// physical-memory-offset = 0x0000_4000_0000_0000 -/// ``` -/// -/// ## Memory Addresses -/// -/// Memory addresses must be positive and page aligned. Since TOML does not support unsigned 64-bit -/// integers, we also support string input to specify addresses larger than `i64::MAX`. For example: -/// -/// ```toml -/// physical-memory-offset = "0xf000_0000_0000_0000" -/// ``` -/// -/// The above example would fail if the address was specified as integer instead (i.e. without -/// the quotes). -/// -/// All memory addresses are optional, even if their corresponding switch is enabled. If no -/// address is specified, the bootloader will choose an unused entry of the level 4 page table -/// at runtime. -#[derive(Debug)] -pub struct Config { - /// Whether to create a virtual mapping of the complete physical memory. - /// - /// Defaults to `false`. - pub map_physical_memory: bool, - /// Map the physical memory at a specified virtual address. - /// - /// If not given, the bootloader searches for a free virtual address dynamically. - /// - /// Only considered if `map_physical_memory` is `true`. - pub physical_memory_offset: Option, - /// Whether to create a recursive entry in the level 4 page table. - /// - /// Defaults to `false`. - pub map_page_table_recursively: bool, - /// Create the recursive mapping in at the given entry of the level 4 page table. - /// - /// If not given, the bootloader searches for a free level 4 entry dynamically. - /// - /// Only considered if `map_page_table_recursively` is `true`. - pub recursive_index: Option, - /// Use the given stack size for the kernel. - /// - /// Defaults to at least 80KiB if not given. - pub kernel_stack_size: Option, - /// Create the kernel stack at the given virtual address. - /// - /// Looks for a free virtual memory region dynamically if not given. - pub kernel_stack_address: Option, - /// Create the boot information at the given virtual address. - /// - /// Looks for a free virtual memory region dynamically if not given. - pub boot_info_address: Option, - /// Whether to map the framebuffer to virtual memory. - /// - /// Defaults to `true`. - pub map_framebuffer: bool, - /// Map the framebuffer memory at the specified virtual address. - /// - /// If not given, the bootloader searches for a free virtual memory region dynamically. - /// - /// Only considered if `map_framebuffer` is `true`. - pub framebuffer_address: Option, - /// Desired minimum height of the framebuffer mode. - /// - /// Defaults to using the default mode if neither `minimum_framebuffer_height` or - /// `minimum_framebuffer_width` is supplied, and using the last available mode that - /// fits them if 1 or more is set. - pub minimum_framebuffer_height: Option, - /// Desired minimum width of the framebuffer mode. - /// - /// Defaults to using the default mode if neither `minimum_framebuffer_height` or - /// `minimum_framebuffer_width` is supplied, and using the last available mode that - /// fits them if 1 or more is set. - pub minimum_framebuffer_width: Option, -} diff --git a/src/lib.rs b/src/lib.rs index 7bb824ff..5292e3a6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,11 +71,6 @@ for all possible configuration options. #![deny(unsafe_op_in_unsafe_fn)] #![warn(missing_docs)] -pub use crate::config::Config; - -/// Configuration options for the bootloader. -mod config; - /// Contains the actual bootloader implementation ("bootloader as a binary"). /// /// Useful for reusing part of the bootloader implementation for other crates. From db7d4d3432547270f0b02e3ed6fc7b23dace2dbd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:53:55 +0100 Subject: [PATCH 041/226] Add and use dependency on api crate --- Cargo.lock | 1 + Cargo.toml | 1 + src/bin/uefi.rs | 5 +---- src/binary/legacy_memory_region.rs | 2 +- src/binary/load_kernel.rs | 6 ++---- src/binary/logger.rs | 2 +- src/binary/mod.rs | 7 ++++--- src/binary/uefi/memory_descriptor.rs | 3 ++- tests/test_kernels/higher_half/src/bin/check_boot_info.rs | 2 +- tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs | 2 +- 10 files changed, 15 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 02a89f5b..579c20b2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -62,6 +62,7 @@ dependencies = [ "anyhow", "argh", "bit_field", + "bootloader_api", "conquer-once", "displaydoc", "fatfs", diff --git a/Cargo.toml b/Cargo.toml index bec1941d..77234623 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ name = "uefi" required-features = ["uefi_bin"] [dependencies] +bootloader_api = { path = "api", version = "0.1.0" } xmas-elf = { version = "0.6.2", optional = true } x86_64 = { version = "0.14.7", optional = true, default-features = false, features = [ "instructions", diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs index 97fbe7c0..97eac83f 100644 --- a/src/bin/uefi.rs +++ b/src/bin/uefi.rs @@ -7,10 +7,7 @@ #[repr(align(4096))] struct PageAligned(T); -use bootloader::{ - binary::{legacy_memory_region::LegacyFrameAllocator, parsed_config::CONFIG, SystemInfo}, - boot_info::FrameBufferInfo, -}; +use bootloader::binary::{legacy_memory_region::LegacyFrameAllocator, SystemInfo}; use core::{arch::asm, mem, panic::PanicInfo, slice}; use uefi::{ prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, diff --git a/src/binary/legacy_memory_region.rs b/src/binary/legacy_memory_region.rs index 831f9a94..b3b0c242 100644 --- a/src/binary/legacy_memory_region.rs +++ b/src/binary/legacy_memory_region.rs @@ -1,4 +1,4 @@ -use crate::boot_info::{MemoryRegion, MemoryRegionKind}; +use bootloader_api::info::{MemoryRegion, MemoryRegionKind}; use core::mem::MaybeUninit; use x86_64::{ structures::paging::{FrameAllocator, PhysFrame, Size4KiB}, diff --git a/src/binary/load_kernel.rs b/src/binary/load_kernel.rs index 1e0870c5..b14a6f67 100644 --- a/src/binary/load_kernel.rs +++ b/src/binary/load_kernel.rs @@ -1,7 +1,5 @@ -use crate::{ - binary::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}, - boot_info::TlsTemplate, -}; +use crate::binary::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}; +use bootloader_api::info::TlsTemplate; use x86_64::{ align_up, structures::paging::{ diff --git a/src/binary/logger.rs b/src/binary/logger.rs index ffb72651..b8b9607f 100644 --- a/src/binary/logger.rs +++ b/src/binary/logger.rs @@ -1,4 +1,4 @@ -use crate::boot_info::{FrameBufferInfo, PixelFormat}; +use bootloader_api::info::{FrameBufferInfo, PixelFormat}; use conquer_once::spin::OnceCell; use core::{ fmt::{self, Write}, diff --git a/src/binary/mod.rs b/src/binary/mod.rs index f5d1bbc6..a8eda3fa 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -1,6 +1,7 @@ -use crate::{ - binary::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}, - boot_info::{BootInfo, FrameBuffer, FrameBufferInfo, MemoryRegion, TlsTemplate}, +use crate::binary::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}; +use bootloader_api::{ + info::{FrameBuffer, FrameBufferInfo, MemoryRegion, TlsTemplate}, + BootInfo, }; use core::{ arch::asm, diff --git a/src/binary/uefi/memory_descriptor.rs b/src/binary/uefi/memory_descriptor.rs index 7d1c3a53..e198587c 100644 --- a/src/binary/uefi/memory_descriptor.rs +++ b/src/binary/uefi/memory_descriptor.rs @@ -1,4 +1,5 @@ -use crate::{binary::legacy_memory_region::LegacyMemoryRegion, boot_info::MemoryRegionKind}; +use crate::binary::legacy_memory_region::LegacyMemoryRegion; +use bootloader_api::info::MemoryRegionKind; use uefi::table::boot::{MemoryDescriptor, MemoryType}; use x86_64::PhysAddr; diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs index d0996f4b..c1103a0c 100644 --- a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader_api::{boot_info::PixelFormat, entry_point, BootInfo}; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs index f10e270b..a80556c3 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader_api::{boot_info::PixelFormat, entry_point, BootInfo}; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; use core::panic::PanicInfo; use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode}; From 3d169fac8a4eb9c3b13650d749f4dd8907619792 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:54:59 +0100 Subject: [PATCH 042/226] Parse version info in api build script and use it --- api/build.rs | 20 ++++++++++++++++++++ api/src/config.rs | 31 +++++++++++++++++++++++-------- api/src/lib.rs | 4 ++++ 3 files changed, 47 insertions(+), 8 deletions(-) diff --git a/api/build.rs b/api/build.rs index ec2e3b70..298a272c 100644 --- a/api/build.rs +++ b/api/build.rs @@ -42,4 +42,24 @@ fn main() { fs::write(&dest_path, code).unwrap(); println!("cargo:rerun-if-changed=build.rs"); + + let version_major: u16 = env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(); + let version_minor: u16 = env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(); + let version_patch: u16 = env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(); + let pre_release: bool = !env!("CARGO_PKG_VERSION_PRE").is_empty(); + + fs::write( + Path::new(&out_dir).join("version_info.rs"), + format!( + " + pub const VERSION_MAJOR: u16 = {}; + pub const VERSION_MINOR: u16 = {}; + pub const VERSION_PATCH: u16 = {}; + pub const VERSION_PRE: bool = {}; + ", + version_major, version_minor, version_patch, pre_release + ), + ) + .unwrap(); + println!("cargo:rerun-if-changed=Cargo.toml"); } diff --git a/api/src/config.rs b/api/src/config.rs index 3f1242e6..602ec8f3 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -1,4 +1,4 @@ -use crate::concat::*; +use crate::{concat::*, version_info}; /// Allows configuring the bootloader behavior. /// @@ -251,7 +251,7 @@ pub(crate) struct ApiVersion { version_minor: u16, /// Bootloader version (patch). version_patch: u16, - /// Whether the bootloader version is a pre-release. + /// Whether the bootloader API version is a pre-release. /// /// We can't store the full prerelease string of the version number since it could be /// arbitrarily long. @@ -259,13 +259,12 @@ pub(crate) struct ApiVersion { } impl ApiVersion { - const fn new_default() -> Self { + pub(crate) const fn new_default() -> Self { Self { - // todo: generate these from build script - version_major: 0, - version_minor: 0, - version_patch: 0, - pre_release: true, + version_major: version_info::VERSION_MAJOR, + version_minor: version_info::VERSION_MINOR, + version_patch: version_info::VERSION_PATCH, + pre_release: version_info::VERSION_PRE, } } @@ -278,6 +277,22 @@ impl ApiVersion { pre_release: rand::random(), } } + + pub fn version_major(&self) -> u16 { + self.version_major + } + + pub fn version_minor(&self) -> u16 { + self.version_minor + } + + pub fn version_patch(&self) -> u16 { + self.version_patch + } + + pub fn pre_release(&self) -> bool { + self.pre_release + } } impl Default for ApiVersion { diff --git a/api/src/lib.rs b/api/src/lib.rs index 078f7c5e..1f38b862 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -16,6 +16,10 @@ mod concat { include!(concat!(env!("OUT_DIR"), "/concat.rs")); } +mod version_info { + include!(concat!(env!("OUT_DIR"), "/version_info.rs")); +} + /// Defines the entry point function. /// /// The function must have the signature `fn(&'static mut BootInfo) -> !`. From 94c5165078bfdc04c42ad4290ce71745396cb4c6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:55:25 +0100 Subject: [PATCH 043/226] Use `ApiVersion` struct in `BootInfo` --- api/src/config.rs | 3 ++- api/src/info.rs | 14 +++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/api/src/config.rs b/api/src/config.rs index 602ec8f3..06e4edbf 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -244,7 +244,8 @@ impl Default for BootloaderConfig { } #[derive(Debug, PartialEq, Eq)] -pub(crate) struct ApiVersion { +#[repr(C)] +pub struct ApiVersion { /// Bootloader version (major). version_major: u16, /// Bootloader version (minor). diff --git a/api/src/info.rs b/api/src/info.rs index 15c647d0..9bcf17ec 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -1,5 +1,7 @@ use core::{ops, slice}; +use crate::config::ApiVersion; + /// This structure represents the information that the bootloader passes to the kernel. /// /// The information is passed as an argument to the entry point. The entry point function must @@ -18,17 +20,7 @@ use core::{ops, slice}; #[repr(C)] #[non_exhaustive] pub struct BootInfo { - /// Bootloader version (major). - pub version_major: u16, - /// Bootloader version (minor). - pub version_minor: u16, - /// Bootloader version (patch). - pub version_patch: u16, - /// Whether the bootloader version is a pre-release. - /// - /// We can't store the full prerelease string of the version number since it could be - /// arbitrarily long. - pub pre_release: bool, + pub api_version: ApiVersion, /// A map of the physical memory regions of the underlying machine. /// /// The bootloader queries this information from the BIOS/UEFI firmware and translates this From 12f2adb81c24a3f5c419b203cfbbeb0eb9f84df3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:55:46 +0100 Subject: [PATCH 044/226] Replace deprecated x86_64 segment methods --- src/binary/gdt.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/binary/gdt.rs b/src/binary/gdt.rs index 8a4610b3..9ac45ade 100644 --- a/src/binary/gdt.rs +++ b/src/binary/gdt.rs @@ -1,5 +1,5 @@ use x86_64::{ - instructions::segmentation, + instructions::segmentation::{self, Segment}, structures::{ gdt::{Descriptor, GlobalDescriptorTable}, paging::PhysFrame, @@ -24,9 +24,9 @@ pub fn create_and_load(frame: PhysFrame) { gdt.load(); unsafe { - segmentation::set_cs(code_selector); - segmentation::load_ds(data_selector); - segmentation::load_es(data_selector); - segmentation::load_ss(data_selector); + segmentation::CS::set_reg(code_selector); + segmentation::DS::set_reg(data_selector); + segmentation::ES::set_reg(data_selector); + segmentation::SS::set_reg(data_selector); } } From 33be56e78436f6a2478348b32cf3fbfd818c4e40 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 26 Dec 2021 14:56:13 +0100 Subject: [PATCH 045/226] Use non-exhaustive `api::BootInfo` struct in bootloaader --- api/src/info.rs | 14 ++++++++++++++ src/binary/mod.rs | 23 ++++++++++------------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/api/src/info.rs b/api/src/info.rs index 9bcf17ec..835c55d3 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -53,6 +53,20 @@ pub struct BootInfo { pub tls_template: Optional, } +impl BootInfo { + pub fn new(memory_regions: MemoryRegions) -> Self { + Self { + api_version: ApiVersion::new_default(), + memory_regions, + framebuffer: Optional::None, + physical_memory_offset: Optional::None, + recursive_index: Optional::None, + rsdp_addr: Optional::None, + tls_template: Optional::None, + } + } +} + /// FFI-safe slice of [`MemoryRegion`] structs, semantically equivalent to /// `&'static mut [MemoryRegion]`. /// diff --git a/src/binary/mod.rs b/src/binary/mod.rs index a8eda3fa..4e1e5e35 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -346,24 +346,21 @@ where log::info!("Create bootinfo"); // create boot info - let boot_info = boot_info.write(BootInfo { - version_major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), - version_minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), - version_patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), - pre_release: !env!("CARGO_PKG_VERSION_PRE").is_empty(), - memory_regions: memory_regions.into(), - framebuffer: mappings + let boot_info = boot_info.write({ + let mut info = BootInfo::new(memory_regions.into()); + info.framebuffer = mappings .framebuffer .map(|addr| FrameBuffer { buffer_start: addr.as_u64(), buffer_byte_len: system_info.framebuffer_info.byte_len, info: system_info.framebuffer_info, }) - .into(), - physical_memory_offset: mappings.physical_memory_offset.map(VirtAddr::as_u64).into(), - recursive_index: mappings.recursive_index.map(Into::into).into(), - rsdp_addr: system_info.rsdp_addr.map(|addr| addr.as_u64()).into(), - tls_template: mappings.tls_template.into(), + .into(); + info.physical_memory_offset = mappings.physical_memory_offset.map(VirtAddr::as_u64).into(); + info.recursive_index = mappings.recursive_index.map(Into::into).into(); + info.rsdp_addr = system_info.rsdp_addr.map(|addr| addr.as_u64()).into(); + info.tls_template = mappings.tls_template.into(); + info }); boot_info @@ -429,7 +426,7 @@ struct Addresses { page_table: PhysFrame, stack_top: VirtAddr, entry_point: VirtAddr, - boot_info: &'static mut crate::boot_info::BootInfo, + boot_info: &'static mut BootInfo, } fn boot_info_location(used_entries: &mut UsedLevel4Entries) -> VirtAddr { From ce7670b8bdd82a54d5ae3bb30cf1e428ad58a753 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Dec 2021 20:04:05 +0100 Subject: [PATCH 046/226] Make `BootInfo `Copy`-able --- api/src/config.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/src/config.rs b/api/src/config.rs index 06e4edbf..f3fcbe27 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -4,7 +4,7 @@ use crate::{concat::*, version_info}; /// /// TODO: describe use together with `entry_point` macro /// TODO: example -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pub struct BootloaderConfig { /// The version of the bootloader API. @@ -243,7 +243,7 @@ impl Default for BootloaderConfig { } } -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(C)] pub struct ApiVersion { /// Bootloader version (major). @@ -303,7 +303,7 @@ impl Default for ApiVersion { } /// Allows to configure the virtual memory mappings created by the bootloader. -#[derive(Debug, Default, PartialEq, Eq)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pub struct Mappings { /// Configures how the kernel stack should be mapped. @@ -363,7 +363,7 @@ impl Mappings { } /// Configuration for the frame buffer used for graphical output. -#[derive(Debug, Default, PartialEq, Eq)] +#[derive(Debug, Default, PartialEq, Eq, Clone, Copy)] #[non_exhaustive] pub struct FrameBuffer { /// Instructs the bootloader to set up a framebuffer format that has at least the given height. From 72c5d455e5fcd4994bff8416971902c88fe2311a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Dec 2021 20:05:44 +0100 Subject: [PATCH 047/226] Rename `PixelFormat` variants and add `Framebuffer::new` constructor --- api/src/info.rs | 12 ++++++++++-- src/bin/uefi.rs | 5 +++-- src/binary/load_kernel.rs | 2 ++ src/binary/logger.rs | 10 ++++++++-- src/binary/mod.rs | 10 ++++++---- .../default_settings/src/bin/check_boot_info.rs | 2 +- .../higher_half/src/bin/check_boot_info.rs | 2 +- .../map_phys_mem/src/bin/check_boot_info.rs | 2 +- 8 files changed, 32 insertions(+), 13 deletions(-) diff --git a/api/src/info.rs b/api/src/info.rs index 835c55d3..d73e3a01 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -165,6 +165,14 @@ pub struct FrameBuffer { } impl FrameBuffer { + pub fn new(buffer_start: u64, buffer_byte_len: usize, info: FrameBufferInfo) -> Self { + Self { + buffer_start, + buffer_byte_len, + info, + } + } + /// Returns the raw bytes of the framebuffer as slice. pub fn buffer(&self) -> &[u8] { unsafe { self.create_buffer() } @@ -216,12 +224,12 @@ pub enum PixelFormat { /// /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] /// for this. - RGB, + Rgb, /// One byte blue, then one byte green, then one byte red. /// /// Length might be larger than 3, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] /// for this. - BGR, + Bgr, /// A single byte, representing the grayscale value. /// /// Length might be larger than 1, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs index 97eac83f..c1bc51d3 100644 --- a/src/bin/uefi.rs +++ b/src/bin/uefi.rs @@ -8,6 +8,7 @@ struct PageAligned(T); use bootloader::binary::{legacy_memory_region::LegacyFrameAllocator, SystemInfo}; +use bootloader_api::info::FrameBufferInfo; use core::{arch::asm, mem, panic::PanicInfo, slice}; use uefi::{ prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, @@ -171,8 +172,8 @@ fn init_logger(st: &SystemTable) -> (PhysAddr, FrameBufferInfo) { horizontal_resolution: mode_info.resolution().0, vertical_resolution: mode_info.resolution().1, pixel_format: match mode_info.pixel_format() { - PixelFormat::Rgb => bootloader::boot_info::PixelFormat::BGR, - PixelFormat::Bgr => bootloader::boot_info::PixelFormat::BGR, + PixelFormat::Rgb => bootloader_api::info::PixelFormat::Rgb, + PixelFormat::Bgr => bootloader_api::info::PixelFormat::Bgr, PixelFormat::Bitmask | PixelFormat::BltOnly => { panic!("Bitmask and BltOnly framebuffers are not supported") } diff --git a/src/binary/load_kernel.rs b/src/binary/load_kernel.rs index b14a6f67..bfadba5b 100644 --- a/src/binary/load_kernel.rs +++ b/src/binary/load_kernel.rs @@ -14,6 +14,8 @@ use xmas_elf::{ ElfFile, }; +use super::Kernel; + struct Loader<'a, M, F> { elf_file: ElfFile<'a>, inner: Inner<'a, M, F>, diff --git a/src/binary/logger.rs b/src/binary/logger.rs index b8b9607f..e47e225e 100644 --- a/src/binary/logger.rs +++ b/src/binary/logger.rs @@ -127,9 +127,15 @@ impl Logger { fn write_pixel(&mut self, x: usize, y: usize, intensity: u8) { let pixel_offset = y * self.info.stride + x; let color = match self.info.pixel_format { - PixelFormat::RGB => [intensity, intensity, intensity / 2, 0], - PixelFormat::BGR => [intensity / 2, intensity, intensity, 0], + PixelFormat::Rgb => [intensity, intensity, intensity / 2, 0], + PixelFormat::Bgr => [intensity / 2, intensity, intensity, 0], PixelFormat::U8 => [if intensity > 200 { 0xf } else { 0 }, 0, 0, 0], + other => { + // set a supported (but invalid) pixel format before panicking to avoid a double + // panic; it might not be readable though + self.info.pixel_format = PixelFormat::Rgb; + panic!("pixel format {:?} not supported in logger", other) + } }; let bytes_per_pixel = self.info.bytes_per_pixel; let byte_offset = pixel_offset * bytes_per_pixel; diff --git a/src/binary/mod.rs b/src/binary/mod.rs index 4e1e5e35..32d215d4 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -350,10 +350,12 @@ where let mut info = BootInfo::new(memory_regions.into()); info.framebuffer = mappings .framebuffer - .map(|addr| FrameBuffer { - buffer_start: addr.as_u64(), - buffer_byte_len: system_info.framebuffer_info.byte_len, - info: system_info.framebuffer_info, + .map(|addr| { + FrameBuffer::new( + addr.as_u64(), + system_info.framebuffer_info.byte_len, + system_info.framebuffer_info, + ) }) .into(); info.physical_memory_offset = mappings.physical_memory_offset.map(VirtAddr::as_u64).into(); diff --git a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs index 3d46987c..2297f7e4 100644 --- a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs +++ b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs @@ -35,7 +35,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { if ![640, 1024].contains(&framebuffer.info().stride) { panic!("unexpected stride `{}`", framebuffer.info().stride); } - assert_eq!(framebuffer.info().pixel_format, PixelFormat::BGR); + assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), framebuffer.info().stride diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs index c1103a0c..93a9f9dd 100644 --- a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -35,7 +35,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { if ![640, 1024].contains(&framebuffer.info().stride) { panic!("unexpected stride `{}`", framebuffer.info().stride); } - assert_eq!(framebuffer.info().pixel_format, PixelFormat::BGR); + assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), framebuffer.info().stride diff --git a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs index a80556c3..567176da 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs @@ -35,7 +35,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { if ![640, 1024].contains(&framebuffer.info().stride) { panic!("unexpected stride `{}`", framebuffer.info().stride); } - assert_eq!(framebuffer.info().pixel_format, PixelFormat::BGR); + assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), framebuffer.info().stride From 048b6a04f1e954cb0b73df313aa9c9bed8fae87b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Dec 2021 20:06:08 +0100 Subject: [PATCH 048/226] Add kernel struct for passing BootloaderConfig around and use mappings --- src/binary/load_kernel.rs | 12 +++--- src/binary/mod.rs | 86 +++++++++++++++++++-------------------- 2 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/binary/load_kernel.rs b/src/binary/load_kernel.rs index bfadba5b..ceca85e0 100644 --- a/src/binary/load_kernel.rs +++ b/src/binary/load_kernel.rs @@ -33,17 +33,17 @@ where F: FrameAllocator, { fn new( - bytes: &'a [u8], + kernel: Kernel<'a>, page_table: &'a mut M, frame_allocator: &'a mut F, ) -> Result { - log::info!("Elf file loaded at {:#p}", bytes); - let kernel_offset = PhysAddr::new(&bytes[0] as *const u8 as u64); + log::info!("Elf file loaded at {:#p}", kernel.elf.input); + let kernel_offset = PhysAddr::new(&kernel.elf.input[0] as *const u8 as u64); if !kernel_offset.is_aligned(PAGE_SIZE) { return Err("Loaded kernel ELF file is not sufficiently aligned"); } - let elf_file = ElfFile::new(bytes)?; + let elf_file = kernel.elf; header::sanity_check(&elf_file)?; let loader = Loader { elf_file, @@ -274,11 +274,11 @@ where /// Returns the kernel entry point address, it's thread local storage template (if any), /// and a structure describing which level 4 page table entries are in use. pub fn load_kernel( - bytes: &[u8], + kernel: Kernel<'_>, page_table: &mut impl MapperAllSizes, frame_allocator: &mut impl FrameAllocator, ) -> Result<(VirtAddr, Option, UsedLevel4Entries), &'static str> { - let mut loader = Loader::new(bytes, page_table, frame_allocator)?; + let mut loader = Loader::new(kernel, page_table, frame_allocator)?; let tls_template = loader.load_segments()?; let used_entries = loader.used_level_4_entries(); diff --git a/src/binary/mod.rs b/src/binary/mod.rs index 32d215d4..f726e5eb 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -1,7 +1,8 @@ use crate::binary::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}; use bootloader_api::{ + config::Mapping, info::{FrameBuffer, FrameBufferInfo, MemoryRegion, TlsTemplate}, - BootInfo, + BootInfo, BootloaderConfig, }; use core::{ arch::asm, @@ -12,11 +13,12 @@ use level_4_entries::UsedLevel4Entries; use usize_conversions::FromUsize; use x86_64::{ structures::paging::{ - FrameAllocator, Mapper, OffsetPageTable, Page, PageTableFlags, PageTableIndex, PhysFrame, - Size2MiB, + page_table::PageTableLevel, FrameAllocator, Mapper, OffsetPageTable, Page, PageTableFlags, + PageTableIndex, PhysFrame, Size2MiB, }, PhysAddr, VirtAddr, }; +use xmas_elf::ElfFile; /// Provides BIOS-specific types and trait implementations. #[cfg(feature = "bios_bin")] @@ -56,13 +58,18 @@ pub struct SystemInfo { pub rsdp_addr: Option, } +pub struct Kernel<'a> { + elf: ElfFile<'a>, + config: BootloaderConfig, +} + /// Loads the kernel ELF executable into memory and switches to it. /// /// This function is a convenience function that first calls [`set_up_mappings`], then /// [`create_boot_info`], and finally [`switch_to_kernel`]. The given arguments are passed /// directly to these functions, so see their docs for more info. pub fn load_and_switch_to_kernel( - kernel_bytes: &[u8], + kernel: Kernel, mut frame_allocator: LegacyFrameAllocator, mut page_tables: PageTables, system_info: SystemInfo, @@ -71,14 +78,16 @@ where I: ExactSizeIterator + Clone, D: LegacyMemoryRegion, { + let config = kernel.config; let mut mappings = set_up_mappings( - kernel_bytes, + kernel, &mut frame_allocator, &mut page_tables, system_info.framebuffer_addr, system_info.framebuffer_info.byte_len, ); let boot_info = create_boot_info( + &config, frame_allocator, &mut page_tables, &mut mappings, @@ -102,7 +111,7 @@ where /// This function reacts to unexpected situations (e.g. invalid kernel ELF file) with a panic, so /// errors are not recoverable. pub fn set_up_mappings( - kernel_bytes: &[u8], + kernel: Kernel, frame_allocator: &mut LegacyFrameAllocator, page_tables: &mut PageTables, framebuffer_addr: PhysAddr, @@ -119,16 +128,17 @@ where // Make the kernel respect the write-protection bits even when in ring 0 by default enable_write_protect_bit(); + let config = kernel.config; let (entry_point, tls_template, mut used_entries) = - load_kernel::load_kernel(kernel_bytes, kernel_page_table, frame_allocator) + load_kernel::load_kernel(kernel, kernel_page_table, frame_allocator) .expect("no entry point"); log::info!("Entry point at: {:#x}", entry_point.as_u64()); // create a stack - let stack_start_addr = kernel_stack_start_location(&mut used_entries); + let stack_start_addr = mapping_addr(config.mappings.kernel_stack, &mut used_entries); let stack_start: Page = Page::containing_address(stack_start_addr); let stack_end = { - let end_addr = stack_start_addr + CONFIG.kernel_stack_size.unwrap_or(20 * PAGE_SIZE); + let end_addr = stack_start_addr + config.kernel_stack_size; Page::containing_address(end_addr - 1u64) }; for page in Page::range_inclusive(stack_start, stack_end) { @@ -172,13 +182,14 @@ where } // map framebuffer - let framebuffer_virt_addr = if CONFIG.map_framebuffer { + let framebuffer_virt_addr = { log::info!("Map framebuffer"); let framebuffer_start_frame: PhysFrame = PhysFrame::containing_address(framebuffer_addr); let framebuffer_end_frame = PhysFrame::containing_address(framebuffer_addr + framebuffer_size - 1u64); - let start_page = Page::containing_address(frame_buffer_location(&mut used_entries)); + let start_page = + Page::containing_address(mapping_addr(config.mappings.framebuffer, &mut used_entries)); for (i, frame) in PhysFrame::range_inclusive(framebuffer_start_frame, framebuffer_end_frame).enumerate() { @@ -194,16 +205,11 @@ where } let framebuffer_virt_addr = start_page.start_address(); Some(framebuffer_virt_addr) - } else { - None }; - let physical_memory_offset = if CONFIG.map_physical_memory { + let physical_memory_offset = if let Some(mapping) = config.mappings.physical_memory { log::info!("Map physical memory"); - let offset = CONFIG - .physical_memory_offset - .map(VirtAddr::new) - .unwrap_or_else(|| used_entries.get_free_address()); + let offset = mapping_addr(mapping, &mut used_entries); let start_frame = PhysFrame::containing_address(PhysAddr::new(0)); let max_phys = frame_allocator.max_phys_addr(); @@ -225,12 +231,19 @@ where None }; - let recursive_index = if CONFIG.map_page_table_recursively { + let recursive_index = if let Some(mapping) = config.mappings.page_table_recursive { log::info!("Map page table recursively"); - let index = CONFIG - .recursive_index - .map(PageTableIndex::new) - .unwrap_or_else(|| used_entries.get_free_entry()); + let offset = mapping_addr(mapping, &mut used_entries); + let table_level = PageTableLevel::Four; + if !offset.is_aligned(table_level.entry_address_space_alignment()) { + panic!( + "Offset for recursive mapping must be properly aligned (must be \ + a multiple of {:#x})", + table_level.entry_address_space_alignment() + ); + } + + let index = offset.p4_index(); let entry = &mut kernel_page_table.level_4_table()[index]; if !entry.is_unused() { @@ -284,6 +297,7 @@ pub struct Mappings { /// reference that is valid in both address spaces. The necessary physical frames /// are taken from the given `frame_allocator`. pub fn create_boot_info( + config: &BootloaderConfig, mut frame_allocator: LegacyFrameAllocator, page_tables: &mut PageTables, mappings: &mut Mappings, @@ -297,7 +311,7 @@ where // allocate and map space for the boot info let (boot_info, memory_regions) = { - let boot_info_addr = boot_info_location(&mut mappings.used_entries); + let boot_info_addr = mapping_addr(config.mappings.boot_info, &mut mappings.used_entries); let boot_info_end = boot_info_addr + mem::size_of::(); let memory_map_regions_addr = boot_info_end.align_up(u64::from_usize(mem::align_of::())); @@ -431,25 +445,11 @@ struct Addresses { boot_info: &'static mut BootInfo, } -fn boot_info_location(used_entries: &mut UsedLevel4Entries) -> VirtAddr { - CONFIG - .boot_info_address - .map(VirtAddr::new) - .unwrap_or_else(|| used_entries.get_free_address()) -} - -fn frame_buffer_location(used_entries: &mut UsedLevel4Entries) -> VirtAddr { - CONFIG - .framebuffer_address - .map(VirtAddr::new) - .unwrap_or_else(|| used_entries.get_free_address()) -} - -fn kernel_stack_start_location(used_entries: &mut UsedLevel4Entries) -> VirtAddr { - CONFIG - .kernel_stack_address - .map(VirtAddr::new) - .unwrap_or_else(|| used_entries.get_free_address()) +fn mapping_addr(mapping: Mapping, used_entries: &mut UsedLevel4Entries) -> VirtAddr { + match mapping { + Mapping::FixedAddress(addr) => VirtAddr::new(addr), + Mapping::Dynamic => used_entries.get_free_address(), + } } fn enable_nxe_bit() { From a9c8e9e79cf58cd6b0a0a9024fc06be00bc7f2df Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Dec 2021 21:08:54 +0100 Subject: [PATCH 049/226] First prototype implementation of ELF loading and config parsing in UEFI binary --- Cargo.lock | 28 +++++------ Cargo.toml | 6 +-- src/bin/uefi.rs | 119 +++++++++++++++++++++++++++++++++++++++++----- src/binary/mod.rs | 4 +- 4 files changed, 126 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 579c20b2..6369986d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,9 +51,9 @@ checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bootloader" @@ -197,9 +197,9 @@ dependencies = [ [[package]] name = "gpt" -version = "2.0.0" +version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b0e3659ffee31427c4aaa68c1e5115c8c86ba71ff11da5a16e5b70f3471d" +checksum = "5dd7365d734a70ac5dd7be791b0c96083852188df015b8c665bb2dadb108a743" dependencies = [ "bitflags", "crc", @@ -288,18 +288,18 @@ checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "392a54546fda6b7cc663379d0e6ce8b324cf88aecc5a499838e1be9781bdce2e" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" dependencies = [ "proc-macro2", ] @@ -398,9 +398,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.72" +version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +checksum = "ecb2e6da8ee5eb9a61068762a32fa9619cc591ceb055b3687f4cd4051ec2e06b" dependencies = [ "proc-macro2", "quote", @@ -494,9 +494,9 @@ dependencies = [ [[package]] name = "uefi" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4630a92e80ac72f2b3dedb865dac3cf9e0215ce7e222301f0a37d8e6e3c5dbf4" +checksum = "cd7363ecc1a80d6a7467b322bfb16e95ac5e19f0b71ba2af3e6592f101820113" dependencies = [ "bitflags", "log", @@ -506,9 +506,9 @@ dependencies = [ [[package]] name = "uefi-macros" -version = "0.3.3" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcca10ca861f34a320d178f3fdb29ffbf05087fc2c70d2a99860e3329bee1a8" +checksum = "7006b85ae8acaf2b448c5f1630a434caaacaedcc0907f12404e4e31c9dafcdb3" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 77234623..c3dedc5e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ authors = ["Philipp Oppermann "] license = "MIT/Apache-2.0" description = "An experimental x86_64 bootloader that works on both BIOS and UEFI systems." repository = "https://github.com/rust-osdev/bootloader" -edition = "2018" +edition = "2021" [workspace] members = [ @@ -39,7 +39,7 @@ x86_64 = { version = "0.14.7", optional = true, default-features = false, featur usize_conversions = { version = "0.2.0", optional = true } bit_field = { version = "0.10.0", optional = true } log = { version = "0.4.8", optional = true } -uefi = { version = "0.11.0", optional = true } +uefi = { version = "0.13.0", optional = true } argh = { version = "0.1.3", optional = true } displaydoc = { version = "0.1.7", optional = true } conquer-once = { version = "0.2.1", optional = true, default-features = false } @@ -50,7 +50,7 @@ thiserror = { version = "1.0.20", optional = true } json = { version = "0.12.4", optional = true } rsdp = { version = "1.0.0", optional = true } fatfs = { version = "0.3.4", optional = true } -gpt = { version = "2.0.0", optional = true } +gpt = { version = "3.0.0", optional = true } [dependencies.font8x8] version = "0.2.5" diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs index c1bc51d3..8e828af3 100644 --- a/src/bin/uefi.rs +++ b/src/bin/uefi.rs @@ -7,24 +7,113 @@ #[repr(align(4096))] struct PageAligned(T); -use bootloader::binary::{legacy_memory_region::LegacyFrameAllocator, SystemInfo}; -use bootloader_api::info::FrameBufferInfo; -use core::{arch::asm, mem, panic::PanicInfo, slice}; +use bootloader::binary::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; +use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; +use core::{arch::asm, mem, panic::PanicInfo, ptr, slice}; use uefi::{ prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, - proto::console::gop::{GraphicsOutput, PixelFormat}, - table::boot::{MemoryDescriptor, MemoryType}, - Completion, + proto::{ + console::gop::{GraphicsOutput, PixelFormat}, + device_path::DevicePath, + loaded_image::LoadedImage, + media::{ + file::{File, FileAttribute, FileInfo, FileMode}, + fs::SimpleFileSystem, + }, + }, + table::boot::{AllocateType, MemoryDescriptor, MemoryType}, + CStr16, Completion, }; use x86_64::{ structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, PhysAddr, VirtAddr, }; +use xmas_elf::ElfFile; #[entry] fn efi_main(image: Handle, st: SystemTable) -> Status { - let (framebuffer_addr, framebuffer_info) = init_logger(&st); - log::info!("Hello World from UEFI bootloader!"); + main_inner(image, st) +} + +fn main_inner(image: Handle, mut st: SystemTable) -> Status { + let mut buf = [0; 100]; + st.stdout() + .output_string( + CStr16::from_str_with_buf("UEFI bootloader started; trying to load kernel", &mut buf) + .unwrap(), + ) + .unwrap() + .unwrap(); + + let file_system_raw = { + let ref this = st.boot_services(); + let loaded_image = this + .handle_protocol::(image)? + .expect("Failed to retrieve `LoadedImage` protocol from handle"); + let loaded_image = unsafe { &*loaded_image.get() }; + + let device_handle = loaded_image.device(); + + let device_path = this + .handle_protocol::(device_handle)? + .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); + let mut device_path = unsafe { &*device_path.get() }; + + let device_handle = this + .locate_device_path::(&mut device_path)? + .expect("Failed to locate `SimpleFileSystem` protocol on device path"); + + this.handle_protocol::(device_handle) + } + .unwrap() + .unwrap(); + let file_system = unsafe { &mut *file_system_raw.get() }; + + let mut root = file_system.open_volume().unwrap().unwrap(); + let kernel_file_handle = root + .open("kernel-x86_64", FileMode::Read, FileAttribute::empty()) + .unwrap() + .unwrap(); + let mut kernel_file = match kernel_file_handle.into_type().unwrap().unwrap() { + uefi::proto::media::file::FileType::Regular(f) => f, + uefi::proto::media::file::FileType::Dir(_) => panic!(), + }; + + let mut buf = [0; 100]; + let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap().unwrap(); + let kernel_size = usize::try_from(kernel_info.file_size()).unwrap(); + + let kernel_ptr = st + .boot_services() + .allocate_pages( + AllocateType::AnyPages, + MemoryType::LOADER_DATA, + ((kernel_size - 1) / 4096) + 1, + ) + .unwrap() + .unwrap() as *mut u8; + unsafe { ptr::write_bytes(kernel_ptr, 0, kernel_size) }; + let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; + kernel_file.read(kernel_slice).unwrap().unwrap(); + + let kernel_elf = ElfFile::new(kernel_slice).unwrap(); + + let config = { + let section = kernel_elf + .find_section_by_name(".bootloader-config") + .unwrap(); + let raw = section.raw_data(&kernel_elf); + BootloaderConfig::deserialize(raw).unwrap() + }; + + let kernel = Kernel { + elf: kernel_elf, + config, + }; + + let (framebuffer_addr, framebuffer_info) = init_logger(&st, config); + log::info!("UEFI bootloader started"); + log::info!("Reading kernel and configuration from disk was successful"); log::info!("Using framebuffer at {:#x}", framebuffer_addr); let mmap_storage = { @@ -62,7 +151,7 @@ fn efi_main(image: Handle, st: SystemTable) -> Status { }; bootloader::binary::load_and_switch_to_kernel( - &KERNEL.0, + kernel, frame_allocator, page_tables, system_info, @@ -135,7 +224,7 @@ fn create_page_tables( } } -fn init_logger(st: &SystemTable) -> (PhysAddr, FrameBufferInfo) { +fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, FrameBufferInfo) { let gop = st .boot_services() .locate_protocol::() @@ -145,8 +234,14 @@ fn init_logger(st: &SystemTable) -> (PhysAddr, FrameBufferInfo) { let mode = { let modes = gop.modes().map(Completion::unwrap); match ( - CONFIG.minimum_framebuffer_height, - CONFIG.minimum_framebuffer_width, + config + .frame_buffer + .minimum_framebuffer_height + .map(|v| usize::try_from(v).unwrap()), + config + .frame_buffer + .minimum_framebuffer_width + .map(|v| usize::try_from(v).unwrap()), ) { (Some(height), Some(width)) => modes .filter(|m| { diff --git a/src/binary/mod.rs b/src/binary/mod.rs index f726e5eb..32aeffae 100644 --- a/src/binary/mod.rs +++ b/src/binary/mod.rs @@ -59,8 +59,8 @@ pub struct SystemInfo { } pub struct Kernel<'a> { - elf: ElfFile<'a>, - config: BootloaderConfig, + pub elf: ElfFile<'a>, + pub config: BootloaderConfig, } /// Loads the kernel ELF executable into memory and switches to it. From 9a3470ff053fc3b9838175fcb76e0838459583ce Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 28 Dec 2021 12:33:32 +0100 Subject: [PATCH 050/226] Update Cargo.lock for basic_example --- examples/basic/Cargo.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock index cbab0afe..aeaf1c02 100644 --- a/examples/basic/Cargo.lock +++ b/examples/basic/Cargo.lock @@ -6,13 +6,9 @@ version = 3 name = "basic_example" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", ] -[[package]] -name = "bootloader" -version = "0.10.4" - [[package]] name = "bootloader-locator" version = "0.0.4" @@ -22,6 +18,10 @@ dependencies = [ "json", ] +[[package]] +name = "bootloader_api" +version = "0.1.0" + [[package]] name = "json" version = "0.12.4" From e76b7e1fbb9a9d1ba8ef6421a5422a3e690ed714 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 28 Dec 2021 12:34:24 +0100 Subject: [PATCH 051/226] Update builder executable for new bootloader design (UEFI only) --- src/bin/builder.rs | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/bin/builder.rs b/src/bin/builder.rs index f5b6fece..37d53f7b 100644 --- a/src/bin/builder.rs +++ b/src/bin/builder.rs @@ -15,10 +15,6 @@ type ExitCode = i32; #[derive(FromArgs)] /// Build the bootloader struct BuildArguments { - /// path to the `Cargo.toml` of the kernel - #[argh(option)] - kernel_manifest: PathBuf, - /// path to the kernel ELF binary #[argh(option)] kernel_binary: PathBuf, @@ -107,8 +103,6 @@ fn main() -> anyhow::Result<()> { if args.quiet { cmd.arg("--quiet"); } - cmd.env("KERNEL", &args.kernel_binary); - cmd.env("KERNEL_MANIFEST", &args.kernel_manifest); assert!(cmd.status()?.success()); // Retrieve binary paths @@ -155,7 +149,7 @@ fn main() -> anyhow::Result<()> { if let Some(out_dir) = &args.out_dir { let efi_file = out_dir.join(format!("boot-{}-{}.efi", executable_name, kernel_name)); - create_uefi_disk_image(&executable_path, &efi_file) + create_uefi_disk_image(&executable_path, &efi_file, &args.kernel_binary) .context("failed to create UEFI disk image")?; } } @@ -176,8 +170,6 @@ fn main() -> anyhow::Result<()> { if args.quiet { cmd.arg("--quiet"); } - cmd.env("KERNEL", &args.kernel_binary); - cmd.env("KERNEL_MANIFEST", &args.kernel_manifest); cmd.env("RUSTFLAGS", "-C opt-level=s"); assert!(cmd.status()?.success()); @@ -233,13 +225,21 @@ fn main() -> anyhow::Result<()> { Ok(()) } -fn create_uefi_disk_image(executable_path: &Path, efi_file: &Path) -> anyhow::Result<()> { +fn create_uefi_disk_image( + executable_path: &Path, + efi_file: &Path, + kernel_binary: &Path, +) -> anyhow::Result<()> { fs::copy(&executable_path, &efi_file).context("failed to copy efi file to out dir")?; let efi_size = fs::metadata(&efi_file) .context("failed to read metadata of efi file")? .len(); + let kernel_size = fs::metadata(&kernel_binary) + .context("failed to read metadata of kernel binary")? + .len(); + // create fat partition let fat_file_path = { const MB: u64 = 1024 * 1024; @@ -252,9 +252,10 @@ fn create_uefi_disk_image(executable_path: &Path, efi_file: &Path) -> anyhow::Re .truncate(true) .open(&fat_path) .context("Failed to create UEFI FAT file")?; - let efi_size_padded_and_rounded = ((efi_size + 1024 * 64 - 1) / MB + 1) * MB; + let fat_size = efi_size + kernel_size; + let fat_size_padded_and_rounded = ((fat_size + 1024 * 64 - 1) / MB + 1) * MB; fat_file - .set_len(efi_size_padded_and_rounded) + .set_len(fat_size_padded_and_rounded) .context("failed to set UEFI FAT file length")?; // create new FAT partition @@ -272,6 +273,11 @@ fn create_uefi_disk_image(executable_path: &Path, efi_file: &Path) -> anyhow::Re bootx64.truncate()?; io::copy(&mut fs::File::open(&executable_path)?, &mut bootx64)?; + // copy kernel to FAT filesystem + let mut kernel_file = root_dir.create_file("kernel-x86_64")?; + kernel_file.truncate()?; + io::copy(&mut fs::File::open(&kernel_binary)?, &mut kernel_file)?; + fat_path }; @@ -315,7 +321,7 @@ fn create_uefi_disk_image(executable_path: &Path, efi_file: &Path) -> anyhow::Re // add add EFI system partition let partition_id = disk - .add_partition("boot", partition_size, gpt::partition_types::EFI, 0) + .add_partition("boot", partition_size, gpt::partition_types::EFI, 0, None) .context("failed to add boot partition")?; let partition = disk From 1563a44d07e38f788bffba76dd116b68ac0e916b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 28 Dec 2021 12:35:29 +0100 Subject: [PATCH 052/226] First working version of dynamic kernel loading in UEFI bootloader --- src/bin/uefi.rs | 173 +++++++++++++++++++++++++++++------------------- 1 file changed, 104 insertions(+), 69 deletions(-) diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs index 8e828af3..b5f34459 100644 --- a/src/bin/uefi.rs +++ b/src/bin/uefi.rs @@ -4,12 +4,9 @@ #![feature(maybe_uninit_extra)] #![deny(unsafe_op_in_unsafe_fn)] -#[repr(align(4096))] -struct PageAligned(T); - use bootloader::binary::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; -use core::{arch::asm, mem, panic::PanicInfo, ptr, slice}; +use core::{arch::asm, cell::UnsafeCell, fmt::Write, mem, panic::PanicInfo, ptr, slice}; use uefi::{ prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, proto::{ @@ -22,7 +19,7 @@ use uefi::{ }, }, table::boot::{AllocateType, MemoryDescriptor, MemoryType}, - CStr16, Completion, + Completion, }; use x86_64::{ structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, @@ -30,38 +27,117 @@ use x86_64::{ }; use xmas_elf::ElfFile; +static SYSTEM_TABLE: VeryUnsafeCell>> = VeryUnsafeCell::new(None); + +struct VeryUnsafeCell(UnsafeCell); + +impl VeryUnsafeCell { + const fn new(v: T) -> Self { + Self(UnsafeCell::new(v)) + } +} + +unsafe impl Sync for VeryUnsafeCell {} + +impl core::ops::Deref for VeryUnsafeCell { + type Target = UnsafeCell; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + #[entry] fn efi_main(image: Handle, st: SystemTable) -> Status { main_inner(image, st) } fn main_inner(image: Handle, mut st: SystemTable) -> Status { - let mut buf = [0; 100]; - st.stdout() - .output_string( - CStr16::from_str_with_buf("UEFI bootloader started; trying to load kernel", &mut buf) - .unwrap(), - ) - .unwrap() - .unwrap(); + // temporarily clone the system table for printing panics + unsafe { + *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); + } + + st.stdout().clear().unwrap().unwrap(); + writeln!( + st.stdout(), + "UEFI bootloader started; trying to load kernel" + ) + .unwrap(); + + let kernel = load_kernel(image, &st); + + let (framebuffer_addr, framebuffer_info) = init_logger(&st, kernel.config); + + // we no longer need the system table for printing panics + unsafe { + *SYSTEM_TABLE.get() = None; + } + + log::info!("UEFI bootloader started"); + log::info!("Reading kernel and configuration from disk was successful"); + log::info!("Using framebuffer at {:#x}", framebuffer_addr); + + let mmap_storage = { + let max_mmap_size = + st.boot_services().memory_map_size() + 8 * mem::size_of::(); + let ptr = st + .boot_services() + .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)? + .log(); + unsafe { slice::from_raw_parts_mut(ptr, max_mmap_size) } + }; + + log::trace!("exiting boot services"); + let (system_table, memory_map) = st + .exit_boot_services(image, mmap_storage) + .expect_success("Failed to exit boot services"); + + let mut frame_allocator = LegacyFrameAllocator::new(memory_map.copied()); + + let page_tables = create_page_tables(&mut frame_allocator); + + let system_info = SystemInfo { + framebuffer_addr, + framebuffer_info, + rsdp_addr: { + use uefi::table::cfg; + let mut config_entries = system_table.config_table().iter(); + // look for an ACPI2 RSDP first + let acpi2_rsdp = config_entries.find(|entry| matches!(entry.guid, cfg::ACPI2_GUID)); + // if no ACPI2 RSDP is found, look for a ACPI1 RSDP + let rsdp = acpi2_rsdp + .or_else(|| config_entries.find(|entry| matches!(entry.guid, cfg::ACPI_GUID))); + rsdp.map(|entry| PhysAddr::new(entry.address as u64)) + }, + }; + bootloader::binary::load_and_switch_to_kernel( + kernel, + frame_allocator, + page_tables, + system_info, + ); +} + +fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { let file_system_raw = { let ref this = st.boot_services(); let loaded_image = this - .handle_protocol::(image)? - .expect("Failed to retrieve `LoadedImage` protocol from handle"); + .handle_protocol::(image) + .expect_success("Failed to retrieve `LoadedImage` protocol from handle"); let loaded_image = unsafe { &*loaded_image.get() }; let device_handle = loaded_image.device(); let device_path = this - .handle_protocol::(device_handle)? - .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); + .handle_protocol::(device_handle) + .expect_success("Failed to retrieve `DevicePath` protocol from image's device handle"); let mut device_path = unsafe { &*device_path.get() }; let device_handle = this - .locate_device_path::(&mut device_path)? - .expect("Failed to locate `SimpleFileSystem` protocol on device path"); + .locate_device_path::(&mut device_path) + .expect_success("Failed to locate `SimpleFileSystem` protocol on device path"); this.handle_protocol::(device_handle) } @@ -72,14 +148,14 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let mut root = file_system.open_volume().unwrap().unwrap(); let kernel_file_handle = root .open("kernel-x86_64", FileMode::Read, FileAttribute::empty()) - .unwrap() + .expect("Failed to load kernel (expected file named `kernel-x86_64`)") .unwrap(); let mut kernel_file = match kernel_file_handle.into_type().unwrap().unwrap() { uefi::proto::media::file::FileType::Regular(f) => f, uefi::proto::media::file::FileType::Dir(_) => panic!(), }; - let mut buf = [0; 100]; + let mut buf = [0; 500]; let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap().unwrap(); let kernel_size = usize::try_from(kernel_info.file_size()).unwrap(); @@ -106,56 +182,10 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { BootloaderConfig::deserialize(raw).unwrap() }; - let kernel = Kernel { + Kernel { elf: kernel_elf, config, - }; - - let (framebuffer_addr, framebuffer_info) = init_logger(&st, config); - log::info!("UEFI bootloader started"); - log::info!("Reading kernel and configuration from disk was successful"); - log::info!("Using framebuffer at {:#x}", framebuffer_addr); - - let mmap_storage = { - let max_mmap_size = - st.boot_services().memory_map_size() + 8 * mem::size_of::(); - let ptr = st - .boot_services() - .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)? - .log(); - unsafe { slice::from_raw_parts_mut(ptr, max_mmap_size) } - }; - - log::trace!("exiting boot services"); - let (system_table, memory_map) = st - .exit_boot_services(image, mmap_storage) - .expect_success("Failed to exit boot services"); - - let mut frame_allocator = LegacyFrameAllocator::new(memory_map.copied()); - - let page_tables = create_page_tables(&mut frame_allocator); - - let system_info = SystemInfo { - framebuffer_addr, - framebuffer_info, - rsdp_addr: { - use uefi::table::cfg; - let mut config_entries = system_table.config_table().iter(); - // look for an ACPI2 RSDP first - let acpi2_rsdp = config_entries.find(|entry| matches!(entry.guid, cfg::ACPI2_GUID)); - // if no ACPI2 RSDP is found, look for a ACPI1 RSDP - let rsdp = acpi2_rsdp - .or_else(|| config_entries.find(|entry| matches!(entry.guid, cfg::ACPI_GUID))); - rsdp.map(|entry| PhysAddr::new(entry.address as u64)) - }, - }; - - bootloader::binary::load_and_switch_to_kernel( - kernel, - frame_allocator, - page_tables, - system_info, - ); + } } /// Creates page table abstraction types for both the bootloader and kernel page tables. @@ -286,12 +316,17 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F #[panic_handler] fn panic(info: &PanicInfo) -> ! { + if let Some(st) = unsafe { &mut *SYSTEM_TABLE.get() } { + let _ = writeln!(st.stdout(), "{}", info); + } + unsafe { bootloader::binary::logger::LOGGER .get() .map(|l| l.force_unlock()) }; log::error!("{}", info); + loop { unsafe { asm!("cli; hlt") }; } From 02d0a0caf0d46bdb08f457d782413a9d4772438d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 28 Dec 2021 19:37:34 +0100 Subject: [PATCH 053/226] Start adjusting first stage for dynamic kernel loading and FAT support --- real_mode/first_stage/16-bit-linker.ld | 32 +++++ real_mode/first_stage/Cargo.lock | 2 + real_mode/first_stage/Cargo.toml | 11 +- real_mode/first_stage/src/boot.s | 14 +- real_mode/first_stage/src/dap.rs | 27 +++- real_mode/first_stage/src/fat.rs | 169 +++++++++++++++++++++++++ real_mode/first_stage/src/lib.rs | 68 ---------- real_mode/first_stage/src/main.rs | 122 ++++++++++++++++++ real_mode/first_stage/src/mbr.rs | 106 ++++++++++++++++ real_mode/first_stage/x86-16bit.json | 12 +- 10 files changed, 466 insertions(+), 97 deletions(-) create mode 100644 real_mode/first_stage/16-bit-linker.ld create mode 100644 real_mode/first_stage/src/fat.rs delete mode 100644 real_mode/first_stage/src/lib.rs create mode 100644 real_mode/first_stage/src/main.rs create mode 100644 real_mode/first_stage/src/mbr.rs diff --git a/real_mode/first_stage/16-bit-linker.ld b/real_mode/first_stage/16-bit-linker.ld new file mode 100644 index 00000000..3f02a46c --- /dev/null +++ b/real_mode/first_stage/16-bit-linker.ld @@ -0,0 +1,32 @@ +ENTRY(_start) + +SECTIONS { + . = 0x500; + _stack_start = .; + . = 0x7c00; + _stack_end = .; + + _mbr_start = .; + .boot : + { + *(.boot .boot.*) + } + .text : + { + *(.text .text.*) + } + .data : + { + *(.rodata .rodata.*) + *(.data .data.*) + *(.got .got.*) + } + _mbr_end = .; + + . = 0x7c00 + 510; + + .magic_number : + { + SHORT(0xaa55) /* magic number for bootable disk */ + } +} diff --git a/real_mode/first_stage/Cargo.lock b/real_mode/first_stage/Cargo.lock index 5574ace3..7b427282 100644 --- a/real_mode/first_stage/Cargo.lock +++ b/real_mode/first_stage/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "first_stage" version = "0.1.0" diff --git a/real_mode/first_stage/Cargo.toml b/real_mode/first_stage/Cargo.toml index 0a4d965e..b68e460a 100644 --- a/real_mode/first_stage/Cargo.toml +++ b/real_mode/first_stage/Cargo.toml @@ -2,19 +2,14 @@ name = "first_stage" version = "0.1.0" authors = ["Philipp Oppermann "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "first_stage" -crate-type = ["staticlib"] - [dependencies] - [profile.release] -opt-level = "s" +opt-level = "z" lto = true codegen-units = 1 -debug = true +# debug = true diff --git a/real_mode/first_stage/src/boot.s b/real_mode/first_stage/src/boot.s index 50163ffb..8f7735a0 100644 --- a/real_mode/first_stage/src/boot.s +++ b/real_mode/first_stage/src/boot.s @@ -1,10 +1,8 @@ .section .boot, "awx" .global _start -.intel_syntax noprefix .code16 -# This stage initializes the stack, enables the A20 line, loads the rest of -# the bootloader from disk, and jumps to stage_2. +# This stage initializes the stack, enables the A20 line _start: # zero segment registers @@ -75,17 +73,7 @@ check_int13h_extensions: rust: # push arguments -arg_0: push dx # disk number - lea eax, _bootloader_start -arg_1: - push eax - lea eax, _second_stage_end -arg_2: - push eax - lea eax, _second_stage_start -arg_3: - push eax call first_stage spin: diff --git a/real_mode/first_stage/src/dap.rs b/real_mode/first_stage/src/dap.rs index cc7711f5..0cdfae14 100644 --- a/real_mode/first_stage/src/dap.rs +++ b/real_mode/first_stage/src/dap.rs @@ -1,3 +1,5 @@ +use core::arch::{asm, global_asm}; + #[repr(packed)] pub struct DiskAddressPacket { /// Size of the DAP structure @@ -26,11 +28,28 @@ impl DiskAddressPacket { } } + pub fn from_lba(memory_buffer_start: u16, start_lba: u64, number_of_sectors: u16) -> Self { + Self { + packet_size: 0x10, + zero: 0, + number_of_sectors, + offset: memory_buffer_start, + segment: 0, + start_lba, + } + } + pub unsafe fn perform_load(&self, disk_number: u16) { let self_addr = self as *const Self as u16; - asm!(" - int 0x13 - jc dap_load_failed - " :: "{si}"(self_addr), "{ax}"(0x4200), "{dx}"(disk_number) :: "intel"); + asm!("mov {1:x}, si", + "mov si, {0:x}", + "int 0x13", + "jc dap_load_failed", + "mov si, {1:x}", + in(reg) self_addr, + out(reg) _, + in("ax") 0x4200u16, + in("dx") disk_number, + ); } } diff --git a/real_mode/first_stage/src/fat.rs b/real_mode/first_stage/src/fat.rs new file mode 100644 index 00000000..30f35a64 --- /dev/null +++ b/real_mode/first_stage/src/fat.rs @@ -0,0 +1,169 @@ +// based on https://github.com/rafalh/rust-fatfs/ + +pub(crate) struct BootSector { + bootjmp: [u8; 3], + oem_name: [u8; 8], + pub(crate) bpb: BiosParameterBlock, + boot_code: [u8; 448], + boot_sig: [u8; 2], +} + +impl BootSector { + pub(crate) fn deserialize(bytes: &[u8]) -> Self { + let mut boot = Self::default(); + let (&bootjmp, bytes) = split_array_ref(bytes); + let (&oem_name, bytes) = split_array_ref(bytes); + + boot.bootjmp = bootjmp; + boot.oem_name = oem_name; + boot.bpb = BiosParameterBlock::deserialize(bytes); + + let bytes = if boot.bpb.is_fat32() { + let (boot_code, bytes): (&[_; 420], _) = split_array_ref(bytes); + boot.boot_code[0..420].copy_from_slice(&boot_code[..]); + bytes + } else { + let (&boot_code, bytes) = split_array_ref(bytes); + boot.boot_code = boot_code; + bytes + }; + let (&boot_sig, bytes) = split_array_ref(bytes); + boot.boot_sig = boot_sig; + boot + } +} + +impl Default for BootSector { + fn default() -> Self { + Self { + bootjmp: Default::default(), + oem_name: Default::default(), + bpb: BiosParameterBlock::default(), + boot_code: [0; 448], + boot_sig: Default::default(), + } + } +} + +#[derive(Default, Debug, Clone)] +pub(crate) struct BiosParameterBlock { + pub(crate) bytes_per_sector: u16, + pub(crate) sectors_per_cluster: u8, + pub(crate) reserved_sectors: u16, + pub(crate) fats: u8, + pub(crate) root_entries: u16, + pub(crate) total_sectors_16: u16, + pub(crate) media: u8, + pub(crate) sectors_per_fat_16: u16, + pub(crate) sectors_per_track: u16, + pub(crate) heads: u16, + pub(crate) hidden_sectors: u32, + pub(crate) total_sectors_32: u32, + + // Extended BIOS Parameter Block + pub(crate) sectors_per_fat_32: u32, + pub(crate) extended_flags: u16, + pub(crate) fs_version: u16, + pub(crate) root_dir_first_cluster: u32, + pub(crate) fs_info_sector: u16, + pub(crate) backup_boot_sector: u16, + pub(crate) reserved_0: [u8; 12], + pub(crate) drive_num: u8, + pub(crate) reserved_1: u8, + pub(crate) ext_sig: u8, + pub(crate) volume_id: u32, + pub(crate) volume_label: [u8; 11], + pub(crate) fs_type_label: [u8; 8], +} + +impl BiosParameterBlock { + pub fn deserialize(bytes: &[u8]) -> Self { + let (&bytes_per_sector, bytes) = split_array_ref(bytes); + let (&[sectors_per_cluster], bytes) = split_array_ref(bytes); + let (&reserved_sectors, bytes) = split_array_ref(bytes); + let (&[fats], bytes) = split_array_ref(bytes); + let (&root_entries, bytes) = split_array_ref(bytes); + let (&total_sectors_16, bytes) = split_array_ref(bytes); + let (&[media], bytes) = split_array_ref(bytes); + let (§ors_per_fat_16, bytes) = split_array_ref(bytes); + let (§ors_per_track, bytes) = split_array_ref(bytes); + let (&heads, bytes) = split_array_ref(bytes); + let (&hidden_sectors, bytes) = split_array_ref(bytes); + let (&total_sectors_32, bytes) = split_array_ref(bytes); + + let mut bpb = Self { + bytes_per_sector: u16::from_le_bytes(bytes_per_sector), + sectors_per_cluster, + reserved_sectors: u16::from_le_bytes(reserved_sectors), + fats, + root_entries: u16::from_le_bytes(root_entries), + total_sectors_16: u16::from_le_bytes(total_sectors_16), + media, + sectors_per_fat_16: u16::from_le_bytes(sectors_per_fat_16), + sectors_per_track: u16::from_le_bytes(sectors_per_track), + heads: u16::from_le_bytes(heads), + hidden_sectors: u32::from_le_bytes(hidden_sectors), + total_sectors_32: u32::from_le_bytes(total_sectors_32), + ..Self::default() + }; + + let (§ors_per_fat_32, bytes) = split_array_ref(bytes); + let (&extended_flags, bytes) = split_array_ref(bytes); + let (&fs_version, bytes) = split_array_ref(bytes); + let (&root_dir_first_cluster, bytes) = split_array_ref(bytes); + let (&fs_info_sector, bytes) = split_array_ref(bytes); + let (&backup_boot_sector, bytes) = split_array_ref(bytes); + let (&reserved_0, bytes) = split_array_ref(bytes); + + if bpb.is_fat32() { + bpb.sectors_per_fat_32 = u32::from_le_bytes(sectors_per_fat_32); + bpb.extended_flags = u16::from_le_bytes(extended_flags); + bpb.fs_version = u16::from_le_bytes(fs_version); + bpb.root_dir_first_cluster = u32::from_le_bytes(root_dir_first_cluster); + bpb.fs_info_sector = u16::from_le_bytes(fs_info_sector); + bpb.backup_boot_sector = u16::from_le_bytes(backup_boot_sector); + bpb.reserved_0 = reserved_0; + } + + let (&[drive_num], bytes) = split_array_ref(bytes); + let (&[reserved_1], bytes) = split_array_ref(bytes); + let (&[ext_sig], bytes) = split_array_ref(bytes); + let (&volume_id, bytes) = split_array_ref(bytes); + let (&volume_label, bytes) = split_array_ref(bytes); + let (&fs_type_label, bytes) = split_array_ref(bytes); + + bpb.drive_num = drive_num; + bpb.reserved_1 = reserved_1; + bpb.ext_sig = ext_sig; // 0x29 + bpb.volume_id = u32::from_le_bytes(volume_id); + bpb.volume_label = volume_label; + bpb.fs_type_label = fs_type_label; + + // when the extended boot signature is anything other than 0x29, the fields are invalid + if bpb.ext_sig != 0x29 { + // fields after ext_sig are not used - clean them + bpb.volume_id = 0; + bpb.volume_label = [0; 11]; + bpb.fs_type_label = [0; 8]; + } + + bpb + } + + pub(crate) fn is_fat32(&self) -> bool { + // because this field must be zero on FAT32, and + // because it must be non-zero on FAT12/FAT16, + // this provides a simple way to detect FAT32 + self.sectors_per_fat_16 == 0 + } +} + +/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 +/// +/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, +/// see https://github.com/rust-lang/rust/issues/90091 +fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { + let (a, b) = slice.split_at(N); + // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) + unsafe { (&*(a.as_ptr() as *const [T; N]), b) } +} diff --git a/real_mode/first_stage/src/lib.rs b/real_mode/first_stage/src/lib.rs deleted file mode 100644 index 45b96a8e..00000000 --- a/real_mode/first_stage/src/lib.rs +++ /dev/null @@ -1,68 +0,0 @@ -#![feature(asm, global_asm)] -#![no_std] - -global_asm!(include_str!("boot.s")); - -mod dap; - -extern "C" { - fn second_stage(disk_number: u16); -} - -/// Test function -#[no_mangle] -pub extern "C" fn first_stage(second_stage_start: u32, second_stage_end: u32, bootloader_start: u32, disk_number: u16) { - load_second_stage(second_stage_start, second_stage_end, bootloader_start, disk_number); - unsafe { second_stage(disk_number); } -} - -fn load_second_stage(second_stage_start: u32, second_stage_end: u32, bootloader_start: u32, disk_number: u16) { - use dap::DiskAddressPacket; - - let file_offset = (second_stage_start - bootloader_start) as u64; - let size = (second_stage_end - second_stage_start) as u32; - - let dap = DiskAddressPacket::new(second_stage_start as u16, file_offset, size); - unsafe { dap.perform_load(disk_number) } -} - -#[no_mangle] -pub extern fn print_char(c: u8) { - let ax = u16::from(c) | 0x0e00; - unsafe { - asm!("int 0x10" :: "{ax}"(ax), "{bx}"(0) :: "intel" ); - } -} - -#[no_mangle] -pub extern "C" fn dap_load_failed() -> ! { - err(b'1'); -} - -#[no_mangle] -pub extern "C" fn no_int13h_extensions() -> ! { - err(b'2'); -} - -#[cold] -fn err(code: u8) -> ! { - for &c in b"Err:" { - print_char(c); - } - print_char(code); - loop { - hlt() - } -} - -fn hlt() { - unsafe { - asm!("hlt":::: "intel","volatile"); - } -} - -#[panic_handler] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - err(b'P'); -} - diff --git a/real_mode/first_stage/src/main.rs b/real_mode/first_stage/src/main.rs new file mode 100644 index 00000000..b39bfb2b --- /dev/null +++ b/real_mode/first_stage/src/main.rs @@ -0,0 +1,122 @@ +#![no_std] +#![no_main] + +use core::{ + arch::{asm, global_asm}, + slice, +}; +use mbr::MasterBootRecord; + +global_asm!(include_str!("boot.s")); + +mod dap; +mod fat; +mod mbr; + +extern "C" { + static _mbr_start: u8; +} + +fn mbr_start() -> *const u8 { + unsafe { &_mbr_start } +} + +#[no_mangle] +pub extern "C" fn first_stage(disk_number: u16) { + let bytes = &unsafe { slice::from_raw_parts(mbr_start(), 512) }; + let mbr = MasterBootRecord::from_bytes(bytes); + + let partition = mbr + .partition_table_entries() + .get(0) + .unwrap_or_else(|| panic!()); + + let partition_buf = u16::try_from(mbr_start() as usize).unwrap_or_else(|_| panic!()) + 512; + + // load first partition into buffer + // TODO: only load headers + let dap = dap::DiskAddressPacket::from_lba( + partition_buf, + partition.logical_block_address.into(), + partition.sector_count.try_into().unwrap(), + ); + unsafe { + dap.perform_load(disk_number); + } + + // try to parse FAT file system + let fat_slice = unsafe { + slice::from_raw_parts( + partition_buf as *const u8, + usize::try_from(partition.sector_count).unwrap_or_else(|_| panic!()) * 512, + ) + }; + let boot_sector = fat::BootSector::deserialize(fat_slice); + + // TODO: get root dir + + // TODO: get offset of `second_stage` file + + // TODO: get offset of `kernel-x86_64` file + + // TODO: load `second_stage` file into memory + + // TODO: jump to `second_stage`, pass offset of `kernel-x86_64` and disk number as arguments + + loop {} +} + +fn load_second_stage( + second_stage_start: u32, + second_stage_end: u32, + bootloader_start: u32, + disk_number: u16, +) { + use dap::DiskAddressPacket; + + let file_offset = (second_stage_start - bootloader_start) as u64; + let size = (second_stage_end - second_stage_start) as u32; + + let dap = DiskAddressPacket::new(second_stage_start as u16, file_offset, size); + unsafe { dap.perform_load(disk_number) } +} + +#[no_mangle] +pub extern "C" fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10", in("ax") ax, in("bx") 0); + } +} + +#[no_mangle] +pub extern "C" fn dap_load_failed() -> ! { + err(b'1'); +} + +#[no_mangle] +pub extern "C" fn no_int13h_extensions() -> ! { + err(b'2'); +} + +#[cold] +fn err(code: u8) -> ! { + for &c in b"Err:" { + print_char(c); + } + print_char(code); + loop { + hlt() + } +} + +fn hlt() { + unsafe { + asm!("hlt"); + } +} + +#[panic_handler] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + err(b'P'); +} diff --git a/real_mode/first_stage/src/mbr.rs b/real_mode/first_stage/src/mbr.rs new file mode 100644 index 00000000..4a23abbf --- /dev/null +++ b/real_mode/first_stage/src/mbr.rs @@ -0,0 +1,106 @@ +// Based on https://docs.rs/mbr-nostd + +/// A struct representing an MBR partition table. +pub struct MasterBootRecord { + entries: [PartitionTableEntry; MAX_ENTRIES], +} + +const BUFFER_SIZE: usize = 512; +const TABLE_OFFSET: usize = 446; +const ENTRY_SIZE: usize = 16; +const SUFFIX_BYTES: [u8; 2] = [0x55, 0xaa]; +const MAX_ENTRIES: usize = (BUFFER_SIZE - TABLE_OFFSET - 2) / ENTRY_SIZE; + +impl MasterBootRecord { + /// Parses the MBR table from a raw byte buffer. + + pub fn from_bytes(buffer: &[u8]) -> MasterBootRecord { + if buffer.len() < BUFFER_SIZE { + panic!(); + } else if buffer[BUFFER_SIZE - SUFFIX_BYTES.len()..BUFFER_SIZE] != SUFFIX_BYTES[..] { + panic!(); + } + let mut entries = [PartitionTableEntry::empty(); MAX_ENTRIES]; + for idx in 0..MAX_ENTRIES { + let offset = TABLE_OFFSET + idx * ENTRY_SIZE; + let partition_type = PartitionType::from_mbr_tag_byte(buffer[offset + 4]); + if let PartitionType::Unknown(c) = partition_type { + panic!(); + } + let lba = + u32::from_le_bytes(buffer[offset + 8..].try_into().unwrap_or_else(|_| panic!())); + let len = u32::from_le_bytes( + buffer[offset + 12..] + .try_into() + .unwrap_or_else(|_| panic!()), + ); + entries[idx] = PartitionTableEntry::new(partition_type, lba, len); + } + MasterBootRecord { entries } + } + + pub fn partition_table_entries(&self) -> &[PartitionTableEntry] { + &self.entries[..] + } +} + +/// The type of a particular partition. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub enum PartitionType { + Unused, + Unknown(u8), + Fat12(u8), + Fat16(u8), + Fat32(u8), + LinuxExt(u8), + HfsPlus(u8), + ISO9660(u8), + NtfsExfat(u8), +} + +impl PartitionType { + /// Parses a partition type from the type byte in the MBR's table. + pub fn from_mbr_tag_byte(tag: u8) -> PartitionType { + match tag { + 0x0 => PartitionType::Unused, + 0x01 => PartitionType::Fat12(tag), + 0x04 | 0x06 | 0x0e => PartitionType::Fat16(tag), + 0x0b | 0x0c | 0x1b | 0x1c => PartitionType::Fat32(tag), + 0x83 => PartitionType::LinuxExt(tag), + 0x07 => PartitionType::NtfsExfat(tag), + 0xaf => PartitionType::HfsPlus(tag), + _ => PartitionType::Unknown(tag), + } + } +} + +/// An entry in a partition table. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct PartitionTableEntry { + /// The type of partition in this entry. + pub partition_type: PartitionType, + + /// The index of the first block of this entry. + pub logical_block_address: u32, + + /// The total number of blocks in this entry. + pub sector_count: u32, +} + +impl PartitionTableEntry { + pub fn new( + partition_type: PartitionType, + logical_block_address: u32, + sector_count: u32, + ) -> PartitionTableEntry { + PartitionTableEntry { + partition_type, + logical_block_address, + sector_count, + } + } + + pub fn empty() -> PartitionTableEntry { + PartitionTableEntry::new(PartitionType::Unused, 0, 0) + } +} diff --git a/real_mode/first_stage/x86-16bit.json b/real_mode/first_stage/x86-16bit.json index 35bd2784..b3f34cbc 100644 --- a/real_mode/first_stage/x86-16bit.json +++ b/real_mode/first_stage/x86-16bit.json @@ -1,7 +1,7 @@ { "arch": "x86", "cpu": "i386", - "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", + "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128", "dynamic-linking": false, "executables": true, "linker-flavor": "ld.lld", @@ -16,6 +16,10 @@ "panic-strategy": "abort", "os": "none", "vendor": "unknown", - "relocation_model": "static", - "eliminate_frame_pointer": true -} \ No newline at end of file + "relocation-model": "static", + "pre-link-args": { + "ld.lld": [ + "--script=16-bit-linker.ld" + ] + } +} From 2b7f49e1c1bcecd6e592e2efb9b6e35e2588e848 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 15:01:38 +0100 Subject: [PATCH 054/226] Move rodata to separate section So that we see when it gets big accidentally --- real_mode/first_stage/16-bit-linker.ld | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/real_mode/first_stage/16-bit-linker.ld b/real_mode/first_stage/16-bit-linker.ld index 3f02a46c..a2cb69e8 100644 --- a/real_mode/first_stage/16-bit-linker.ld +++ b/real_mode/first_stage/16-bit-linker.ld @@ -15,6 +15,10 @@ SECTIONS { { *(.text .text.*) } + .rodata : + { + *(.rodata .rodata.*) + } .data : { *(.rodata .rodata.*) From e918cc1cd6c7cb1faa32f5f9e6dffe949c7bacc4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 15:01:58 +0100 Subject: [PATCH 055/226] Add partition table to output file --- real_mode/first_stage/16-bit-linker.ld | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/real_mode/first_stage/16-bit-linker.ld b/real_mode/first_stage/16-bit-linker.ld index a2cb69e8..5cf5db94 100644 --- a/real_mode/first_stage/16-bit-linker.ld +++ b/real_mode/first_stage/16-bit-linker.ld @@ -27,6 +27,15 @@ SECTIONS { } _mbr_end = .; + . = 0x7c00 + 446; + .partition_table : + { + SHORT(0x0000) /* partition table entry 0 */ + SHORT(0x0000) /* partition table entry 1 */ + SHORT(0x0000) /* partition table entry 2 */ + SHORT(0x0000) /* partition table entry 3 */ + } + . = 0x7c00 + 510; .magic_number : From 708ee38eb96794a3501074add4d76b29ce19b60f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 15:02:10 +0100 Subject: [PATCH 056/226] Use opt level s instead of z --- real_mode/first_stage/Cargo.toml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/real_mode/first_stage/Cargo.toml b/real_mode/first_stage/Cargo.toml index b68e460a..51115870 100644 --- a/real_mode/first_stage/Cargo.toml +++ b/real_mode/first_stage/Cargo.toml @@ -9,7 +9,8 @@ edition = "2021" [dependencies] [profile.release] -opt-level = "z" +opt-level = "s" lto = true codegen-units = 1 + # debug = true From 226e55da27b4db41bd6de8ab193054fa99cc6766 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 15:03:04 +0100 Subject: [PATCH 057/226] Make executable smaller and improve screen output --- real_mode/first_stage/src/boot.s | 4 +- real_mode/first_stage/src/dap.rs | 7 ++- real_mode/first_stage/src/fail.rs | 59 +++++++++++++++++++++ real_mode/first_stage/src/fat.rs | 12 +---- real_mode/first_stage/src/main.rs | 71 +++++++++---------------- real_mode/first_stage/src/mbr.rs | 88 ++++++++++++++++++++++++------- 6 files changed, 164 insertions(+), 77 deletions(-) create mode 100644 real_mode/first_stage/src/fail.rs diff --git a/real_mode/first_stage/src/boot.s b/real_mode/first_stage/src/boot.s index 8f7735a0..f4ca1330 100644 --- a/real_mode/first_stage/src/boot.s +++ b/real_mode/first_stage/src/boot.s @@ -65,11 +65,13 @@ unreal_mode: mov word ptr ds:[eax], bx check_int13h_extensions: + push 'y' # error code mov ah, 0x41 mov bx, 0x55aa # dl contains drive number int 0x13 - jc no_int13h_extensions + jc fail + pop ax # pop error code again rust: # push arguments diff --git a/real_mode/first_stage/src/dap.rs b/real_mode/first_stage/src/dap.rs index 0cdfae14..35ce6fe6 100644 --- a/real_mode/first_stage/src/dap.rs +++ b/real_mode/first_stage/src/dap.rs @@ -41,10 +41,13 @@ impl DiskAddressPacket { pub unsafe fn perform_load(&self, disk_number: u16) { let self_addr = self as *const Self as u16; - asm!("mov {1:x}, si", + asm!( + "push 0x7a", // error code `z`, passed to `fail` on error + "mov {1:x}, si", "mov si, {0:x}", "int 0x13", - "jc dap_load_failed", + "jc fail", + "pop si", // remove error code again "mov si, {1:x}", in(reg) self_addr, out(reg) _, diff --git a/real_mode/first_stage/src/fail.rs b/real_mode/first_stage/src/fail.rs new file mode 100644 index 00000000..17fc1ee0 --- /dev/null +++ b/real_mode/first_stage/src/fail.rs @@ -0,0 +1,59 @@ +use core::arch::asm; + +pub trait UnwrapOrFail { + type Out; + + fn unwrap_or_fail(self, code: u8) -> Self::Out; +} + +impl UnwrapOrFail for Option { + type Out = T; + + fn unwrap_or_fail(self, code: u8) -> Self::Out { + match self { + Some(v) => v, + None => fail(code), + } + } +} + +impl UnwrapOrFail for Result { + type Out = T; + + fn unwrap_or_fail(self, code: u8) -> Self::Out { + match self { + Ok(v) => v, + Err(_) => fail(code), + } + } +} + +#[no_mangle] +pub extern "C" fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10", in("ax") ax, in("bx") 0); + } +} + +#[cold] +#[inline(never)] +#[no_mangle] +pub extern "C" fn fail(code: u8) -> ! { + print_char(b'!'); + print_char(code); + loop { + hlt() + } +} + +fn hlt() { + unsafe { + asm!("hlt"); + } +} + +#[panic_handler] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + fail(b'P'); +} diff --git a/real_mode/first_stage/src/fat.rs b/real_mode/first_stage/src/fat.rs index 30f35a64..41fc0ca4 100644 --- a/real_mode/first_stage/src/fat.rs +++ b/real_mode/first_stage/src/fat.rs @@ -1,5 +1,7 @@ // based on https://github.com/rafalh/rust-fatfs/ +use super::split_array_ref; + pub(crate) struct BootSector { bootjmp: [u8; 3], oem_name: [u8; 8], @@ -157,13 +159,3 @@ impl BiosParameterBlock { self.sectors_per_fat_16 == 0 } } - -/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 -/// -/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, -/// see https://github.com/rust-lang/rust/issues/90091 -fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { - let (a, b) = slice.split_at(N); - // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) - unsafe { (&*(a.as_ptr() as *const [T; N]), b) } -} diff --git a/real_mode/first_stage/src/main.rs b/real_mode/first_stage/src/main.rs index b39bfb2b..fdb44033 100644 --- a/real_mode/first_stage/src/main.rs +++ b/real_mode/first_stage/src/main.rs @@ -5,11 +5,13 @@ use core::{ arch::{asm, global_asm}, slice, }; +use fail::{fail, print_char, UnwrapOrFail}; use mbr::MasterBootRecord; global_asm!(include_str!("boot.s")); mod dap; +mod fail; mod fat; mod mbr; @@ -23,34 +25,38 @@ fn mbr_start() -> *const u8 { #[no_mangle] pub extern "C" fn first_stage(disk_number: u16) { + print_char(b'1'); let bytes = &unsafe { slice::from_raw_parts(mbr_start(), 512) }; - let mbr = MasterBootRecord::from_bytes(bytes); + let partition = mbr::get_partition(bytes, 0); - let partition = mbr - .partition_table_entries() - .get(0) - .unwrap_or_else(|| panic!()); - - let partition_buf = u16::try_from(mbr_start() as usize).unwrap_or_else(|_| panic!()) + 512; + print_char(b'2'); + let partition_buf = u16::try_from(mbr_start() as usize).unwrap_or_fail(b'a') + 512; // load first partition into buffer // TODO: only load headers let dap = dap::DiskAddressPacket::from_lba( partition_buf, partition.logical_block_address.into(), - partition.sector_count.try_into().unwrap(), + partition.sector_count.try_into().unwrap_or_fail(b'b'), ); unsafe { dap.perform_load(disk_number); } + if partition.sector_count == 0 { + fail(b'c'); + } + + print_char(b'3'); // try to parse FAT file system let fat_slice = unsafe { slice::from_raw_parts( partition_buf as *const u8, - usize::try_from(partition.sector_count).unwrap_or_else(|_| panic!()) * 512, + usize::try_from(partition.sector_count).unwrap_or_else(|_| fail(b'a')) * 512, ) }; + + print_char(b'4'); let boot_sector = fat::BootSector::deserialize(fat_slice); // TODO: get root dir @@ -81,42 +87,15 @@ fn load_second_stage( unsafe { dap.perform_load(disk_number) } } -#[no_mangle] -pub extern "C" fn print_char(c: u8) { - let ax = u16::from(c) | 0x0e00; - unsafe { - asm!("int 0x10", in("ax") ax, in("bx") 0); - } -} - -#[no_mangle] -pub extern "C" fn dap_load_failed() -> ! { - err(b'1'); -} - -#[no_mangle] -pub extern "C" fn no_int13h_extensions() -> ! { - err(b'2'); -} - -#[cold] -fn err(code: u8) -> ! { - for &c in b"Err:" { - print_char(c); - } - print_char(code); - loop { - hlt() +/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 +/// +/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, +/// see https://github.com/rust-lang/rust/issues/90091 +fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { + if N > slice.len() { + fail(b'S'); } -} - -fn hlt() { - unsafe { - asm!("hlt"); - } -} - -#[panic_handler] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - err(b'P'); + let (a, b) = slice.split_at(N); + // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) + unsafe { (&*(a.as_ptr() as *const [T; N]), b) } } diff --git a/real_mode/first_stage/src/mbr.rs b/real_mode/first_stage/src/mbr.rs index 4a23abbf..baa3d7fe 100644 --- a/real_mode/first_stage/src/mbr.rs +++ b/real_mode/first_stage/src/mbr.rs @@ -1,5 +1,36 @@ // Based on https://docs.rs/mbr-nostd +use super::fail::{fail, UnwrapOrFail}; + +pub fn get_partition(buffer: &[u8], index: usize) -> PartitionTableEntry { + if buffer.len() < BUFFER_SIZE { + fail(b'a'); + } else if buffer.get(BUFFER_SIZE - SUFFIX_BYTES.len()..BUFFER_SIZE) != Some(&SUFFIX_BYTES[..]) { + fail(b'b'); + } + + let offset = TABLE_OFFSET + index * ENTRY_SIZE; + let buffer = buffer.get(offset..).unwrap_or_fail(b'c'); + + let partition_type = *buffer.get(4).unwrap_or_fail(b'd'); + + let lba = u32::from_le_bytes( + buffer + .get(8..) + .and_then(|s| s.get(..4)) + .and_then(|s| s.try_into().ok()) + .unwrap_or_fail(b'e'), + ); + let len = u32::from_le_bytes( + buffer + .get(12..) + .and_then(|s| s.get(..4)) + .and_then(|s| s.try_into().ok()) + .unwrap_or_fail(b'f'), + ); + PartitionTableEntry::new(partition_type, lba, len) +} + /// A struct representing an MBR partition table. pub struct MasterBootRecord { entries: [PartitionTableEntry; MAX_ENTRIES], @@ -16,26 +47,38 @@ impl MasterBootRecord { pub fn from_bytes(buffer: &[u8]) -> MasterBootRecord { if buffer.len() < BUFFER_SIZE { - panic!(); - } else if buffer[BUFFER_SIZE - SUFFIX_BYTES.len()..BUFFER_SIZE] != SUFFIX_BYTES[..] { - panic!(); + fail(b'1'); + } else if buffer.get(BUFFER_SIZE - SUFFIX_BYTES.len()..BUFFER_SIZE) + != Some(&SUFFIX_BYTES[..]) + { + fail(b'2'); } let mut entries = [PartitionTableEntry::empty(); MAX_ENTRIES]; + for idx in 0..MAX_ENTRIES { let offset = TABLE_OFFSET + idx * ENTRY_SIZE; - let partition_type = PartitionType::from_mbr_tag_byte(buffer[offset + 4]); - if let PartitionType::Unknown(c) = partition_type { - panic!(); - } - let lba = - u32::from_le_bytes(buffer[offset + 8..].try_into().unwrap_or_else(|_| panic!())); + let buffer = buffer.get(offset..).unwrap_or_fail(b'8'); + + let partition_type = *buffer.get(4).unwrap_or_fail(b'4'); + + let lba = u32::from_le_bytes( + buffer + .get(8..) + .and_then(|s| s.get(..4)) + .and_then(|s| s.try_into().ok()) + .unwrap_or_fail(b'5'), + ); let len = u32::from_le_bytes( - buffer[offset + 12..] - .try_into() - .unwrap_or_else(|_| panic!()), + buffer + .get(12..) + .and_then(|s| s.get(..4)) + .and_then(|s| s.try_into().ok()) + .unwrap_or_fail(b'6'), ); - entries[idx] = PartitionTableEntry::new(partition_type, lba, len); + *entries.get_mut(idx).unwrap_or_fail(b'7') = + PartitionTableEntry::new(partition_type, lba, len); } + MasterBootRecord { entries } } @@ -45,7 +88,7 @@ impl MasterBootRecord { } /// The type of a particular partition. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq)] pub enum PartitionType { Unused, Unknown(u8), @@ -72,13 +115,22 @@ impl PartitionType { _ => PartitionType::Unknown(tag), } } + + pub fn known_type(tag: u8) -> bool { + match tag { + 0x0 | 0x01 | 0x04 | 0x06 | 0x0e | 0x0b | 0x0c | 0x1b | 0x1c | 0x83 | 0x07 | 0xaf => { + true + } + _ => false, + } + } } /// An entry in a partition table. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Copy, Clone, Eq, PartialEq)] pub struct PartitionTableEntry { /// The type of partition in this entry. - pub partition_type: PartitionType, + pub partition_type: u8, /// The index of the first block of this entry. pub logical_block_address: u32, @@ -89,7 +141,7 @@ pub struct PartitionTableEntry { impl PartitionTableEntry { pub fn new( - partition_type: PartitionType, + partition_type: u8, logical_block_address: u32, sector_count: u32, ) -> PartitionTableEntry { @@ -101,6 +153,6 @@ impl PartitionTableEntry { } pub fn empty() -> PartitionTableEntry { - PartitionTableEntry::new(PartitionType::Unused, 0, 0) + PartitionTableEntry::new(0, 0, 0) } } From a33301016f6b54f73275e64f487114cca57ab8ec Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 15:06:38 +0100 Subject: [PATCH 058/226] Add a basic Readme with build instructions --- real_mode/first_stage/README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 real_mode/first_stage/README.md diff --git a/real_mode/first_stage/README.md b/real_mode/first_stage/README.md new file mode 100644 index 00000000..5c9cf4e6 --- /dev/null +++ b/real_mode/first_stage/README.md @@ -0,0 +1,16 @@ +# First Stage: Bootsector + +This executable needs to fit into the 512-byte boot sector, so we need to use all kinds of tricks to keep the size down. + +## Build Commands + +1. `cargo build --release -Zbuild-std=core --target x86-16bit.json -Zbuild-std-features=compiler-builtins-mem` +2. `objcopy -I elf32-i386 -O binary target/x86-16bit/release/first_stage target/disk_image.bin + +To run in QEMU: + +- `qemu-system-x86_64 -drive format=raw,file=target/disk_image.bin` + +To print the contents of the ELF file, e.g. for trying to bring the size down: + +- `objdump -xsdS -M i8086,intel target/x86-16bit/release/first_stage` From f6c3d68b0a1488cdc97ebf2e76cdc0f3eecc1f00 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 15:24:07 +0100 Subject: [PATCH 059/226] Remove unreal mode code from `boot.s` We can do this later in the second stage. --- real_mode/first_stage/src/boot.s | 60 -------------------------------- 1 file changed, 60 deletions(-) diff --git a/real_mode/first_stage/src/boot.s b/real_mode/first_stage/src/boot.s index f4ca1330..992ce693 100644 --- a/real_mode/first_stage/src/boot.s +++ b/real_mode/first_stage/src/boot.s @@ -30,40 +30,6 @@ enable_a20: out 0x92, al enable_a20_after: -enter_protected_mode: - # clear interrupts - cli - push ds - push es - - lgdt [gdt32info] - - mov eax, cr0 - or al, 1 # set protected mode bit - mov cr0, eax - - jmp protected_mode # tell 386/486 to not crash - -protected_mode: - mov bx, 0x10 - mov ds, bx # set data segment - mov es, bx # set extra segment - - and al, 0xfe # clear protected mode bit - mov cr0, eax - -unreal_mode: - pop es # get back old extra segment - pop ds # get back old data segment - sti - - # back to real mode, but internal data segment register is still loaded - # with gdt segment -> we can access the full 4GiB of memory - - mov bx, 0x0f01 # attrib/char of smiley - mov eax, 0xb8f00 # note 32 bit offset - mov word ptr ds:[eax], bx - check_int13h_extensions: push 'y' # error code mov ah, 0x41 @@ -82,29 +48,3 @@ spin: hlt jmp spin -gdt32info: - .word gdt32_end - gdt32 - 1 # last byte in table - .word gdt32 # start of table - -gdt32: - # entry 0 is always unused - .quad 0 -codedesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x9a - .byte 0xcf - .byte 0 -datadesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x92 - .byte 0xcf - .byte 0 -gdt32_end: From a253b3f4eca8f35cfbeace0f74f71b2c74cfbd19 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 15:49:15 +0100 Subject: [PATCH 060/226] Start integrating first-stage executable into bootloader --- Cargo.lock | 4 + Cargo.toml | 10 ++ {real_mode => bios}/first_stage/.gitignore | 0 .../first_stage/16-bit-linker.ld | 0 {real_mode => bios}/first_stage/Cargo.toml | 9 +- {real_mode => bios}/first_stage/README.md | 0 bios/first_stage/src/boot unreal.s | 110 ++++++++++++++++++ {real_mode => bios}/first_stage/src/boot.s | 0 {real_mode => bios}/first_stage/src/dap.rs | 0 {real_mode => bios}/first_stage/src/fail.rs | 0 {real_mode => bios}/first_stage/src/fat.rs | 0 {real_mode => bios}/first_stage/src/main.rs | 0 {real_mode => bios}/first_stage/src/mbr.rs | 0 .../first_stage/x86-16bit.json | 2 +- real_mode/first_stage/Cargo.lock | 7 -- 15 files changed, 126 insertions(+), 16 deletions(-) rename {real_mode => bios}/first_stage/.gitignore (100%) rename {real_mode => bios}/first_stage/16-bit-linker.ld (100%) rename {real_mode => bios}/first_stage/Cargo.toml (67%) rename {real_mode => bios}/first_stage/README.md (100%) create mode 100644 bios/first_stage/src/boot unreal.s rename {real_mode => bios}/first_stage/src/boot.s (100%) rename {real_mode => bios}/first_stage/src/dap.rs (100%) rename {real_mode => bios}/first_stage/src/fail.rs (100%) rename {real_mode => bios}/first_stage/src/fat.rs (100%) rename {real_mode => bios}/first_stage/src/main.rs (100%) rename {real_mode => bios}/first_stage/src/mbr.rs (100%) rename {real_mode => bios}/first_stage/x86-16bit.json (92%) delete mode 100644 real_mode/first_stage/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index 6369986d..a14b6a40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,10 @@ dependencies = [ "rand", ] +[[package]] +name = "bootloader_first_stage" +version = "0.1.0" + [[package]] name = "build_const" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index c3dedc5e..fb28a453 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" [workspace] members = [ "api", + "bios/first_stage", "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", @@ -103,6 +104,15 @@ lto = false debug = true overflow-checks = true +[profile.first-stage] +inherits = "release" +opt-level = "s" +lto = true +codegen-units = 1 +debug = false +overflow-checks = false + + [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" diff --git a/real_mode/first_stage/.gitignore b/bios/first_stage/.gitignore similarity index 100% rename from real_mode/first_stage/.gitignore rename to bios/first_stage/.gitignore diff --git a/real_mode/first_stage/16-bit-linker.ld b/bios/first_stage/16-bit-linker.ld similarity index 100% rename from real_mode/first_stage/16-bit-linker.ld rename to bios/first_stage/16-bit-linker.ld diff --git a/real_mode/first_stage/Cargo.toml b/bios/first_stage/Cargo.toml similarity index 67% rename from real_mode/first_stage/Cargo.toml rename to bios/first_stage/Cargo.toml index 51115870..df726a5d 100644 --- a/real_mode/first_stage/Cargo.toml +++ b/bios/first_stage/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "first_stage" +name = "bootloader_first_stage" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2021" @@ -7,10 +7,3 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] - -[profile.release] -opt-level = "s" -lto = true -codegen-units = 1 - -# debug = true diff --git a/real_mode/first_stage/README.md b/bios/first_stage/README.md similarity index 100% rename from real_mode/first_stage/README.md rename to bios/first_stage/README.md diff --git a/bios/first_stage/src/boot unreal.s b/bios/first_stage/src/boot unreal.s new file mode 100644 index 00000000..9aa5405b --- /dev/null +++ b/bios/first_stage/src/boot unreal.s @@ -0,0 +1,110 @@ +.section .boot, "awx" +.global _start +.code16 + +# This stage initializes the stack, enables the A20 line + +_start: + # zero segment registers + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # clear the direction flag (e.g. go forward in memory when using + # instructions like lodsb) + cld + + # initialize stack + mov sp, 0x7c00 + +enable_a20: + # enable A20-Line via IO-Port 92, might not work on all motherboards + in al, 0x92 + test al, 2 + jnz enable_a20_after + or al, 2 + and al, 0xFE + out 0x92, al +enable_a20_after: + +enter_protected_mode: + # clear interrupts + cli + push ds + push es + + lgdt [gdt32info] + + mov eax, cr0 + or al, 1 # set protected mode bit + mov cr0, eax + + jmp protected_mode # tell 386/486 to not crash + +protected_mode: + mov bx, 0x10 + mov ds, bx # set data segment + mov es, bx # set extra segment + + and al, 0xfe # clear protected mode bit + mov cr0, eax + +unreal_mode: + pop es # get back old extra segment + pop ds # get back old data segment + sti + + # back to real mode, but internal data segment register is still loaded + # with gdt segment -> we can access the full 4GiB of memory + + mov bx, 0x0f02 # attrib/char of smiley + mov eax, 0xb8f00 # note 32 bit offset + mov word ptr ds:[eax], bx + +check_int13h_extensions: + push 'y' # error code + mov ah, 0x41 + mov bx, 0x55aa + # dl contains drive number + int 0x13 + jc fail + pop ax # pop error code again + +rust: + # push arguments + push dx # disk number + call first_stage + +spin: + hlt + jmp spin + +gdt32info: + .word gdt32_end - gdt32 - 1 # last byte in table + .word gdt32 # start of table + +gdt32: + # entry 0 is always unused + .quad 0 +codedesc: + .byte 0xff + .byte 0xff + .byte 0 + .byte 0 + .byte 0 + .byte 0x9a + .byte 0xcf + .byte 0 +datadesc: + .byte 0xff + .byte 0xff + .byte 0 + .byte 0 + .byte 0 + .byte 0x92 + .byte 0xcf + .byte 0 +gdt32_end: diff --git a/real_mode/first_stage/src/boot.s b/bios/first_stage/src/boot.s similarity index 100% rename from real_mode/first_stage/src/boot.s rename to bios/first_stage/src/boot.s diff --git a/real_mode/first_stage/src/dap.rs b/bios/first_stage/src/dap.rs similarity index 100% rename from real_mode/first_stage/src/dap.rs rename to bios/first_stage/src/dap.rs diff --git a/real_mode/first_stage/src/fail.rs b/bios/first_stage/src/fail.rs similarity index 100% rename from real_mode/first_stage/src/fail.rs rename to bios/first_stage/src/fail.rs diff --git a/real_mode/first_stage/src/fat.rs b/bios/first_stage/src/fat.rs similarity index 100% rename from real_mode/first_stage/src/fat.rs rename to bios/first_stage/src/fat.rs diff --git a/real_mode/first_stage/src/main.rs b/bios/first_stage/src/main.rs similarity index 100% rename from real_mode/first_stage/src/main.rs rename to bios/first_stage/src/main.rs diff --git a/real_mode/first_stage/src/mbr.rs b/bios/first_stage/src/mbr.rs similarity index 100% rename from real_mode/first_stage/src/mbr.rs rename to bios/first_stage/src/mbr.rs diff --git a/real_mode/first_stage/x86-16bit.json b/bios/first_stage/x86-16bit.json similarity index 92% rename from real_mode/first_stage/x86-16bit.json rename to bios/first_stage/x86-16bit.json index b3f34cbc..50984651 100644 --- a/real_mode/first_stage/x86-16bit.json +++ b/bios/first_stage/x86-16bit.json @@ -19,7 +19,7 @@ "relocation-model": "static", "pre-link-args": { "ld.lld": [ - "--script=16-bit-linker.ld" + "--script=bios/first_stage/16-bit-linker.ld" ] } } diff --git a/real_mode/first_stage/Cargo.lock b/real_mode/first_stage/Cargo.lock deleted file mode 100644 index 7b427282..00000000 --- a/real_mode/first_stage/Cargo.lock +++ /dev/null @@ -1,7 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "first_stage" -version = "0.1.0" From a28bbf161bfada5bc8cf12de43a54ef17cbe92b2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 18:11:28 +0100 Subject: [PATCH 061/226] Only load first sector of partition --- bios/first_stage/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bios/first_stage/src/main.rs b/bios/first_stage/src/main.rs index fdb44033..14e51370 100644 --- a/bios/first_stage/src/main.rs +++ b/bios/first_stage/src/main.rs @@ -37,7 +37,7 @@ pub extern "C" fn first_stage(disk_number: u16) { let dap = dap::DiskAddressPacket::from_lba( partition_buf, partition.logical_block_address.into(), - partition.sector_count.try_into().unwrap_or_fail(b'b'), + 1, // partition.sector_count.try_into().unwrap_or_fail(b'b'), ); unsafe { dap.perform_load(disk_number); From 9583eafafe7aaa5282d4739006fc7172d8d1552e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 29 Dec 2021 18:11:55 +0100 Subject: [PATCH 062/226] Create BIOS disk image from first stage with single FAT partition --- src/bin/builder.rs | 12 +++---- src/disk_image.rs | 86 +++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/bin/builder.rs b/src/bin/builder.rs index 37d53f7b..64edc13b 100644 --- a/src/bin/builder.rs +++ b/src/bin/builder.rs @@ -156,12 +156,9 @@ fn main() -> anyhow::Result<()> { if args.firmware.bios() { let mut cmd = Command::new(env!("CARGO")); - cmd.arg("build").arg("--bin").arg("bios"); - cmd.arg("--profile").arg("release"); - cmd.arg("-Z").arg("unstable-options"); - cmd.arg("--target").arg("x86_64-bootloader.json"); - cmd.arg("--features") - .arg(args.features.join(" ") + " bios_bin"); + cmd.arg("build").arg("-p").arg("bootloader_first_stage"); + cmd.arg("--profile").arg("first-stage"); + cmd.arg("--target").arg("bios/first_stage/x86-16bit.json"); cmd.arg("-Zbuild-std=core"); cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); if let Some(target_dir) = &args.target_dir { @@ -170,7 +167,6 @@ fn main() -> anyhow::Result<()> { if args.quiet { cmd.arg("--quiet"); } - cmd.env("RUSTFLAGS", "-C opt-level=s"); assert!(cmd.status()?.success()); // Retrieve binary paths @@ -201,7 +197,7 @@ fn main() -> anyhow::Result<()> { .unwrap() .join(format!("boot-{}-{}.img", executable_name, kernel_name)); - create_disk_image(&executable_path, &output_bin_path) + create_disk_image(&executable_path, &output_bin_path, &args.kernel_binary) .context("Failed to create bootable disk image")?; if let Some(out_dir) = &args.out_dir { diff --git a/src/disk_image.rs b/src/disk_image.rs index 7b8e3610..6fb32598 100644 --- a/src/disk_image.rs +++ b/src/disk_image.rs @@ -1,17 +1,25 @@ -use std::{io, path::Path, process::Command}; +use anyhow::Context; +use std::{ + fs, + io::{self, Seek, Write}, + path::Path, + process::Command, +}; use thiserror::Error; /// Creates a bootable disk image from the given bootloader executable. pub fn create_disk_image( bootloader_elf_path: &Path, output_bin_path: &Path, -) -> Result<(), DiskImageError> { - let llvm_tools = llvm_tools::LlvmTools::new()?; + kernel_binary: &Path, +) -> anyhow::Result<()> { + let llvm_tools = + llvm_tools::LlvmTools::new().map_err(|err| anyhow::anyhow!("failed to get llvm tools"))?; let objcopy = llvm_tools .tool(&llvm_tools::exe("llvm-objcopy")) .ok_or(DiskImageError::LlvmObjcopyNotFound)?; - // convert bootloader to binary + // convert first stage to binary let mut cmd = Command::new(objcopy); cmd.arg("-I").arg("elf64-x86-64"); cmd.arg("-O").arg("binary"); @@ -25,9 +33,77 @@ pub fn create_disk_image( if !output.status.success() { return Err(DiskImageError::ObjcopyFailed { stderr: output.stderr, - }); + }) + .context("objcopy failed"); } + use std::fs::OpenOptions; + let mut disk_image = OpenOptions::new() + .write(true) + .open(&output_bin_path) + .map_err(|err| DiskImageError::Io { + message: "failed to open boot image", + error: err, + })?; + let file_size = disk_image + .metadata() + .map_err(|err| DiskImageError::Io { + message: "failed to get size of boot image", + error: err, + })? + .len(); + const BLOCK_SIZE: u64 = 512; + assert_eq!(file_size, BLOCK_SIZE); + + let kernel_size = fs::metadata(&kernel_binary) + .context("failed to read metadata of kernel binary")? + .len(); + + // create fat partition + const MB: u64 = 1024 * 1024; + let fat_size = kernel_size; // TODO plus second stage size + let fat_size_padded_and_rounded = ((fat_size + 1024 * 64 - 1) / MB + 1) * MB; + let fat_file_path = { + let fat_path = output_bin_path.with_extension("fat"); + let fat_file = fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(&fat_path) + .context("Failed to create UEFI FAT file")?; + fat_file + .set_len(fat_size_padded_and_rounded) + .context("failed to set UEFI FAT file length")?; + + // create new FAT partition + let format_options = fatfs::FormatVolumeOptions::new().volume_label(*b"BOOT "); + fatfs::format_volume(&fat_file, format_options) + .context("Failed to format UEFI FAT file")?; + + // copy kernel to FAT filesystem + let partition = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new()) + .context("Failed to open FAT file system of UEFI FAT file")?; + let root_dir = partition.root_dir(); + let mut kernel_file = root_dir.create_file("kernel-x86_64")?; + kernel_file.truncate()?; + io::copy(&mut fs::File::open(&kernel_binary)?, &mut kernel_file)?; + + fat_path + }; + + disk_image.seek(io::SeekFrom::Start(446))?; + disk_image.write_all(&[0x80, 0, 0, 0, 0x04, 0, 0, 0])?; + let start_sector = 1u32.to_le_bytes(); + let size_sectors = u32::try_from(&fat_size_padded_and_rounded / 512) + .unwrap() + .to_le_bytes(); + disk_image.write_all(&start_sector)?; + disk_image.write_all(&size_sectors)?; + + disk_image.seek(io::SeekFrom::Start(512))?; + io::copy(&mut fs::File::open(&kernel_binary)?, &mut disk_image)?; + pad_to_nearest_block_size(output_bin_path)?; Ok(()) } From 8fb5eff0de2f776979271b771e78c94af752135e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Tue, 1 Mar 2022 23:25:19 +0100 Subject: [PATCH 063/226] Restructure into sub-crates --- .cargo/config.toml | 3 - .vscode/settings.json | 7 +- Cargo.lock | 73 ++++++++++++------ Cargo.toml | 77 +++---------------- bios/Cargo.toml | 14 ++++ {src => bios/src}/asm/e820.s | 0 {src => bios/src}/asm/stage_1.s | 0 {src => bios/src}/asm/stage_2.s | 0 {src => bios/src}/asm/stage_3.s | 0 {src => bios/src}/asm/vesa.s | 0 src/bin/bios.rs => bios/src/main.rs | 77 ++++++++----------- .../bios => bios/src}/memory_descriptor.rs | 3 +- common/Cargo.toml | 21 +++++ {src/binary => common/src}/gdt.rs | 0 .../src}/legacy_memory_region.rs | 7 +- {src/binary => common/src}/level_4_entries.rs | 0 src/binary/mod.rs => common/src/lib.rs | 22 +++++- {src/binary => common/src}/load_kernel.rs | 2 +- {src/binary => common/src}/logger.rs | 0 src/binary/bios/mod.rs | 2 - src/binary/uefi/mod.rs | 1 - src/lib.rs | 26 ------- src/{bin/builder.rs => main.rs} | 0 uefi/Cargo.toml | 13 ++++ uefi/README.md | 7 ++ src/bin/uefi.rs => uefi/src/main.rs | 35 +++------ .../uefi => uefi/src}/memory_descriptor.rs | 13 ++-- 27 files changed, 199 insertions(+), 204 deletions(-) create mode 100644 bios/Cargo.toml rename {src => bios/src}/asm/e820.s (100%) rename {src => bios/src}/asm/stage_1.s (100%) rename {src => bios/src}/asm/stage_2.s (100%) rename {src => bios/src}/asm/stage_3.s (100%) rename {src => bios/src}/asm/vesa.s (100%) rename src/bin/bios.rs => bios/src/main.rs (80%) rename {src/binary/bios => bios/src}/memory_descriptor.rs (86%) create mode 100644 common/Cargo.toml rename {src/binary => common/src}/gdt.rs (100%) rename {src/binary => common/src}/legacy_memory_region.rs (96%) rename {src/binary => common/src}/level_4_entries.rs (100%) rename src/binary/mod.rs => common/src/lib.rs (96%) rename {src/binary => common/src}/load_kernel.rs (99%) rename {src/binary => common/src}/logger.rs (100%) delete mode 100644 src/binary/bios/mod.rs delete mode 100644 src/binary/uefi/mod.rs rename src/{bin/builder.rs => main.rs} (100%) create mode 100644 uefi/Cargo.toml create mode 100644 uefi/README.md rename src/bin/uefi.rs => uefi/src/main.rs (93%) rename {src/binary/uefi => uefi/src}/memory_descriptor.rs (56%) diff --git a/.cargo/config.toml b/.cargo/config.toml index 7e0eed7a..7f3f182e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,2 @@ [target.x86_64-unknown-uefi] runner = "cargo run -p runner" - -[alias] -builder = "run --bin builder --features builder --quiet --" diff --git a/.vscode/settings.json b/.vscode/settings.json index 2a201078..d7489c73 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,15 +5,12 @@ ], "rust-analyzer.cargo.target": "x86_64-unknown-uefi", "editor.formatOnSave": true, - "rust-analyzer.cargo.features": [ - "uefi_bin" - ], "rust-analyzer.checkOnSave.enable": true, "rust-analyzer.checkOnSave.overrideCommand": [ "cargo", "c", - "--features", - "uefi_bin", + "-p", + "bootloader-x86_64-uefi", "--target", "x86_64-unknown-uefi", "-Zbuild-std=core", diff --git a/Cargo.lock b/Cargo.lock index a14b6a40..2bba218a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -61,27 +61,16 @@ version = "0.10.10" dependencies = [ "anyhow", "argh", - "bit_field", - "bootloader_api", - "conquer-once", "displaydoc", "fatfs", - "font8x8", "gpt", "json", "llvm-tools", - "log", "proc-macro2", "quote", - "rsdp", "serde", - "spinning_top", "thiserror", "toml", - "uefi", - "usize_conversions", - "x86_64", - "xmas-elf", ] [[package]] @@ -93,6 +82,44 @@ dependencies = [ "json", ] +[[package]] +name = "bootloader-x86_64-bios" +version = "0.1.0" +dependencies = [ + "bootloader-x86_64-common", + "bootloader_api", + "log", + "rsdp", + "usize_conversions", + "x86_64", +] + +[[package]] +name = "bootloader-x86_64-common" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "conquer-once", + "font8x8", + "log", + "spinning_top", + "uefi", + "usize_conversions", + "x86_64", + "xmas-elf", +] + +[[package]] +name = "bootloader-x86_64-uefi" +version = "0.1.0" +dependencies = [ + "bootloader-x86_64-common", + "bootloader_api", + "log", + "uefi", + "x86_64", +] + [[package]] name = "bootloader_api" version = "0.1.0" @@ -137,18 +164,18 @@ dependencies = [ [[package]] name = "conquer-once" -version = "0.2.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96eb12fb69466716fbae9009d389e6a30830ae8975e170eff2d2cff579f9efa3" +checksum = "7c6d3a9775a69f6d1fe2cc888999b67ed30257d3da4d2af91984e722f2ec918a" dependencies = [ "conquer-util", ] [[package]] name = "conquer-util" -version = "0.2.0" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654fb2472cc369d311c547103a1fa81d467bef370ae7a0680f65939895b1182a" +checksum = "e763eef8846b13b380f37dfecda401770b0ca4e56e95170237bd7c25c7db3582" [[package]] name = "crc" @@ -184,9 +211,9 @@ dependencies = [ [[package]] name = "font8x8" -version = "0.2.7" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e63201c624b8c8883921b1a1accc8916c4fa9dbfb15d122b26e4dde945b86bbf" +checksum = "875488b8711a968268c7cf5d139578713097ca4635a76044e8fe8eedf831d07e" [[package]] name = "getrandom" @@ -350,9 +377,9 @@ dependencies = [ [[package]] name = "rsdp" -version = "1.1.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4f3deb5b80701c91a04117876f7d44996b2da123a822054166180e970f5226" +checksum = "66d3add2fc55ef37511bcf81a08ee7a09eff07b23aae38b06a29024a38c604b1" dependencies = [ "log", ] @@ -582,9 +609,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "x86_64" -version = "0.14.7" +version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb611915c917c6296d11e23f71ff1ecfe49c5766daba92cd3df52df6b58285b6" +checksum = "958ab3202b01bc43ba2eb832102c4a487ed93151667a2289062e5f2b00058be2" dependencies = [ "bit_field", "bitflags", @@ -593,9 +620,9 @@ dependencies = [ [[package]] name = "xmas-elf" -version = "0.6.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22678df5df766e8d1e5d609da69f0c3132d794edf6ab5e75e7abcd2270d4cf58" +checksum = "8d29b4d8e7beaceb4e77447ba941a7600d23d0319ab52da0461abea214832d5a" dependencies = [ "zero", ] diff --git a/Cargo.toml b/Cargo.toml index fb28a453..1b799e59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,9 @@ edition = "2021" [workspace] members = [ "api", + "common", + "uefi", + "bios", "bios/first_stage", "tests/runner", "tests/test_kernels/default_settings", @@ -18,46 +21,16 @@ members = [ ] exclude = ["examples/basic", "examples/test_framework"] -[[bin]] -name = "builder" -required-features = ["builder"] - -[[bin]] -name = "bios" -required-features = ["bios_bin"] - -[[bin]] -name = "uefi" -required-features = ["uefi_bin"] [dependencies] -bootloader_api = { path = "api", version = "0.1.0" } -xmas-elf = { version = "0.6.2", optional = true } -x86_64 = { version = "0.14.7", optional = true, default-features = false, features = [ - "instructions", - "inline_asm", -] } -usize_conversions = { version = "0.2.0", optional = true } -bit_field = { version = "0.10.0", optional = true } -log = { version = "0.4.8", optional = true } -uefi = { version = "0.13.0", optional = true } -argh = { version = "0.1.3", optional = true } -displaydoc = { version = "0.1.7", optional = true } -conquer-once = { version = "0.2.1", optional = true, default-features = false } -spinning_top = { version = "0.2.1", optional = true } -anyhow = { version = "1.0.32", optional = true } -llvm-tools = { version = "0.1.1", optional = true } -thiserror = { version = "1.0.20", optional = true } -json = { version = "0.12.4", optional = true } -rsdp = { version = "1.0.0", optional = true } -fatfs = { version = "0.3.4", optional = true } -gpt = { version = "3.0.0", optional = true } - -[dependencies.font8x8] -version = "0.2.5" -default-features = false -features = ["unicode"] -optional = true +argh = "0.1.3" +displaydoc = "0.1.7" +anyhow = "1.0.32" +llvm-tools = "0.1.1" +thiserror = "1.0.20" +json = "0.12.4" +fatfs = "0.3.4" +gpt = "3.0.0" [build-dependencies] llvm-tools-build = { version = "0.1", optional = true, package = "llvm-tools" } @@ -66,34 +39,6 @@ serde = { version = "1.0", features = ["derive"], optional = true } quote = { version = "1.0", optional = true } proc-macro2 = { version = "1.0", optional = true } -[features] -default = [] -builder = [ - "argh", - "thiserror", - "displaydoc", - "anyhow", - "llvm-tools", - "json", - "fatfs", - "gpt", -] -bios_bin = ["binary", "rsdp"] -uefi_bin = ["binary", "uefi"] -binary = [ - "llvm-tools-build", - "x86_64", - "toml", - "xmas-elf", - "usize_conversions", - "log", - "conquer-once", - "spinning_top", - "serde", - "font8x8", - "quote", - "proc-macro2", -] [profile.dev] panic = "abort" diff --git a/bios/Cargo.toml b/bios/Cargo.toml new file mode 100644 index 00000000..288a49c6 --- /dev/null +++ b/bios/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "bootloader-x86_64-bios" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bootloader_api = { version = "0.1.0", path = "../api" } +bootloader-x86_64-common = { version = "0.1.0", path = "../common" } +log = "0.4.14" +x86_64 = "0.14.8" +usize_conversions = "0.2.0" +rsdp = "2.0.0" diff --git a/src/asm/e820.s b/bios/src/asm/e820.s similarity index 100% rename from src/asm/e820.s rename to bios/src/asm/e820.s diff --git a/src/asm/stage_1.s b/bios/src/asm/stage_1.s similarity index 100% rename from src/asm/stage_1.s rename to bios/src/asm/stage_1.s diff --git a/src/asm/stage_2.s b/bios/src/asm/stage_2.s similarity index 100% rename from src/asm/stage_2.s rename to bios/src/asm/stage_2.s diff --git a/src/asm/stage_3.s b/bios/src/asm/stage_3.s similarity index 100% rename from src/asm/stage_3.s rename to bios/src/asm/stage_3.s diff --git a/src/asm/vesa.s b/bios/src/asm/vesa.s similarity index 100% rename from src/asm/vesa.s rename to bios/src/asm/vesa.s diff --git a/src/bin/bios.rs b/bios/src/main.rs similarity index 80% rename from src/bin/bios.rs rename to bios/src/main.rs index 0c5e9404..78d0b8fb 100644 --- a/src/bin/bios.rs +++ b/bios/src/main.rs @@ -2,12 +2,10 @@ #![no_std] #![no_main] -#[cfg(not(target_os = "none"))] -compile_error!("The bootloader crate must be compiled for the `x86_64-bootloader.json` target"); - -use bootloader::{ - binary::SystemInfo, - boot_info::{FrameBufferInfo, PixelFormat}, +use crate::memory_descriptor::E820MemoryRegion; +use bootloader_api::info::{FrameBufferInfo, PixelFormat}; +use bootloader_x86_64_common::{ + load_and_switch_to_kernel, logger::LOGGER, Kernel, PageTables, SystemInfo, }; use core::{ arch::{asm, global_asm}, @@ -21,12 +19,13 @@ use x86_64::structures::paging::{ }; use x86_64::{PhysAddr, VirtAddr}; -global_asm!(include_str!("../asm/stage_1.s")); -global_asm!(include_str!("../asm/stage_2.s")); -global_asm!(include_str!(concat!(env!("OUT_DIR"), "/vesa_config.s"))); -global_asm!(include_str!("../asm/vesa.s")); -global_asm!(include_str!("../asm/e820.s")); -global_asm!(include_str!("../asm/stage_3.s")); +mod memory_descriptor; + +global_asm!(include_str!("asm/stage_1.s")); +global_asm!(include_str!("asm/stage_2.s")); +global_asm!(include_str!("asm/vesa.s")); +global_asm!(include_str!("asm/e820.s")); +global_asm!(include_str!("asm/stage_3.s")); // values defined in `vesa.s` extern "C" { @@ -76,9 +75,7 @@ fn bootloader_main( memory_map_addr: VirtAddr, memory_map_entry_count: u64, ) -> ! { - use bootloader::binary::{ - bios::memory_descriptor::E820MemoryRegion, legacy_memory_region::LegacyFrameAllocator, - }; + use bootloader_x86_64_common::legacy_memory_region::LegacyFrameAllocator; let e820_memory_map = { let ptr = usize_from(memory_map_addr.as_u64()) as *const E820MemoryRegion; @@ -141,11 +138,11 @@ fn bootloader_main( VBEModeInfo_greenfieldposition, VBEModeInfo_bluefieldposition, ) { - (0, 8, 16) => PixelFormat::RGB, - (16, 8, 0) => PixelFormat::BGR, + (0, 8, 16) => PixelFormat::Rgb, + (16, 8, 0) => PixelFormat::Bgr, (r, g, b) => { error = Some(("invalid rgb field positions", r, g, b)); - PixelFormat::RGB // default to RBG so that we can print something + PixelFormat::Rgb // default to RBG so that we can print something } }, ) @@ -159,10 +156,11 @@ fn bootloader_main( let page_tables = create_page_tables(&mut frame_allocator); - let kernel = { + let kernel_slice = { let ptr = kernel_start.as_u64() as *const u8; unsafe { slice::from_raw_parts(ptr, usize_from(kernel_size)) } }; + let kernel = Kernel::parse(kernel_slice); let system_info = SystemInfo { framebuffer_addr, @@ -170,12 +168,7 @@ fn bootloader_main( rsdp_addr: detect_rsdp(), }; - bootloader::binary::load_and_switch_to_kernel( - kernel, - frame_allocator, - page_tables, - system_info, - ); + load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info); } fn init_logger( @@ -190,7 +183,7 @@ fn init_logger( let ptr = framebuffer_start.as_u64() as *mut u8; let slice = unsafe { slice::from_raw_parts_mut(ptr, framebuffer_size) }; - let info = bootloader::boot_info::FrameBufferInfo { + let info = FrameBufferInfo { byte_len: framebuffer_size, horizontal_resolution, vertical_resolution, @@ -199,15 +192,13 @@ fn init_logger( pixel_format, }; - bootloader::binary::init_logger(slice, info); + bootloader_x86_64_common::init_logger(slice, info); info } /// Creates page table abstraction types for both the bootloader and kernel page tables. -fn create_page_tables( - frame_allocator: &mut impl FrameAllocator, -) -> bootloader::binary::PageTables { +fn create_page_tables(frame_allocator: &mut impl FrameAllocator) -> PageTables { // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 let phys_offset = VirtAddr::new(0); @@ -235,7 +226,7 @@ fn create_page_tables( ) }; - bootloader::binary::PageTables { + PageTables { bootloader: bootloader_page_table, kernel: kernel_page_table, kernel_level_4_frame, @@ -257,32 +248,28 @@ fn detect_rsdp() -> Option { physical_address: usize, size: usize, ) -> PhysicalMapping { - PhysicalMapping { - physical_start: physical_address, - virtual_start: NonNull::new(physical_address as *mut _).unwrap(), - region_length: size, - mapped_length: size, - handler: Self, - } + PhysicalMapping::new( + physical_address, + NonNull::new(physical_address as *mut _).unwrap(), + size, + size, + Self, + ) } - fn unmap_physical_region(&self, _region: &PhysicalMapping) {} + fn unmap_physical_region(_region: &PhysicalMapping) {} } unsafe { Rsdp::search_for_on_bios(IdentityMapped) .ok() - .map(|mapping| PhysAddr::new(mapping.physical_start as u64)) + .map(|mapping| PhysAddr::new(mapping.physical_start() as u64)) } } #[panic_handler] fn panic(info: &PanicInfo) -> ! { - unsafe { - bootloader::binary::logger::LOGGER - .get() - .map(|l| l.force_unlock()) - }; + unsafe { LOGGER.get().map(|l| l.force_unlock()) }; log::error!("{}", info); loop { unsafe { asm!("cli; hlt") }; diff --git a/src/binary/bios/memory_descriptor.rs b/bios/src/memory_descriptor.rs similarity index 86% rename from src/binary/bios/memory_descriptor.rs rename to bios/src/memory_descriptor.rs index fcd80c12..1bb14859 100644 --- a/src/binary/bios/memory_descriptor.rs +++ b/bios/src/memory_descriptor.rs @@ -1,4 +1,5 @@ -use crate::{binary::legacy_memory_region::LegacyMemoryRegion, boot_info::MemoryRegionKind}; +use bootloader_api::info::MemoryRegionKind; +use bootloader_x86_64_common::legacy_memory_region::LegacyMemoryRegion; use x86_64::PhysAddr; impl LegacyMemoryRegion for E820MemoryRegion { diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 00000000..9ae4e87d --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "bootloader-x86_64-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bootloader_api = { version = "0.1.0", path = "../api" } +conquer-once = { version = "0.3.2", default-features = false } +log = "0.4.14" +spinning_top = "0.2.4" +uefi = "0.13" +usize_conversions = "0.2.0" +x86_64 = { version = "0.14.8", default-features = false } +xmas-elf = "0.8.0" + +[dependencies.font8x8] +version = "0.3.1" +default-features = false +features = ["unicode"] diff --git a/src/binary/gdt.rs b/common/src/gdt.rs similarity index 100% rename from src/binary/gdt.rs rename to common/src/gdt.rs diff --git a/src/binary/legacy_memory_region.rs b/common/src/legacy_memory_region.rs similarity index 96% rename from src/binary/legacy_memory_region.rs rename to common/src/legacy_memory_region.rs index b3b0c242..583d095f 100644 --- a/src/binary/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -127,7 +127,6 @@ where } } // some mappings created by the UEFI firmware become usable again at this point - #[cfg(feature = "uefi_bin")] MemoryRegionKind::UnknownUefi(other) => { use uefi::table::boot::MemoryType as M; match M(other) { @@ -152,7 +151,11 @@ where } let initialized = &mut regions[..next_index]; - unsafe { MaybeUninit::slice_assume_init_mut(initialized) } + unsafe { + // inlined variant of: `MaybeUninit::slice_assume_init_mut(initialized)` + // TODO: undo inlining when `slice_assume_init_mut` becomes stable + &mut *(initialized as *mut [_] as *mut [_]) + } } fn add_region( diff --git a/src/binary/level_4_entries.rs b/common/src/level_4_entries.rs similarity index 100% rename from src/binary/level_4_entries.rs rename to common/src/level_4_entries.rs diff --git a/src/binary/mod.rs b/common/src/lib.rs similarity index 96% rename from src/binary/mod.rs rename to common/src/lib.rs index 32aeffae..056b9e00 100644 --- a/src/binary/mod.rs +++ b/common/src/lib.rs @@ -1,4 +1,7 @@ -use crate::binary::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}; +#![no_std] +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}; use bootloader_api::{ config::Mapping, info::{FrameBuffer, FrameBufferInfo, MemoryRegion, TlsTemplate}, @@ -63,6 +66,23 @@ pub struct Kernel<'a> { pub config: BootloaderConfig, } +impl<'a> Kernel<'a> { + pub fn parse(kernel_slice: &'a [u8]) -> Self { + let kernel_elf = ElfFile::new(kernel_slice).unwrap(); + let config = { + let section = kernel_elf + .find_section_by_name(".bootloader-config") + .unwrap(); + let raw = section.raw_data(&kernel_elf); + BootloaderConfig::deserialize(raw).unwrap() + }; + Kernel { + elf: kernel_elf, + config, + } + } +} + /// Loads the kernel ELF executable into memory and switches to it. /// /// This function is a convenience function that first calls [`set_up_mappings`], then diff --git a/src/binary/load_kernel.rs b/common/src/load_kernel.rs similarity index 99% rename from src/binary/load_kernel.rs rename to common/src/load_kernel.rs index ceca85e0..d25c2407 100644 --- a/src/binary/load_kernel.rs +++ b/common/src/load_kernel.rs @@ -1,4 +1,4 @@ -use crate::binary::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}; +use crate::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}; use bootloader_api::info::TlsTemplate; use x86_64::{ align_up, diff --git a/src/binary/logger.rs b/common/src/logger.rs similarity index 100% rename from src/binary/logger.rs rename to common/src/logger.rs diff --git a/src/binary/bios/mod.rs b/src/binary/bios/mod.rs deleted file mode 100644 index 3a4aeab9..00000000 --- a/src/binary/bios/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -/// Provides an abstraction type for a BIOS-provided memory region. -pub mod memory_descriptor; diff --git a/src/binary/uefi/mod.rs b/src/binary/uefi/mod.rs deleted file mode 100644 index 7679880c..00000000 --- a/src/binary/uefi/mod.rs +++ /dev/null @@ -1 +0,0 @@ -mod memory_descriptor; diff --git a/src/lib.rs b/src/lib.rs index 5292e3a6..ea46e3d1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -65,35 +65,9 @@ The bootloader can be configured through a `[package.metadata.bootloader]` table for all possible configuration options. */ -#![cfg_attr(not(feature = "builder"), no_std)] -#![feature(maybe_uninit_extra)] -#![feature(maybe_uninit_slice)] -#![deny(unsafe_op_in_unsafe_fn)] #![warn(missing_docs)] -/// Contains the actual bootloader implementation ("bootloader as a binary"). -/// -/// Useful for reusing part of the bootloader implementation for other crates. -/// -/// Only available when the `binary` feature is enabled. -#[cfg(feature = "binary")] -pub mod binary; - /// Provides a function to turn a bootloader executable into a disk image. /// /// Used by the `builder` binary. Only available when the `builder` feature is enabled. -#[cfg(feature = "builder")] pub mod disk_image; - -#[cfg(all(target_arch = "x86", not(feature = "builder")))] -compile_error!( - "This crate currently does not support 32-bit protected mode. \ - See https://github.com/rust-osdev/bootloader/issues/70 for more information." -); - -#[cfg(all( - not(target_arch = "x86_64"), - not(target_arch = "x86"), - not(feature = "builder") -))] -compile_error!("This crate only supports the x86_64 architecture."); diff --git a/src/bin/builder.rs b/src/main.rs similarity index 100% rename from src/bin/builder.rs rename to src/main.rs diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml new file mode 100644 index 00000000..b0ba7b7a --- /dev/null +++ b/uefi/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "bootloader-x86_64-uefi" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bootloader_api = { version = "0.1.0", path = "../api" } +bootloader-x86_64-common = { version = "0.1.0", path = "../common" } +log = "0.4.14" +uefi = "0.13.0" +x86_64 = "0.14.8" diff --git a/uefi/README.md b/uefi/README.md new file mode 100644 index 00000000..19630fad --- /dev/null +++ b/uefi/README.md @@ -0,0 +1,7 @@ +# UEFI Bootloader for `x86_64` + +## Build + +``` +cargo b --target x86_64-unknown-uefi --release -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem +``` diff --git a/src/bin/uefi.rs b/uefi/src/main.rs similarity index 93% rename from src/bin/uefi.rs rename to uefi/src/main.rs index b5f34459..8df0688e 100644 --- a/src/bin/uefi.rs +++ b/uefi/src/main.rs @@ -1,11 +1,11 @@ #![no_std] #![no_main] #![feature(abi_efiapi)] -#![feature(maybe_uninit_extra)] #![deny(unsafe_op_in_unsafe_fn)] -use bootloader::binary::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; +use crate::memory_descriptor::UefiMemoryDescriptor; use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; +use bootloader_x86_64_common::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; use core::{arch::asm, cell::UnsafeCell, fmt::Write, mem, panic::PanicInfo, ptr, slice}; use uefi::{ prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, @@ -25,7 +25,8 @@ use x86_64::{ structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, PhysAddr, VirtAddr, }; -use xmas_elf::ElfFile; + +mod memory_descriptor; static SYSTEM_TABLE: VeryUnsafeCell>> = VeryUnsafeCell::new(None); @@ -93,7 +94,8 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { .exit_boot_services(image, mmap_storage) .expect_success("Failed to exit boot services"); - let mut frame_allocator = LegacyFrameAllocator::new(memory_map.copied()); + let mut frame_allocator = + LegacyFrameAllocator::new(memory_map.copied().map(UefiMemoryDescriptor)); let page_tables = create_page_tables(&mut frame_allocator); @@ -112,7 +114,7 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { }, }; - bootloader::binary::load_and_switch_to_kernel( + bootloader_x86_64_common::load_and_switch_to_kernel( kernel, frame_allocator, page_tables, @@ -172,26 +174,13 @@ fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; kernel_file.read(kernel_slice).unwrap().unwrap(); - let kernel_elf = ElfFile::new(kernel_slice).unwrap(); - - let config = { - let section = kernel_elf - .find_section_by_name(".bootloader-config") - .unwrap(); - let raw = section.raw_data(&kernel_elf); - BootloaderConfig::deserialize(raw).unwrap() - }; - - Kernel { - elf: kernel_elf, - config, - } + Kernel::parse(kernel_slice) } /// Creates page table abstraction types for both the bootloader and kernel page tables. fn create_page_tables( frame_allocator: &mut impl FrameAllocator, -) -> bootloader::binary::PageTables { +) -> bootloader_x86_64_common::PageTables { // UEFI identity-maps all memory, so the offset between physical and virtual addresses is 0 let phys_offset = VirtAddr::new(0); @@ -247,7 +236,7 @@ fn create_page_tables( ) }; - bootloader::binary::PageTables { + bootloader_x86_64_common::PageTables { bootloader: bootloader_page_table, kernel: kernel_page_table, kernel_level_4_frame, @@ -309,7 +298,7 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F log::info!("UEFI boot"); - bootloader::binary::init_logger(slice, info); + bootloader_x86_64_common::init_logger(slice, info); (PhysAddr::new(framebuffer.as_mut_ptr() as u64), info) } @@ -321,7 +310,7 @@ fn panic(info: &PanicInfo) -> ! { } unsafe { - bootloader::binary::logger::LOGGER + bootloader_x86_64_common::logger::LOGGER .get() .map(|l| l.force_unlock()) }; diff --git a/src/binary/uefi/memory_descriptor.rs b/uefi/src/memory_descriptor.rs similarity index 56% rename from src/binary/uefi/memory_descriptor.rs rename to uefi/src/memory_descriptor.rs index e198587c..2ec4d952 100644 --- a/src/binary/uefi/memory_descriptor.rs +++ b/uefi/src/memory_descriptor.rs @@ -1,21 +1,24 @@ -use crate::binary::legacy_memory_region::LegacyMemoryRegion; use bootloader_api::info::MemoryRegionKind; +use bootloader_x86_64_common::legacy_memory_region::LegacyMemoryRegion; use uefi::table::boot::{MemoryDescriptor, MemoryType}; use x86_64::PhysAddr; +#[derive(Debug, Copy, Clone)] +pub struct UefiMemoryDescriptor(pub MemoryDescriptor); + const PAGE_SIZE: u64 = 4096; -impl<'a> LegacyMemoryRegion for MemoryDescriptor { +impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { fn start(&self) -> PhysAddr { - PhysAddr::new(self.phys_start) + PhysAddr::new(self.0.phys_start) } fn len(&self) -> u64 { - self.page_count * PAGE_SIZE + self.0.page_count * PAGE_SIZE } fn kind(&self) -> MemoryRegionKind { - match self.ty { + match self.0.ty { MemoryType::CONVENTIONAL => MemoryRegionKind::Usable, other => MemoryRegionKind::UnknownUefi(other.0), } From ee9bf20314be3e304f4210c03ba73348d4868c7c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 13:55:30 +0200 Subject: [PATCH 064/226] Replace artifact dependency with build script Artifact dependencies and `-Zbuild-std` don't work together yet. --- Cargo.toml | 6 ++++++ build.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 1b799e59..1d7da92b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,12 @@ json = "0.12.4" fatfs = "0.3.4" gpt = "3.0.0" +# [dependencies.bootloader-x86_64-uefi] +# version = "0.1.0" +# path = "uefi" +# artifact = ["bin"] +# target = "x86_64-unknown-uefi" + [build-dependencies] llvm-tools-build = { version = "0.1", optional = true, package = "llvm-tools" } toml = { version = "0.5.1", optional = true } diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..5fd5b10c --- /dev/null +++ b/build.rs @@ -0,0 +1,42 @@ +use std::{ + path::{Path, PathBuf}, + process::Command, +}; + +fn main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + + let uefi_path = build_uefi_bootloader(&out_dir); + + println!( + "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", + uefi_path.display() + ); +} + +fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); + let mut cmd = Command::new(cargo); + cmd.arg("install").arg("bootloader-x86_64-uefi"); + // TODO: remove, only for testing, replace by `--version` + cmd.arg("--path").arg("uefi"); + cmd.arg("--locked"); + cmd.arg("--target").arg("x86_64-unknown-uefi"); + cmd.arg("-Zbuild-std=core") + .arg("-Zbuild-std-features=compiler-builtins-mem"); + cmd.arg("--root").arg(out_dir); + cmd.env_remove("RUSTFLAGS"); + let status = cmd + .status() + .expect("failed to run cargo install for uefi bootloader"); + if status.success() { + let path = out_dir.join("bin").join("bootloader-x86_64-uefi.efi"); + assert!( + path.exists(), + "uefi bootloader executable does not exist after building" + ); + path + } else { + panic!("failed to build uefi bootloader"); + } +} From aefa4aa68fa25c7c52a2d84dbf4f664dee3e6390 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 13:56:05 +0200 Subject: [PATCH 065/226] Add function to create UEFI disk image to library --- src/lib.rs | 139 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index ea46e3d1..836dc0c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,138 @@ for all possible configuration options. #![warn(missing_docs)] -/// Provides a function to turn a bootloader executable into a disk image. -/// -/// Used by the `builder` binary. Only available when the `builder` feature is enabled. -pub mod disk_image; +use std::{ + fs::{self, File}, + io::{self, Seek}, + path::Path, +}; + +use anyhow::Context; + +pub fn create_uefi_disk_image( + kernel_binary: &Path, + out_fat_path: &Path, + out_gpt_path: &Path, +) -> anyhow::Result<()> { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + + create_fat_filesystem(bootloader_path, kernel_binary, &out_fat_path) + .context("failed to create UEFI FAT filesystem")?; + create_gpt_disk(out_fat_path, out_gpt_path); + + Ok(()) +} + +fn create_fat_filesystem( + bootloader_efi_file: &Path, + kernel_binary: &Path, + out_fat_path: &Path, +) -> anyhow::Result<()> { + const MB: u64 = 1024 * 1024; + + // retrieve size of `.efi` file and round it up + let efi_size = fs::metadata(&bootloader_efi_file).unwrap().len(); + // size of a megabyte + // round it to next megabyte + let efi_size_rounded = ((efi_size - 1) / MB + 1) * MB; + + // create new filesystem image file at the given path and set its length + let fat_file = fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(&out_fat_path) + .unwrap(); + fat_file.set_len(efi_size_rounded).unwrap(); + + // create new FAT file system and open it + let label = { + if let Some(name) = bootloader_efi_file.file_stem() { + let converted = name.to_string_lossy(); + let name = converted.as_bytes(); + let mut label = [0u8; 11]; + let name = &name[..label.len()]; + let slice = &mut label[..name.len()]; + slice.copy_from_slice(name); + label + } else { + *b"MY_RUST_OS!" + } + }; + let format_options = fatfs::FormatVolumeOptions::new().volume_label(label); + fatfs::format_volume(&fat_file, format_options).context("Failed to format UEFI FAT file")?; + let filesystem = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new()) + .context("Failed to open FAT file system of UEFI FAT file")?; + + // copy EFI file to FAT filesystem + let root_dir = filesystem.root_dir(); + root_dir.create_dir("efi").unwrap(); + root_dir.create_dir("efi/boot").unwrap(); + let mut bootx64 = root_dir.create_file("efi/boot/bootx64.efi").unwrap(); + bootx64.truncate().unwrap(); + io::copy( + &mut fs::File::open(&bootloader_efi_file).unwrap(), + &mut bootx64, + ) + .unwrap(); + + // copy kernel to FAT filesystem + let mut kernel_file = root_dir.create_file("kernel-x86_64")?; + kernel_file.truncate()?; + io::copy(&mut fs::File::open(&kernel_binary)?, &mut kernel_file)?; + + Ok(()) +} + +fn create_gpt_disk(fat_image: &Path, out_gpt_path: &Path) { + // create new file + let mut disk = fs::OpenOptions::new() + .create(true) + .truncate(true) + .read(true) + .write(true) + .open(&out_gpt_path) + .unwrap(); + + // set file size + let partition_size: u64 = fs::metadata(&fat_image).unwrap().len(); + let disk_size = partition_size + 1024 * 64; // for GPT headers + disk.set_len(disk_size).unwrap(); + + // create a protective MBR at LBA0 so that disk is not considered + // unformatted on BIOS systems + let mbr = gpt::mbr::ProtectiveMBR::with_lb_size( + u32::try_from((disk_size / 512) - 1).unwrap_or(0xFF_FF_FF_FF), + ); + mbr.overwrite_lba0(&mut disk).unwrap(); + + // create new GPT structure + let block_size = gpt::disk::LogicalBlockSize::Lb512; + let mut gpt = gpt::GptConfig::new() + .writable(true) + .initialized(false) + .logical_block_size(block_size) + .create_from_device(Box::new(&mut disk), None) + .unwrap(); + gpt.update_partitions(Default::default()).unwrap(); + + // add new EFI system partition and get its byte offset in the file + let partition_id = gpt + .add_partition("boot", partition_size, gpt::partition_types::EFI, 0, None) + .unwrap(); + let partition = gpt.partitions().get(&partition_id).unwrap(); + let start_offset = partition.bytes_start(block_size).unwrap(); + + // close the GPT structure and write out changes + gpt.write().unwrap(); + + // place the FAT filesystem in the newly created partition + disk.seek(io::SeekFrom::Start(start_offset)).unwrap(); + io::copy(&mut File::open(&fat_image).unwrap(), &mut disk).unwrap(); +} + +// Provides a function to turn a bootloader executable into a disk image. +// +// Used by the `builder` binary. Only available when the `builder` feature is enabled. +// pub mod disk_image; From 31dc52c7d0bb60623fa640d1b096ebad3a7426d0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 14:02:10 +0200 Subject: [PATCH 066/226] Set subcrate version to 0.1.0-alpha.0 --- Cargo.lock | 8 ++------ Cargo.toml | 2 +- api/Cargo.toml | 2 +- common/Cargo.toml | 4 ++-- uefi/Cargo.toml | 6 +++--- 5 files changed, 9 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2bba218a..23d91ad9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,19 +57,15 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bootloader" -version = "0.10.10" +version = "0.11.0-alpha.0" dependencies = [ "anyhow", - "argh", - "displaydoc", "fatfs", "gpt", - "json", "llvm-tools", "proc-macro2", "quote", "serde", - "thiserror", "toml", ] @@ -111,7 +107,7 @@ dependencies = [ [[package]] name = "bootloader-x86_64-uefi" -version = "0.1.0" +version = "0.1.0-alpha.0" dependencies = [ "bootloader-x86_64-common", "bootloader_api", diff --git a/Cargo.toml b/Cargo.toml index 1d7da92b..6fadde58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bootloader" -version = "0.10.10" +version = "0.11.0-alpha.0" authors = ["Philipp Oppermann "] license = "MIT/Apache-2.0" description = "An experimental x86_64 bootloader that works on both BIOS and UEFI systems." diff --git a/api/Cargo.toml b/api/Cargo.toml index 904a5455..c1ed63e7 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bootloader_api" -version = "0.1.0" +version = "0.1.0-alpha.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/Cargo.toml b/common/Cargo.toml index 9ae4e87d..0698cc39 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,12 +1,12 @@ [package] name = "bootloader-x86_64-common" -version = "0.1.0" +version = "0.1.0-alpha.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bootloader_api = { version = "0.1.0", path = "../api" } +bootloader_api = { version = "0.1.0-alpha.0", path = "../api" } conquer-once = { version = "0.3.2", default-features = false } log = "0.4.14" spinning_top = "0.2.4" diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index b0ba7b7a..624acb3c 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -1,13 +1,13 @@ [package] name = "bootloader-x86_64-uefi" -version = "0.1.0" +version = "0.1.0-alpha.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bootloader_api = { version = "0.1.0", path = "../api" } -bootloader-x86_64-common = { version = "0.1.0", path = "../common" } +bootloader_api = { version = "0.1.0-alpha.0", path = "../api" } +bootloader-x86_64-common = { version = "0.1.0-alpha.0", path = "../common" } log = "0.4.14" uefi = "0.13.0" x86_64 = "0.14.8" From a80203243a023a7349c4ace76f8d47b62a550116 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 14:16:26 +0200 Subject: [PATCH 067/226] Support both local builds and builds as dependency --- .cargo/config.toml | 3 +++ build.rs | 10 ++++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 7f3f182e..02652ade 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,5 @@ +[env] +BOOTLOADER_LOCAL_BUILD = "1" + [target.x86_64-unknown-uefi] runner = "cargo run -p runner" diff --git a/build.rs b/build.rs index 5fd5b10c..1ce6a477 100644 --- a/build.rs +++ b/build.rs @@ -3,6 +3,8 @@ use std::{ process::Command, }; +const BOOTLOADER_X86_64_UEFI_VERSION: &str = "0.1.0-alpha.0"; + fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); @@ -18,8 +20,12 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-uefi"); - // TODO: remove, only for testing, replace by `--version` - cmd.arg("--path").arg("uefi"); + if std::env::var("BOOTLOADER_LOCAL_BUILD").is_ok() { + // local build + cmd.arg("--path").arg("uefi"); + } else { + cmd.arg("--version").arg(BOOTLOADER_X86_64_UEFI_VERSION); + } cmd.arg("--locked"); cmd.arg("--target").arg("x86_64-unknown-uefi"); cmd.arg("-Zbuild-std=core") From 6047a899a5ecbf5d2e40c414718cdd0fe70286a6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 14:19:45 +0200 Subject: [PATCH 068/226] Comment out main.rs for now --- Cargo.lock | 119 +---------------------------------- Cargo.toml | 12 ---- src/{main.rs => main_bak.rs} | 0 3 files changed, 2 insertions(+), 129 deletions(-) rename src/{main.rs => main_bak.rs} (100%) diff --git a/Cargo.lock b/Cargo.lock index 23d91ad9..6750f11d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,35 +8,6 @@ version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" -[[package]] -name = "argh" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91792f088f87cdc7a2cfb1d617fa5ea18d7f1dc22ef0e1b5f82f3157cdc522be" -dependencies = [ - "argh_derive", - "argh_shared", -] - -[[package]] -name = "argh_derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4eb0c0c120ad477412dc95a4ce31e38f2113e46bd13511253f79196ca68b067" -dependencies = [ - "argh_shared", - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "argh_shared" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "781f336cc9826dbaddb9754cb5db61e64cab4f69668bd19dcc4a0394a86f4cb1" - [[package]] name = "autocfg" version = "1.0.1" @@ -62,11 +33,6 @@ dependencies = [ "anyhow", "fatfs", "gpt", - "llvm-tools", - "proc-macro2", - "quote", - "serde", - "toml", ] [[package]] @@ -92,7 +58,7 @@ dependencies = [ [[package]] name = "bootloader-x86_64-common" -version = "0.1.0" +version = "0.1.0-alpha.0" dependencies = [ "bootloader_api", "conquer-once", @@ -118,7 +84,7 @@ dependencies = [ [[package]] name = "bootloader_api" -version = "0.1.0" +version = "0.1.0-alpha.0" dependencies = [ "rand", ] @@ -182,17 +148,6 @@ dependencies = [ "build_const", ] -[[package]] -name = "displaydoc" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc2ab4d5a16117f9029e9a6b5e4e79f4c67f6519bc134210d4d4a04ba31f41b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "fatfs" version = "0.3.5" @@ -234,15 +189,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "heck" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "json" version = "0.12.4" @@ -255,12 +201,6 @@ version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" -[[package]] -name = "llvm-tools" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" - [[package]] name = "locate-cargo-manifest" version = "0.2.2" @@ -394,26 +334,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" -[[package]] -name = "serde" -version = "1.0.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.126" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "spinning_top" version = "0.2.4" @@ -461,26 +381,6 @@ dependencies = [ "x86_64", ] -[[package]] -name = "thiserror" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "time" version = "0.1.43" @@ -491,15 +391,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "toml" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" -dependencies = [ - "serde", -] - [[package]] name = "uart_16550" version = "0.2.14" @@ -542,12 +433,6 @@ dependencies = [ "syn", ] -[[package]] -name = "unicode-segmentation" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" - [[package]] name = "unicode-xid" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 6fadde58..a98ff54a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,12 +23,7 @@ exclude = ["examples/basic", "examples/test_framework"] [dependencies] -argh = "0.1.3" -displaydoc = "0.1.7" anyhow = "1.0.32" -llvm-tools = "0.1.1" -thiserror = "1.0.20" -json = "0.12.4" fatfs = "0.3.4" gpt = "3.0.0" @@ -38,13 +33,6 @@ gpt = "3.0.0" # artifact = ["bin"] # target = "x86_64-unknown-uefi" -[build-dependencies] -llvm-tools-build = { version = "0.1", optional = true, package = "llvm-tools" } -toml = { version = "0.5.1", optional = true } -serde = { version = "1.0", features = ["derive"], optional = true } -quote = { version = "1.0", optional = true } -proc-macro2 = { version = "1.0", optional = true } - [profile.dev] panic = "abort" diff --git a/src/main.rs b/src/main_bak.rs similarity index 100% rename from src/main.rs rename to src/main_bak.rs From 138b1986fee69099514ae2ce19ac281f860adbc0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 14:20:02 +0200 Subject: [PATCH 069/226] Update versions in bios impl --- bios/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bios/Cargo.toml b/bios/Cargo.toml index 288a49c6..a7c461bc 100644 --- a/bios/Cargo.toml +++ b/bios/Cargo.toml @@ -6,8 +6,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bootloader_api = { version = "0.1.0", path = "../api" } -bootloader-x86_64-common = { version = "0.1.0", path = "../common" } +bootloader_api = { version = "0.1.0-alpha.0", path = "../api" } +bootloader-x86_64-common = { version = "0.1.0-alpha.0", path = "../common" } log = "0.4.14" x86_64 = "0.14.8" usize_conversions = "0.2.0" From d30685ebe6e167f930533937a40316dc7314d6f6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 14:25:03 +0200 Subject: [PATCH 070/226] Only build local version if it exists --- build.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.rs b/build.rs index 1ce6a477..d0afabee 100644 --- a/build.rs +++ b/build.rs @@ -20,7 +20,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-uefi"); - if std::env::var("BOOTLOADER_LOCAL_BUILD").is_ok() { + if std::env::var("BOOTLOADER_LOCAL_BUILD").is_ok() && Path::new("uefi").exists() { // local build cmd.arg("--path").arg("uefi"); } else { From 6e34ad1bad84d0671d2a0411a09f028e6cfaa287 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 14:28:21 +0200 Subject: [PATCH 071/226] Add description and license to crates --- api/Cargo.toml | 2 ++ common/Cargo.toml | 2 ++ uefi/Cargo.toml | 2 ++ 3 files changed, 6 insertions(+) diff --git a/api/Cargo.toml b/api/Cargo.toml index c1ed63e7..a2fe956f 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -2,6 +2,8 @@ name = "bootloader_api" version = "0.1.0-alpha.0" edition = "2021" +description = "Makes a kernel compatible with the bootloader crate" +license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/common/Cargo.toml b/common/Cargo.toml index 0698cc39..6ac12f15 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -2,6 +2,8 @@ name = "bootloader-x86_64-common" version = "0.1.0-alpha.0" edition = "2021" +description = "Common code for the x86_64 bootloader implementations" +license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 624acb3c..362f1083 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -2,6 +2,8 @@ name = "bootloader-x86_64-uefi" version = "0.1.0-alpha.0" edition = "2021" +description = "UEFI bootloader for x86_64" +license = "MIT/Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html From 8c00c9e6152efb159a8c4c87e420c18fef09c443 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 14:31:53 +0200 Subject: [PATCH 072/226] Fix x86_64 dependency of common crate --- Cargo.lock | 11 +++++++++-- common/Cargo.toml | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6750f11d..f5ea3f7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -328,6 +328,12 @@ dependencies = [ "locate-cargo-manifest", ] +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + [[package]] name = "scopeguard" version = "1.1.0" @@ -490,12 +496,13 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "x86_64" -version = "0.14.8" +version = "0.14.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "958ab3202b01bc43ba2eb832102c4a487ed93151667a2289062e5f2b00058be2" +checksum = "958cd5cb28e720db2f59ee9dc4235b5f82a183d079fb0e6caf43ad074cfdc66a" dependencies = [ "bit_field", "bitflags", + "rustversion", "volatile", ] diff --git a/common/Cargo.toml b/common/Cargo.toml index 6ac12f15..05f6cfe9 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -14,7 +14,7 @@ log = "0.4.14" spinning_top = "0.2.4" uefi = "0.13" usize_conversions = "0.2.0" -x86_64 = { version = "0.14.8", default-features = false } +x86_64 = { version = "0.14.8" } xmas-elf = "0.8.0" [dependencies.font8x8] From 71accbbe48c770568a89113b22b83109da9fe455 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 16:52:10 +0200 Subject: [PATCH 073/226] Remove BOOTLOADER_LOCAL_BUILD env variable To make local path dependencies work. --- .cargo/config.toml | 3 --- build.rs | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.cargo/config.toml b/.cargo/config.toml index 02652ade..7f3f182e 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,5 +1,2 @@ -[env] -BOOTLOADER_LOCAL_BUILD = "1" - [target.x86_64-unknown-uefi] runner = "cargo run -p runner" diff --git a/build.rs b/build.rs index d0afabee..2866a239 100644 --- a/build.rs +++ b/build.rs @@ -20,7 +20,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-uefi"); - if std::env::var("BOOTLOADER_LOCAL_BUILD").is_ok() && Path::new("uefi").exists() { + if Path::new("uefi").exists() { // local build cmd.arg("--path").arg("uefi"); } else { From c7a5b3360e2e9d675970b10fb261d398f3d9787d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 18:01:46 +0200 Subject: [PATCH 074/226] Fix: include kernel size in FAT size calculation --- src/lib.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 836dc0c4..519c1f8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,9 +98,9 @@ fn create_fat_filesystem( // retrieve size of `.efi` file and round it up let efi_size = fs::metadata(&bootloader_efi_file).unwrap().len(); - // size of a megabyte - // round it to next megabyte - let efi_size_rounded = ((efi_size - 1) / MB + 1) * MB; + let kernel_size = fs::metadata(&kernel_binary) + .context("failed to read metadata of kernel binary")? + .len(); // create new filesystem image file at the given path and set its length let fat_file = fs::OpenOptions::new() @@ -110,7 +110,9 @@ fn create_fat_filesystem( .truncate(true) .open(&out_fat_path) .unwrap(); - fat_file.set_len(efi_size_rounded).unwrap(); + let fat_size = efi_size + kernel_size; + let fat_size_padded_and_rounded = ((fat_size + 1024 * 64 - 1) / MB + 1) * MB; + fat_file.set_len(fat_size_padded_and_rounded).unwrap(); // create new FAT file system and open it let label = { @@ -146,7 +148,8 @@ fn create_fat_filesystem( // copy kernel to FAT filesystem let mut kernel_file = root_dir.create_file("kernel-x86_64")?; kernel_file.truncate()?; - io::copy(&mut fs::File::open(&kernel_binary)?, &mut kernel_file)?; + io::copy(&mut fs::File::open(&kernel_binary)?, &mut kernel_file) + .context("failed to copy kernel to UEFI FAT filesystem")?; Ok(()) } From 4d4b5849831c12e81e2c6897fbf051fda73aa02d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 19:43:05 +0200 Subject: [PATCH 075/226] Update test framework to rewrite --- .cargo/config.toml | 4 +- Cargo.lock | 89 ++++++++++++------- Cargo.toml | 18 ++++ rust-toolchain.toml | 1 + src/lib.rs | 8 +- tests/default_settings.rs | 25 +++--- tests/higher_half.rs | 25 ++---- tests/map_phys_mem.rs | 21 ++--- tests/pie.rs | 21 ++--- tests/runner/Cargo.toml | 6 +- tests/runner/src/lib.rs | 40 +++++++++ tests/runner/src/main.rs | 81 ----------------- .../default_settings/.cargo/config.toml | 10 --- .../test_kernels/default_settings/.gitignore | 1 - .../src/bin/check_boot_info.rs | 1 - .../x86_64-default_settings.json | 15 ---- .../higher_half/.cargo/config.toml | 10 --- tests/test_kernels/higher_half/.gitignore | 1 - tests/test_kernels/higher_half/Cargo.toml | 9 +- .../higher_half/src/bin/check_boot_info.rs | 1 - .../higher_half/x86_64-higher_half.json | 18 ---- .../map_phys_mem/.cargo/config.toml | 10 --- tests/test_kernels/map_phys_mem/.gitignore | 1 - tests/test_kernels/map_phys_mem/Cargo.toml | 9 +- .../map_phys_mem/src/bin/access_phys_mem.rs | 4 +- .../map_phys_mem/src/bin/check_boot_info.rs | 5 +- tests/test_kernels/map_phys_mem/src/lib.rs | 8 ++ .../map_phys_mem/x86_64-map_phys_mem.json | 15 ---- tests/test_kernels/pie/.cargo/config.toml | 10 --- tests/test_kernels/pie/.gitignore | 1 - tests/test_kernels/pie/Cargo.toml | 10 +-- tests/test_kernels/pie/src/bin/basic_boot.rs | 2 +- .../pie/src/bin/check_boot_info.rs | 5 +- .../pie/src/bin/global_variable.rs | 2 +- .../test_kernels/pie/src/bin/should_panic.rs | 2 +- tests/test_kernels/pie/x86_64-pie.json | 16 ---- 36 files changed, 186 insertions(+), 319 deletions(-) create mode 100644 tests/runner/src/lib.rs delete mode 100644 tests/runner/src/main.rs delete mode 100644 tests/test_kernels/default_settings/.cargo/config.toml delete mode 100644 tests/test_kernels/default_settings/.gitignore delete mode 100644 tests/test_kernels/default_settings/x86_64-default_settings.json delete mode 100644 tests/test_kernels/higher_half/.cargo/config.toml delete mode 100644 tests/test_kernels/higher_half/.gitignore delete mode 100644 tests/test_kernels/higher_half/x86_64-higher_half.json delete mode 100644 tests/test_kernels/map_phys_mem/.cargo/config.toml delete mode 100644 tests/test_kernels/map_phys_mem/.gitignore delete mode 100644 tests/test_kernels/map_phys_mem/x86_64-map_phys_mem.json delete mode 100644 tests/test_kernels/pie/.cargo/config.toml delete mode 100644 tests/test_kernels/pie/.gitignore delete mode 100644 tests/test_kernels/pie/x86_64-pie.json diff --git a/.cargo/config.toml b/.cargo/config.toml index 7f3f182e..dfa84e85 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,2 +1,2 @@ -[target.x86_64-unknown-uefi] -runner = "cargo run -p runner" +[unstable] +bindeps = true diff --git a/Cargo.lock b/Cargo.lock index 174aa0b2..ac8d2c83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,12 @@ version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" +[[package]] +name = "arrayvec" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" + [[package]] name = "autocfg" version = "1.1.0" @@ -31,20 +37,16 @@ name = "bootloader" version = "0.11.0-alpha.0" dependencies = [ "anyhow", + "bootloader_test_runner", "fatfs", "gpt", "rand", "rand_chacha", "raw-cpuid", -] - -[[package]] -name = "bootloader-locator" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaaa9db3339d32c2622f2e5d0731eb82a468d3439797c9d4fe426744fe2bd551" -dependencies = [ - "json", + "test_kernel_default_settings", + "test_kernel_higher_half", + "test_kernel_map_phys_mem", + "test_kernel_pie", ] [[package]] @@ -99,6 +101,14 @@ dependencies = [ name = "bootloader_first_stage" version = "0.1.0" +[[package]] +name = "bootloader_test_runner" +version = "0.1.0" +dependencies = [ + "bootloader", + "strip-ansi-escapes", +] + [[package]] name = "build_const" version = "0.2.2" @@ -189,27 +199,12 @@ dependencies = [ "uuid", ] -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - [[package]] name = "libc" version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" -[[package]] -name = "locate-cargo-manifest" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db985b63431fe09e8d71f50aeceffcc31e720cb86be8dad2f38d084c5a328466" -dependencies = [ - "json", -] - [[package]] name = "lock_api" version = "0.4.7" @@ -326,14 +321,6 @@ dependencies = [ "log", ] -[[package]] -name = "runner" -version = "0.1.0" -dependencies = [ - "bootloader-locator", - "locate-cargo-manifest", -] - [[package]] name = "rustversion" version = "1.0.6" @@ -355,6 +342,15 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strip-ansi-escapes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "011cbb39cf7c1f62871aea3cc46e5817b0937b49e9447370c93cacbe93a766d8" +dependencies = [ + "vte", +] + [[package]] name = "syn" version = "1.0.91" @@ -397,7 +393,7 @@ dependencies = [ name = "test_kernel_pie" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", "uart_16550", "x86_64", ] @@ -466,6 +462,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" +[[package]] +name = "utf8parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" + [[package]] name = "uuid" version = "0.8.2" @@ -481,6 +483,27 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c2dbd44eb8b53973357e6e207e370f0c1059990df850aca1eca8947cf464f0" +[[package]] +name = "vte" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" +dependencies = [ + "arrayvec", + "utf8parse", + "vte_generate_state_changes", +] + +[[package]] +name = "vte_generate_state_changes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d257817081c7dffcdbab24b9e62d2def62e2ff7d00b1c20062551e6cccc145ff" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index aa450a26..586572d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["profile-rustflags"] + [package] name = "bootloader" version = "0.11.0-alpha.0" @@ -31,6 +33,13 @@ raw-cpuid = { version = "10.2.0", optional = true } rand = { version = "0.8.4", optional = true, default-features = false } rand_chacha = { version = "0.3.1", optional = true, default-features = false } +[dev-dependencies] +bootloader_test_runner = { path = "tests/runner" } +test_kernel_default_settings = { path = "tests/test_kernels/default_settings", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_higher_half = { path = "tests/test_kernels/higher_half", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_map_phys_mem = { path = "tests/test_kernels/map_phys_mem", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = "x86_64-unknown-none" } + # [dependencies.bootloader-x86_64-uefi] # version = "0.1.0" # path = "uefi" @@ -55,6 +64,15 @@ codegen-units = 1 debug = false overflow-checks = false +[profile.test.package.test_kernel_higher_half] +rustflags = [ + "-C", + "link-args=--image-base 0xFFFF800000000000", + "-C", + "relocation-model=static", # pic in higher half not supported yet + "-C", + "code-model=large", +] [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 3d8f3dee..d04213d1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,3 +1,4 @@ [toolchain] channel = "nightly" components = ["rustfmt", "clippy", "rust-src", "llvm-tools-preview"] +targets = ["x86_64-unknown-none"] diff --git a/src/lib.rs b/src/lib.rs index 519c1f8a..28ed212a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,14 +67,13 @@ for all possible configuration options. #![warn(missing_docs)] +use anyhow::Context; use std::{ fs::{self, File}, io::{self, Seek}, path::Path, }; -use anyhow::Context; - pub fn create_uefi_disk_image( kernel_binary: &Path, out_fat_path: &Path, @@ -200,8 +199,3 @@ fn create_gpt_disk(fat_image: &Path, out_gpt_path: &Path) { disk.seek(io::SeekFrom::Start(start_offset)).unwrap(); io::copy(&mut File::open(&fat_image).unwrap(), &mut disk).unwrap(); } - -// Provides a function to turn a bootloader executable into a disk image. -// -// Used by the `builder` binary. Only available when the `builder` feature is enabled. -// pub mod disk_image; diff --git a/tests/default_settings.rs b/tests/default_settings.rs index cf0a817a..d610508c 100644 --- a/tests/default_settings.rs +++ b/tests/default_settings.rs @@ -1,27 +1,22 @@ -use std::process::Command; +use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_binary("basic_boot"); + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_basic_boot" + )); } #[test] fn should_panic() { - run_test_binary("should_panic"); + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_should_panic" + )); } #[test] fn check_boot_info() { - run_test_binary("check_boot_info"); -} - -fn run_test_binary(bin_name: &str) { - let mut cmd = Command::new(env!("CARGO")); - cmd.current_dir("tests/test_kernels/default_settings"); - cmd.arg("run"); - cmd.arg("--bin").arg(bin_name); - cmd.arg("--target").arg("x86_64-default_settings.json"); - cmd.arg("-Zbuild-std=core"); - cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); - assert!(cmd.status().unwrap().success()); + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_DEFAULT_SETTINGS_check_boot_info" + )); } diff --git a/tests/higher_half.rs b/tests/higher_half.rs index 4adf487d..c2b9ac91 100644 --- a/tests/higher_half.rs +++ b/tests/higher_half.rs @@ -1,32 +1,25 @@ -use std::process::Command; +use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_binary("basic_boot"); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_basic_boot")); } #[test] fn should_panic() { - run_test_binary("should_panic"); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_should_panic")); } #[test] fn check_boot_info() { - run_test_binary("check_boot_info"); + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_check_boot_info" + )); } #[test] fn verify_higher_half() { - run_test_binary("verify_higher_half"); -} - -fn run_test_binary(bin_name: &str) { - let mut cmd = Command::new(env!("CARGO")); - cmd.current_dir("tests/test_kernels/higher_half"); - cmd.arg("run"); - cmd.arg("--bin").arg(bin_name); - cmd.arg("--target").arg("x86_64-higher_half.json"); - cmd.arg("-Zbuild-std=core"); - cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); - assert!(cmd.status().unwrap().success()); + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_HIGHER_HALF_verify_higher_half" + )); } diff --git a/tests/map_phys_mem.rs b/tests/map_phys_mem.rs index f7e1b61a..b19ba987 100644 --- a/tests/map_phys_mem.rs +++ b/tests/map_phys_mem.rs @@ -1,22 +1,15 @@ -use std::process::Command; +use bootloader_test_runner::run_test_kernel; #[test] fn check_boot_info() { - run_test_binary("check_boot_info"); + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_check_boot_info" + )); } #[test] fn access_phys_mem() { - run_test_binary("access_phys_mem"); -} - -fn run_test_binary(bin_name: &str) { - let mut cmd = Command::new(env!("CARGO")); - cmd.current_dir("tests/test_kernels/map_phys_mem"); - cmd.arg("run"); - cmd.arg("--bin").arg(bin_name); - cmd.arg("--target").arg("x86_64-map_phys_mem.json"); - cmd.arg("-Zbuild-std=core"); - cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); - assert!(cmd.status().unwrap().success()); + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_MAP_PHYS_MEM_access_phys_mem" + )); } diff --git a/tests/pie.rs b/tests/pie.rs index 565fb6e4..c2d30d80 100644 --- a/tests/pie.rs +++ b/tests/pie.rs @@ -1,32 +1,21 @@ -use std::process::Command; +use bootloader_test_runner::run_test_kernel; #[test] fn basic_boot() { - run_test_binary("basic_boot"); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_basic_boot")); } #[test] fn should_panic() { - run_test_binary("should_panic"); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_should_panic")); } #[test] fn check_boot_info() { - run_test_binary("check_boot_info"); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_check_boot_info")); } #[test] fn global_variable() { - run_test_binary("global_variable"); -} - -fn run_test_binary(bin_name: &str) { - let mut cmd = Command::new(env!("CARGO")); - cmd.current_dir("tests/test_kernels/pie"); - cmd.arg("run"); - cmd.arg("--bin").arg(bin_name); - cmd.arg("--target").arg("x86_64-pie.json"); - cmd.arg("-Zbuild-std=core"); - cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); - assert!(cmd.status().unwrap().success()); + run_test_kernel(env!("CARGO_BIN_FILE_TEST_KERNEL_PIE_global_variable")); } diff --git a/tests/runner/Cargo.toml b/tests/runner/Cargo.toml index 93550faa..23999465 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "runner" +name = "bootloader_test_runner" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2018" @@ -7,5 +7,5 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bootloader-locator = "0.0.4" -locate-cargo-manifest = "0.2.1" +bootloader = { path = "../.." } +strip-ansi-escapes = "0.1.1" diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs new file mode 100644 index 00000000..46f2cc91 --- /dev/null +++ b/tests/runner/src/lib.rs @@ -0,0 +1,40 @@ +use std::{io::Write, path::Path, process::Command}; + +const QEMU_ARGS: &[&str] = &[ + "-device", + "isa-debug-exit,iobase=0xf4,iosize=0x04", + "-serial", + "stdio", + "-display", + "none", + "--no-reboot", +]; + +pub fn run_test_kernel(kernel_binary_path: &str) { + let kernel_path = Path::new(kernel_binary_path); + let out_fat_path = kernel_path.with_extension("fat"); + let out_gpt_path = kernel_path.with_extension("gpt"); + bootloader::create_uefi_disk_image(kernel_path, &out_fat_path, &out_gpt_path).unwrap(); + + let mut run_cmd = Command::new("qemu-system-x86_64"); + run_cmd + .arg("-drive") + .arg(format!("format=raw,file={}", out_gpt_path.display())); + run_cmd.args(QEMU_ARGS); + run_cmd.args(std::env::args().skip(2).collect::>()); + run_cmd.arg("-bios").arg("OVMF-pure-efi.fd"); + + let child_output = run_cmd.output().unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stderr) + .unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stdout) + .unwrap(); + + match child_output.status.code() { + Some(33) => {} // success + Some(35) => panic!("Test failed"), // success + other => panic!("Test failed with unexpected exit code `{:?}`", other), + } +} diff --git a/tests/runner/src/main.rs b/tests/runner/src/main.rs deleted file mode 100644 index cdad97b8..00000000 --- a/tests/runner/src/main.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::{ - io::Write, - path::{Path, PathBuf}, - process::Command, -}; - -const QEMU_ARGS: &[&str] = &[ - "-device", - "isa-debug-exit,iobase=0xf4,iosize=0x04", - "-serial", - "stdio", - "-display", - "none", - "--no-reboot", -]; - -fn main() { - let kernel_binary_path = { - let path = PathBuf::from(std::env::args().nth(1).unwrap()); - path.canonicalize().unwrap() - }; - - let disk_image = create_disk_image(&kernel_binary_path, false); - - let mut run_cmd = Command::new("qemu-system-x86_64"); - run_cmd - .arg("-drive") - .arg(format!("format=raw,file={}", disk_image.display())); - run_cmd.args(QEMU_ARGS); - run_cmd.args(std::env::args().skip(2).collect::>()); - - let child_output = run_cmd.output().unwrap(); - std::io::stderr().write_all(&child_output.stderr).unwrap(); - std::io::stderr().write_all(&child_output.stdout).unwrap(); - - match child_output.status.code() { - Some(33) => {} // success - Some(35) => panic!("Test failed"), // success - other => panic!("Test failed with unexpected exit code `{:?}`", other), - } -} - -pub fn create_disk_image(kernel_binary_path: &Path, bios_only: bool) -> PathBuf { - let bootloader_manifest_path = bootloader_locator::locate_bootloader("bootloader").unwrap(); - let kernel_manifest_path = locate_cargo_manifest::locate_manifest().unwrap(); - - let mut build_cmd = Command::new(env!("CARGO")); - build_cmd.current_dir(bootloader_manifest_path.parent().unwrap()); - build_cmd.arg("builder"); - build_cmd - .arg("--kernel-manifest") - .arg(&kernel_manifest_path); - build_cmd.arg("--kernel-binary").arg(&kernel_binary_path); - build_cmd - .arg("--target-dir") - .arg(kernel_manifest_path.parent().unwrap().join("target")); - build_cmd - .arg("--out-dir") - .arg(kernel_binary_path.parent().unwrap()); - //build_cmd.arg("--quiet"); - if bios_only { - build_cmd.arg("--firmware").arg("bios"); - } - - if !build_cmd.status().unwrap().success() { - panic!("build failed"); - } - - let kernel_binary_name = kernel_binary_path.file_name().unwrap().to_str().unwrap(); - let disk_image = kernel_binary_path - .parent() - .unwrap() - .join(format!("boot-bios-{}.img", kernel_binary_name)); - if !disk_image.exists() { - panic!( - "Disk image does not exist at {} after bootloader build", - disk_image.display() - ); - } - disk_image -} diff --git a/tests/test_kernels/default_settings/.cargo/config.toml b/tests/test_kernels/default_settings/.cargo/config.toml deleted file mode 100644 index 08a49f5a..00000000 --- a/tests/test_kernels/default_settings/.cargo/config.toml +++ /dev/null @@ -1,10 +0,0 @@ -[unstable] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# build-std = ["core"] - -[build] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# target = "x86_64-example-kernel.json" - -[target.'cfg(target_os = "none")'] -runner = "cargo run --manifest-path ../../runner/Cargo.toml" \ No newline at end of file diff --git a/tests/test_kernels/default_settings/.gitignore b/tests/test_kernels/default_settings/.gitignore deleted file mode 100644 index 1de56593..00000000 --- a/tests/test_kernels/default_settings/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs index 2297f7e4..84b22d1e 100644 --- a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs +++ b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs @@ -50,7 +50,6 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check rsdp_addr let rsdp = boot_info.rsdp_addr.into_option().unwrap(); assert!(rsdp > 0x000E0000); - assert!(rsdp < 0x000FFFFF); // the test kernel has no TLS template assert_eq!(boot_info.tls_template.into_option(), None); diff --git a/tests/test_kernels/default_settings/x86_64-default_settings.json b/tests/test_kernels/default_settings/x86_64-default_settings.json deleted file mode 100644 index 9afe809f..00000000 --- a/tests/test_kernels/default_settings/x86_64-default_settings.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "panic-strategy": "abort", - "disable-redzone": true, - "features": "-mmx,-sse,+soft-float" - } diff --git a/tests/test_kernels/higher_half/.cargo/config.toml b/tests/test_kernels/higher_half/.cargo/config.toml deleted file mode 100644 index 08a49f5a..00000000 --- a/tests/test_kernels/higher_half/.cargo/config.toml +++ /dev/null @@ -1,10 +0,0 @@ -[unstable] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# build-std = ["core"] - -[build] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# target = "x86_64-example-kernel.json" - -[target.'cfg(target_os = "none")'] -runner = "cargo run --manifest-path ../../runner/Cargo.toml" \ No newline at end of file diff --git a/tests/test_kernels/higher_half/.gitignore b/tests/test_kernels/higher_half/.gitignore deleted file mode 100644 index 1de56593..00000000 --- a/tests/test_kernels/higher_half/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/tests/test_kernels/higher_half/Cargo.toml b/tests/test_kernels/higher_half/Cargo.toml index 7e12ba2f..8c4b5036 100644 --- a/tests/test_kernels/higher_half/Cargo.toml +++ b/tests/test_kernels/higher_half/Cargo.toml @@ -1,3 +1,5 @@ +cargo-features = ["profile-rustflags"] + [package] name = "test_kernel_higher_half" version = "0.1.0" @@ -6,5 +8,10 @@ edition = "2018" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = ["instructions", "inline_asm"] } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } uart_16550 = "0.2.10" + +# set to higher half through profile.test.rustflags key in top-level Cargo.toml diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs index 93a9f9dd..938e874b 100644 --- a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -50,7 +50,6 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check rsdp_addr let rsdp = boot_info.rsdp_addr.into_option().unwrap(); assert!(rsdp > 0x000E0000); - assert!(rsdp < 0x000FFFFF); // the test kernel has no TLS template assert_eq!(boot_info.tls_template.into_option(), None); diff --git a/tests/test_kernels/higher_half/x86_64-higher_half.json b/tests/test_kernels/higher_half/x86_64-higher_half.json deleted file mode 100644 index 3e5ca842..00000000 --- a/tests/test_kernels/higher_half/x86_64-higher_half.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "panic-strategy": "abort", - "disable-redzone": true, - "features": "-mmx,-sse,+soft-float", - "pre-link-args": { - "ld.lld": ["--image-base", "0xFFFF800000000000", "--gc-sections"] - } - } diff --git a/tests/test_kernels/map_phys_mem/.cargo/config.toml b/tests/test_kernels/map_phys_mem/.cargo/config.toml deleted file mode 100644 index 08a49f5a..00000000 --- a/tests/test_kernels/map_phys_mem/.cargo/config.toml +++ /dev/null @@ -1,10 +0,0 @@ -[unstable] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# build-std = ["core"] - -[build] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# target = "x86_64-example-kernel.json" - -[target.'cfg(target_os = "none")'] -runner = "cargo run --manifest-path ../../runner/Cargo.toml" \ No newline at end of file diff --git a/tests/test_kernels/map_phys_mem/.gitignore b/tests/test_kernels/map_phys_mem/.gitignore deleted file mode 100644 index 1de56593..00000000 --- a/tests/test_kernels/map_phys_mem/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/tests/test_kernels/map_phys_mem/Cargo.toml b/tests/test_kernels/map_phys_mem/Cargo.toml index ea4f81ec..b00cc996 100644 --- a/tests/test_kernels/map_phys_mem/Cargo.toml +++ b/tests/test_kernels/map_phys_mem/Cargo.toml @@ -6,9 +6,8 @@ edition = "2018" [target.'cfg(target_arch = "x86_64")'.dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = ["instructions", "inline_asm"] } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } uart_16550 = "0.2.10" - -[package.metadata.bootloader] -map-physical-memory = true -physical-memory-offset = 0x0000_4000_0000_0000 diff --git a/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs b/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs index dc41b19c..f51d5d25 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs @@ -3,9 +3,9 @@ use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; -use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode}; +use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode, BOOTLOADER_CONFIG}; -entry_point!(kernel_main); +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); fn kernel_main(boot_info: &'static mut BootInfo) -> ! { let phys_mem_offset = boot_info.physical_memory_offset.into_option().unwrap(); diff --git a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs index 567176da..aa65d86f 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs @@ -3,9 +3,9 @@ use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; use core::panic::PanicInfo; -use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode}; +use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode, BOOTLOADER_CONFIG}; -entry_point!(kernel_main); +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check memory regions @@ -53,7 +53,6 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check rsdp_addr let rsdp = boot_info.rsdp_addr.into_option().unwrap(); assert!(rsdp > 0x000E0000); - assert!(rsdp < 0x000FFFFF); // the test kernel has no TLS template assert_eq!(boot_info.tls_template.into_option(), None); diff --git a/tests/test_kernels/map_phys_mem/src/lib.rs b/tests/test_kernels/map_phys_mem/src/lib.rs index 4e46fdb6..cfd76ad5 100644 --- a/tests/test_kernels/map_phys_mem/src/lib.rs +++ b/tests/test_kernels/map_phys_mem/src/lib.rs @@ -1,5 +1,13 @@ #![no_std] +use bootloader_api::{config::Mapping, BootloaderConfig}; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_4000_0000_0000)); + config +}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum QemuExitCode { diff --git a/tests/test_kernels/map_phys_mem/x86_64-map_phys_mem.json b/tests/test_kernels/map_phys_mem/x86_64-map_phys_mem.json deleted file mode 100644 index 9afe809f..00000000 --- a/tests/test_kernels/map_phys_mem/x86_64-map_phys_mem.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "panic-strategy": "abort", - "disable-redzone": true, - "features": "-mmx,-sse,+soft-float" - } diff --git a/tests/test_kernels/pie/.cargo/config.toml b/tests/test_kernels/pie/.cargo/config.toml deleted file mode 100644 index 08a49f5a..00000000 --- a/tests/test_kernels/pie/.cargo/config.toml +++ /dev/null @@ -1,10 +0,0 @@ -[unstable] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# build-std = ["core"] - -[build] -# TODO: Uncomment once https://github.com/rust-lang/cargo/issues/8643 is merged -# target = "x86_64-example-kernel.json" - -[target.'cfg(target_os = "none")'] -runner = "cargo run --manifest-path ../../runner/Cargo.toml" \ No newline at end of file diff --git a/tests/test_kernels/pie/.gitignore b/tests/test_kernels/pie/.gitignore deleted file mode 100644 index 1de56593..00000000 --- a/tests/test_kernels/pie/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target \ No newline at end of file diff --git a/tests/test_kernels/pie/Cargo.toml b/tests/test_kernels/pie/Cargo.toml index 520f8192..c6ee298a 100644 --- a/tests/test_kernels/pie/Cargo.toml +++ b/tests/test_kernels/pie/Cargo.toml @@ -5,9 +5,9 @@ authors = ["Tom Dohrmann "] edition = "2018" [dependencies] -bootloader = { path = "../../.." } -x86_64 = { version = "0.14.7", default-features = false, features = ["instructions", "inline_asm"] } +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } uart_16550 = "0.2.10" - -[package.metadata.bootloader] -aslr = true diff --git a/tests/test_kernels/pie/src/bin/basic_boot.rs b/tests/test_kernels/pie/src/bin/basic_boot.rs index f480d860..261e5236 100644 --- a/tests/test_kernels/pie/src/bin/basic_boot.rs +++ b/tests/test_kernels/pie/src/bin/basic_boot.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_pie::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/pie/src/bin/check_boot_info.rs b/tests/test_kernels/pie/src/bin/check_boot_info.rs index 909f99c2..b4f15c41 100644 --- a/tests/test_kernels/pie/src/bin/check_boot_info.rs +++ b/tests/test_kernels/pie/src/bin/check_boot_info.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{boot_info::PixelFormat, entry_point, BootInfo}; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; use core::panic::PanicInfo; use test_kernel_pie::{exit_qemu, QemuExitCode}; @@ -35,7 +35,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { if ![640, 1024].contains(&framebuffer.info().stride) { panic!("unexpected stride `{}`", framebuffer.info().stride); } - assert_eq!(framebuffer.info().pixel_format, PixelFormat::BGR); + assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), framebuffer.info().stride @@ -50,7 +50,6 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check rsdp_addr let rsdp = boot_info.rsdp_addr.into_option().unwrap(); assert!(rsdp > 0x000E0000); - assert!(rsdp < 0x000FFFFF); // the test kernel has no TLS template assert_eq!(boot_info.tls_template.into_option(), None); diff --git a/tests/test_kernels/pie/src/bin/global_variable.rs b/tests/test_kernels/pie/src/bin/global_variable.rs index d6a6f634..dad5869e 100644 --- a/tests/test_kernels/pie/src/bin/global_variable.rs +++ b/tests/test_kernels/pie/src/bin/global_variable.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::{ panic::PanicInfo, sync::atomic::{AtomicU64, Ordering}, diff --git a/tests/test_kernels/pie/src/bin/should_panic.rs b/tests/test_kernels/pie/src/bin/should_panic.rs index a5b0c2fc..3c82784c 100644 --- a/tests/test_kernels/pie/src/bin/should_panic.rs +++ b/tests/test_kernels/pie/src/bin/should_panic.rs @@ -1,7 +1,7 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; +use bootloader_api::{entry_point, BootInfo}; use core::panic::PanicInfo; use test_kernel_pie::{exit_qemu, QemuExitCode}; diff --git a/tests/test_kernels/pie/x86_64-pie.json b/tests/test_kernels/pie/x86_64-pie.json deleted file mode 100644 index 082ab7ad..00000000 --- a/tests/test_kernels/pie/x86_64-pie.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "panic-strategy": "abort", - "disable-redzone": true, - "features": "-mmx,-sse,+soft-float", - "position-independent-executables": true - } From ca53780c3ab5afa639c60260738be1716fc59e4f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 20:15:05 +0200 Subject: [PATCH 076/226] Use ovmf-prebuilt crate for running UEFI tests --- Cargo.lock | 7 +++++++ tests/runner/Cargo.toml | 1 + tests/runner/src/lib.rs | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ac8d2c83..3634f2ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,6 +106,7 @@ name = "bootloader_test_runner" version = "0.1.0" dependencies = [ "bootloader", + "ovmf-prebuilt", "strip-ansi-escapes", ] @@ -249,6 +250,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "ovmf-prebuilt" +version = "0.1.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa50141d081512ab30fd9e7e7692476866df5098b028536ad6680212e717fa8d" + [[package]] name = "ppv-lite86" version = "0.2.16" diff --git a/tests/runner/Cargo.toml b/tests/runner/Cargo.toml index 23999465..67c0f979 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -9,3 +9,4 @@ edition = "2018" [dependencies] bootloader = { path = "../.." } strip-ansi-escapes = "0.1.1" +ovmf-prebuilt = "0.1.0-alpha.1" diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 46f2cc91..d62e519d 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -22,7 +22,7 @@ pub fn run_test_kernel(kernel_binary_path: &str) { .arg(format!("format=raw,file={}", out_gpt_path.display())); run_cmd.args(QEMU_ARGS); run_cmd.args(std::env::args().skip(2).collect::>()); - run_cmd.arg("-bios").arg("OVMF-pure-efi.fd"); + run_cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); let child_output = run_cmd.output().unwrap(); strip_ansi_escapes::Writer::new(std::io::stderr()) From f2a9dec73a2e9a34827790456cfa823d4b1ca5dc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 20:17:08 +0200 Subject: [PATCH 077/226] Remove outdated mods declarations --- bios/src/main.rs | 1 - common/src/lib.rs | 7 ------- 2 files changed, 8 deletions(-) diff --git a/bios/src/main.rs b/bios/src/main.rs index b615f569..09573303 100644 --- a/bios/src/main.rs +++ b/bios/src/main.rs @@ -1,4 +1,3 @@ - #![no_std] #![no_main] diff --git a/common/src/lib.rs b/common/src/lib.rs index 037a0e5a..d2c9b704 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -19,13 +19,6 @@ use x86_64::{ }; use xmas_elf::ElfFile; -/// Provides BIOS-specific types and trait implementations. -#[cfg(feature = "bios_bin")] -pub mod bios; -/// Provides UEFI-specific trait implementations. -#[cfg(feature = "uefi_bin")] -mod uefi; - /// Provides a function to gather entropy and build a RNG. mod entropy; mod gdt; From 74e173291b0dfcd88babb3f6e26eff8dbb33a1c4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 20:36:03 +0200 Subject: [PATCH 078/226] Fix: Don't pass cli arguments to QEMU --- tests/runner/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index d62e519d..89f48885 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -21,7 +21,6 @@ pub fn run_test_kernel(kernel_binary_path: &str) { .arg("-drive") .arg(format!("format=raw,file={}", out_gpt_path.display())); run_cmd.args(QEMU_ARGS); - run_cmd.args(std::env::args().skip(2).collect::>()); run_cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); let child_output = run_cmd.output().unwrap(); From 302b8aed7db2c8a7e4daf9e0292f063f118d0353 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 20:36:43 +0200 Subject: [PATCH 079/226] CI: Run tests in parallel --- .github/workflows/ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 25bfdbf3..359969bc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,7 +57,6 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: "-- --test-threads 1" - name: "Example: `basic`" working-directory: examples/basic From a4e67f3c95b25004a3c661ffd94d0d980a7939d0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 20:41:49 +0200 Subject: [PATCH 080/226] Remove fragile framebuffer checks --- .../default_settings/src/bin/check_boot_info.rs | 15 --------------- .../higher_half/src/bin/check_boot_info.rs | 15 --------------- .../map_phys_mem/src/bin/check_boot_info.rs | 15 --------------- tests/test_kernels/pie/src/bin/check_boot_info.rs | 15 --------------- 4 files changed, 60 deletions(-) diff --git a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs index 84b22d1e..b0e3eefb 100644 --- a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs +++ b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs @@ -14,27 +14,12 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check framebuffer let framebuffer = boot_info.framebuffer.as_ref().unwrap(); assert_eq!(framebuffer.info().byte_len, framebuffer.buffer().len()); - if ![640, 1024].contains(&framebuffer.info().horizontal_resolution) { - panic!( - "unexpected horizontal_resolution `{}`", - framebuffer.info().horizontal_resolution - ); - } - if ![480, 768].contains(&framebuffer.info().vertical_resolution) { - panic!( - "unexpected vertical_resolution `{}`", - framebuffer.info().vertical_resolution - ); - } if ![3, 4].contains(&framebuffer.info().bytes_per_pixel) { panic!( "unexpected bytes_per_pixel `{}`", framebuffer.info().bytes_per_pixel ); } - if ![640, 1024].contains(&framebuffer.info().stride) { - panic!("unexpected stride `{}`", framebuffer.info().stride); - } assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs index 938e874b..e3609286 100644 --- a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -14,27 +14,12 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check framebuffer let framebuffer = boot_info.framebuffer.as_ref().unwrap(); assert_eq!(framebuffer.info().byte_len, framebuffer.buffer().len()); - if ![640, 1024].contains(&framebuffer.info().horizontal_resolution) { - panic!( - "unexpected horizontal_resolution `{}`", - framebuffer.info().horizontal_resolution - ); - } - if ![480, 768].contains(&framebuffer.info().vertical_resolution) { - panic!( - "unexpected vertical_resolution `{}`", - framebuffer.info().vertical_resolution - ); - } if ![3, 4].contains(&framebuffer.info().bytes_per_pixel) { panic!( "unexpected bytes_per_pixel `{}`", framebuffer.info().bytes_per_pixel ); } - if ![640, 1024].contains(&framebuffer.info().stride) { - panic!("unexpected stride `{}`", framebuffer.info().stride); - } assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), diff --git a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs index aa65d86f..e66b0487 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs @@ -14,27 +14,12 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check framebuffer let framebuffer = boot_info.framebuffer.as_ref().unwrap(); assert_eq!(framebuffer.info().byte_len, framebuffer.buffer().len()); - if ![640, 1024].contains(&framebuffer.info().horizontal_resolution) { - panic!( - "unexpected horizontal_resolution `{}`", - framebuffer.info().horizontal_resolution - ); - } - if ![480, 768].contains(&framebuffer.info().vertical_resolution) { - panic!( - "unexpected vertical_resolution `{}`", - framebuffer.info().vertical_resolution - ); - } if ![3, 4].contains(&framebuffer.info().bytes_per_pixel) { panic!( "unexpected bytes_per_pixel `{}`", framebuffer.info().bytes_per_pixel ); } - if ![640, 1024].contains(&framebuffer.info().stride) { - panic!("unexpected stride `{}`", framebuffer.info().stride); - } assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), diff --git a/tests/test_kernels/pie/src/bin/check_boot_info.rs b/tests/test_kernels/pie/src/bin/check_boot_info.rs index b4f15c41..deb126e8 100644 --- a/tests/test_kernels/pie/src/bin/check_boot_info.rs +++ b/tests/test_kernels/pie/src/bin/check_boot_info.rs @@ -14,27 +14,12 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // check framebuffer let framebuffer = boot_info.framebuffer.as_ref().unwrap(); assert_eq!(framebuffer.info().byte_len, framebuffer.buffer().len()); - if ![640, 1024].contains(&framebuffer.info().horizontal_resolution) { - panic!( - "unexpected horizontal_resolution `{}`", - framebuffer.info().horizontal_resolution - ); - } - if ![480, 768].contains(&framebuffer.info().vertical_resolution) { - panic!( - "unexpected vertical_resolution `{}`", - framebuffer.info().vertical_resolution - ); - } if ![3, 4].contains(&framebuffer.info().bytes_per_pixel) { panic!( "unexpected bytes_per_pixel `{}`", framebuffer.info().bytes_per_pixel ); } - if ![640, 1024].contains(&framebuffer.info().stride) { - panic!("unexpected stride `{}`", framebuffer.info().stride); - } assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), From 09fc7bdc9e335c846ec35148ec184f6f85648e8e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 9 Apr 2022 20:50:43 +0200 Subject: [PATCH 081/226] Remove examples --- .github/workflows/ci.yml | 8 - examples/README.md | 4 - examples/basic/.cargo/config.toml | 7 - examples/basic/.gitignore | 1 - examples/basic/Cargo.lock | 46 ----- examples/basic/Cargo.toml | 14 -- examples/basic/README.md | 17 -- examples/basic/simple_boot/Cargo.toml | 11 -- examples/basic/simple_boot/src/main.rs | 78 --------- examples/basic/src/main.rs | 24 --- examples/basic/x86_64-custom.json | 15 -- examples/test_framework/.cargo/config.toml | 8 - examples/test_framework/.gitignore | 1 - examples/test_framework/Cargo.lock | 193 --------------------- examples/test_framework/Cargo.toml | 17 -- examples/test_framework/README.md | 18 -- examples/test_framework/boot/Cargo.toml | 12 -- examples/test_framework/boot/src/main.rs | 105 ----------- examples/test_framework/src/main.rs | 89 ---------- examples/test_framework/src/serial.rs | 38 ---- examples/test_framework/x86_64-custom.json | 15 -- 21 files changed, 721 deletions(-) delete mode 100644 examples/README.md delete mode 100644 examples/basic/.cargo/config.toml delete mode 100644 examples/basic/.gitignore delete mode 100644 examples/basic/Cargo.lock delete mode 100644 examples/basic/Cargo.toml delete mode 100644 examples/basic/README.md delete mode 100644 examples/basic/simple_boot/Cargo.toml delete mode 100644 examples/basic/simple_boot/src/main.rs delete mode 100644 examples/basic/src/main.rs delete mode 100644 examples/basic/x86_64-custom.json delete mode 100644 examples/test_framework/.cargo/config.toml delete mode 100644 examples/test_framework/.gitignore delete mode 100644 examples/test_framework/Cargo.lock delete mode 100644 examples/test_framework/Cargo.toml delete mode 100644 examples/test_framework/README.md delete mode 100644 examples/test_framework/boot/Cargo.toml delete mode 100644 examples/test_framework/boot/src/main.rs delete mode 100644 examples/test_framework/src/main.rs delete mode 100644 examples/test_framework/src/serial.rs delete mode 100644 examples/test_framework/x86_64-custom.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 359969bc..f17eb994 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,14 +58,6 @@ jobs: with: command: test - - name: "Example: `basic`" - working-directory: examples/basic - run: cargo kimage - - - name: "Example: `test_framework` example" - working-directory: examples/test_framework - run: cargo ktest - fmt: name: Check Formatting runs-on: ubuntu-latest diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index fe989f61..00000000 --- a/examples/README.md +++ /dev/null @@ -1,4 +0,0 @@ -## Usage Examples - -- [`basic`](basic) -- [`test_framework`](test_framework) diff --git a/examples/basic/.cargo/config.toml b/examples/basic/.cargo/config.toml deleted file mode 100644 index 7db03db9..00000000 --- a/examples/basic/.cargo/config.toml +++ /dev/null @@ -1,7 +0,0 @@ -[target.'cfg(target_os = "none")'] -runner = "cargo run --package simple_boot --" - -[alias] -kbuild = "build --target x86_64-custom.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem" -kimage = "run --target x86_64-custom.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem -- --no-run" -krun = "run --target x86_64-custom.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem" diff --git a/examples/basic/.gitignore b/examples/basic/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/examples/basic/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/basic/Cargo.lock b/examples/basic/Cargo.lock deleted file mode 100644 index aeaf1c02..00000000 --- a/examples/basic/Cargo.lock +++ /dev/null @@ -1,46 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "basic_example" -version = "0.1.0" -dependencies = [ - "bootloader_api", -] - -[[package]] -name = "bootloader-locator" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaaa9db3339d32c2622f2e5d0731eb82a468d3439797c9d4fe426744fe2bd551" -dependencies = [ - "json", -] - -[[package]] -name = "bootloader_api" -version = "0.1.0" - -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - -[[package]] -name = "locate-cargo-manifest" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db985b63431fe09e8d71f50aeceffcc31e720cb86be8dad2f38d084c5a328466" -dependencies = [ - "json", -] - -[[package]] -name = "simple_boot" -version = "0.1.0" -dependencies = [ - "bootloader-locator", - "locate-cargo-manifest", -] diff --git a/examples/basic/Cargo.toml b/examples/basic/Cargo.toml deleted file mode 100644 index a29f8951..00000000 --- a/examples/basic/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "basic_example" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[workspace] -members = [ - "simple_boot", -] - -[dependencies] -bootloader_api = { path = "../../api" } # replace this with a version number diff --git a/examples/basic/README.md b/examples/basic/README.md deleted file mode 100644 index 32c37c7f..00000000 --- a/examples/basic/README.md +++ /dev/null @@ -1,17 +0,0 @@ -# Basic Example - -This a minimal example how to create a bootable disk image with the `bootloader` crate. - -## Structure - -The kernel code is in `src/main.rs`. It requires some special build instructions to recompile the `core` library for the custom target defined in `x86_64-custom.json`. It depends on the `bootloader` crate for booting.. - -The `simple_boot` sub-crate is responsible for combining the kernel with the bootloader to create bootable disk images. It is configured as a [custom _runner_](https://doc.rust-lang.org/cargo/reference/config.html#targettriplerunner), which means that cargo will automatically invoke it on `cargo run`. The compiled kernel will hereby be passed as an argument. - -## Build Commands - -The `.cargo/config.toml` file defines command aliases for the common commands: - -- To build the kernel, run **`cargo kbuild`**. -- To build the kernel and turn it into a bootable disk image, run **`cargo kimage`** (short for "kernel image"). This will invoke our `boot` sub-crate with an additional `--no-run` argument so that it just creates the disk image and exits. -- To additionally run the kernel in QEMU after creating the disk image, run **`cargo krun`**. diff --git a/examples/basic/simple_boot/Cargo.toml b/examples/basic/simple_boot/Cargo.toml deleted file mode 100644 index 5302cdb4..00000000 --- a/examples/basic/simple_boot/Cargo.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "simple_boot" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bootloader-locator = "0.0.4" # for locating the `bootloader` dependency on disk -locate-cargo-manifest = "0.2.0" # for locating the kernel's `Cargo.toml` diff --git a/examples/basic/simple_boot/src/main.rs b/examples/basic/simple_boot/src/main.rs deleted file mode 100644 index 92814ba0..00000000 --- a/examples/basic/simple_boot/src/main.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - process::Command, -}; - -const RUN_ARGS: &[&str] = &["--no-reboot", "-s"]; - -fn main() { - let mut args = std::env::args().skip(1); // skip executable name - - let kernel_binary_path = { - let path = PathBuf::from(args.next().unwrap()); - path.canonicalize().unwrap() - }; - let no_boot = if let Some(arg) = args.next() { - match arg.as_str() { - "--no-run" => true, - other => panic!("unexpected argument `{}`", other), - } - } else { - false - }; - - let bios = create_disk_images(&kernel_binary_path); - - if no_boot { - println!("Created disk image at `{}`", bios.display()); - return; - } - - let mut run_cmd = Command::new("qemu-system-x86_64"); - run_cmd - .arg("-drive") - .arg(format!("format=raw,file={}", bios.display())); - run_cmd.args(RUN_ARGS); - - let exit_status = run_cmd.status().unwrap(); - if !exit_status.success() { - std::process::exit(exit_status.code().unwrap_or(1)); - } -} - -pub fn create_disk_images(kernel_binary_path: &Path) -> PathBuf { - let bootloader_manifest_path = bootloader_locator::locate_bootloader("bootloader").unwrap(); - let kernel_manifest_path = locate_cargo_manifest::locate_manifest().unwrap(); - - let mut build_cmd = Command::new(env!("CARGO")); - build_cmd.current_dir(bootloader_manifest_path.parent().unwrap()); - build_cmd.arg("builder"); - build_cmd - .arg("--kernel-manifest") - .arg(&kernel_manifest_path); - build_cmd.arg("--kernel-binary").arg(&kernel_binary_path); - build_cmd - .arg("--target-dir") - .arg(kernel_manifest_path.parent().unwrap().join("target")); - build_cmd - .arg("--out-dir") - .arg(kernel_binary_path.parent().unwrap()); - build_cmd.arg("--quiet"); - - if !build_cmd.status().unwrap().success() { - panic!("build failed"); - } - - let kernel_binary_name = kernel_binary_path.file_name().unwrap().to_str().unwrap(); - let disk_image = kernel_binary_path - .parent() - .unwrap() - .join(format!("boot-bios-{}.img", kernel_binary_name)); - if !disk_image.exists() { - panic!( - "Disk image does not exist at {} after bootloader build", - disk_image.display() - ); - } - disk_image -} diff --git a/examples/basic/src/main.rs b/examples/basic/src/main.rs deleted file mode 100644 index 9ac8d38a..00000000 --- a/examples/basic/src/main.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![no_std] -#![no_main] - -use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; - -entry_point!(kernel_main); - -fn kernel_main(boot_info: &'static mut BootInfo) -> ! { - // turn the screen gray - if let Some(framebuffer) = boot_info.framebuffer.as_mut() { - for byte in framebuffer.buffer_mut() { - *byte = 0x90; - } - } - - loop {} -} - - -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} diff --git a/examples/basic/x86_64-custom.json b/examples/basic/x86_64-custom.json deleted file mode 100644 index c1c29f9e..00000000 --- a/examples/basic/x86_64-custom.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "panic-strategy": "abort", - "disable-redzone": true, - "features": "-mmx,-sse,+soft-float" - } \ No newline at end of file diff --git a/examples/test_framework/.cargo/config.toml b/examples/test_framework/.cargo/config.toml deleted file mode 100644 index c930ed2a..00000000 --- a/examples/test_framework/.cargo/config.toml +++ /dev/null @@ -1,8 +0,0 @@ -[target.'cfg(target_os = "none")'] -runner = "cargo run --package boot --" - -[alias] -kbuild = "build --target x86_64-custom.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem" -kimage = "run --target x86_64-custom.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem -- --no-run" -krun = "run --target x86_64-custom.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem" -ktest = "test --target x86_64-custom.json -Zbuild-std=core -Zbuild-std-features=compiler-builtins-mem" diff --git a/examples/test_framework/.gitignore b/examples/test_framework/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/examples/test_framework/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/examples/test_framework/Cargo.lock b/examples/test_framework/Cargo.lock deleted file mode 100644 index 916dbd55..00000000 --- a/examples/test_framework/Cargo.lock +++ /dev/null @@ -1,193 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "bit_field" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "boot" -version = "0.1.0" -dependencies = [ - "bootloader-locator", - "locate-cargo-manifest", - "runner-utils", -] - -[[package]] -name = "bootloader" -version = "0.10.4" - -[[package]] -name = "bootloader-locator" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaaa9db3339d32c2622f2e5d0731eb82a468d3439797c9d4fe426744fe2bd551" -dependencies = [ - "json", -] - -[[package]] -name = "json" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" - -[[package]] -name = "libc" -version = "0.2.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" - -[[package]] -name = "locate-cargo-manifest" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db985b63431fe09e8d71f50aeceffcc31e720cb86be8dad2f38d084c5a328466" -dependencies = [ - "json", -] - -[[package]] -name = "lock_api" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" -dependencies = [ - "scopeguard", -] - -[[package]] -name = "proc-macro2" -version = "1.0.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "quote" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "runner-utils" -version = "0.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9dc6848b056990cd51e72aa5556bdbea4a96013e8b18635d183c84159c2988f" -dependencies = [ - "thiserror", - "wait-timeout", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "spin" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b87bbf98cb81332a56c1ee8929845836f85e8ddd693157c30d76660196014478" -dependencies = [ - "lock_api", -] - -[[package]] -name = "syn" -version = "1.0.72" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" -dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", -] - -[[package]] -name = "test_framework_example" -version = "0.1.0" -dependencies = [ - "bootloader", - "spin", - "uart_16550", - "x86_64", -] - -[[package]] -name = "thiserror" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "uart_16550" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503a6c0e6d82daa87985e662d120c0176b09587c92a68db22781b28ae95405dd" -dependencies = [ - "bitflags", - "x86_64", -] - -[[package]] -name = "unicode-xid" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" - -[[package]] -name = "volatile" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4c2dbd44eb8b53973357e6e207e370f0c1059990df850aca1eca8947cf464f0" - -[[package]] -name = "wait-timeout" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" -dependencies = [ - "libc", -] - -[[package]] -name = "x86_64" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13f09cffc08ee86bf5e4d147f107a43de0885c53ffad799b39f4ad203fb2a27d" -dependencies = [ - "bit_field", - "bitflags", - "volatile", -] diff --git a/examples/test_framework/Cargo.toml b/examples/test_framework/Cargo.toml deleted file mode 100644 index 01bbd691..00000000 --- a/examples/test_framework/Cargo.toml +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "test_framework_example" -version = "0.1.0" -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[workspace] -members = [ - "boot", -] - -[dependencies] -bootloader_api = { path = "../../api" } # replace this with a version number -x86_64 = "0.14.7" -uart_16550 = "0.2.14" -spin = { version = "0.9.0", features = ["lazy"] } diff --git a/examples/test_framework/README.md b/examples/test_framework/README.md deleted file mode 100644 index edd9bb0b..00000000 --- a/examples/test_framework/README.md +++ /dev/null @@ -1,18 +0,0 @@ -# Test Framework Example - -This examples showcases how kernels can implement unit and integration testing using the `bootloader` crate. - -## Structure - -The kernel code is in `src/main.rs`. It requires some special build instructions to recompile the `core` library for the custom target defined in `x86_64-custom.json`. It depends on the `bootloader` crate for booting and [uses the unstable `custom_test_frameworks`](https://os.phil-opp.com/testing/#custom-test-frameworks) feature. - -The `boot` sub-crate is responsible for combining the kernel with the bootloader to create bootable disk images. It is configured as a [custom _runner_](https://doc.rust-lang.org/cargo/reference/config.html#targettriplerunner), which means that cargo will automatically invoke it on `cargo run` and `cargo test`. The compiled kernel will hereby be passed as an argument. - -## Build Commands - -The `.cargo/config.toml` file defines command aliases for the common commands: - -- To build the kernel, run **`cargo kbuild`**. -- To build the kernel and turn it into a bootable disk image, run **`cargo kimage`** (short for "kernel image"). This will invoke our `boot` sub-crate with an additional `--no-run` argument so that it just creates the disk image and exits. -- To additionally run the kernel in QEMU after creating the disk image, run **`cargo krun`**. -- To run the unit tests in QEMU, run **`cargo ktest`**. diff --git a/examples/test_framework/boot/Cargo.toml b/examples/test_framework/boot/Cargo.toml deleted file mode 100644 index 89ab8998..00000000 --- a/examples/test_framework/boot/Cargo.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "boot" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -bootloader-locator = "0.0.4" # for locating the `bootloader` dependency on disk -runner-utils = "0.0.2" # small helper functions for custom runners (e.g. timeouts) -locate-cargo-manifest = "0.2.0" # for locating the kernel's `Cargo.toml` diff --git a/examples/test_framework/boot/src/main.rs b/examples/test_framework/boot/src/main.rs deleted file mode 100644 index e10ea6d5..00000000 --- a/examples/test_framework/boot/src/main.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - process::{Command, ExitStatus}, - time::Duration, -}; - -const RUN_ARGS: &[&str] = &["--no-reboot", "-s"]; -const TEST_ARGS: &[&str] = &[ - "-device", - "isa-debug-exit,iobase=0xf4,iosize=0x04", - "-serial", - "stdio", - "-display", - "none", - "--no-reboot", -]; -const TEST_TIMEOUT_SECS: u64 = 10; - -fn main() { - let mut args = std::env::args().skip(1); // skip executable name - - let kernel_binary_path = { - let path = PathBuf::from(args.next().unwrap()); - path.canonicalize().unwrap() - }; - let no_boot = if let Some(arg) = args.next() { - match arg.as_str() { - "--no-run" => true, - other => panic!("unexpected argument `{}`", other), - } - } else { - false - }; - - let bios = create_disk_images(&kernel_binary_path); - - if no_boot { - println!("Created disk image at `{}`", bios.display()); - return; - } - - let mut run_cmd = Command::new("qemu-system-x86_64"); - run_cmd - .arg("-drive") - .arg(format!("format=raw,file={}", bios.display())); - - let binary_kind = runner_utils::binary_kind(&kernel_binary_path); - if binary_kind.is_test() { - run_cmd.args(TEST_ARGS); - - let exit_status = run_test_command(run_cmd); - match exit_status.code() { - Some(33) => {} // success - other => panic!("Test failed (exit code: {:?})", other), - } - } else { - run_cmd.args(RUN_ARGS); - - let exit_status = run_cmd.status().unwrap(); - if !exit_status.success() { - std::process::exit(exit_status.code().unwrap_or(1)); - } - } -} - -fn run_test_command(mut cmd: Command) -> ExitStatus { - runner_utils::run_with_timeout(&mut cmd, Duration::from_secs(TEST_TIMEOUT_SECS)).unwrap() -} - -pub fn create_disk_images(kernel_binary_path: &Path) -> PathBuf { - let bootloader_manifest_path = bootloader_locator::locate_bootloader("bootloader").unwrap(); - let kernel_manifest_path = locate_cargo_manifest::locate_manifest().unwrap(); - - let mut build_cmd = Command::new(env!("CARGO")); - build_cmd.current_dir(bootloader_manifest_path.parent().unwrap()); - build_cmd.arg("builder"); - build_cmd - .arg("--kernel-manifest") - .arg(&kernel_manifest_path); - build_cmd.arg("--kernel-binary").arg(&kernel_binary_path); - build_cmd - .arg("--target-dir") - .arg(kernel_manifest_path.parent().unwrap().join("target")); - build_cmd - .arg("--out-dir") - .arg(kernel_binary_path.parent().unwrap()); - build_cmd.arg("--quiet"); - - if !build_cmd.status().unwrap().success() { - panic!("build failed"); - } - - let kernel_binary_name = kernel_binary_path.file_name().unwrap().to_str().unwrap(); - let disk_image = kernel_binary_path - .parent() - .unwrap() - .join(format!("boot-bios-{}.img", kernel_binary_name)); - if !disk_image.exists() { - panic!( - "Disk image does not exist at {} after bootloader build", - disk_image.display() - ); - } - disk_image -} diff --git a/examples/test_framework/src/main.rs b/examples/test_framework/src/main.rs deleted file mode 100644 index deaa75c2..00000000 --- a/examples/test_framework/src/main.rs +++ /dev/null @@ -1,89 +0,0 @@ -#![no_std] -#![no_main] -#![feature(custom_test_frameworks)] -#![test_runner(test_runner)] -#![reexport_test_harness_main = "test_main"] - -use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; - -mod serial; - -entry_point!(kernel_main); - -fn kernel_main(boot_info: &'static mut BootInfo) -> ! { - // turn the screen gray - if let Some(framebuffer) = boot_info.framebuffer.as_mut() { - for byte in framebuffer.buffer_mut() { - *byte = 0x90; - } - } - - #[cfg(test)] - test_main(); - - loop {} -} - -pub fn test_runner(tests: &[&dyn Testable]) { - serial_println!("Running {} tests", tests.len()); - for test in tests { - test.run(); - } - exit_qemu(QemuExitCode::Success); -} - -pub trait Testable { - fn run(&self) -> (); -} - -impl Testable for T -where - T: Fn(), -{ - fn run(&self) { - serial_print!("{}...\t", core::any::type_name::()); - self(); - serial_println!("[ok]"); - } -} - -#[cfg(not(test))] -#[panic_handler] -fn panic(_info: &PanicInfo) -> ! { - loop {} -} - -#[cfg(test)] -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - serial_println!("[failed]\n"); - serial_println!("Error: {}\n", info); - exit_qemu(QemuExitCode::Failed); -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -#[repr(u32)] -pub enum QemuExitCode { - Success = 0x10, - Failed = 0x11, -} - -pub fn exit_qemu(exit_code: QemuExitCode) -> ! { - use x86_64::instructions::port::Port; - - unsafe { - let mut port = Port::new(0xf4); - port.write(exit_code as u32); - } - - loop {} -} - -#[cfg(test)] -mod tests { - #[test_case] - fn trivial_assertion() { - assert_eq!(1, 1); - } -} diff --git a/examples/test_framework/src/serial.rs b/examples/test_framework/src/serial.rs deleted file mode 100644 index 3f4a3da7..00000000 --- a/examples/test_framework/src/serial.rs +++ /dev/null @@ -1,38 +0,0 @@ -use spin::{Lazy, Mutex}; -use uart_16550::SerialPort; - -pub static SERIAL1: Lazy> = Lazy::new(|| { - let mut serial_port = unsafe { SerialPort::new(0x3F8) }; - serial_port.init(); - Mutex::new(serial_port) -}); - -#[doc(hidden)] -pub fn _print(args: ::core::fmt::Arguments) { - use core::fmt::Write; - use x86_64::instructions::interrupts; - - interrupts::without_interrupts(|| { - SERIAL1 - .lock() - .write_fmt(args) - .expect("Printing to serial failed"); - }); -} - -/// Prints to the host through the serial interface. -#[macro_export] -macro_rules! serial_print { - ($($arg:tt)*) => { - $crate::serial::_print(format_args!($($arg)*)); - }; -} - -/// Prints to the host through the serial interface, appending a newline. -#[macro_export] -macro_rules! serial_println { - () => ($crate::serial_print!("\n")); - ($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n"))); - ($fmt:expr, $($arg:tt)*) => ($crate::serial_print!( - concat!($fmt, "\n"), $($arg)*)); -} diff --git a/examples/test_framework/x86_64-custom.json b/examples/test_framework/x86_64-custom.json deleted file mode 100644 index c1c29f9e..00000000 --- a/examples/test_framework/x86_64-custom.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "arch": "x86_64", - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "os": "none", - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "panic-strategy": "abort", - "disable-redzone": true, - "features": "-mmx,-sse,+soft-float" - } \ No newline at end of file From c533baefdd749f7efcf0872511060172fc768e29 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 10 Apr 2022 12:47:09 +0200 Subject: [PATCH 082/226] Also run tests of api crate on CI This is very important to ensure correct serialization/deserialization of config. --- .github/workflows/ci.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f17eb994..6a7ab020 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: pull_request: schedule: - - cron: '40 5 * * *' # every day at 5:40 + - cron: "40 5 * * *" # every day at 5:40 jobs: check: @@ -53,7 +53,13 @@ jobs: - name: "Print QEMU Version" run: qemu-system-x86_64 --version - - name: Run `cargo test` + - name: Run api tests + uses: actions-rs/cargo@v1 + with: + command: test + args: -p bootloader_api + + - name: Run integration tests uses: actions-rs/cargo@v1 with: command: test From 6d9fefe892ee6d00dfb7bc30905a6127b3960bef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 10 Apr 2022 12:47:54 +0200 Subject: [PATCH 083/226] Increase random config test iterations to 10k --- api/src/config.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/src/config.rs b/api/src/config.rs index 2bfab2c8..4f69174c 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -546,7 +546,7 @@ mod tests { #[test] fn mapping_serde() { - for _ in 0..1000 { + for _ in 0..10000 { let config = Mapping::random(); assert_eq!(Mapping::deserialize(&config.serialize()), Ok(config)); } @@ -554,7 +554,7 @@ mod tests { #[test] fn config_serde() { - for _ in 0..1000 { + for _ in 0..10000 { let config = BootloaderConfig::random(); assert_eq!( BootloaderConfig::deserialize(&config.serialize()), From 393c3086d2067503e8254777924834c5f2b8862f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 10 Apr 2022 12:48:37 +0200 Subject: [PATCH 084/226] Fix copy&paste error in `verify_higher_half` test --- tests/test_kernels/higher_half/src/bin/verify_higher_half.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs index e92bf9ea..7ffd7986 100644 --- a/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs +++ b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs @@ -22,7 +22,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // verify that the stack is located in the higher half of the address space. let stack_addr = &rip; assert_eq!( - (boot_info as *const _ as usize) & 0xffff800000000000, + (stack_addr as *const _ as usize) & 0xffff800000000000, 0xffff800000000000 ); From 5dbc5038d71af72095315c7de194370f1ddb8540 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 10 Apr 2022 14:57:49 +0200 Subject: [PATCH 085/226] Remove `uefi` dependency of `common` crate --- Cargo.lock | 1 - api/src/info.rs | 4 +--- common/Cargo.toml | 1 - common/src/legacy_memory_region.rs | 19 +++++-------------- uefi/src/memory_descriptor.rs | 16 ++++++++++++++++ 5 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3634f2ed..25519ac2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -73,7 +73,6 @@ dependencies = [ "rand_chacha", "raw-cpuid", "spinning_top", - "uefi", "usize_conversions", "x86_64", "xmas-elf", diff --git a/api/src/info.rs b/api/src/info.rs index d73e3a01..555ad0ca 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -147,11 +147,9 @@ pub enum MemoryRegionKind { Bootloader, /// An unknown memory region reported by the UEFI firmware. /// - /// This should only be used if the UEFI memory type is known as usable. + /// Contains the UEFI memory type tag. UnknownUefi(u32), /// An unknown memory region reported by the BIOS firmware. - /// - /// This should only be used if the BIOS memory type is known as usable. UnknownBios(u32), } diff --git a/common/Cargo.toml b/common/Cargo.toml index 48cafade..ae34aca1 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -12,7 +12,6 @@ bootloader_api = { version = "0.1.0-alpha.0", path = "../api" } conquer-once = { version = "0.3.2", default-features = false } log = "0.4.14" spinning_top = "0.2.4" -uefi = "0.13" usize_conversions = "0.2.0" x86_64 = { version = "0.14.8" } xmas-elf = "0.8.0" diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 583d095f..1801199b 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -13,6 +13,9 @@ pub trait LegacyMemoryRegion: Copy + core::fmt::Debug { fn len(&self) -> u64; /// Returns the type of the region, e.g. whether it is usable or reserved. fn kind(&self) -> MemoryRegionKind; + + /// Mark additional regions as usable just before the bootloader jumps to the kernel. + fn on_bootloader_exit(&mut self) {} } /// A physical frame allocator based on a BIOS or UEFI provided memory map. @@ -101,10 +104,11 @@ where ) -> &mut [MemoryRegion] { let mut next_index = 0; - for descriptor in self.original { + for mut descriptor in self.original { let mut start = descriptor.start(); let end = start + descriptor.len(); let next_free = self.next_frame.start_address(); + descriptor.on_bootloader_exit(); let kind = match descriptor.kind() { MemoryRegionKind::Usable => { if end <= next_free { @@ -126,19 +130,6 @@ where MemoryRegionKind::Usable } } - // some mappings created by the UEFI firmware become usable again at this point - MemoryRegionKind::UnknownUefi(other) => { - use uefi::table::boot::MemoryType as M; - match M(other) { - M::LOADER_CODE - | M::LOADER_DATA - | M::BOOT_SERVICES_CODE - | M::BOOT_SERVICES_DATA - | M::RUNTIME_SERVICES_CODE - | M::RUNTIME_SERVICES_DATA => MemoryRegionKind::Usable, - other => MemoryRegionKind::UnknownUefi(other.0), - } - } other => other, }; diff --git a/uefi/src/memory_descriptor.rs b/uefi/src/memory_descriptor.rs index 2ec4d952..8fe8bf1c 100644 --- a/uefi/src/memory_descriptor.rs +++ b/uefi/src/memory_descriptor.rs @@ -23,4 +23,20 @@ impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { other => MemoryRegionKind::UnknownUefi(other.0), } } + + fn on_bootloader_exit(&mut self) { + match self.0.ty { + // the bootloader is about to exit, so we can reallocate its data + MemoryType::LOADER_CODE + | MemoryType::LOADER_DATA + | MemoryType::BOOT_SERVICES_CODE + | MemoryType::BOOT_SERVICES_DATA + | MemoryType::RUNTIME_SERVICES_CODE + | MemoryType::RUNTIME_SERVICES_DATA => { + // we don't need this data anymore + self.0.ty = MemoryType::CONVENTIONAL; + } + _ => {} + } + } } From 6134fedfa1ce0cf7adddc3f8b13cca21201f7cbd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 10 Apr 2022 16:46:11 +0200 Subject: [PATCH 086/226] Replace `rand_chacha` with `rand_hc` The former seems to use some SSE features, which cause LLVM errors since we're compiling for a target that has SSE disabled. --- Cargo.lock | 11 ++++++++++- common/Cargo.toml | 2 +- common/src/entropy.rs | 7 ++++--- common/src/level_4_entries.rs | 4 ++-- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25519ac2..46522377 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -70,7 +70,7 @@ dependencies = [ "log", "noto-sans-mono-bitmap", "rand", - "rand_chacha", + "rand_hc", "raw-cpuid", "spinning_top", "usize_conversions", @@ -309,6 +309,15 @@ dependencies = [ "getrandom", ] +[[package]] +name = "rand_hc" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" +dependencies = [ + "rand_core", +] + [[package]] name = "raw-cpuid" version = "10.3.0" diff --git a/common/Cargo.toml b/common/Cargo.toml index ae34aca1..1dc65539 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -17,7 +17,7 @@ x86_64 = { version = "0.14.8" } xmas-elf = "0.8.0" raw-cpuid = "10.2.0" rand = { version = "0.8.4", default-features = false } -rand_chacha = { version = "0.3.1", default-features = false } +rand_hc = "0.3.1" [dependencies.noto-sans-mono-bitmap] version = "0.1.2" diff --git a/common/src/entropy.rs b/common/src/entropy.rs index 65ef3821..c38f22ef 100644 --- a/common/src/entropy.rs +++ b/common/src/entropy.rs @@ -1,9 +1,10 @@ -use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; +use rand::SeedableRng; +use rand_hc::Hc128Rng; use raw_cpuid::CpuId; use x86_64::instructions::{port::Port, random::RdRand}; /// Gather entropy from various sources to seed a RNG. -pub fn build_rng() -> ChaCha20Rng { +pub fn build_rng() -> Hc128Rng { const ENTROPY_SOURCES: [fn() -> [u8; 32]; 3] = [rd_rand_entropy, tsc_entropy, pit_entropy]; // Collect entropy from different sources and xor them all together. @@ -17,7 +18,7 @@ pub fn build_rng() -> ChaCha20Rng { } // Construct the RNG. - ChaCha20Rng::from_seed(seed) + Hc128Rng::from_seed(seed) } /// Gather entropy by requesting random numbers with `RDRAND` instruction if it's available. diff --git a/common/src/level_4_entries.rs b/common/src/level_4_entries.rs index f3296bf2..e3addb96 100644 --- a/common/src/level_4_entries.rs +++ b/common/src/level_4_entries.rs @@ -5,7 +5,7 @@ use rand::{ distributions::{Distribution, Uniform}, seq::IteratorRandom, }; -use rand_chacha::ChaCha20Rng; +use rand_hc::Hc128Rng; use usize_conversions::IntoUsize; use x86_64::{ structures::paging::{Page, PageTableIndex, Size4KiB}, @@ -21,7 +21,7 @@ pub struct UsedLevel4Entries { entry_state: [bool; 512], /// A random number generator that should be used to generate random addresses or /// `None` if aslr is disabled. - rng: Option, + rng: Option, } impl UsedLevel4Entries { From 0b8f95d97da03a6654632675e0bf86865396f50b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 10 Apr 2022 16:47:06 +0200 Subject: [PATCH 087/226] Remove uneeded dependencies --- Cargo.toml | 3 --- 1 file changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 586572d6..b34aa8f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,9 +29,6 @@ exclude = ["examples/basic", "examples/test_framework"] anyhow = "1.0.32" fatfs = "0.3.4" gpt = "3.0.0" -raw-cpuid = { version = "10.2.0", optional = true } -rand = { version = "0.8.4", optional = true, default-features = false } -rand_chacha = { version = "0.3.1", optional = true, default-features = false } [dev-dependencies] bootloader_test_runner = { path = "tests/runner" } From eb6d71f244c54031bddbf87042d710bcdd1aa0bb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 10 Apr 2022 17:00:36 +0200 Subject: [PATCH 088/226] Start integrating BIOS bootloader into build system We can use an artifact dependency on the outer level since the x86_64-unknown-none target is Tier 2 and thus requires no -Zbuild-std. For the boot_sector, we need to compile with a specific linker script and profile. We might need to duplicate the profile settings in the boot_sector Cargo.toml when publishing. --- Cargo.lock | 14 +- Cargo.toml | 15 +- bios/Cargo.toml | 2 +- bios/{first_stage => boot_sector}/.gitignore | 0 .../16-bit-linker.ld | 0 bios/{first_stage => boot_sector}/Cargo.toml | 2 +- bios/{first_stage => boot_sector}/README.md | 0 bios/boot_sector/build.rs | 9 + .../src/boot unreal.s | 0 bios/{first_stage => boot_sector}/src/boot.s | 0 bios/{first_stage => boot_sector}/src/dap.rs | 0 bios/{first_stage => boot_sector}/src/fail.rs | 0 bios/{first_stage => boot_sector}/src/fat.rs | 0 bios/{first_stage => boot_sector}/src/main.rs | 0 bios/{first_stage => boot_sector}/src/mbr.rs | 0 bios/build.rs | 52 ++++ bios/src/main.rs | 81 +---- bios/src/main_bak.rs | 276 ++++++++++++++++++ bios/{first_stage => }/x86-16bit.json | 7 +- 19 files changed, 355 insertions(+), 103 deletions(-) rename bios/{first_stage => boot_sector}/.gitignore (100%) rename bios/{first_stage => boot_sector}/16-bit-linker.ld (100%) rename bios/{first_stage => boot_sector}/Cargo.toml (82%) rename bios/{first_stage => boot_sector}/README.md (100%) create mode 100644 bios/boot_sector/build.rs rename bios/{first_stage => boot_sector}/src/boot unreal.s (100%) rename bios/{first_stage => boot_sector}/src/boot.s (100%) rename bios/{first_stage => boot_sector}/src/dap.rs (100%) rename bios/{first_stage => boot_sector}/src/fail.rs (100%) rename bios/{first_stage => boot_sector}/src/fat.rs (100%) rename bios/{first_stage => boot_sector}/src/main.rs (100%) rename bios/{first_stage => boot_sector}/src/mbr.rs (100%) create mode 100644 bios/build.rs create mode 100644 bios/src/main_bak.rs rename bios/{first_stage => }/x86-16bit.json (81%) diff --git a/Cargo.lock b/Cargo.lock index 46522377..c87cbbfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,12 +37,10 @@ name = "bootloader" version = "0.11.0-alpha.0" dependencies = [ "anyhow", + "bootloader-x86_64-bios", "bootloader_test_runner", "fatfs", "gpt", - "rand", - "rand_chacha", - "raw-cpuid", "test_kernel_default_settings", "test_kernel_higher_half", "test_kernel_map_phys_mem", @@ -51,7 +49,7 @@ dependencies = [ [[package]] name = "bootloader-x86_64-bios" -version = "0.1.0" +version = "0.1.0-alpha.0" dependencies = [ "bootloader-x86_64-common", "bootloader_api", @@ -61,6 +59,10 @@ dependencies = [ "x86_64", ] +[[package]] +name = "bootloader-x86_64-bios-boot-sector" +version = "0.1.0" + [[package]] name = "bootloader-x86_64-common" version = "0.1.0-alpha.0" @@ -96,10 +98,6 @@ dependencies = [ "rand", ] -[[package]] -name = "bootloader_first_stage" -version = "0.1.0" - [[package]] name = "bootloader_test_runner" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b34aa8f3..9d8e3665 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ members = [ "common", "uefi", "bios", - "bios/first_stage", + "bios/boot_sector", "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", @@ -30,6 +30,12 @@ anyhow = "1.0.32" fatfs = "0.3.4" gpt = "3.0.0" +[dependencies.bootloader-x86_64-bios] +version = "0.1.0-alpha.0" +path = "bios" +artifact = ["bin"] +target = "x86_64-unknown-none" + [dev-dependencies] bootloader_test_runner = { path = "tests/runner" } test_kernel_default_settings = { path = "tests/test_kernels/default_settings", artifact = "bin", target = "x86_64-unknown-none" } @@ -37,13 +43,6 @@ test_kernel_higher_half = { path = "tests/test_kernels/higher_half", artifact = test_kernel_map_phys_mem = { path = "tests/test_kernels/map_phys_mem", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = "x86_64-unknown-none" } -# [dependencies.bootloader-x86_64-uefi] -# version = "0.1.0" -# path = "uefi" -# artifact = ["bin"] -# target = "x86_64-unknown-uefi" - - [profile.dev] panic = "abort" diff --git a/bios/Cargo.toml b/bios/Cargo.toml index a7c461bc..9b6798da 100644 --- a/bios/Cargo.toml +++ b/bios/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bootloader-x86_64-bios" -version = "0.1.0" +version = "0.1.0-alpha.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/bios/first_stage/.gitignore b/bios/boot_sector/.gitignore similarity index 100% rename from bios/first_stage/.gitignore rename to bios/boot_sector/.gitignore diff --git a/bios/first_stage/16-bit-linker.ld b/bios/boot_sector/16-bit-linker.ld similarity index 100% rename from bios/first_stage/16-bit-linker.ld rename to bios/boot_sector/16-bit-linker.ld diff --git a/bios/first_stage/Cargo.toml b/bios/boot_sector/Cargo.toml similarity index 82% rename from bios/first_stage/Cargo.toml rename to bios/boot_sector/Cargo.toml index df726a5d..620a036f 100644 --- a/bios/first_stage/Cargo.toml +++ b/bios/boot_sector/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bootloader_first_stage" +name = "bootloader-x86_64-bios-boot-sector" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2021" diff --git a/bios/first_stage/README.md b/bios/boot_sector/README.md similarity index 100% rename from bios/first_stage/README.md rename to bios/boot_sector/README.md diff --git a/bios/boot_sector/build.rs b/bios/boot_sector/build.rs new file mode 100644 index 00000000..d2e1f58c --- /dev/null +++ b/bios/boot_sector/build.rs @@ -0,0 +1,9 @@ +use std::path::Path; + +fn main() { + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); + println!( + "cargo:rustc-link-arg-bins=--script={}", + local_path.join("16-bit-linker.ld").display() + ) +} diff --git a/bios/first_stage/src/boot unreal.s b/bios/boot_sector/src/boot unreal.s similarity index 100% rename from bios/first_stage/src/boot unreal.s rename to bios/boot_sector/src/boot unreal.s diff --git a/bios/first_stage/src/boot.s b/bios/boot_sector/src/boot.s similarity index 100% rename from bios/first_stage/src/boot.s rename to bios/boot_sector/src/boot.s diff --git a/bios/first_stage/src/dap.rs b/bios/boot_sector/src/dap.rs similarity index 100% rename from bios/first_stage/src/dap.rs rename to bios/boot_sector/src/dap.rs diff --git a/bios/first_stage/src/fail.rs b/bios/boot_sector/src/fail.rs similarity index 100% rename from bios/first_stage/src/fail.rs rename to bios/boot_sector/src/fail.rs diff --git a/bios/first_stage/src/fat.rs b/bios/boot_sector/src/fat.rs similarity index 100% rename from bios/first_stage/src/fat.rs rename to bios/boot_sector/src/fat.rs diff --git a/bios/first_stage/src/main.rs b/bios/boot_sector/src/main.rs similarity index 100% rename from bios/first_stage/src/main.rs rename to bios/boot_sector/src/main.rs diff --git a/bios/first_stage/src/mbr.rs b/bios/boot_sector/src/mbr.rs similarity index 100% rename from bios/first_stage/src/mbr.rs rename to bios/boot_sector/src/mbr.rs diff --git a/bios/build.rs b/bios/build.rs new file mode 100644 index 00000000..d5026906 --- /dev/null +++ b/bios/build.rs @@ -0,0 +1,52 @@ +use std::{ + path::{Path, PathBuf}, + process::Command, +}; + +const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = "0.1.0-alpha.0"; + +fn main() { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + + let bios_boot_sector_path = build_bios_boot_sector(&out_dir); + + println!( + "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", + bios_boot_sector_path.display() + ); +} + +fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); + let mut cmd = Command::new(cargo); + cmd.arg("install").arg("bootloader-x86_64-bios-boot-sector"); + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("boot_sector"); + if local_path.exists() { + // local build + cmd.arg("--path").arg(&local_path); + } else { + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); + } + cmd.arg("--locked"); + cmd.arg("--target").arg("x86-16bit.json"); + cmd.arg("--profile").arg("first-stage"); + cmd.arg("-Zbuild-std=core") + .arg("-Zbuild-std-features=compiler-builtins-mem"); + cmd.arg("--root").arg(out_dir); + let status = cmd + .status() + .expect("failed to run cargo install for bios boot sector"); + if status.success() { + let path = out_dir + .join("bin") + .join("bootloader-x86_64-bios-boot-sector"); + assert!( + path.exists(), + "bios boot sector executable does not exist after building" + ); + path + } else { + panic!("failed to build bios boot sector"); + } +} diff --git a/bios/src/main.rs b/bios/src/main.rs index 09573303..50516e52 100644 --- a/bios/src/main.rs +++ b/bios/src/main.rs @@ -20,54 +20,6 @@ use x86_64::{PhysAddr, VirtAddr}; mod memory_descriptor; -global_asm!(include_str!("asm/stage_1.s")); -global_asm!(include_str!("asm/stage_2.s")); -global_asm!(include_str!("asm/vesa.s")); -global_asm!(include_str!("asm/e820.s")); -global_asm!(include_str!("asm/stage_3.s")); - -// values defined in `vesa.s` -extern "C" { - static VBEModeInfo_physbaseptr: u32; - static VBEModeInfo_bytesperscanline: u16; - static VBEModeInfo_xresolution: u16; - static VBEModeInfo_yresolution: u16; - static VBEModeInfo_bitsperpixel: u8; - static VBEModeInfo_redfieldposition: u8; - static VBEModeInfo_greenfieldposition: u8; - static VBEModeInfo_bluefieldposition: u8; -} - -// Symbols defined in `linker.ld` -extern "C" { - static mmap_ent: usize; - static _memory_map: usize; - static _kernel_start_addr: usize; - static _kernel_end_addr: usize; - static _kernel_size: usize; -} - -#[no_mangle] -pub unsafe extern "C" fn stage_4() -> ! { - // Set stack segment - asm!( - "mov ax, 0x0; mov ss, ax", - out("ax") _, - ); - - let kernel_start = 0x400000; - let kernel_size = &_kernel_size as *const _ as u64; - let memory_map_addr = &_memory_map as *const _ as u64; - let memory_map_entry_count = (mmap_ent & 0xff) as u64; // Extract lower 8 bits - - bootloader_main( - PhysAddr::new(kernel_start), - kernel_size, - VirtAddr::new(memory_map_addr), - memory_map_entry_count, - ) -} - fn bootloader_main( kernel_start: PhysAddr, kernel_size: u64, @@ -119,40 +71,11 @@ fn bootloader_main( } } - let framebuffer_addr = PhysAddr::new(unsafe { VBEModeInfo_physbaseptr }.into()); - let mut error = None; - let framebuffer_info = unsafe { - let framebuffer_size = - usize::from(VBEModeInfo_yresolution) * usize::from(VBEModeInfo_bytesperscanline); - let bytes_per_pixel = VBEModeInfo_bitsperpixel / 8; - init_logger( - framebuffer_addr, - framebuffer_size.into(), - VBEModeInfo_xresolution.into(), - VBEModeInfo_yresolution.into(), - bytes_per_pixel.into(), - (VBEModeInfo_bytesperscanline / u16::from(bytes_per_pixel)).into(), - match ( - VBEModeInfo_redfieldposition, - VBEModeInfo_greenfieldposition, - VBEModeInfo_bluefieldposition, - ) { - (0, 8, 16) => PixelFormat::Rgb, - (16, 8, 0) => PixelFormat::Bgr, - (r, g, b) => { - error = Some(("invalid rgb field positions", r, g, b)); - PixelFormat::Rgb // default to RBG so that we can print something - } - }, - ) - }; + let framebuffer_addr = todo!(); + let framebuffer_info = todo!(); log::info!("BIOS boot"); - if let Some((msg, r, g, b)) = error { - panic!("{}: r: {}, g: {}, b: {}", msg, r, g, b); - } - let page_tables = create_page_tables(&mut frame_allocator); let kernel_slice = { diff --git a/bios/src/main_bak.rs b/bios/src/main_bak.rs new file mode 100644 index 00000000..09573303 --- /dev/null +++ b/bios/src/main_bak.rs @@ -0,0 +1,276 @@ +#![no_std] +#![no_main] + +use crate::memory_descriptor::E820MemoryRegion; +use bootloader_api::info::{FrameBufferInfo, PixelFormat}; +use bootloader_x86_64_common::{ + load_and_switch_to_kernel, logger::LOGGER, Kernel, PageTables, SystemInfo, +}; +use core::{ + arch::{asm, global_asm}, + panic::PanicInfo, + slice, +}; +use usize_conversions::usize_from; +use x86_64::structures::paging::{FrameAllocator, OffsetPageTable}; +use x86_64::structures::paging::{ + Mapper, PageTable, PageTableFlags, PhysFrame, Size2MiB, Size4KiB, +}; +use x86_64::{PhysAddr, VirtAddr}; + +mod memory_descriptor; + +global_asm!(include_str!("asm/stage_1.s")); +global_asm!(include_str!("asm/stage_2.s")); +global_asm!(include_str!("asm/vesa.s")); +global_asm!(include_str!("asm/e820.s")); +global_asm!(include_str!("asm/stage_3.s")); + +// values defined in `vesa.s` +extern "C" { + static VBEModeInfo_physbaseptr: u32; + static VBEModeInfo_bytesperscanline: u16; + static VBEModeInfo_xresolution: u16; + static VBEModeInfo_yresolution: u16; + static VBEModeInfo_bitsperpixel: u8; + static VBEModeInfo_redfieldposition: u8; + static VBEModeInfo_greenfieldposition: u8; + static VBEModeInfo_bluefieldposition: u8; +} + +// Symbols defined in `linker.ld` +extern "C" { + static mmap_ent: usize; + static _memory_map: usize; + static _kernel_start_addr: usize; + static _kernel_end_addr: usize; + static _kernel_size: usize; +} + +#[no_mangle] +pub unsafe extern "C" fn stage_4() -> ! { + // Set stack segment + asm!( + "mov ax, 0x0; mov ss, ax", + out("ax") _, + ); + + let kernel_start = 0x400000; + let kernel_size = &_kernel_size as *const _ as u64; + let memory_map_addr = &_memory_map as *const _ as u64; + let memory_map_entry_count = (mmap_ent & 0xff) as u64; // Extract lower 8 bits + + bootloader_main( + PhysAddr::new(kernel_start), + kernel_size, + VirtAddr::new(memory_map_addr), + memory_map_entry_count, + ) +} + +fn bootloader_main( + kernel_start: PhysAddr, + kernel_size: u64, + memory_map_addr: VirtAddr, + memory_map_entry_count: u64, +) -> ! { + use bootloader_x86_64_common::legacy_memory_region::LegacyFrameAllocator; + + let e820_memory_map = { + let ptr = usize_from(memory_map_addr.as_u64()) as *const E820MemoryRegion; + unsafe { slice::from_raw_parts(ptr, usize_from(memory_map_entry_count)) } + }; + let max_phys_addr = e820_memory_map + .iter() + .map(|r| r.start_addr + r.len) + .max() + .expect("no physical memory regions found"); + + let mut frame_allocator = { + let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); + let next_free = kernel_end + 1; + LegacyFrameAllocator::new_starting_at(next_free, e820_memory_map.iter().copied()) + }; + + // We identity-map all memory, so the offset between physical and virtual addresses is 0 + let phys_offset = VirtAddr::new(0); + + let mut bootloader_page_table = { + let frame = x86_64::registers::control::Cr3::read().0; + let table: *mut PageTable = (phys_offset + frame.start_address().as_u64()).as_mut_ptr(); + unsafe { OffsetPageTable::new(&mut *table, phys_offset) } + }; + // identity-map remaining physical memory (first gigabyte is already identity-mapped) + { + let start_frame: PhysFrame = + PhysFrame::containing_address(PhysAddr::new(4096 * 512 * 512)); + let end_frame = PhysFrame::containing_address(PhysAddr::new(max_phys_addr - 1)); + for frame in PhysFrame::range_inclusive(start_frame, end_frame) { + unsafe { + bootloader_page_table + .identity_map( + frame, + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + &mut frame_allocator, + ) + .unwrap() + .flush() + }; + } + } + + let framebuffer_addr = PhysAddr::new(unsafe { VBEModeInfo_physbaseptr }.into()); + let mut error = None; + let framebuffer_info = unsafe { + let framebuffer_size = + usize::from(VBEModeInfo_yresolution) * usize::from(VBEModeInfo_bytesperscanline); + let bytes_per_pixel = VBEModeInfo_bitsperpixel / 8; + init_logger( + framebuffer_addr, + framebuffer_size.into(), + VBEModeInfo_xresolution.into(), + VBEModeInfo_yresolution.into(), + bytes_per_pixel.into(), + (VBEModeInfo_bytesperscanline / u16::from(bytes_per_pixel)).into(), + match ( + VBEModeInfo_redfieldposition, + VBEModeInfo_greenfieldposition, + VBEModeInfo_bluefieldposition, + ) { + (0, 8, 16) => PixelFormat::Rgb, + (16, 8, 0) => PixelFormat::Bgr, + (r, g, b) => { + error = Some(("invalid rgb field positions", r, g, b)); + PixelFormat::Rgb // default to RBG so that we can print something + } + }, + ) + }; + + log::info!("BIOS boot"); + + if let Some((msg, r, g, b)) = error { + panic!("{}: r: {}, g: {}, b: {}", msg, r, g, b); + } + + let page_tables = create_page_tables(&mut frame_allocator); + + let kernel_slice = { + let ptr = kernel_start.as_u64() as *const u8; + unsafe { slice::from_raw_parts(ptr, usize_from(kernel_size)) } + }; + let kernel = Kernel::parse(kernel_slice); + + let system_info = SystemInfo { + framebuffer_addr, + framebuffer_info, + rsdp_addr: detect_rsdp(), + }; + + load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info); +} + +fn init_logger( + framebuffer_start: PhysAddr, + framebuffer_size: usize, + horizontal_resolution: usize, + vertical_resolution: usize, + bytes_per_pixel: usize, + stride: usize, + pixel_format: PixelFormat, +) -> FrameBufferInfo { + let ptr = framebuffer_start.as_u64() as *mut u8; + let slice = unsafe { slice::from_raw_parts_mut(ptr, framebuffer_size) }; + + let info = FrameBufferInfo { + byte_len: framebuffer_size, + horizontal_resolution, + vertical_resolution, + bytes_per_pixel, + stride, + pixel_format, + }; + + bootloader_x86_64_common::init_logger(slice, info); + + info +} + +/// Creates page table abstraction types for both the bootloader and kernel page tables. +fn create_page_tables(frame_allocator: &mut impl FrameAllocator) -> PageTables { + // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 + let phys_offset = VirtAddr::new(0); + + // copy the currently active level 4 page table, because it might be read-only + let bootloader_page_table = { + let frame = x86_64::registers::control::Cr3::read().0; + let table: *mut PageTable = (phys_offset + frame.start_address().as_u64()).as_mut_ptr(); + unsafe { OffsetPageTable::new(&mut *table, phys_offset) } + }; + + // create a new page table hierarchy for the kernel + let (kernel_page_table, kernel_level_4_frame) = { + // get an unused frame for new level 4 page table + let frame: PhysFrame = frame_allocator.allocate_frame().expect("no unused frames"); + log::info!("New page table at: {:#?}", &frame); + // get the corresponding virtual address + let addr = phys_offset + frame.start_address().as_u64(); + // initialize a new page table + let ptr = addr.as_mut_ptr(); + unsafe { *ptr = PageTable::new() }; + let level_4_table = unsafe { &mut *ptr }; + ( + unsafe { OffsetPageTable::new(level_4_table, phys_offset) }, + frame, + ) + }; + + PageTables { + bootloader: bootloader_page_table, + kernel: kernel_page_table, + kernel_level_4_frame, + } +} + +fn detect_rsdp() -> Option { + use core::ptr::NonNull; + use rsdp::{ + handler::{AcpiHandler, PhysicalMapping}, + Rsdp, + }; + + #[derive(Clone)] + struct IdentityMapped; + impl AcpiHandler for IdentityMapped { + unsafe fn map_physical_region( + &self, + physical_address: usize, + size: usize, + ) -> PhysicalMapping { + PhysicalMapping::new( + physical_address, + NonNull::new(physical_address as *mut _).unwrap(), + size, + size, + Self, + ) + } + + fn unmap_physical_region(_region: &PhysicalMapping) {} + } + + unsafe { + Rsdp::search_for_on_bios(IdentityMapped) + .ok() + .map(|mapping| PhysAddr::new(mapping.physical_start() as u64)) + } +} + +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + unsafe { LOGGER.get().map(|l| l.force_unlock()) }; + log::error!("{}", info); + loop { + unsafe { asm!("cli; hlt") }; + } +} diff --git a/bios/first_stage/x86-16bit.json b/bios/x86-16bit.json similarity index 81% rename from bios/first_stage/x86-16bit.json rename to bios/x86-16bit.json index 50984651..f5ed774e 100644 --- a/bios/first_stage/x86-16bit.json +++ b/bios/x86-16bit.json @@ -16,10 +16,5 @@ "panic-strategy": "abort", "os": "none", "vendor": "unknown", - "relocation-model": "static", - "pre-link-args": { - "ld.lld": [ - "--script=bios/first_stage/16-bit-linker.ld" - ] - } + "relocation-model": "static" } From bf1895b34b9829e3aeb43da596248bf2e96a031c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Apr 2022 13:53:37 +0200 Subject: [PATCH 089/226] Fix partition table --- bios/boot_sector/16-bit-linker.ld | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/bios/boot_sector/16-bit-linker.ld b/bios/boot_sector/16-bit-linker.ld index 5cf5db94..8c6d224c 100644 --- a/bios/boot_sector/16-bit-linker.ld +++ b/bios/boot_sector/16-bit-linker.ld @@ -30,10 +30,18 @@ SECTIONS { . = 0x7c00 + 446; .partition_table : { - SHORT(0x0000) /* partition table entry 0 */ - SHORT(0x0000) /* partition table entry 1 */ - SHORT(0x0000) /* partition table entry 2 */ - SHORT(0x0000) /* partition table entry 3 */ + /* partition table entry 0 */ + QUAD(0) + QUAD(0) + /* partition table entry 1 */ + QUAD(0) + QUAD(0) + /* partition table entry 2 */ + QUAD(0) + QUAD(0) + /* partition table entry 3 */ + QUAD(0) + QUAD(0) } . = 0x7c00 + 510; From 0d77948df2c331612c94e186672a34a1694f23de Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Apr 2022 13:54:09 +0200 Subject: [PATCH 090/226] Simplify MBR code and choose bootable partition (instead of always using the first one) --- bios/boot_sector/16-bit-linker.ld | 1 + bios/boot_sector/src/main.rs | 18 +++--- bios/boot_sector/src/mbr.rs | 92 +++++++++---------------------- 3 files changed, 38 insertions(+), 73 deletions(-) diff --git a/bios/boot_sector/16-bit-linker.ld b/bios/boot_sector/16-bit-linker.ld index 8c6d224c..435d0b52 100644 --- a/bios/boot_sector/16-bit-linker.ld +++ b/bios/boot_sector/16-bit-linker.ld @@ -28,6 +28,7 @@ SECTIONS { _mbr_end = .; . = 0x7c00 + 446; + _partition_table = .; .partition_table : { /* partition table entry 0 */ diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 14e51370..44bd1980 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -6,7 +6,6 @@ use core::{ slice, }; use fail::{fail, print_char, UnwrapOrFail}; -use mbr::MasterBootRecord; global_asm!(include_str!("boot.s")); @@ -17,32 +16,37 @@ mod mbr; extern "C" { static _mbr_start: u8; + static _partition_table: u8; } fn mbr_start() -> *const u8 { unsafe { &_mbr_start } } +unsafe fn partition_table() -> *const u8 { + unsafe { &_partition_table } +} + #[no_mangle] pub extern "C" fn first_stage(disk_number: u16) { print_char(b'1'); - let bytes = &unsafe { slice::from_raw_parts(mbr_start(), 512) }; - let partition = mbr::get_partition(bytes, 0); + let partition_table = &unsafe { slice::from_raw_parts(partition_table(), 16 * 4) }; + let boot_partition = mbr::boot_partition(partition_table).unwrap_or_fail(b'x'); print_char(b'2'); let partition_buf = u16::try_from(mbr_start() as usize).unwrap_or_fail(b'a') + 512; - // load first partition into buffer + // load boot partition into buffer // TODO: only load headers let dap = dap::DiskAddressPacket::from_lba( partition_buf, - partition.logical_block_address.into(), + boot_partition.logical_block_address.into(), 1, // partition.sector_count.try_into().unwrap_or_fail(b'b'), ); unsafe { dap.perform_load(disk_number); } - if partition.sector_count == 0 { + if boot_partition.sector_count == 0 { fail(b'c'); } @@ -52,7 +56,7 @@ pub extern "C" fn first_stage(disk_number: u16) { let fat_slice = unsafe { slice::from_raw_parts( partition_buf as *const u8, - usize::try_from(partition.sector_count).unwrap_or_else(|_| fail(b'a')) * 512, + usize::try_from(boot_partition.sector_count).unwrap_or_else(|_| fail(b'a')) * 512, ) }; diff --git a/bios/boot_sector/src/mbr.rs b/bios/boot_sector/src/mbr.rs index baa3d7fe..9456786e 100644 --- a/bios/boot_sector/src/mbr.rs +++ b/bios/boot_sector/src/mbr.rs @@ -2,17 +2,29 @@ use super::fail::{fail, UnwrapOrFail}; -pub fn get_partition(buffer: &[u8], index: usize) -> PartitionTableEntry { - if buffer.len() < BUFFER_SIZE { +/// Returns the first bootable partition in the partition table. +pub fn boot_partition(partitions_raw: &[u8]) -> Option { + for index in 0..4 { + let entry = get_partition(partitions_raw, index); + if entry.bootable { + return Some(entry); + } + } + None +} + +pub fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTableEntry { + if partitions_raw.len() < PARTITIONS_AREA_SIZE { fail(b'a'); - } else if buffer.get(BUFFER_SIZE - SUFFIX_BYTES.len()..BUFFER_SIZE) != Some(&SUFFIX_BYTES[..]) { - fail(b'b'); } - let offset = TABLE_OFFSET + index * ENTRY_SIZE; - let buffer = buffer.get(offset..).unwrap_or_fail(b'c'); + let offset = index * ENTRY_SIZE; + let buffer = partitions_raw.get(offset..).unwrap_or_fail(b'c'); + + let bootable_raw = *buffer.get(0).unwrap_or_fail(b'd'); + let bootable = bootable_raw == 0x80; - let partition_type = *buffer.get(4).unwrap_or_fail(b'd'); + let partition_type = *buffer.get(4).unwrap_or_fail(b'e'); let lba = u32::from_le_bytes( buffer @@ -28,64 +40,11 @@ pub fn get_partition(buffer: &[u8], index: usize) -> PartitionTableEntry { .and_then(|s| s.try_into().ok()) .unwrap_or_fail(b'f'), ); - PartitionTableEntry::new(partition_type, lba, len) + PartitionTableEntry::new(bootable, partition_type, lba, len) } -/// A struct representing an MBR partition table. -pub struct MasterBootRecord { - entries: [PartitionTableEntry; MAX_ENTRIES], -} - -const BUFFER_SIZE: usize = 512; -const TABLE_OFFSET: usize = 446; +const PARTITIONS_AREA_SIZE: usize = 16 * 4; const ENTRY_SIZE: usize = 16; -const SUFFIX_BYTES: [u8; 2] = [0x55, 0xaa]; -const MAX_ENTRIES: usize = (BUFFER_SIZE - TABLE_OFFSET - 2) / ENTRY_SIZE; - -impl MasterBootRecord { - /// Parses the MBR table from a raw byte buffer. - - pub fn from_bytes(buffer: &[u8]) -> MasterBootRecord { - if buffer.len() < BUFFER_SIZE { - fail(b'1'); - } else if buffer.get(BUFFER_SIZE - SUFFIX_BYTES.len()..BUFFER_SIZE) - != Some(&SUFFIX_BYTES[..]) - { - fail(b'2'); - } - let mut entries = [PartitionTableEntry::empty(); MAX_ENTRIES]; - - for idx in 0..MAX_ENTRIES { - let offset = TABLE_OFFSET + idx * ENTRY_SIZE; - let buffer = buffer.get(offset..).unwrap_or_fail(b'8'); - - let partition_type = *buffer.get(4).unwrap_or_fail(b'4'); - - let lba = u32::from_le_bytes( - buffer - .get(8..) - .and_then(|s| s.get(..4)) - .and_then(|s| s.try_into().ok()) - .unwrap_or_fail(b'5'), - ); - let len = u32::from_le_bytes( - buffer - .get(12..) - .and_then(|s| s.get(..4)) - .and_then(|s| s.try_into().ok()) - .unwrap_or_fail(b'6'), - ); - *entries.get_mut(idx).unwrap_or_fail(b'7') = - PartitionTableEntry::new(partition_type, lba, len); - } - - MasterBootRecord { entries } - } - - pub fn partition_table_entries(&self) -> &[PartitionTableEntry] { - &self.entries[..] - } -} /// The type of a particular partition. #[derive(Copy, Clone, Eq, PartialEq)] @@ -129,6 +88,9 @@ impl PartitionType { /// An entry in a partition table. #[derive(Copy, Clone, Eq, PartialEq)] pub struct PartitionTableEntry { + /// Whether this partition is a boot partition. + pub bootable: bool, + /// The type of partition in this entry. pub partition_type: u8, @@ -141,18 +103,16 @@ pub struct PartitionTableEntry { impl PartitionTableEntry { pub fn new( + bootable: bool, partition_type: u8, logical_block_address: u32, sector_count: u32, ) -> PartitionTableEntry { PartitionTableEntry { + bootable, partition_type, logical_block_address, sector_count, } } - - pub fn empty() -> PartitionTableEntry { - PartitionTableEntry::new(0, 0, 0) - } } From 01fdcbc4026820906029ca34390283231e02dda5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Apr 2022 13:58:29 +0200 Subject: [PATCH 091/226] Move bios boot sector build to top level build script --- bios/build.rs | 52 --------------------------- build.rs | 45 ++++++++++++++++++++++- bios/x86-16bit.json => x86-16bit.json | 0 3 files changed, 44 insertions(+), 53 deletions(-) delete mode 100644 bios/build.rs rename bios/x86-16bit.json => x86-16bit.json (100%) diff --git a/bios/build.rs b/bios/build.rs deleted file mode 100644 index d5026906..00000000 --- a/bios/build.rs +++ /dev/null @@ -1,52 +0,0 @@ -use std::{ - path::{Path, PathBuf}, - process::Command, -}; - -const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = "0.1.0-alpha.0"; - -fn main() { - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); - - let bios_boot_sector_path = build_bios_boot_sector(&out_dir); - - println!( - "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", - bios_boot_sector_path.display() - ); -} - -fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { - let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); - let mut cmd = Command::new(cargo); - cmd.arg("install").arg("bootloader-x86_64-bios-boot-sector"); - let local_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("boot_sector"); - if local_path.exists() { - // local build - cmd.arg("--path").arg(&local_path); - } else { - cmd.arg("--version") - .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); - } - cmd.arg("--locked"); - cmd.arg("--target").arg("x86-16bit.json"); - cmd.arg("--profile").arg("first-stage"); - cmd.arg("-Zbuild-std=core") - .arg("-Zbuild-std-features=compiler-builtins-mem"); - cmd.arg("--root").arg(out_dir); - let status = cmd - .status() - .expect("failed to run cargo install for bios boot sector"); - if status.success() { - let path = out_dir - .join("bin") - .join("bootloader-x86_64-bios-boot-sector"); - assert!( - path.exists(), - "bios boot sector executable does not exist after building" - ); - path - } else { - panic!("failed to build bios boot sector"); - } -} diff --git a/build.rs b/build.rs index 2866a239..b0833349 100644 --- a/build.rs +++ b/build.rs @@ -4,16 +4,22 @@ use std::{ }; const BOOTLOADER_X86_64_UEFI_VERSION: &str = "0.1.0-alpha.0"; +const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = "0.1.0-alpha.0"; fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let uefi_path = build_uefi_bootloader(&out_dir); - println!( "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", uefi_path.display() ); + + let bios_boot_sector_path = build_bios_boot_sector(&out_dir); + println!( + "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", + bios_boot_sector_path.display() + ); } fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { @@ -46,3 +52,40 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { panic!("failed to build uefi bootloader"); } } + +fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); + let mut cmd = Command::new(cargo); + cmd.arg("install").arg("bootloader-x86_64-bios-boot-sector"); + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("bios") + .join("boot_sector"); + if local_path.exists() { + // local build + cmd.arg("--path").arg(&local_path); + } else { + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); + } + cmd.arg("--locked"); + cmd.arg("--target").arg("x86-16bit.json"); + cmd.arg("--profile").arg("first-stage"); + cmd.arg("-Zbuild-std=core") + .arg("-Zbuild-std-features=compiler-builtins-mem"); + cmd.arg("--root").arg(out_dir); + let status = cmd + .status() + .expect("failed to run cargo install for bios boot sector"); + if status.success() { + let path = out_dir + .join("bin") + .join("bootloader-x86_64-bios-boot-sector"); + assert!( + path.exists(), + "bios boot sector executable does not exist after building" + ); + path + } else { + panic!("failed to build bios boot sector"); + } +} diff --git a/bios/x86-16bit.json b/x86-16bit.json similarity index 100% rename from bios/x86-16bit.json rename to x86-16bit.json From d8b50dcb801dfc775e6f211a8f0e3d3bf2486ad7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Apr 2022 14:23:40 +0200 Subject: [PATCH 092/226] Refactor FAT creation function to take arbitrary file list --- src/fat.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 76 +++++++------------------------------------------- 2 files changed, 92 insertions(+), 66 deletions(-) create mode 100644 src/fat.rs diff --git a/src/fat.rs b/src/fat.rs new file mode 100644 index 00000000..4e474a25 --- /dev/null +++ b/src/fat.rs @@ -0,0 +1,82 @@ +use anyhow::Context; +use std::{collections::BTreeMap, fs, io, path::Path}; + +use crate::KERNEL_FILE_NAME; + +pub fn create_fat_filesystem( + files: BTreeMap<&str, &Path>, + out_fat_path: &Path, +) -> anyhow::Result<()> { + const MB: u64 = 1024 * 1024; + + // calculate needed size + let mut needed_size = 0; + for path in files.values() { + let file_size = fs::metadata(path) + .with_context(|| format!("failed to read metadata of file `{}`", path.display()))? + .len(); + needed_size += file_size; + } + + // create new filesystem image file at the given path and set its length + let fat_file = fs::OpenOptions::new() + .read(true) + .write(true) + .create(true) + .truncate(true) + .open(&out_fat_path) + .unwrap(); + let fat_size_padded_and_rounded = ((needed_size + 1024 * 64 - 1) / MB + 1) * MB; + fat_file.set_len(fat_size_padded_and_rounded).unwrap(); + + // choose a file system label + let mut label = *b"MY_RUST_OS!"; + if let Some(path) = files.get(KERNEL_FILE_NAME) { + if let Some(name) = path.file_stem() { + let converted = name.to_string_lossy(); + let name = converted.as_bytes(); + let mut new_label = [0u8; 11]; + let name = &name[..new_label.len()]; + let slice = &mut new_label[..name.len()]; + slice.copy_from_slice(name); + label = new_label; + } + } + + // format the file system and open it + let format_options = fatfs::FormatVolumeOptions::new().volume_label(label); + fatfs::format_volume(&fat_file, format_options).context("Failed to format UEFI FAT file")?; + let filesystem = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new()) + .context("Failed to open FAT file system of UEFI FAT file")?; + + // copy files to file system + let root_dir = filesystem.root_dir(); + for (target_path_raw, file_path) in files { + let target_path = Path::new(target_path_raw); + // create parent directories + let ancestors: Vec<_> = target_path.ancestors().skip(1).collect(); + for ancestor in ancestors.into_iter().rev().skip(1) { + root_dir + .create_dir(&ancestor.display().to_string()) + .with_context(|| { + format!( + "failed to create directory `{}` on FAT filesystem", + ancestor.display() + ) + })?; + } + + let mut new_file = root_dir + .create_file(target_path_raw) + .with_context(|| format!("failed to create file at `{}`", target_path.display()))?; + new_file.truncate().unwrap(); + io::copy( + &mut fs::File::open(file_path) + .with_context(|| format!("failed to open `{}` for copying", file_path.display()))?, + &mut new_file, + ) + .with_context(|| format!("failed to copy `{}` to FAT filesystem", file_path.display()))?; + } + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index 28ed212a..ac5e38eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -69,11 +69,16 @@ for all possible configuration options. use anyhow::Context; use std::{ + collections::BTreeMap, fs::{self, File}, io::{self, Seek}, path::Path, }; +mod fat; + +const KERNEL_FILE_NAME: &str = "kernel-x86_64"; + pub fn create_uefi_disk_image( kernel_binary: &Path, out_fat_path: &Path, @@ -81,78 +86,17 @@ pub fn create_uefi_disk_image( ) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - create_fat_filesystem(bootloader_path, kernel_binary, &out_fat_path) + let mut files = BTreeMap::new(); + files.insert("efi/boot/bootx64.efi", bootloader_path); + files.insert(KERNEL_FILE_NAME, kernel_binary); + + fat::create_fat_filesystem(files, &out_fat_path) .context("failed to create UEFI FAT filesystem")?; create_gpt_disk(out_fat_path, out_gpt_path); Ok(()) } -fn create_fat_filesystem( - bootloader_efi_file: &Path, - kernel_binary: &Path, - out_fat_path: &Path, -) -> anyhow::Result<()> { - const MB: u64 = 1024 * 1024; - - // retrieve size of `.efi` file and round it up - let efi_size = fs::metadata(&bootloader_efi_file).unwrap().len(); - let kernel_size = fs::metadata(&kernel_binary) - .context("failed to read metadata of kernel binary")? - .len(); - - // create new filesystem image file at the given path and set its length - let fat_file = fs::OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(&out_fat_path) - .unwrap(); - let fat_size = efi_size + kernel_size; - let fat_size_padded_and_rounded = ((fat_size + 1024 * 64 - 1) / MB + 1) * MB; - fat_file.set_len(fat_size_padded_and_rounded).unwrap(); - - // create new FAT file system and open it - let label = { - if let Some(name) = bootloader_efi_file.file_stem() { - let converted = name.to_string_lossy(); - let name = converted.as_bytes(); - let mut label = [0u8; 11]; - let name = &name[..label.len()]; - let slice = &mut label[..name.len()]; - slice.copy_from_slice(name); - label - } else { - *b"MY_RUST_OS!" - } - }; - let format_options = fatfs::FormatVolumeOptions::new().volume_label(label); - fatfs::format_volume(&fat_file, format_options).context("Failed to format UEFI FAT file")?; - let filesystem = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new()) - .context("Failed to open FAT file system of UEFI FAT file")?; - - // copy EFI file to FAT filesystem - let root_dir = filesystem.root_dir(); - root_dir.create_dir("efi").unwrap(); - root_dir.create_dir("efi/boot").unwrap(); - let mut bootx64 = root_dir.create_file("efi/boot/bootx64.efi").unwrap(); - bootx64.truncate().unwrap(); - io::copy( - &mut fs::File::open(&bootloader_efi_file).unwrap(), - &mut bootx64, - ) - .unwrap(); - - // copy kernel to FAT filesystem - let mut kernel_file = root_dir.create_file("kernel-x86_64")?; - kernel_file.truncate()?; - io::copy(&mut fs::File::open(&kernel_binary)?, &mut kernel_file) - .context("failed to copy kernel to UEFI FAT filesystem")?; - - Ok(()) -} - fn create_gpt_disk(fat_image: &Path, out_gpt_path: &Path) { // create new file let mut disk = fs::OpenOptions::new() From 3812a8684e62cb8e6c685126a9ee6a4a895f3651 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Apr 2022 15:39:15 +0200 Subject: [PATCH 093/226] Refactor GPT creation and move it to separate module --- src/gpt.rs | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 50 +++----------------------------------- 2 files changed, 73 insertions(+), 47 deletions(-) create mode 100644 src/gpt.rs diff --git a/src/gpt.rs b/src/gpt.rs new file mode 100644 index 00000000..8adecbfd --- /dev/null +++ b/src/gpt.rs @@ -0,0 +1,70 @@ +use anyhow::Context; +use std::{ + fs::{self, File}, + io::{self, Seek}, + path::Path, +}; + +pub fn create_gpt_disk(fat_image: &Path, out_gpt_path: &Path) -> anyhow::Result<()> { + // create new file + let mut disk = fs::OpenOptions::new() + .create(true) + .truncate(true) + .read(true) + .write(true) + .open(&out_gpt_path) + .with_context(|| format!("failed to create GPT file at `{}`", out_gpt_path.display()))?; + + // set file size + let partition_size: u64 = fs::metadata(&fat_image) + .context("failed to read metadata of fat image")? + .len(); + let disk_size = partition_size + 1024 * 64; // for GPT headers + disk.set_len(disk_size) + .context("failed to set GPT image file length")?; + + // create a protective MBR at LBA0 so that disk is not considered + // unformatted on BIOS systems + let mbr = gpt::mbr::ProtectiveMBR::with_lb_size( + u32::try_from((disk_size / 512) - 1).unwrap_or(0xFF_FF_FF_FF), + ); + mbr.overwrite_lba0(&mut disk) + .context("failed to write protective MBR")?; + + // create new GPT structure + let block_size = gpt::disk::LogicalBlockSize::Lb512; + let mut gpt = gpt::GptConfig::new() + .writable(true) + .initialized(false) + .logical_block_size(block_size) + .create_from_device(Box::new(&mut disk), None) + .context("failed to create GPT structure in file")?; + gpt.update_partitions(Default::default()) + .context("failed to update GPT partitions")?; + + // add new EFI system partition and get its byte offset in the file + let partition_id = gpt + .add_partition("boot", partition_size, gpt::partition_types::EFI, 0, None) + .context("failed to add boot EFI partition")?; + let partition = gpt + .partitions() + .get(&partition_id) + .context("failed to open boot partition after creation")?; + let start_offset = partition + .bytes_start(block_size) + .context("failed to get start offset of boot partition")?; + + // close the GPT structure and write out changes + gpt.write().context("failed to write out GPT changes")?; + + // place the FAT filesystem in the newly created partition + disk.seek(io::SeekFrom::Start(start_offset)) + .context("failed to seek to start offset")?; + io::copy( + &mut File::open(&fat_image).context("failed to open FAT image")?, + &mut disk, + ) + .context("failed to copy FAT image to GPT disk")?; + + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index ac5e38eb..aab87f1e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,6 +76,7 @@ use std::{ }; mod fat; +mod gpt; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; @@ -92,54 +93,9 @@ pub fn create_uefi_disk_image( fat::create_fat_filesystem(files, &out_fat_path) .context("failed to create UEFI FAT filesystem")?; - create_gpt_disk(out_fat_path, out_gpt_path); + gpt::create_gpt_disk(out_fat_path, out_gpt_path) + .context("failed to create UEFI GPT disk image")?; Ok(()) } -fn create_gpt_disk(fat_image: &Path, out_gpt_path: &Path) { - // create new file - let mut disk = fs::OpenOptions::new() - .create(true) - .truncate(true) - .read(true) - .write(true) - .open(&out_gpt_path) - .unwrap(); - - // set file size - let partition_size: u64 = fs::metadata(&fat_image).unwrap().len(); - let disk_size = partition_size + 1024 * 64; // for GPT headers - disk.set_len(disk_size).unwrap(); - - // create a protective MBR at LBA0 so that disk is not considered - // unformatted on BIOS systems - let mbr = gpt::mbr::ProtectiveMBR::with_lb_size( - u32::try_from((disk_size / 512) - 1).unwrap_or(0xFF_FF_FF_FF), - ); - mbr.overwrite_lba0(&mut disk).unwrap(); - - // create new GPT structure - let block_size = gpt::disk::LogicalBlockSize::Lb512; - let mut gpt = gpt::GptConfig::new() - .writable(true) - .initialized(false) - .logical_block_size(block_size) - .create_from_device(Box::new(&mut disk), None) - .unwrap(); - gpt.update_partitions(Default::default()).unwrap(); - - // add new EFI system partition and get its byte offset in the file - let partition_id = gpt - .add_partition("boot", partition_size, gpt::partition_types::EFI, 0, None) - .unwrap(); - let partition = gpt.partitions().get(&partition_id).unwrap(); - let start_offset = partition.bytes_start(block_size).unwrap(); - - // close the GPT structure and write out changes - gpt.write().unwrap(); - - // place the FAT filesystem in the newly created partition - disk.seek(io::SeekFrom::Start(start_offset)).unwrap(); - io::copy(&mut File::open(&fat_image).unwrap(), &mut disk).unwrap(); -} From 5310e4b97743094d45dd0711939e1961463da9e3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Apr 2022 16:27:54 +0200 Subject: [PATCH 094/226] Integrate BIOS bootsector into build system --- Cargo.lock | 108 +++++++++++++++++++++++++++++++++++ Cargo.toml | 4 ++ bios/boot_sector/src/main.rs | 2 + build.rs | 30 +++++++++- src/lib.rs | 32 ++++++++--- src/mbr.rs | 64 +++++++++++++++++++++ tests/runner/src/lib.rs | 7 ++- 7 files changed, 237 insertions(+), 10 deletions(-) create mode 100644 src/mbr.rs diff --git a/Cargo.lock b/Cargo.lock index c87cbbfb..7060b484 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bit_field" version = "0.10.1" @@ -32,6 +41,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "bootloader" version = "0.11.0-alpha.0" @@ -41,6 +62,8 @@ dependencies = [ "bootloader_test_runner", "fatfs", "gpt", + "llvm-tools", + "mbrman", "test_kernel_default_settings", "test_kernel_higher_half", "test_kernel_map_phys_mem", @@ -174,6 +197,12 @@ dependencies = [ "log", ] +[[package]] +name = "funty" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e" + [[package]] name = "getrandom" version = "0.2.6" @@ -203,6 +232,12 @@ version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" +[[package]] +name = "llvm-tools" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" + [[package]] name = "lock_api" version = "0.4.7" @@ -222,6 +257,18 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mbrman" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6f6de0b8cfc56e13dc3ac3c662d9419f8aa5e3ebceb2d0eb25abce49e00395" +dependencies = [ + "bincode", + "bitvec", + "serde", + "thiserror", +] + [[package]] name = "noto-sans-mono-bitmap" version = "0.1.5" @@ -277,6 +324,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb" + [[package]] name = "rand" version = "0.8.5" @@ -346,6 +399,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "serde" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.136" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "spinning_top" version = "0.2.4" @@ -375,6 +448,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "test_kernel_default_settings" version = "0.1.0" @@ -411,6 +490,26 @@ dependencies = [ "x86_64", ] +[[package]] +name = "thiserror" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "time" version = "0.1.43" @@ -545,6 +644,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "wyz" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188" +dependencies = [ + "tap", +] + [[package]] name = "x86_64" version = "0.14.9" diff --git a/Cargo.toml b/Cargo.toml index 9d8e3665..e33945bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,7 @@ exclude = ["examples/basic", "examples/test_framework"] anyhow = "1.0.32" fatfs = "0.3.4" gpt = "3.0.0" +mbrman = "0.4.2" [dependencies.bootloader-x86_64-bios] version = "0.1.0-alpha.0" @@ -70,6 +71,9 @@ rustflags = [ "code-model=large", ] +[build-dependencies] +llvm-tools = "0.1.1" + [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 44bd1980..1a6f511a 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -63,6 +63,8 @@ pub extern "C" fn first_stage(disk_number: u16) { print_char(b'4'); let boot_sector = fat::BootSector::deserialize(fat_slice); + print_char(b'5'); + // TODO: get root dir // TODO: get offset of `second_stage` file diff --git a/build.rs b/build.rs index b0833349..92b78251 100644 --- a/build.rs +++ b/build.rs @@ -76,7 +76,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { let status = cmd .status() .expect("failed to run cargo install for bios boot sector"); - if status.success() { + let elf_path = if status.success() { let path = out_dir .join("bin") .join("bootloader-x86_64-bios-boot-sector"); @@ -87,5 +87,33 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { path } else { panic!("failed to build bios boot sector"); + }; + convert_elf_to_bin(elf_path) +} + +fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { + let flat_binary_path = elf_path.with_extension("bin"); + + let llvm_tools = llvm_tools::LlvmTools::new().expect("failed to get llvm tools"); + let objcopy = llvm_tools + .tool(&llvm_tools::exe("llvm-objcopy")) + .expect("LlvmObjcopyNotFound"); + + // convert first stage to binary + let mut cmd = Command::new(objcopy); + cmd.arg("-I").arg("elf64-x86-64"); + cmd.arg("-O").arg("binary"); + cmd.arg("--binary-architecture=i386:x86-64"); + cmd.arg(&elf_path); + cmd.arg(&flat_binary_path); + let output = cmd + .output() + .expect("failed to execute llvm-objcopy command"); + if !output.status.success() { + panic!( + "objcopy failed: {}", + String::from_utf8_lossy(&output.stderr) + ); } + flat_binary_path } diff --git a/src/lib.rs b/src/lib.rs index aab87f1e..59310b4b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,25 +77,41 @@ use std::{ mod fat; mod gpt; +mod mbr; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; -pub fn create_uefi_disk_image( - kernel_binary: &Path, - out_fat_path: &Path, - out_gpt_path: &Path, -) -> anyhow::Result<()> { +/// Creates a bootable FAT partition at the given path. +pub fn create_boot_partition(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); let mut files = BTreeMap::new(); files.insert("efi/boot/bootx64.efi", bootloader_path); files.insert(KERNEL_FILE_NAME, kernel_binary); - fat::create_fat_filesystem(files, &out_fat_path) - .context("failed to create UEFI FAT filesystem")?; - gpt::create_gpt_disk(out_fat_path, out_gpt_path) + fat::create_fat_filesystem(files, &out_path).context("failed to create UEFI FAT filesystem")?; + + Ok(()) +} + +pub fn create_uefi_disk_image( + boot_partition_path: &Path, + out_gpt_path: &Path, +) -> anyhow::Result<()> { + gpt::create_gpt_disk(boot_partition_path, out_gpt_path) .context("failed to create UEFI GPT disk image")?; Ok(()) } +pub fn create_bios_disk_image( + boot_partition_path: &Path, + out_mbr_path: &Path, +) -> anyhow::Result<()> { + let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); + + mbr::create_mbr_disk(bootsector_path, boot_partition_path, out_mbr_path) + .context("failed to create BIOS MBR disk image")?; + + Ok(()) +} diff --git a/src/mbr.rs b/src/mbr.rs new file mode 100644 index 00000000..eb13f94a --- /dev/null +++ b/src/mbr.rs @@ -0,0 +1,64 @@ +use anyhow::Context; +use std::{ + fs::{self, File}, + io, + path::Path, +}; +const SECTOR_SIZE: u32 = 512; + +pub fn create_mbr_disk( + bootsector_path: &Path, + boot_partition_path: &Path, + out_mbr_path: &Path, +) -> anyhow::Result<()> { + let mut boot_sector = File::open(bootsector_path).context("failed to open boot sector")?; + let mut mbr = + mbrman::MBR::read_from(&mut boot_sector, SECTOR_SIZE).context("failed to read MBR")?; + + for (index, partition) in mbr.iter() { + if !partition.is_unused() { + anyhow::bail!("partition {index} should be unused"); + } + } + + let mut boot_partition = + File::open(boot_partition_path).context("failed to open FAT boot partition")?; + let boot_partition_size = boot_partition + .metadata() + .context("failed to read file metadata of FAT boot partition")? + .len(); + + mbr[1] = mbrman::MBRPartitionEntry { + boot: true, + starting_lba: 1, + sectors: (boot_partition_size / u64::from(SECTOR_SIZE)) + .try_into() + .context("size of FAT partition is larger than u32::MAX")?, + //TODO: is this the correct type? + sys: 0x0c, // FAT32 with LBA + + first_chs: mbrman::CHS::empty(), + last_chs: mbrman::CHS::empty(), + }; + + let mut disk = fs::OpenOptions::new() + .create(true) + .truncate(true) + .read(true) + .write(true) + .open(&out_mbr_path) + .with_context(|| { + format!( + "failed to create MBR disk image at `{}`", + out_mbr_path.display() + ) + })?; + + mbr.write_into(&mut disk) + .context("failed to write MBR header to disk image")?; + + io::copy(&mut boot_partition, &mut disk) + .context("failed to copy FAT image to MBR disk image")?; + + Ok(()) +} diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 89f48885..a7292a59 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -13,8 +13,13 @@ const QEMU_ARGS: &[&str] = &[ pub fn run_test_kernel(kernel_binary_path: &str) { let kernel_path = Path::new(kernel_binary_path); let out_fat_path = kernel_path.with_extension("fat"); + bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap(); let out_gpt_path = kernel_path.with_extension("gpt"); - bootloader::create_uefi_disk_image(kernel_path, &out_fat_path, &out_gpt_path).unwrap(); + bootloader::create_uefi_disk_image(&out_fat_path, &out_gpt_path).unwrap(); + let out_mbr_path = kernel_path.with_extension("mbr"); + bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap(); + + // TODO: run tests with BIOS bootloader too let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd From b14b4e3beaa18a91e8005644e875304c14979a25 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 16 Apr 2022 16:47:24 +0200 Subject: [PATCH 095/226] Remove some env variables when building boot sector Clippy sets some of these environment variables, which causes size problems. --- build.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.rs b/build.rs index 92b78251..34da7619 100644 --- a/build.rs +++ b/build.rs @@ -73,6 +73,8 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { cmd.arg("-Zbuild-std=core") .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); + cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); // used by clippy let status = cmd .status() .expect("failed to run cargo install for bios boot sector"); From 54adb4c4c28c26956352da5f930b7cfd30aaf1fe Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Apr 2022 14:07:03 +0200 Subject: [PATCH 096/226] Rename boot sector linker script --- bios/boot_sector/{16-bit-linker.ld => boot-sector-link.ld} | 2 ++ bios/boot_sector/build.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) rename bios/boot_sector/{16-bit-linker.ld => boot-sector-link.ld} (96%) diff --git a/bios/boot_sector/16-bit-linker.ld b/bios/boot_sector/boot-sector-link.ld similarity index 96% rename from bios/boot_sector/16-bit-linker.ld rename to bios/boot_sector/boot-sector-link.ld index 435d0b52..26995bc2 100644 --- a/bios/boot_sector/16-bit-linker.ld +++ b/bios/boot_sector/boot-sector-link.ld @@ -51,4 +51,6 @@ SECTIONS { { SHORT(0xaa55) /* magic number for bootable disk */ } + + _second_stage_start = .; } diff --git a/bios/boot_sector/build.rs b/bios/boot_sector/build.rs index d2e1f58c..d657e5a4 100644 --- a/bios/boot_sector/build.rs +++ b/bios/boot_sector/build.rs @@ -4,6 +4,6 @@ fn main() { let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); println!( "cargo:rustc-link-arg-bins=--script={}", - local_path.join("16-bit-linker.ld").display() + local_path.join("boot-sector-link.ld").display() ) } From b3207dbf62b27202f3afa2fc19e0b8bf4893b1fb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Apr 2022 14:07:56 +0200 Subject: [PATCH 097/226] Build and load a second stage --- Cargo.lock | 4 + Cargo.toml | 1 + bios/boot_sector/src/error.rs | 4 + bios/boot_sector/src/fat.rs | 125 ++++++++++--- bios/boot_sector/src/main.rs | 43 ++++- bios/boot_sector/src/mbr.rs | 8 +- bios/second_stage/.gitignore | 1 + bios/second_stage/Cargo.toml | 9 + bios/second_stage/build.rs | 9 + bios/second_stage/second-stage-link.ld | 19 ++ bios/second_stage/src/boot unreal.s | 110 ++++++++++++ bios/second_stage/src/dap.rs | 58 ++++++ bios/second_stage/src/fail.rs | 59 ++++++ bios/second_stage/src/fat.rs | 240 +++++++++++++++++++++++++ bios/second_stage/src/main.rs | 64 +++++++ bios/second_stage/src/mbr.rs | 124 +++++++++++++ build.rs | 45 +++++ src/lib.rs | 12 +- src/mbr.rs | 47 ++++- 19 files changed, 941 insertions(+), 41 deletions(-) create mode 100644 bios/boot_sector/src/error.rs create mode 100644 bios/second_stage/.gitignore create mode 100644 bios/second_stage/Cargo.toml create mode 100644 bios/second_stage/build.rs create mode 100644 bios/second_stage/second-stage-link.ld create mode 100644 bios/second_stage/src/boot unreal.s create mode 100644 bios/second_stage/src/dap.rs create mode 100644 bios/second_stage/src/fail.rs create mode 100644 bios/second_stage/src/fat.rs create mode 100644 bios/second_stage/src/main.rs create mode 100644 bios/second_stage/src/mbr.rs diff --git a/Cargo.lock b/Cargo.lock index 7060b484..42783548 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -86,6 +86,10 @@ dependencies = [ name = "bootloader-x86_64-bios-boot-sector" version = "0.1.0" +[[package]] +name = "bootloader-x86_64-bios-second-stage" +version = "0.1.0" + [[package]] name = "bootloader-x86_64-common" version = "0.1.0-alpha.0" diff --git a/Cargo.toml b/Cargo.toml index e33945bc..14f927fe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "uefi", "bios", "bios/boot_sector", + "bios/second_stage", "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", diff --git a/bios/boot_sector/src/error.rs b/bios/boot_sector/src/error.rs new file mode 100644 index 00000000..318aaafa --- /dev/null +++ b/bios/boot_sector/src/error.rs @@ -0,0 +1,4 @@ +/// The boot sector did not find the second stage partition. +/// +/// The BIOS bootloader requires a special second stage partition with partition type 0x20. +pub const NO_SECOND_STAGE_PARTITION: u8 = b'x'; diff --git a/bios/boot_sector/src/fat.rs b/bios/boot_sector/src/fat.rs index 41fc0ca4..b96388a7 100644 --- a/bios/boot_sector/src/fat.rs +++ b/bios/boot_sector/src/fat.rs @@ -2,35 +2,36 @@ use super::split_array_ref; +// Size of single directory entry in bytes +const DIR_ENTRY_SIZE: u32 = 32; + pub(crate) struct BootSector { - bootjmp: [u8; 3], - oem_name: [u8; 8], pub(crate) bpb: BiosParameterBlock, - boot_code: [u8; 448], - boot_sig: [u8; 2], } impl BootSector { pub(crate) fn deserialize(bytes: &[u8]) -> Self { let mut boot = Self::default(); - let (&bootjmp, bytes) = split_array_ref(bytes); - let (&oem_name, bytes) = split_array_ref(bytes); + // let (&bootjmp, bytes) = split_array_ref(bytes); + // let (&oem_name, bytes) = split_array_ref(bytes); + + let bytes = &bytes[3 + 8..]; - boot.bootjmp = bootjmp; - boot.oem_name = oem_name; + // boot.bootjmp = bootjmp; + // boot.oem_name = oem_name; boot.bpb = BiosParameterBlock::deserialize(bytes); - let bytes = if boot.bpb.is_fat32() { - let (boot_code, bytes): (&[_; 420], _) = split_array_ref(bytes); - boot.boot_code[0..420].copy_from_slice(&boot_code[..]); - bytes - } else { - let (&boot_code, bytes) = split_array_ref(bytes); - boot.boot_code = boot_code; - bytes - }; - let (&boot_sig, bytes) = split_array_ref(bytes); - boot.boot_sig = boot_sig; + // let bytes = if boot.bpb.is_fat32() { + // let (boot_code, bytes): (&[_; 420], _) = split_array_ref(bytes); + // boot.boot_code[0..420].copy_from_slice(&boot_code[..]); + // bytes + // } else { + // let (&boot_code, bytes) = split_array_ref(bytes); + // boot.boot_code = boot_code; + // bytes + // }; + // let (&boot_sig, bytes) = split_array_ref(bytes); + // boot.boot_sig = boot_sig; boot } } @@ -38,11 +39,7 @@ impl BootSector { impl Default for BootSector { fn default() -> Self { Self { - bootjmp: Default::default(), - oem_name: Default::default(), bpb: BiosParameterBlock::default(), - boot_code: [0; 448], - boot_sig: Default::default(), } } } @@ -158,4 +155,86 @@ impl BiosParameterBlock { // this provides a simple way to detect FAT32 self.sectors_per_fat_16 == 0 } + + pub(crate) fn sectors_per_fat(&self) -> u32 { + if self.is_fat32() { + self.sectors_per_fat_32 + } else { + u32::from(self.sectors_per_fat_16) + } + } + + pub(crate) fn total_sectors(&self) -> u32 { + if self.total_sectors_16 == 0 { + self.total_sectors_32 + } else { + u32::from(self.total_sectors_16) + } + } + + pub(crate) fn reserved_sectors(&self) -> u32 { + u32::from(self.reserved_sectors) + } + + pub(crate) fn root_dir_sectors(&self) -> u32 { + let root_dir_bytes = u32::from(self.root_entries) * DIR_ENTRY_SIZE; + (root_dir_bytes + u32::from(self.bytes_per_sector) - 1) / u32::from(self.bytes_per_sector) + } + + pub(crate) fn sectors_per_all_fats(&self) -> u32 { + u32::from(self.fats) * self.sectors_per_fat() + } + + pub(crate) fn first_data_sector(&self) -> u32 { + let root_dir_sectors = self.root_dir_sectors(); + let fat_sectors = self.sectors_per_all_fats(); + self.reserved_sectors() + fat_sectors + root_dir_sectors + } + + pub(crate) fn total_clusters(&self) -> u32 { + let total_sectors = self.total_sectors(); + let first_data_sector = self.first_data_sector(); + let data_sectors = total_sectors - first_data_sector; + data_sectors / u32::from(self.sectors_per_cluster) + } + + pub fn fat_type(&self) -> FatType { + FatType::from_clusters(self.total_clusters()) + } + + /// Returns a root directory object allowing for futher penetration of a filesystem structure. + pub fn check_root_dir(&self) { + match self.fat_type() { + FatType::Fat12 | FatType::Fat16 => crate::fail(b'y'), + FatType::Fat32 => { + self.root_dir_first_cluster; + crate::fail(b'z'); + } + } + } +} + +pub enum FatType { + /// 12 bits per FAT entry + Fat12, + /// 16 bits per FAT entry + Fat16, + /// 32 bits per FAT entry + Fat32, +} + +impl FatType { + const FAT16_MIN_CLUSTERS: u32 = 4085; + const FAT32_MIN_CLUSTERS: u32 = 65525; + const FAT32_MAX_CLUSTERS: u32 = 0x0FFF_FFF4; + + pub(crate) fn from_clusters(total_clusters: u32) -> Self { + if total_clusters < Self::FAT16_MIN_CLUSTERS { + FatType::Fat12 + } else if total_clusters < Self::FAT32_MIN_CLUSTERS { + FatType::Fat16 + } else { + FatType::Fat32 + } + } } diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 1a6f511a..4da32aae 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -5,11 +5,13 @@ use core::{ arch::{asm, global_asm}, slice, }; +use error::NO_SECOND_STAGE_PARTITION; use fail::{fail, print_char, UnwrapOrFail}; global_asm!(include_str!("boot.s")); mod dap; +mod error; mod fail; mod fat; mod mbr; @@ -17,6 +19,7 @@ mod mbr; extern "C" { static _mbr_start: u8; static _partition_table: u8; + static _second_stage_start: u8; } fn mbr_start() -> *const u8 { @@ -27,41 +30,65 @@ unsafe fn partition_table() -> *const u8 { unsafe { &_partition_table } } +fn second_stage_start() -> *const () { + let ptr: *const u8 = unsafe { &_second_stage_start }; + ptr as *const () +} + #[no_mangle] pub extern "C" fn first_stage(disk_number: u16) { print_char(b'1'); let partition_table = &unsafe { slice::from_raw_parts(partition_table(), 16 * 4) }; - let boot_partition = mbr::boot_partition(partition_table).unwrap_or_fail(b'x'); + let second_stage_partition = + mbr::boot_partition(partition_table).unwrap_or_fail(NO_SECOND_STAGE_PARTITION); print_char(b'2'); - let partition_buf = u16::try_from(mbr_start() as usize).unwrap_or_fail(b'a') + 512; + let target_addr = u16::try_from(second_stage_start() as usize).unwrap_or_fail(b'a'); // load boot partition into buffer // TODO: only load headers let dap = dap::DiskAddressPacket::from_lba( - partition_buf, - boot_partition.logical_block_address.into(), - 1, // partition.sector_count.try_into().unwrap_or_fail(b'b'), + target_addr, + second_stage_partition.logical_block_address.into(), + second_stage_partition + .sector_count + .try_into() + .unwrap_or_fail(b'b'), ); unsafe { dap.perform_load(disk_number); } - if boot_partition.sector_count == 0 { + if second_stage_partition.sector_count == 0 { fail(b'c'); } print_char(b'3'); + // jump to second stage + let second_stage_entry_point: extern "C" fn(disk_number: u16) = + unsafe { core::mem::transmute(target_addr as *const ()) }; + unsafe { second_stage_entry_point(disk_number) } + + print_char(b'R'); + print_char(b'R'); + print_char(b'R'); + print_char(b'R'); + print_char(b'R'); + loop {} + // try to parse FAT file system let fat_slice = unsafe { slice::from_raw_parts( - partition_buf as *const u8, - usize::try_from(boot_partition.sector_count).unwrap_or_else(|_| fail(b'a')) * 512, + target_addr as *const u8, + usize::try_from(second_stage_partition.sector_count).unwrap_or_else(|_| fail(b'a')) + * 512, ) }; print_char(b'4'); let boot_sector = fat::BootSector::deserialize(fat_slice); + let root_dir = boot_sector.bpb.root_dir_first_cluster; + boot_sector.bpb.check_root_dir(); print_char(b'5'); diff --git a/bios/boot_sector/src/mbr.rs b/bios/boot_sector/src/mbr.rs index 9456786e..f7b00d6d 100644 --- a/bios/boot_sector/src/mbr.rs +++ b/bios/boot_sector/src/mbr.rs @@ -2,11 +2,14 @@ use super::fail::{fail, UnwrapOrFail}; +/// We use this partition type to store the second bootloader stage; +const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; + /// Returns the first bootable partition in the partition table. pub fn boot_partition(partitions_raw: &[u8]) -> Option { for index in 0..4 { let entry = get_partition(partitions_raw, index); - if entry.bootable { + if entry.partition_type == BOOTLOADER_SECOND_STAGE_PARTITION_TYPE { return Some(entry); } } @@ -49,6 +52,7 @@ const ENTRY_SIZE: usize = 16; /// The type of a particular partition. #[derive(Copy, Clone, Eq, PartialEq)] pub enum PartitionType { + BootloaderSecondStage, Unused, Unknown(u8), Fat12(u8), @@ -64,6 +68,8 @@ impl PartitionType { /// Parses a partition type from the type byte in the MBR's table. pub fn from_mbr_tag_byte(tag: u8) -> PartitionType { match tag { + // we use partition type 0x20 to store the second bootloader stage + 0x20 => PartitionType::BootloaderSecondStage, 0x0 => PartitionType::Unused, 0x01 => PartitionType::Fat12(tag), 0x04 | 0x06 | 0x0e => PartitionType::Fat16(tag), diff --git a/bios/second_stage/.gitignore b/bios/second_stage/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/bios/second_stage/.gitignore @@ -0,0 +1 @@ +/target diff --git a/bios/second_stage/Cargo.toml b/bios/second_stage/Cargo.toml new file mode 100644 index 00000000..64852738 --- /dev/null +++ b/bios/second_stage/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bootloader-x86_64-bios-second-stage" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/bios/second_stage/build.rs b/bios/second_stage/build.rs new file mode 100644 index 00000000..945a419d --- /dev/null +++ b/bios/second_stage/build.rs @@ -0,0 +1,9 @@ +use std::path::Path; + +fn main() { + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); + println!( + "cargo:rustc-link-arg-bins=--script={}", + local_path.join("second-stage-link.ld").display() + ) +} diff --git a/bios/second_stage/second-stage-link.ld b/bios/second_stage/second-stage-link.ld new file mode 100644 index 00000000..9f3b3e11 --- /dev/null +++ b/bios/second_stage/second-stage-link.ld @@ -0,0 +1,19 @@ +ENTRY(_start) + +SECTIONS { + . = 0x7c00 + 512; + .text : + { + *(.text .text.*) + } + .rodata : + { + *(.rodata .rodata.*) + } + .data : + { + *(.rodata .rodata.*) + *(.data .data.*) + *(.got .got.*) + } +} diff --git a/bios/second_stage/src/boot unreal.s b/bios/second_stage/src/boot unreal.s new file mode 100644 index 00000000..9aa5405b --- /dev/null +++ b/bios/second_stage/src/boot unreal.s @@ -0,0 +1,110 @@ +.section .boot, "awx" +.global _start +.code16 + +# This stage initializes the stack, enables the A20 line + +_start: + # zero segment registers + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # clear the direction flag (e.g. go forward in memory when using + # instructions like lodsb) + cld + + # initialize stack + mov sp, 0x7c00 + +enable_a20: + # enable A20-Line via IO-Port 92, might not work on all motherboards + in al, 0x92 + test al, 2 + jnz enable_a20_after + or al, 2 + and al, 0xFE + out 0x92, al +enable_a20_after: + +enter_protected_mode: + # clear interrupts + cli + push ds + push es + + lgdt [gdt32info] + + mov eax, cr0 + or al, 1 # set protected mode bit + mov cr0, eax + + jmp protected_mode # tell 386/486 to not crash + +protected_mode: + mov bx, 0x10 + mov ds, bx # set data segment + mov es, bx # set extra segment + + and al, 0xfe # clear protected mode bit + mov cr0, eax + +unreal_mode: + pop es # get back old extra segment + pop ds # get back old data segment + sti + + # back to real mode, but internal data segment register is still loaded + # with gdt segment -> we can access the full 4GiB of memory + + mov bx, 0x0f02 # attrib/char of smiley + mov eax, 0xb8f00 # note 32 bit offset + mov word ptr ds:[eax], bx + +check_int13h_extensions: + push 'y' # error code + mov ah, 0x41 + mov bx, 0x55aa + # dl contains drive number + int 0x13 + jc fail + pop ax # pop error code again + +rust: + # push arguments + push dx # disk number + call first_stage + +spin: + hlt + jmp spin + +gdt32info: + .word gdt32_end - gdt32 - 1 # last byte in table + .word gdt32 # start of table + +gdt32: + # entry 0 is always unused + .quad 0 +codedesc: + .byte 0xff + .byte 0xff + .byte 0 + .byte 0 + .byte 0 + .byte 0x9a + .byte 0xcf + .byte 0 +datadesc: + .byte 0xff + .byte 0xff + .byte 0 + .byte 0 + .byte 0 + .byte 0x92 + .byte 0xcf + .byte 0 +gdt32_end: diff --git a/bios/second_stage/src/dap.rs b/bios/second_stage/src/dap.rs new file mode 100644 index 00000000..35ce6fe6 --- /dev/null +++ b/bios/second_stage/src/dap.rs @@ -0,0 +1,58 @@ +use core::arch::{asm, global_asm}; + +#[repr(packed)] +pub struct DiskAddressPacket { + /// Size of the DAP structure + packet_size: u8, + /// always zero + zero: u8, + /// Number of sectors to transfer + number_of_sectors: u16, + /// Offset to memory buffer + offset: u16, + /// Segment of memory buffer + segment: u16, + /// Start logical block address + start_lba: u64, +} + +impl DiskAddressPacket { + pub fn new(memory_buffer_start: u16, file_offset: u64, bytes: u32) -> Self { + Self { + packet_size: 0x10, + zero: 0, + number_of_sectors: (bytes / 512) as u16, + offset: memory_buffer_start, + segment: 0, + start_lba: file_offset / 512, + } + } + + pub fn from_lba(memory_buffer_start: u16, start_lba: u64, number_of_sectors: u16) -> Self { + Self { + packet_size: 0x10, + zero: 0, + number_of_sectors, + offset: memory_buffer_start, + segment: 0, + start_lba, + } + } + + pub unsafe fn perform_load(&self, disk_number: u16) { + let self_addr = self as *const Self as u16; + asm!( + "push 0x7a", // error code `z`, passed to `fail` on error + "mov {1:x}, si", + "mov si, {0:x}", + "int 0x13", + "jc fail", + "pop si", // remove error code again + "mov si, {1:x}", + in(reg) self_addr, + out(reg) _, + in("ax") 0x4200u16, + in("dx") disk_number, + ); + } +} diff --git a/bios/second_stage/src/fail.rs b/bios/second_stage/src/fail.rs new file mode 100644 index 00000000..17fc1ee0 --- /dev/null +++ b/bios/second_stage/src/fail.rs @@ -0,0 +1,59 @@ +use core::arch::asm; + +pub trait UnwrapOrFail { + type Out; + + fn unwrap_or_fail(self, code: u8) -> Self::Out; +} + +impl UnwrapOrFail for Option { + type Out = T; + + fn unwrap_or_fail(self, code: u8) -> Self::Out { + match self { + Some(v) => v, + None => fail(code), + } + } +} + +impl UnwrapOrFail for Result { + type Out = T; + + fn unwrap_or_fail(self, code: u8) -> Self::Out { + match self { + Ok(v) => v, + Err(_) => fail(code), + } + } +} + +#[no_mangle] +pub extern "C" fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10", in("ax") ax, in("bx") 0); + } +} + +#[cold] +#[inline(never)] +#[no_mangle] +pub extern "C" fn fail(code: u8) -> ! { + print_char(b'!'); + print_char(code); + loop { + hlt() + } +} + +fn hlt() { + unsafe { + asm!("hlt"); + } +} + +#[panic_handler] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + fail(b'P'); +} diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs new file mode 100644 index 00000000..b96388a7 --- /dev/null +++ b/bios/second_stage/src/fat.rs @@ -0,0 +1,240 @@ +// based on https://github.com/rafalh/rust-fatfs/ + +use super::split_array_ref; + +// Size of single directory entry in bytes +const DIR_ENTRY_SIZE: u32 = 32; + +pub(crate) struct BootSector { + pub(crate) bpb: BiosParameterBlock, +} + +impl BootSector { + pub(crate) fn deserialize(bytes: &[u8]) -> Self { + let mut boot = Self::default(); + // let (&bootjmp, bytes) = split_array_ref(bytes); + // let (&oem_name, bytes) = split_array_ref(bytes); + + let bytes = &bytes[3 + 8..]; + + // boot.bootjmp = bootjmp; + // boot.oem_name = oem_name; + boot.bpb = BiosParameterBlock::deserialize(bytes); + + // let bytes = if boot.bpb.is_fat32() { + // let (boot_code, bytes): (&[_; 420], _) = split_array_ref(bytes); + // boot.boot_code[0..420].copy_from_slice(&boot_code[..]); + // bytes + // } else { + // let (&boot_code, bytes) = split_array_ref(bytes); + // boot.boot_code = boot_code; + // bytes + // }; + // let (&boot_sig, bytes) = split_array_ref(bytes); + // boot.boot_sig = boot_sig; + boot + } +} + +impl Default for BootSector { + fn default() -> Self { + Self { + bpb: BiosParameterBlock::default(), + } + } +} + +#[derive(Default, Debug, Clone)] +pub(crate) struct BiosParameterBlock { + pub(crate) bytes_per_sector: u16, + pub(crate) sectors_per_cluster: u8, + pub(crate) reserved_sectors: u16, + pub(crate) fats: u8, + pub(crate) root_entries: u16, + pub(crate) total_sectors_16: u16, + pub(crate) media: u8, + pub(crate) sectors_per_fat_16: u16, + pub(crate) sectors_per_track: u16, + pub(crate) heads: u16, + pub(crate) hidden_sectors: u32, + pub(crate) total_sectors_32: u32, + + // Extended BIOS Parameter Block + pub(crate) sectors_per_fat_32: u32, + pub(crate) extended_flags: u16, + pub(crate) fs_version: u16, + pub(crate) root_dir_first_cluster: u32, + pub(crate) fs_info_sector: u16, + pub(crate) backup_boot_sector: u16, + pub(crate) reserved_0: [u8; 12], + pub(crate) drive_num: u8, + pub(crate) reserved_1: u8, + pub(crate) ext_sig: u8, + pub(crate) volume_id: u32, + pub(crate) volume_label: [u8; 11], + pub(crate) fs_type_label: [u8; 8], +} + +impl BiosParameterBlock { + pub fn deserialize(bytes: &[u8]) -> Self { + let (&bytes_per_sector, bytes) = split_array_ref(bytes); + let (&[sectors_per_cluster], bytes) = split_array_ref(bytes); + let (&reserved_sectors, bytes) = split_array_ref(bytes); + let (&[fats], bytes) = split_array_ref(bytes); + let (&root_entries, bytes) = split_array_ref(bytes); + let (&total_sectors_16, bytes) = split_array_ref(bytes); + let (&[media], bytes) = split_array_ref(bytes); + let (§ors_per_fat_16, bytes) = split_array_ref(bytes); + let (§ors_per_track, bytes) = split_array_ref(bytes); + let (&heads, bytes) = split_array_ref(bytes); + let (&hidden_sectors, bytes) = split_array_ref(bytes); + let (&total_sectors_32, bytes) = split_array_ref(bytes); + + let mut bpb = Self { + bytes_per_sector: u16::from_le_bytes(bytes_per_sector), + sectors_per_cluster, + reserved_sectors: u16::from_le_bytes(reserved_sectors), + fats, + root_entries: u16::from_le_bytes(root_entries), + total_sectors_16: u16::from_le_bytes(total_sectors_16), + media, + sectors_per_fat_16: u16::from_le_bytes(sectors_per_fat_16), + sectors_per_track: u16::from_le_bytes(sectors_per_track), + heads: u16::from_le_bytes(heads), + hidden_sectors: u32::from_le_bytes(hidden_sectors), + total_sectors_32: u32::from_le_bytes(total_sectors_32), + ..Self::default() + }; + + let (§ors_per_fat_32, bytes) = split_array_ref(bytes); + let (&extended_flags, bytes) = split_array_ref(bytes); + let (&fs_version, bytes) = split_array_ref(bytes); + let (&root_dir_first_cluster, bytes) = split_array_ref(bytes); + let (&fs_info_sector, bytes) = split_array_ref(bytes); + let (&backup_boot_sector, bytes) = split_array_ref(bytes); + let (&reserved_0, bytes) = split_array_ref(bytes); + + if bpb.is_fat32() { + bpb.sectors_per_fat_32 = u32::from_le_bytes(sectors_per_fat_32); + bpb.extended_flags = u16::from_le_bytes(extended_flags); + bpb.fs_version = u16::from_le_bytes(fs_version); + bpb.root_dir_first_cluster = u32::from_le_bytes(root_dir_first_cluster); + bpb.fs_info_sector = u16::from_le_bytes(fs_info_sector); + bpb.backup_boot_sector = u16::from_le_bytes(backup_boot_sector); + bpb.reserved_0 = reserved_0; + } + + let (&[drive_num], bytes) = split_array_ref(bytes); + let (&[reserved_1], bytes) = split_array_ref(bytes); + let (&[ext_sig], bytes) = split_array_ref(bytes); + let (&volume_id, bytes) = split_array_ref(bytes); + let (&volume_label, bytes) = split_array_ref(bytes); + let (&fs_type_label, bytes) = split_array_ref(bytes); + + bpb.drive_num = drive_num; + bpb.reserved_1 = reserved_1; + bpb.ext_sig = ext_sig; // 0x29 + bpb.volume_id = u32::from_le_bytes(volume_id); + bpb.volume_label = volume_label; + bpb.fs_type_label = fs_type_label; + + // when the extended boot signature is anything other than 0x29, the fields are invalid + if bpb.ext_sig != 0x29 { + // fields after ext_sig are not used - clean them + bpb.volume_id = 0; + bpb.volume_label = [0; 11]; + bpb.fs_type_label = [0; 8]; + } + + bpb + } + + pub(crate) fn is_fat32(&self) -> bool { + // because this field must be zero on FAT32, and + // because it must be non-zero on FAT12/FAT16, + // this provides a simple way to detect FAT32 + self.sectors_per_fat_16 == 0 + } + + pub(crate) fn sectors_per_fat(&self) -> u32 { + if self.is_fat32() { + self.sectors_per_fat_32 + } else { + u32::from(self.sectors_per_fat_16) + } + } + + pub(crate) fn total_sectors(&self) -> u32 { + if self.total_sectors_16 == 0 { + self.total_sectors_32 + } else { + u32::from(self.total_sectors_16) + } + } + + pub(crate) fn reserved_sectors(&self) -> u32 { + u32::from(self.reserved_sectors) + } + + pub(crate) fn root_dir_sectors(&self) -> u32 { + let root_dir_bytes = u32::from(self.root_entries) * DIR_ENTRY_SIZE; + (root_dir_bytes + u32::from(self.bytes_per_sector) - 1) / u32::from(self.bytes_per_sector) + } + + pub(crate) fn sectors_per_all_fats(&self) -> u32 { + u32::from(self.fats) * self.sectors_per_fat() + } + + pub(crate) fn first_data_sector(&self) -> u32 { + let root_dir_sectors = self.root_dir_sectors(); + let fat_sectors = self.sectors_per_all_fats(); + self.reserved_sectors() + fat_sectors + root_dir_sectors + } + + pub(crate) fn total_clusters(&self) -> u32 { + let total_sectors = self.total_sectors(); + let first_data_sector = self.first_data_sector(); + let data_sectors = total_sectors - first_data_sector; + data_sectors / u32::from(self.sectors_per_cluster) + } + + pub fn fat_type(&self) -> FatType { + FatType::from_clusters(self.total_clusters()) + } + + /// Returns a root directory object allowing for futher penetration of a filesystem structure. + pub fn check_root_dir(&self) { + match self.fat_type() { + FatType::Fat12 | FatType::Fat16 => crate::fail(b'y'), + FatType::Fat32 => { + self.root_dir_first_cluster; + crate::fail(b'z'); + } + } + } +} + +pub enum FatType { + /// 12 bits per FAT entry + Fat12, + /// 16 bits per FAT entry + Fat16, + /// 32 bits per FAT entry + Fat32, +} + +impl FatType { + const FAT16_MIN_CLUSTERS: u32 = 4085; + const FAT32_MIN_CLUSTERS: u32 = 65525; + const FAT32_MAX_CLUSTERS: u32 = 0x0FFF_FFF4; + + pub(crate) fn from_clusters(total_clusters: u32) -> Self { + if total_clusters < Self::FAT16_MIN_CLUSTERS { + FatType::Fat12 + } else if total_clusters < Self::FAT32_MIN_CLUSTERS { + FatType::Fat16 + } else { + FatType::Fat32 + } + } +} diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs new file mode 100644 index 00000000..161a22cf --- /dev/null +++ b/bios/second_stage/src/main.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] + +use core::{ + arch::{asm, global_asm}, + slice, +}; + +// mod dap; +mod fail; +// mod fat; +// mod mbr; + +#[no_mangle] +pub extern "C" fn _start(disk_number: u16) { + fail::print_char(b'_'); + fail::print_char(b'_'); + fail::print_char(b'S'); + fail::print_char(b':'); + + fail::print_char(b'1'); + loop {} + + // try to parse FAT file system + // let fat_slice = unsafe { + // slice::from_raw_parts( + // partition_buf as *const u8, + // usize::try_from(second_stage_partition.sector_count).unwrap_or_else(|_| fail(b'a')) + // * 512, + // ) + // }; + + // print_char(b'4'); + // let boot_sector = fat::BootSector::deserialize(fat_slice); + // let root_dir = boot_sector.bpb.root_dir_first_cluster; + // boot_sector.bpb.check_root_dir(); + + // print_char(b'5'); + + // TODO: get root dir + + // TODO: get offset of `second_stage` file + + // TODO: get offset of `kernel-x86_64` file + + // TODO: load `second_stage` file into memory + + // TODO: jump to `second_stage`, pass offset of `kernel-x86_64` and disk number as arguments + + loop {} +} + +// /// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 +// /// +// /// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, +// /// see https://github.com/rust-lang/rust/issues/90091 +// fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { +// if N > slice.len() { +// fail(b'S'); +// } +// let (a, b) = slice.split_at(N); +// // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) +// unsafe { (&*(a.as_ptr() as *const [T; N]), b) } +// } diff --git a/bios/second_stage/src/mbr.rs b/bios/second_stage/src/mbr.rs new file mode 100644 index 00000000..f7b00d6d --- /dev/null +++ b/bios/second_stage/src/mbr.rs @@ -0,0 +1,124 @@ +// Based on https://docs.rs/mbr-nostd + +use super::fail::{fail, UnwrapOrFail}; + +/// We use this partition type to store the second bootloader stage; +const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; + +/// Returns the first bootable partition in the partition table. +pub fn boot_partition(partitions_raw: &[u8]) -> Option { + for index in 0..4 { + let entry = get_partition(partitions_raw, index); + if entry.partition_type == BOOTLOADER_SECOND_STAGE_PARTITION_TYPE { + return Some(entry); + } + } + None +} + +pub fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTableEntry { + if partitions_raw.len() < PARTITIONS_AREA_SIZE { + fail(b'a'); + } + + let offset = index * ENTRY_SIZE; + let buffer = partitions_raw.get(offset..).unwrap_or_fail(b'c'); + + let bootable_raw = *buffer.get(0).unwrap_or_fail(b'd'); + let bootable = bootable_raw == 0x80; + + let partition_type = *buffer.get(4).unwrap_or_fail(b'e'); + + let lba = u32::from_le_bytes( + buffer + .get(8..) + .and_then(|s| s.get(..4)) + .and_then(|s| s.try_into().ok()) + .unwrap_or_fail(b'e'), + ); + let len = u32::from_le_bytes( + buffer + .get(12..) + .and_then(|s| s.get(..4)) + .and_then(|s| s.try_into().ok()) + .unwrap_or_fail(b'f'), + ); + PartitionTableEntry::new(bootable, partition_type, lba, len) +} + +const PARTITIONS_AREA_SIZE: usize = 16 * 4; +const ENTRY_SIZE: usize = 16; + +/// The type of a particular partition. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum PartitionType { + BootloaderSecondStage, + Unused, + Unknown(u8), + Fat12(u8), + Fat16(u8), + Fat32(u8), + LinuxExt(u8), + HfsPlus(u8), + ISO9660(u8), + NtfsExfat(u8), +} + +impl PartitionType { + /// Parses a partition type from the type byte in the MBR's table. + pub fn from_mbr_tag_byte(tag: u8) -> PartitionType { + match tag { + // we use partition type 0x20 to store the second bootloader stage + 0x20 => PartitionType::BootloaderSecondStage, + 0x0 => PartitionType::Unused, + 0x01 => PartitionType::Fat12(tag), + 0x04 | 0x06 | 0x0e => PartitionType::Fat16(tag), + 0x0b | 0x0c | 0x1b | 0x1c => PartitionType::Fat32(tag), + 0x83 => PartitionType::LinuxExt(tag), + 0x07 => PartitionType::NtfsExfat(tag), + 0xaf => PartitionType::HfsPlus(tag), + _ => PartitionType::Unknown(tag), + } + } + + pub fn known_type(tag: u8) -> bool { + match tag { + 0x0 | 0x01 | 0x04 | 0x06 | 0x0e | 0x0b | 0x0c | 0x1b | 0x1c | 0x83 | 0x07 | 0xaf => { + true + } + _ => false, + } + } +} + +/// An entry in a partition table. +#[derive(Copy, Clone, Eq, PartialEq)] +pub struct PartitionTableEntry { + /// Whether this partition is a boot partition. + pub bootable: bool, + + /// The type of partition in this entry. + pub partition_type: u8, + + /// The index of the first block of this entry. + pub logical_block_address: u32, + + /// The total number of blocks in this entry. + pub sector_count: u32, +} + +impl PartitionTableEntry { + pub fn new( + bootable: bool, + partition_type: u8, + logical_block_address: u32, + sector_count: u32, + ) -> PartitionTableEntry { + PartitionTableEntry { + bootable, + partition_type, + logical_block_address, + sector_count, + } + } +} diff --git a/build.rs b/build.rs index 34da7619..e8fcf534 100644 --- a/build.rs +++ b/build.rs @@ -5,6 +5,7 @@ use std::{ const BOOTLOADER_X86_64_UEFI_VERSION: &str = "0.1.0-alpha.0"; const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = "0.1.0-alpha.0"; +const BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION: &str = "0.1.0-alpha.0"; fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); @@ -20,6 +21,11 @@ fn main() { "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", bios_boot_sector_path.display() ); + let bios_second_stage_path = build_bios_second_stage(&out_dir); + println!( + "cargo:rustc-env=BIOS_SECOND_STAGE_PATH={}", + bios_second_stage_path.display() + ); } fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { @@ -92,6 +98,45 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { }; convert_elf_to_bin(elf_path) } +fn build_bios_second_stage(out_dir: &Path) -> PathBuf { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); + let mut cmd = Command::new(cargo); + cmd.arg("install") + .arg("bootloader-x86_64-bios-second-stage"); + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("bios") + .join("second_stage"); + if local_path.exists() { + // local build + cmd.arg("--path").arg(&local_path); + } else { + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION); + } + cmd.arg("--locked"); + cmd.arg("--target").arg("x86-16bit.json"); + cmd.arg("-Zbuild-std=core") + .arg("-Zbuild-std-features=compiler-builtins-mem"); + cmd.arg("--root").arg(out_dir); + cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); // used by clippy + let status = cmd + .status() + .expect("failed to run cargo install for bios second stage"); + let elf_path = if status.success() { + let path = out_dir + .join("bin") + .join("bootloader-x86_64-bios-second-stage"); + assert!( + path.exists(), + "bios second stage executable does not exist after building" + ); + path + } else { + panic!("failed to build bios second stage"); + }; + convert_elf_to_bin(elf_path) +} fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { let flat_binary_path = elf_path.with_extension("bin"); diff --git a/src/lib.rs b/src/lib.rs index 59310b4b..a6e4418e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -109,9 +109,15 @@ pub fn create_bios_disk_image( out_mbr_path: &Path, ) -> anyhow::Result<()> { let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); - - mbr::create_mbr_disk(bootsector_path, boot_partition_path, out_mbr_path) - .context("failed to create BIOS MBR disk image")?; + let second_stage_path = Path::new(env!("BIOS_SECOND_STAGE_PATH")); + + mbr::create_mbr_disk( + bootsector_path, + second_stage_path, + boot_partition_path, + out_mbr_path, + ) + .context("failed to create BIOS MBR disk image")?; Ok(()) } diff --git a/src/mbr.rs b/src/mbr.rs index eb13f94a..9b44b9b4 100644 --- a/src/mbr.rs +++ b/src/mbr.rs @@ -1,13 +1,14 @@ use anyhow::Context; use std::{ fs::{self, File}, - io, + io::{self, Read, Seek, SeekFrom}, path::Path, }; const SECTOR_SIZE: u32 = 512; pub fn create_mbr_disk( bootsector_path: &Path, + second_stage_path: &Path, boot_partition_path: &Path, out_mbr_path: &Path, ) -> anyhow::Result<()> { @@ -21,17 +22,38 @@ pub fn create_mbr_disk( } } + let mut second_stage = + File::open(second_stage_path).context("failed to open second stage binary")?; + let second_stage_size = second_stage + .metadata() + .context("failed to read file metadata of second stage")? + .len(); + let second_stage_start_sector = 1; + let second_stage_sectors = ((second_stage_size - 1) / u64::from(SECTOR_SIZE) + 1) + .try_into() + .context("size of second stage is larger than u32::MAX")?; + mbr[1] = mbrman::MBRPartitionEntry { + boot: true, + starting_lba: second_stage_start_sector, + sectors: second_stage_sectors, + // see BOOTLOADER_SECOND_STAGE_PARTITION_TYPE in `boot_sector` crate + sys: 0x20, + + first_chs: mbrman::CHS::empty(), + last_chs: mbrman::CHS::empty(), + }; + let mut boot_partition = File::open(boot_partition_path).context("failed to open FAT boot partition")?; + let boot_partition_start_sector = second_stage_start_sector + second_stage_sectors; let boot_partition_size = boot_partition .metadata() .context("failed to read file metadata of FAT boot partition")? .len(); - - mbr[1] = mbrman::MBRPartitionEntry { - boot: true, - starting_lba: 1, - sectors: (boot_partition_size / u64::from(SECTOR_SIZE)) + mbr[2] = mbrman::MBRPartitionEntry { + boot: false, + starting_lba: boot_partition_start_sector, + sectors: ((boot_partition_size - 1) / u64::from(SECTOR_SIZE) + 1) .try_into() .context("size of FAT partition is larger than u32::MAX")?, //TODO: is this the correct type? @@ -57,6 +79,19 @@ pub fn create_mbr_disk( mbr.write_into(&mut disk) .context("failed to write MBR header to disk image")?; + // second stage + assert_eq!( + disk.stream_position() + .context("failed to get disk image seek position")?, + (second_stage_start_sector * SECTOR_SIZE).into() + ); + io::copy(&mut second_stage, &mut disk) + .context("failed to copy second stage binary to MBR disk image")?; + + // fat partition + disk.seek(SeekFrom::Start( + (boot_partition_start_sector * SECTOR_SIZE).into(), + )); io::copy(&mut boot_partition, &mut disk) .context("failed to copy FAT image to MBR disk image")?; From 4d41efe84c14487d824f5270667ed93baa274772 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Apr 2022 14:10:38 +0200 Subject: [PATCH 098/226] Use pie relocation model for second stage --- bios/second_stage/build.rs | 9 --------- bios/second_stage/second-stage-link.ld | 19 ------------------- build.rs | 2 +- x86-16bit-second-stage.json | 20 ++++++++++++++++++++ 4 files changed, 21 insertions(+), 29 deletions(-) delete mode 100644 bios/second_stage/build.rs delete mode 100644 bios/second_stage/second-stage-link.ld create mode 100644 x86-16bit-second-stage.json diff --git a/bios/second_stage/build.rs b/bios/second_stage/build.rs deleted file mode 100644 index 945a419d..00000000 --- a/bios/second_stage/build.rs +++ /dev/null @@ -1,9 +0,0 @@ -use std::path::Path; - -fn main() { - let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); - println!( - "cargo:rustc-link-arg-bins=--script={}", - local_path.join("second-stage-link.ld").display() - ) -} diff --git a/bios/second_stage/second-stage-link.ld b/bios/second_stage/second-stage-link.ld deleted file mode 100644 index 9f3b3e11..00000000 --- a/bios/second_stage/second-stage-link.ld +++ /dev/null @@ -1,19 +0,0 @@ -ENTRY(_start) - -SECTIONS { - . = 0x7c00 + 512; - .text : - { - *(.text .text.*) - } - .rodata : - { - *(.rodata .rodata.*) - } - .data : - { - *(.rodata .rodata.*) - *(.data .data.*) - *(.got .got.*) - } -} diff --git a/build.rs b/build.rs index e8fcf534..771e66ad 100644 --- a/build.rs +++ b/build.rs @@ -114,7 +114,7 @@ fn build_bios_second_stage(out_dir: &Path) -> PathBuf { .arg(BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION); } cmd.arg("--locked"); - cmd.arg("--target").arg("x86-16bit.json"); + cmd.arg("--target").arg("x86-16bit-second-stage.json"); cmd.arg("-Zbuild-std=core") .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); diff --git a/x86-16bit-second-stage.json b/x86-16bit-second-stage.json new file mode 100644 index 00000000..ee6f67ad --- /dev/null +++ b/x86-16bit-second-stage.json @@ -0,0 +1,20 @@ +{ + "arch": "x86", + "cpu": "i386", + "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128", + "dynamic-linking": false, + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "llvm-target": "i386-unknown-none-code16", + "max-atomic-width": 64, + "position-independent-executables": false, + "disable-redzone": true, + "target-c-int-width": "32", + "target-pointer-width": "32", + "target-endian": "little", + "panic-strategy": "abort", + "os": "none", + "vendor": "unknown", + "relocation-model": "pie" +} From 5fd311542c73f3014136ce0b4ecbed133443c01b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 17 Apr 2022 14:23:17 +0200 Subject: [PATCH 099/226] Clean up boot sector code --- bios/boot_sector/src/boot unreal.s | 110 ------------- bios/boot_sector/src/dap.rs | 42 ++--- bios/boot_sector/src/fat.rs | 240 ----------------------------- bios/boot_sector/src/main.rs | 86 ++--------- bios/boot_sector/src/mbr.rs | 66 ++------ 5 files changed, 38 insertions(+), 506 deletions(-) delete mode 100644 bios/boot_sector/src/boot unreal.s delete mode 100644 bios/boot_sector/src/fat.rs diff --git a/bios/boot_sector/src/boot unreal.s b/bios/boot_sector/src/boot unreal.s deleted file mode 100644 index 9aa5405b..00000000 --- a/bios/boot_sector/src/boot unreal.s +++ /dev/null @@ -1,110 +0,0 @@ -.section .boot, "awx" -.global _start -.code16 - -# This stage initializes the stack, enables the A20 line - -_start: - # zero segment registers - xor ax, ax - mov ds, ax - mov es, ax - mov ss, ax - mov fs, ax - mov gs, ax - - # clear the direction flag (e.g. go forward in memory when using - # instructions like lodsb) - cld - - # initialize stack - mov sp, 0x7c00 - -enable_a20: - # enable A20-Line via IO-Port 92, might not work on all motherboards - in al, 0x92 - test al, 2 - jnz enable_a20_after - or al, 2 - and al, 0xFE - out 0x92, al -enable_a20_after: - -enter_protected_mode: - # clear interrupts - cli - push ds - push es - - lgdt [gdt32info] - - mov eax, cr0 - or al, 1 # set protected mode bit - mov cr0, eax - - jmp protected_mode # tell 386/486 to not crash - -protected_mode: - mov bx, 0x10 - mov ds, bx # set data segment - mov es, bx # set extra segment - - and al, 0xfe # clear protected mode bit - mov cr0, eax - -unreal_mode: - pop es # get back old extra segment - pop ds # get back old data segment - sti - - # back to real mode, but internal data segment register is still loaded - # with gdt segment -> we can access the full 4GiB of memory - - mov bx, 0x0f02 # attrib/char of smiley - mov eax, 0xb8f00 # note 32 bit offset - mov word ptr ds:[eax], bx - -check_int13h_extensions: - push 'y' # error code - mov ah, 0x41 - mov bx, 0x55aa - # dl contains drive number - int 0x13 - jc fail - pop ax # pop error code again - -rust: - # push arguments - push dx # disk number - call first_stage - -spin: - hlt - jmp spin - -gdt32info: - .word gdt32_end - gdt32 - 1 # last byte in table - .word gdt32 # start of table - -gdt32: - # entry 0 is always unused - .quad 0 -codedesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x9a - .byte 0xcf - .byte 0 -datadesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x92 - .byte 0xcf - .byte 0 -gdt32_end: diff --git a/bios/boot_sector/src/dap.rs b/bios/boot_sector/src/dap.rs index 35ce6fe6..7f0b583a 100644 --- a/bios/boot_sector/src/dap.rs +++ b/bios/boot_sector/src/dap.rs @@ -1,6 +1,7 @@ -use core::arch::{asm, global_asm}; +use core::arch::asm; #[repr(packed)] +#[allow(dead_code)] // the structure format is defined by the hardware pub struct DiskAddressPacket { /// Size of the DAP structure packet_size: u8, @@ -17,17 +18,6 @@ pub struct DiskAddressPacket { } impl DiskAddressPacket { - pub fn new(memory_buffer_start: u16, file_offset: u64, bytes: u32) -> Self { - Self { - packet_size: 0x10, - zero: 0, - number_of_sectors: (bytes / 512) as u16, - offset: memory_buffer_start, - segment: 0, - start_lba: file_offset / 512, - } - } - pub fn from_lba(memory_buffer_start: u16, start_lba: u64, number_of_sectors: u16) -> Self { Self { packet_size: 0x10, @@ -41,18 +31,20 @@ impl DiskAddressPacket { pub unsafe fn perform_load(&self, disk_number: u16) { let self_addr = self as *const Self as u16; - asm!( - "push 0x7a", // error code `z`, passed to `fail` on error - "mov {1:x}, si", - "mov si, {0:x}", - "int 0x13", - "jc fail", - "pop si", // remove error code again - "mov si, {1:x}", - in(reg) self_addr, - out(reg) _, - in("ax") 0x4200u16, - in("dx") disk_number, - ); + unsafe { + asm!( + "push 0x7a", // error code `z`, passed to `fail` on error + "mov {1:x}, si", + "mov si, {0:x}", + "int 0x13", + "jc fail", + "pop si", // remove error code again + "mov si, {1:x}", + in(reg) self_addr, + out(reg) _, + in("ax") 0x4200u16, + in("dx") disk_number, + ); + } } } diff --git a/bios/boot_sector/src/fat.rs b/bios/boot_sector/src/fat.rs deleted file mode 100644 index b96388a7..00000000 --- a/bios/boot_sector/src/fat.rs +++ /dev/null @@ -1,240 +0,0 @@ -// based on https://github.com/rafalh/rust-fatfs/ - -use super::split_array_ref; - -// Size of single directory entry in bytes -const DIR_ENTRY_SIZE: u32 = 32; - -pub(crate) struct BootSector { - pub(crate) bpb: BiosParameterBlock, -} - -impl BootSector { - pub(crate) fn deserialize(bytes: &[u8]) -> Self { - let mut boot = Self::default(); - // let (&bootjmp, bytes) = split_array_ref(bytes); - // let (&oem_name, bytes) = split_array_ref(bytes); - - let bytes = &bytes[3 + 8..]; - - // boot.bootjmp = bootjmp; - // boot.oem_name = oem_name; - boot.bpb = BiosParameterBlock::deserialize(bytes); - - // let bytes = if boot.bpb.is_fat32() { - // let (boot_code, bytes): (&[_; 420], _) = split_array_ref(bytes); - // boot.boot_code[0..420].copy_from_slice(&boot_code[..]); - // bytes - // } else { - // let (&boot_code, bytes) = split_array_ref(bytes); - // boot.boot_code = boot_code; - // bytes - // }; - // let (&boot_sig, bytes) = split_array_ref(bytes); - // boot.boot_sig = boot_sig; - boot - } -} - -impl Default for BootSector { - fn default() -> Self { - Self { - bpb: BiosParameterBlock::default(), - } - } -} - -#[derive(Default, Debug, Clone)] -pub(crate) struct BiosParameterBlock { - pub(crate) bytes_per_sector: u16, - pub(crate) sectors_per_cluster: u8, - pub(crate) reserved_sectors: u16, - pub(crate) fats: u8, - pub(crate) root_entries: u16, - pub(crate) total_sectors_16: u16, - pub(crate) media: u8, - pub(crate) sectors_per_fat_16: u16, - pub(crate) sectors_per_track: u16, - pub(crate) heads: u16, - pub(crate) hidden_sectors: u32, - pub(crate) total_sectors_32: u32, - - // Extended BIOS Parameter Block - pub(crate) sectors_per_fat_32: u32, - pub(crate) extended_flags: u16, - pub(crate) fs_version: u16, - pub(crate) root_dir_first_cluster: u32, - pub(crate) fs_info_sector: u16, - pub(crate) backup_boot_sector: u16, - pub(crate) reserved_0: [u8; 12], - pub(crate) drive_num: u8, - pub(crate) reserved_1: u8, - pub(crate) ext_sig: u8, - pub(crate) volume_id: u32, - pub(crate) volume_label: [u8; 11], - pub(crate) fs_type_label: [u8; 8], -} - -impl BiosParameterBlock { - pub fn deserialize(bytes: &[u8]) -> Self { - let (&bytes_per_sector, bytes) = split_array_ref(bytes); - let (&[sectors_per_cluster], bytes) = split_array_ref(bytes); - let (&reserved_sectors, bytes) = split_array_ref(bytes); - let (&[fats], bytes) = split_array_ref(bytes); - let (&root_entries, bytes) = split_array_ref(bytes); - let (&total_sectors_16, bytes) = split_array_ref(bytes); - let (&[media], bytes) = split_array_ref(bytes); - let (§ors_per_fat_16, bytes) = split_array_ref(bytes); - let (§ors_per_track, bytes) = split_array_ref(bytes); - let (&heads, bytes) = split_array_ref(bytes); - let (&hidden_sectors, bytes) = split_array_ref(bytes); - let (&total_sectors_32, bytes) = split_array_ref(bytes); - - let mut bpb = Self { - bytes_per_sector: u16::from_le_bytes(bytes_per_sector), - sectors_per_cluster, - reserved_sectors: u16::from_le_bytes(reserved_sectors), - fats, - root_entries: u16::from_le_bytes(root_entries), - total_sectors_16: u16::from_le_bytes(total_sectors_16), - media, - sectors_per_fat_16: u16::from_le_bytes(sectors_per_fat_16), - sectors_per_track: u16::from_le_bytes(sectors_per_track), - heads: u16::from_le_bytes(heads), - hidden_sectors: u32::from_le_bytes(hidden_sectors), - total_sectors_32: u32::from_le_bytes(total_sectors_32), - ..Self::default() - }; - - let (§ors_per_fat_32, bytes) = split_array_ref(bytes); - let (&extended_flags, bytes) = split_array_ref(bytes); - let (&fs_version, bytes) = split_array_ref(bytes); - let (&root_dir_first_cluster, bytes) = split_array_ref(bytes); - let (&fs_info_sector, bytes) = split_array_ref(bytes); - let (&backup_boot_sector, bytes) = split_array_ref(bytes); - let (&reserved_0, bytes) = split_array_ref(bytes); - - if bpb.is_fat32() { - bpb.sectors_per_fat_32 = u32::from_le_bytes(sectors_per_fat_32); - bpb.extended_flags = u16::from_le_bytes(extended_flags); - bpb.fs_version = u16::from_le_bytes(fs_version); - bpb.root_dir_first_cluster = u32::from_le_bytes(root_dir_first_cluster); - bpb.fs_info_sector = u16::from_le_bytes(fs_info_sector); - bpb.backup_boot_sector = u16::from_le_bytes(backup_boot_sector); - bpb.reserved_0 = reserved_0; - } - - let (&[drive_num], bytes) = split_array_ref(bytes); - let (&[reserved_1], bytes) = split_array_ref(bytes); - let (&[ext_sig], bytes) = split_array_ref(bytes); - let (&volume_id, bytes) = split_array_ref(bytes); - let (&volume_label, bytes) = split_array_ref(bytes); - let (&fs_type_label, bytes) = split_array_ref(bytes); - - bpb.drive_num = drive_num; - bpb.reserved_1 = reserved_1; - bpb.ext_sig = ext_sig; // 0x29 - bpb.volume_id = u32::from_le_bytes(volume_id); - bpb.volume_label = volume_label; - bpb.fs_type_label = fs_type_label; - - // when the extended boot signature is anything other than 0x29, the fields are invalid - if bpb.ext_sig != 0x29 { - // fields after ext_sig are not used - clean them - bpb.volume_id = 0; - bpb.volume_label = [0; 11]; - bpb.fs_type_label = [0; 8]; - } - - bpb - } - - pub(crate) fn is_fat32(&self) -> bool { - // because this field must be zero on FAT32, and - // because it must be non-zero on FAT12/FAT16, - // this provides a simple way to detect FAT32 - self.sectors_per_fat_16 == 0 - } - - pub(crate) fn sectors_per_fat(&self) -> u32 { - if self.is_fat32() { - self.sectors_per_fat_32 - } else { - u32::from(self.sectors_per_fat_16) - } - } - - pub(crate) fn total_sectors(&self) -> u32 { - if self.total_sectors_16 == 0 { - self.total_sectors_32 - } else { - u32::from(self.total_sectors_16) - } - } - - pub(crate) fn reserved_sectors(&self) -> u32 { - u32::from(self.reserved_sectors) - } - - pub(crate) fn root_dir_sectors(&self) -> u32 { - let root_dir_bytes = u32::from(self.root_entries) * DIR_ENTRY_SIZE; - (root_dir_bytes + u32::from(self.bytes_per_sector) - 1) / u32::from(self.bytes_per_sector) - } - - pub(crate) fn sectors_per_all_fats(&self) -> u32 { - u32::from(self.fats) * self.sectors_per_fat() - } - - pub(crate) fn first_data_sector(&self) -> u32 { - let root_dir_sectors = self.root_dir_sectors(); - let fat_sectors = self.sectors_per_all_fats(); - self.reserved_sectors() + fat_sectors + root_dir_sectors - } - - pub(crate) fn total_clusters(&self) -> u32 { - let total_sectors = self.total_sectors(); - let first_data_sector = self.first_data_sector(); - let data_sectors = total_sectors - first_data_sector; - data_sectors / u32::from(self.sectors_per_cluster) - } - - pub fn fat_type(&self) -> FatType { - FatType::from_clusters(self.total_clusters()) - } - - /// Returns a root directory object allowing for futher penetration of a filesystem structure. - pub fn check_root_dir(&self) { - match self.fat_type() { - FatType::Fat12 | FatType::Fat16 => crate::fail(b'y'), - FatType::Fat32 => { - self.root_dir_first_cluster; - crate::fail(b'z'); - } - } - } -} - -pub enum FatType { - /// 12 bits per FAT entry - Fat12, - /// 16 bits per FAT entry - Fat16, - /// 32 bits per FAT entry - Fat32, -} - -impl FatType { - const FAT16_MIN_CLUSTERS: u32 = 4085; - const FAT32_MIN_CLUSTERS: u32 = 65525; - const FAT32_MAX_CLUSTERS: u32 = 0x0FFF_FFF4; - - pub(crate) fn from_clusters(total_clusters: u32) -> Self { - if total_clusters < Self::FAT16_MIN_CLUSTERS { - FatType::Fat12 - } else if total_clusters < Self::FAT32_MIN_CLUSTERS { - FatType::Fat16 - } else { - FatType::Fat32 - } - } -} diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 4da32aae..675357db 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -1,10 +1,8 @@ #![no_std] #![no_main] +#![warn(unsafe_op_in_unsafe_fn)] -use core::{ - arch::{asm, global_asm}, - slice, -}; +use core::{arch::global_asm, slice}; use error::NO_SECOND_STAGE_PARTITION; use fail::{fail, print_char, UnwrapOrFail}; @@ -13,19 +11,13 @@ global_asm!(include_str!("boot.s")); mod dap; mod error; mod fail; -mod fat; mod mbr; extern "C" { - static _mbr_start: u8; static _partition_table: u8; static _second_stage_start: u8; } -fn mbr_start() -> *const u8 { - unsafe { &_mbr_start } -} - unsafe fn partition_table() -> *const u8 { unsafe { &_partition_table } } @@ -37,16 +29,15 @@ fn second_stage_start() -> *const () { #[no_mangle] pub extern "C" fn first_stage(disk_number: u16) { + // read partition table and look for second stage partition print_char(b'1'); let partition_table = &unsafe { slice::from_raw_parts(partition_table(), 16 * 4) }; let second_stage_partition = mbr::boot_partition(partition_table).unwrap_or_fail(NO_SECOND_STAGE_PARTITION); + // load second stage partition into memory print_char(b'2'); let target_addr = u16::try_from(second_stage_start() as usize).unwrap_or_fail(b'a'); - - // load boot partition into buffer - // TODO: only load headers let dap = dap::DiskAddressPacket::from_lba( target_addr, second_stage_partition.logical_block_address.into(), @@ -62,73 +53,14 @@ pub extern "C" fn first_stage(disk_number: u16) { fail(b'c'); } - print_char(b'3'); - // jump to second stage + print_char(b'3'); let second_stage_entry_point: extern "C" fn(disk_number: u16) = unsafe { core::mem::transmute(target_addr as *const ()) }; - unsafe { second_stage_entry_point(disk_number) } - - print_char(b'R'); - print_char(b'R'); - print_char(b'R'); - print_char(b'R'); - print_char(b'R'); - loop {} - - // try to parse FAT file system - let fat_slice = unsafe { - slice::from_raw_parts( - target_addr as *const u8, - usize::try_from(second_stage_partition.sector_count).unwrap_or_else(|_| fail(b'a')) - * 512, - ) - }; - - print_char(b'4'); - let boot_sector = fat::BootSector::deserialize(fat_slice); - let root_dir = boot_sector.bpb.root_dir_first_cluster; - boot_sector.bpb.check_root_dir(); - - print_char(b'5'); - - // TODO: get root dir - - // TODO: get offset of `second_stage` file - - // TODO: get offset of `kernel-x86_64` file - - // TODO: load `second_stage` file into memory - - // TODO: jump to `second_stage`, pass offset of `kernel-x86_64` and disk number as arguments + second_stage_entry_point(disk_number); + for _ in 0..10 { + print_char(b'R'); + } loop {} } - -fn load_second_stage( - second_stage_start: u32, - second_stage_end: u32, - bootloader_start: u32, - disk_number: u16, -) { - use dap::DiskAddressPacket; - - let file_offset = (second_stage_start - bootloader_start) as u64; - let size = (second_stage_end - second_stage_start) as u32; - - let dap = DiskAddressPacket::new(second_stage_start as u16, file_offset, size); - unsafe { dap.perform_load(disk_number) } -} - -/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 -/// -/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, -/// see https://github.com/rust-lang/rust/issues/90091 -fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { - if N > slice.len() { - fail(b'S'); - } - let (a, b) = slice.split_at(N); - // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) - unsafe { (&*(a.as_ptr() as *const [T; N]), b) } -} diff --git a/bios/boot_sector/src/mbr.rs b/bios/boot_sector/src/mbr.rs index f7b00d6d..dfe6daa7 100644 --- a/bios/boot_sector/src/mbr.rs +++ b/bios/boot_sector/src/mbr.rs @@ -1,12 +1,10 @@ -// Based on https://docs.rs/mbr-nostd - use super::fail::{fail, UnwrapOrFail}; /// We use this partition type to store the second bootloader stage; const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; /// Returns the first bootable partition in the partition table. -pub fn boot_partition(partitions_raw: &[u8]) -> Option { +pub(crate) fn boot_partition(partitions_raw: &[u8]) -> Option { for index in 0..4 { let entry = get_partition(partitions_raw, index); if entry.partition_type == BOOTLOADER_SECOND_STAGE_PARTITION_TYPE { @@ -16,7 +14,10 @@ pub fn boot_partition(partitions_raw: &[u8]) -> Option { None } -pub fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTableEntry { +pub(crate) fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTableEntry { + const PARTITIONS_AREA_SIZE: usize = 16 * 4; + const ENTRY_SIZE: usize = 16; + if partitions_raw.len() < PARTITIONS_AREA_SIZE { fail(b'a'); } @@ -46,65 +47,22 @@ pub fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTableEntry PartitionTableEntry::new(bootable, partition_type, lba, len) } -const PARTITIONS_AREA_SIZE: usize = 16 * 4; -const ENTRY_SIZE: usize = 16; - -/// The type of a particular partition. -#[derive(Copy, Clone, Eq, PartialEq)] -pub enum PartitionType { - BootloaderSecondStage, - Unused, - Unknown(u8), - Fat12(u8), - Fat16(u8), - Fat32(u8), - LinuxExt(u8), - HfsPlus(u8), - ISO9660(u8), - NtfsExfat(u8), -} - -impl PartitionType { - /// Parses a partition type from the type byte in the MBR's table. - pub fn from_mbr_tag_byte(tag: u8) -> PartitionType { - match tag { - // we use partition type 0x20 to store the second bootloader stage - 0x20 => PartitionType::BootloaderSecondStage, - 0x0 => PartitionType::Unused, - 0x01 => PartitionType::Fat12(tag), - 0x04 | 0x06 | 0x0e => PartitionType::Fat16(tag), - 0x0b | 0x0c | 0x1b | 0x1c => PartitionType::Fat32(tag), - 0x83 => PartitionType::LinuxExt(tag), - 0x07 => PartitionType::NtfsExfat(tag), - 0xaf => PartitionType::HfsPlus(tag), - _ => PartitionType::Unknown(tag), - } - } - - pub fn known_type(tag: u8) -> bool { - match tag { - 0x0 | 0x01 | 0x04 | 0x06 | 0x0e | 0x0b | 0x0c | 0x1b | 0x1c | 0x83 | 0x07 | 0xaf => { - true - } - _ => false, - } - } -} - /// An entry in a partition table. +/// +/// Based on https://docs.rs/mbr-nostd #[derive(Copy, Clone, Eq, PartialEq)] -pub struct PartitionTableEntry { +pub(crate) struct PartitionTableEntry { /// Whether this partition is a boot partition. - pub bootable: bool, + pub(crate) bootable: bool, /// The type of partition in this entry. - pub partition_type: u8, + pub(crate) partition_type: u8, /// The index of the first block of this entry. - pub logical_block_address: u32, + pub(crate) logical_block_address: u32, /// The total number of blocks in this entry. - pub sector_count: u32, + pub(crate) sector_count: u32, } impl PartitionTableEntry { From 6a014df2133f74de9e222e4c3942bef65e2cdbfe Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Sun, 24 Apr 2022 18:06:58 +0200 Subject: [PATCH 100/226] update uefi --- Cargo.lock | 34 +++++++++++++++++----------------- uefi/Cargo.toml | 2 +- uefi/src/main.rs | 42 ++++++++++++++++++++---------------------- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42783548..57ccfb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -254,9 +254,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.16" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ "cfg-if", ] @@ -312,18 +312,18 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.37" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec757218438d5fda206afc041538b2f6d889286160d649a86a24d37e1235afd1" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632d02bff7f874a36f33ea8bb416cd484b90cc66c1194b1a1110d067a7013f58" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" dependencies = [ "proc-macro2", ] @@ -443,13 +443,13 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.91" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b683b2b825c8eef438b77c36a06dc262294da3d5a5813fac20da149241dcd44d" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", ] [[package]] @@ -545,9 +545,9 @@ dependencies = [ [[package]] name = "uefi" -version = "0.13.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd7363ecc1a80d6a7467b322bfb16e95ac5e19f0b71ba2af3e6592f101820113" +checksum = "705535cf386e4b033cc7acdea55ec8710f3dde2f07457218791aac35c83be21f" dependencies = [ "bitflags", "log", @@ -557,9 +557,9 @@ dependencies = [ [[package]] name = "uefi-macros" -version = "0.5.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7006b85ae8acaf2b448c5f1630a434caaacaedcc0907f12404e4e31c9dafcdb3" +checksum = "0b9917831bc5abb78c2e6a0f4fba2be165105ed53d288718c999e0efbd433bb7" dependencies = [ "proc-macro2", "quote", @@ -567,10 +567,10 @@ dependencies = [ ] [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "unicode-ident" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "usize_conversions" diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 362f1083..109b9298 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -11,5 +11,5 @@ license = "MIT/Apache-2.0" bootloader_api = { version = "0.1.0-alpha.0", path = "../api" } bootloader-x86_64-common = { version = "0.1.0-alpha.0", path = "../common" } log = "0.4.14" -uefi = "0.13.0" +uefi = "0.16.0" x86_64 = "0.14.8" diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 8df0688e..e3c25bad 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -19,7 +19,7 @@ use uefi::{ }, }, table::boot::{AllocateType, MemoryDescriptor, MemoryType}, - Completion, + CStr16, }; use x86_64::{ structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, @@ -59,14 +59,14 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } - st.stdout().clear().unwrap().unwrap(); + st.stdout().clear().unwrap(); writeln!( st.stdout(), "UEFI bootloader started; trying to load kernel" ) .unwrap(); - let kernel = load_kernel(image, &st); + let kernel = load_kernel(image, &mut st); let (framebuffer_addr, framebuffer_info) = init_logger(&st, kernel.config); @@ -81,18 +81,17 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let mmap_storage = { let max_mmap_size = - st.boot_services().memory_map_size() + 8 * mem::size_of::(); + st.boot_services().memory_map_size().map_size + 8 * mem::size_of::(); let ptr = st .boot_services() - .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)? - .log(); + .allocate_pool(MemoryType::LOADER_DATA, max_mmap_size)?; unsafe { slice::from_raw_parts_mut(ptr, max_mmap_size) } }; log::trace!("exiting boot services"); let (system_table, memory_map) = st .exit_boot_services(image, mmap_storage) - .expect_success("Failed to exit boot services"); + .expect("Failed to exit boot services"); let mut frame_allocator = LegacyFrameAllocator::new(memory_map.copied().map(UefiMemoryDescriptor)); @@ -127,38 +126,38 @@ fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { let ref this = st.boot_services(); let loaded_image = this .handle_protocol::(image) - .expect_success("Failed to retrieve `LoadedImage` protocol from handle"); + .expect("Failed to retrieve `LoadedImage` protocol from handle"); let loaded_image = unsafe { &*loaded_image.get() }; let device_handle = loaded_image.device(); let device_path = this .handle_protocol::(device_handle) - .expect_success("Failed to retrieve `DevicePath` protocol from image's device handle"); + .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); let mut device_path = unsafe { &*device_path.get() }; let device_handle = this .locate_device_path::(&mut device_path) - .expect_success("Failed to locate `SimpleFileSystem` protocol on device path"); + .expect("Failed to locate `SimpleFileSystem` protocol on device path"); this.handle_protocol::(device_handle) } - .unwrap() .unwrap(); let file_system = unsafe { &mut *file_system_raw.get() }; - let mut root = file_system.open_volume().unwrap().unwrap(); + let mut root = file_system.open_volume().unwrap(); + let mut buf = [0; 14 * 2]; + let filename = CStr16::from_str_with_buf("kernel-x86_64", &mut buf).unwrap(); let kernel_file_handle = root - .open("kernel-x86_64", FileMode::Read, FileAttribute::empty()) - .expect("Failed to load kernel (expected file named `kernel-x86_64`)") - .unwrap(); - let mut kernel_file = match kernel_file_handle.into_type().unwrap().unwrap() { + .open(filename, FileMode::Read, FileAttribute::empty()) + .expect("Failed to load kernel (expected file named `kernel-x86_64`)"); + let mut kernel_file = match kernel_file_handle.into_type().unwrap() { uefi::proto::media::file::FileType::Regular(f) => f, uefi::proto::media::file::FileType::Dir(_) => panic!(), }; let mut buf = [0; 500]; - let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap().unwrap(); + let kernel_info: &mut FileInfo = kernel_file.get_info(&mut buf).unwrap(); let kernel_size = usize::try_from(kernel_info.file_size()).unwrap(); let kernel_ptr = st @@ -168,11 +167,10 @@ fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { MemoryType::LOADER_DATA, ((kernel_size - 1) / 4096) + 1, ) - .unwrap() .unwrap() as *mut u8; unsafe { ptr::write_bytes(kernel_ptr, 0, kernel_size) }; let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; - kernel_file.read(kernel_slice).unwrap().unwrap(); + kernel_file.read(kernel_slice).unwrap(); Kernel::parse(kernel_slice) } @@ -247,11 +245,11 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F let gop = st .boot_services() .locate_protocol::() - .expect_success("failed to locate gop"); + .expect("failed to locate gop"); let gop = unsafe { &mut *gop.get() }; let mode = { - let modes = gop.modes().map(Completion::unwrap); + let modes = gop.modes(); match ( config .frame_buffer @@ -275,7 +273,7 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F }; if let Some(mode) = mode { gop.set_mode(&mode) - .expect_success("Failed to apply the desired display mode"); + .expect("Failed to apply the desired display mode"); } let mode_info = gop.current_mode_info(); From 92dd42db7b1eb38272fa495becc7455d43f4aaaf Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Tue, 10 May 2022 13:07:09 +0200 Subject: [PATCH 101/226] implement UEFI PXE --- src/lib.rs | 10 ++++++ src/pxe.rs | 32 +++++++++++++++++ tests/runner/src/lib.rs | 45 ++++++++++++++++++++++-- uefi/src/main.rs | 77 +++++++++++++++++++++++++++++++++++++++-- 4 files changed, 159 insertions(+), 5 deletions(-) create mode 100644 src/pxe.rs diff --git a/src/lib.rs b/src/lib.rs index a6e4418e..60c7a841 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -78,6 +78,7 @@ use std::{ mod fat; mod gpt; mod mbr; +mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; @@ -121,3 +122,12 @@ pub fn create_bios_disk_image( Ok(()) } + +pub fn create_uefi_pxe_tftp_folder(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + + pxe::create_uefi_tftp_folder(bootloader_path, kernel_binary, out_path) + .context("failed to create UEFI PXE tftp folder")?; + + Ok(()) +} diff --git a/src/pxe.rs b/src/pxe.rs new file mode 100644 index 00000000..9329cec2 --- /dev/null +++ b/src/pxe.rs @@ -0,0 +1,32 @@ +use std::path::Path; + +use anyhow::Context; + +pub fn create_uefi_tftp_folder( + bootloader_path: &Path, + kernel_binary: &Path, + out_path: &Path, +) -> anyhow::Result<()> { + std::fs::create_dir_all(out_path) + .with_context(|| format!("failed to create out dir at {}", out_path.display()))?; + + let to = out_path.join("bootloader"); + std::fs::copy(bootloader_path, &to).with_context(|| { + format!( + "failed to copy bootloader from {} to {}", + bootloader_path.display(), + to.display() + ) + })?; + + let to = out_path.join("kernel-x86_64"); + std::fs::copy(kernel_binary, &to).with_context(|| { + format!( + "failed to copy kernel from {} to {}", + kernel_binary.display(), + to.display() + ) + })?; + + Ok(()) +} diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index a7292a59..94015731 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -11,6 +11,12 @@ const QEMU_ARGS: &[&str] = &[ ]; pub fn run_test_kernel(kernel_binary_path: &str) { + run_test_kernel_on_uefi(kernel_binary_path); + run_test_kernel_on_uefi_pxe(kernel_binary_path); + // TODO: run tests with BIOS bootloader too +} + +pub fn run_test_kernel_on_uefi(kernel_binary_path: &str) { let kernel_path = Path::new(kernel_binary_path); let out_fat_path = kernel_path.with_extension("fat"); bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap(); @@ -19,8 +25,6 @@ pub fn run_test_kernel(kernel_binary_path: &str) { let out_mbr_path = kernel_path.with_extension("mbr"); bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap(); - // TODO: run tests with BIOS bootloader too - let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd .arg("-drive") @@ -42,3 +46,40 @@ pub fn run_test_kernel(kernel_binary_path: &str) { other => panic!("Test failed with unexpected exit code `{:?}`", other), } } + +pub fn run_test_kernel_on_uefi_pxe(kernel_binary_path: &str) { + let kernel_path = Path::new(kernel_binary_path); + let out_tftp_path = kernel_path.with_extension(".tftp"); + + bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap(); + + let out_fat_path = kernel_path.with_extension("fat"); + bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap(); + let out_gpt_path = kernel_path.with_extension("gpt"); + bootloader::create_uefi_disk_image(&out_fat_path, &out_gpt_path).unwrap(); + let out_mbr_path = kernel_path.with_extension("mbr"); + bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap(); + + let mut run_cmd = Command::new("qemu-system-x86_64"); + run_cmd.arg("-netdev").arg(format!( + "user,id=net0,net=192.168.17.0/24,tftp={},bootfile=bootloader,id=net0", + out_tftp_path.display() + )); + run_cmd.arg("-device").arg("virtio-net-pci,netdev=net0"); + run_cmd.args(QEMU_ARGS); + run_cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); + + let child_output = run_cmd.output().unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stderr) + .unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stdout) + .unwrap(); + + match child_output.status.code() { + Some(33) => {} // success + Some(35) => panic!("Test failed"), // success + other => panic!("Test failed with unexpected exit code `{:?}`", other), + } +} diff --git a/uefi/src/main.rs b/uefi/src/main.rs index e3c25bad..36974171 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -17,9 +17,13 @@ use uefi::{ file::{File, FileAttribute, FileInfo, FileMode}, fs::SimpleFileSystem, }, + network::{ + pxe::{BaseCode, DhcpV4Packet}, + IpAddress, + }, }, table::boot::{AllocateType, MemoryDescriptor, MemoryType}, - CStr16, + CStr16, CStr8, }; use x86_64::{ structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, @@ -122,6 +126,16 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { } fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { + let kernel_slice = load_kernel_file(image, st).expect("couldn't find kernel"); + Kernel::parse(kernel_slice) +} + +fn load_kernel_file(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { + load_kernel_file_from_disk(image, st) + .or_else(|| load_kernel_file_from_tftp_boot_server(image, st)) +} + +fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { let file_system_raw = { let ref this = st.boot_services(); let loaded_image = this @@ -138,7 +152,7 @@ fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { let device_handle = this .locate_device_path::(&mut device_path) - .expect("Failed to locate `SimpleFileSystem` protocol on device path"); + .ok()?; this.handle_protocol::(device_handle) } @@ -172,7 +186,64 @@ fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; kernel_file.read(kernel_slice).unwrap(); - Kernel::parse(kernel_slice) + Some(kernel_slice) +} + +fn load_kernel_file_from_tftp_boot_server( + image: Handle, + st: &SystemTable, +) -> Option<&'static mut [u8]> { + let ref this = st.boot_services(); + + let file_system_raw = { + let ref this = st.boot_services(); + let loaded_image = this + .handle_protocol::(image) + .expect("Failed to retrieve `LoadedImage` protocol from handle"); + let loaded_image = unsafe { &*loaded_image.get() }; + + let device_handle = loaded_image.device(); + + let device_path = this + .handle_protocol::(device_handle) + .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); + let mut device_path = unsafe { &*device_path.get() }; + + let device_handle = this + .locate_device_path::(&mut device_path) + .expect("Failed to locate `BaseCode` protocol on device path"); + + this.handle_protocol::(device_handle) + } + .unwrap(); + let base_code = unsafe { &mut *file_system_raw.get() }; + + let mode = base_code.mode(); + assert!(mode.dhcp_ack_received); + let dhcpv4: &DhcpV4Packet = mode.dhcp_ack.as_ref(); + let server_ip = IpAddress::new_v4(dhcpv4.bootp_si_addr); + + let filename = CStr8::from_bytes_with_nul(b"kernel-x86_64\0").unwrap(); + let file_size = base_code.tftp_get_file_size(&server_ip, filename).unwrap(); + + let kernel_size = usize::try_from(file_size).unwrap(); + + let kernel_ptr = st + .boot_services() + .allocate_pages( + AllocateType::AnyPages, + MemoryType::LOADER_DATA, + ((kernel_size - 1) / 4096) + 1, + ) + .unwrap() as *mut u8; + unsafe { ptr::write_bytes(kernel_ptr, 0, kernel_size) }; + let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; + + base_code + .tftp_read_file(&server_ip, filename, Some(kernel_slice)) + .unwrap(); + + Some(kernel_slice) } /// Creates page table abstraction types for both the bootloader and kernel page tables. From f975de96c65d2c850edeae3e8b852f2cf78c14a6 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Thu, 26 May 2022 16:45:02 +0200 Subject: [PATCH 102/226] add some comments --- src/lib.rs | 2 ++ uefi/src/main.rs | 52 ++++++++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 60c7a841..9a9463fd 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,6 +123,8 @@ pub fn create_bios_disk_image( Ok(()) } +/// Prepare a folder for use with booting over UEFI_PXE. The dhcp server should +/// have the filename option set to `bootloader`. pub fn create_uefi_pxe_tftp_folder(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 36974171..2a10a2db 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -130,6 +130,7 @@ fn load_kernel(image: Handle, st: &SystemTable) -> Kernel<'static> { Kernel::parse(kernel_slice) } +/// Try to load a kernel file from the boot device. fn load_kernel_file(image: Handle, st: &SystemTable) -> Option<&'static mut [u8]> { load_kernel_file_from_disk(image, st) .or_else(|| load_kernel_file_from_tftp_boot_server(image, st)) @@ -189,45 +190,48 @@ fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<& Some(kernel_slice) } +/// Try to load a kernel from a TFTP boot server. fn load_kernel_file_from_tftp_boot_server( image: Handle, st: &SystemTable, ) -> Option<&'static mut [u8]> { - let ref this = st.boot_services(); + let this = st.boot_services(); - let file_system_raw = { - let ref this = st.boot_services(); - let loaded_image = this - .handle_protocol::(image) - .expect("Failed to retrieve `LoadedImage` protocol from handle"); - let loaded_image = unsafe { &*loaded_image.get() }; + // Try to locate a `BaseCode` protocol on the boot device. - let device_handle = loaded_image.device(); + let loaded_image = this + .handle_protocol::(image) + .expect("Failed to retrieve `LoadedImage` protocol from handle"); + let loaded_image = unsafe { &*loaded_image.get() }; - let device_path = this - .handle_protocol::(device_handle) - .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); - let mut device_path = unsafe { &*device_path.get() }; + let device_handle = loaded_image.device(); - let device_handle = this - .locate_device_path::(&mut device_path) - .expect("Failed to locate `BaseCode` protocol on device path"); + let device_path = this + .handle_protocol::(device_handle) + .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); + let mut device_path = unsafe { &*device_path.get() }; - this.handle_protocol::(device_handle) - } - .unwrap(); - let base_code = unsafe { &mut *file_system_raw.get() }; + let device_handle = this.locate_device_path::(&mut device_path).ok()?; + + let base_code_raw = this.handle_protocol::(device_handle).unwrap(); + let base_code = unsafe { &mut *base_code_raw.get() }; + // Find the TFTP boot server. let mode = base_code.mode(); assert!(mode.dhcp_ack_received); let dhcpv4: &DhcpV4Packet = mode.dhcp_ack.as_ref(); let server_ip = IpAddress::new_v4(dhcpv4.bootp_si_addr); let filename = CStr8::from_bytes_with_nul(b"kernel-x86_64\0").unwrap(); - let file_size = base_code.tftp_get_file_size(&server_ip, filename).unwrap(); - let kernel_size = usize::try_from(file_size).unwrap(); + // Determine the kernel file size. + let file_size = base_code + .tftp_get_file_size(&server_ip, filename) + .expect("Failed to query the kernel file size"); + let kernel_size = + usize::try_from(file_size).expect("The kernel file size should fit into usize"); + // Allocate some memory for the kernel file. let kernel_ptr = st .boot_services() .allocate_pages( @@ -235,13 +239,13 @@ fn load_kernel_file_from_tftp_boot_server( MemoryType::LOADER_DATA, ((kernel_size - 1) / 4096) + 1, ) - .unwrap() as *mut u8; - unsafe { ptr::write_bytes(kernel_ptr, 0, kernel_size) }; + .expect("Failed to allocate memory for the kernel file") as *mut u8; let kernel_slice = unsafe { slice::from_raw_parts_mut(kernel_ptr, kernel_size) }; + // Load the kernel file. base_code .tftp_read_file(&server_ip, filename, Some(kernel_slice)) - .unwrap(); + .expect("Failed to read kernel file from the TFTP boot server"); Some(kernel_slice) } From bd2a59dfd57edced92b1fc230f8bfccffe70a4d2 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Fri, 27 May 2022 13:01:41 +0200 Subject: [PATCH 103/226] fix copy-paste error Co-authored-by: Philipp Oppermann --- tests/runner/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 94015731..f38ec42e 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -78,8 +78,8 @@ pub fn run_test_kernel_on_uefi_pxe(kernel_binary_path: &str) { .unwrap(); match child_output.status.code() { - Some(33) => {} // success - Some(35) => panic!("Test failed"), // success + Some(33) => {} // success + Some(35) => panic!("Test failed"), other => panic!("Test failed with unexpected exit code `{:?}`", other), } } From 4f6f6bb1772ee782cb19389e0ef2b74353036b8a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 6 Jun 2022 15:57:44 +0200 Subject: [PATCH 104/226] Minor docs improvements --- src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 9a9463fd..86e1ecf4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,8 +123,11 @@ pub fn create_bios_disk_image( Ok(()) } -/// Prepare a folder for use with booting over UEFI_PXE. The dhcp server should -/// have the filename option set to `bootloader`. +/// Prepare a folder for use with booting over UEFI_PXE. +/// +/// This places the bootloader executable under the path "bootloader". The +/// DHCP server should set the filename option to that path, otherwise the +/// bootloader won't be found. pub fn create_uefi_pxe_tftp_folder(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); From ca31f969c91ece05e17499e0caf839a7e2b00253 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 6 Jun 2022 15:58:03 +0200 Subject: [PATCH 105/226] Remove dead code in PXE boot test runner --- tests/runner/src/lib.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index f38ec42e..c6096bb2 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -53,13 +53,6 @@ pub fn run_test_kernel_on_uefi_pxe(kernel_binary_path: &str) { bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap(); - let out_fat_path = kernel_path.with_extension("fat"); - bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap(); - let out_gpt_path = kernel_path.with_extension("gpt"); - bootloader::create_uefi_disk_image(&out_fat_path, &out_gpt_path).unwrap(); - let out_mbr_path = kernel_path.with_extension("mbr"); - bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap(); - let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd.arg("-netdev").arg(format!( "user,id=net0,net=192.168.17.0/24,tftp={},bootfile=bootloader,id=net0", From 13c4d5eb8bae9a873d21e5a1d6508c3e7bda5785 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 6 Jun 2022 16:40:43 +0200 Subject: [PATCH 106/226] Replace deprecated `handle_protocol` calls with `open_protocol` --- uefi/src/main.rs | 82 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 64 insertions(+), 18 deletions(-) diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 2a10a2db..094df8af 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -8,7 +8,7 @@ use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; use bootloader_x86_64_common::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; use core::{arch::asm, cell::UnsafeCell, fmt::Write, mem, panic::PanicInfo, ptr, slice}; use uefi::{ - prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, + prelude::{entry, Boot, Handle, Status, SystemTable}, proto::{ console::gop::{GraphicsOutput, PixelFormat}, device_path::DevicePath, @@ -22,7 +22,9 @@ use uefi::{ IpAddress, }, }, - table::boot::{AllocateType, MemoryDescriptor, MemoryType}, + table::boot::{ + AllocateType, MemoryDescriptor, MemoryType, OpenProtocolAttributes, OpenProtocolParams, + }, CStr16, CStr8, }; use x86_64::{ @@ -140,25 +142,46 @@ fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<& let file_system_raw = { let ref this = st.boot_services(); let loaded_image = this - .handle_protocol::(image) + .open_protocol::( + OpenProtocolParams { + handle: image, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) .expect("Failed to retrieve `LoadedImage` protocol from handle"); - let loaded_image = unsafe { &*loaded_image.get() }; + let loaded_image = unsafe { &*loaded_image.interface.get() }; let device_handle = loaded_image.device(); let device_path = this - .handle_protocol::(device_handle) + .open_protocol::( + OpenProtocolParams { + handle: device_handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); - let mut device_path = unsafe { &*device_path.get() }; + let mut device_path = unsafe { &*device_path.interface.get() }; - let device_handle = this + let fs_handle = this .locate_device_path::(&mut device_path) .ok()?; - this.handle_protocol::(device_handle) + this.open_protocol::( + OpenProtocolParams { + handle: fs_handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) } .unwrap(); - let file_system = unsafe { &mut *file_system_raw.get() }; + let file_system = unsafe { &mut *file_system_raw.interface.get() }; let mut root = file_system.open_volume().unwrap(); let mut buf = [0; 14 * 2]; @@ -200,21 +223,44 @@ fn load_kernel_file_from_tftp_boot_server( // Try to locate a `BaseCode` protocol on the boot device. let loaded_image = this - .handle_protocol::(image) + .open_protocol::( + OpenProtocolParams { + handle: image, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) .expect("Failed to retrieve `LoadedImage` protocol from handle"); - let loaded_image = unsafe { &*loaded_image.get() }; + let loaded_image = unsafe { &*loaded_image.interface.get() }; let device_handle = loaded_image.device(); let device_path = this - .handle_protocol::(device_handle) + .open_protocol::( + OpenProtocolParams { + handle: device_handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) .expect("Failed to retrieve `DevicePath` protocol from image's device handle"); - let mut device_path = unsafe { &*device_path.get() }; - - let device_handle = this.locate_device_path::(&mut device_path).ok()?; - - let base_code_raw = this.handle_protocol::(device_handle).unwrap(); - let base_code = unsafe { &mut *base_code_raw.get() }; + let mut device_path = unsafe { &*device_path.interface.get() }; + + let base_code_handle = this.locate_device_path::(&mut device_path).ok()?; + + let base_code_raw = this + .open_protocol::( + OpenProtocolParams { + handle: base_code_handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + .unwrap(); + let base_code = unsafe { &mut *base_code_raw.interface.get() }; // Find the TFTP boot server. let mode = base_code.mode(); From 3b6aa00dc33005f4353568c5dccad694d0f54f90 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 11 Jun 2022 12:53:11 +0200 Subject: [PATCH 107/226] WIP: try to start loading FAT partition in second stage --- Cargo.lock | 25 +- bios/boot_sector/src/main.rs | 18 +- bios/second_stage/Cargo.toml | 3 + bios/second_stage/build.rs | 9 + bios/second_stage/second-stage-link.ld | 19 + bios/second_stage/src/dap.rs | 1 + bios/second_stage/src/disk.rs | 68 ++ bios/second_stage/src/fail.rs | 59 -- bios/second_stage/src/fat.rs | 9 +- bios/second_stage/src/fat_bpb.rs | 885 +++++++++++++++++++++++++ bios/second_stage/src/main.rs | 122 +++- bios/second_stage/src/mbr.rs | 124 ---- bios/second_stage/src/mini_fat.rs | 370 +++++++++++ bios/second_stage/src/screen.rs | 45 ++ build.rs | 7 + tests/runner/src/lib.rs | 56 +- x86-16bit-second-stage.json | 2 +- 17 files changed, 1587 insertions(+), 235 deletions(-) create mode 100644 bios/second_stage/build.rs create mode 100644 bios/second_stage/second-stage-link.ld create mode 100644 bios/second_stage/src/disk.rs delete mode 100644 bios/second_stage/src/fail.rs create mode 100644 bios/second_stage/src/fat_bpb.rs delete mode 100644 bios/second_stage/src/mbr.rs create mode 100644 bios/second_stage/src/mini_fat.rs create mode 100644 bios/second_stage/src/screen.rs diff --git a/Cargo.lock b/Cargo.lock index 57ccfb91..4ab9d637 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,7 +60,7 @@ dependencies = [ "anyhow", "bootloader-x86_64-bios", "bootloader_test_runner", - "fatfs", + "fatfs 0.3.5", "gpt", "llvm-tools", "mbrman", @@ -89,6 +89,11 @@ version = "0.1.0" [[package]] name = "bootloader-x86_64-bios-second-stage" version = "0.1.0" +dependencies = [ + "byteorder", + "fatfs 0.4.0", + "mbr-nostd", +] [[package]] name = "bootloader-x86_64-common" @@ -201,6 +206,15 @@ dependencies = [ "log", ] +[[package]] +name = "fatfs" +version = "0.4.0" +source = "git+https://github.com/rafalh/rust-fatfs.git#4892fb1791b29616d8573235eb83644003d13791" +dependencies = [ + "bitflags", + "log", +] + [[package]] name = "funty" version = "1.2.0" @@ -261,6 +275,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mbr-nostd" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e7b12e539fe14d6423dc8864bd5b5fc1f04fd14f5d9152a137de285e4329f7" +dependencies = [ + "byteorder", +] + [[package]] name = "mbrman" version = "0.4.2" diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 675357db..8570c75a 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -14,11 +14,16 @@ mod fail; mod mbr; extern "C" { + static _mbr_start: u8; static _partition_table: u8; static _second_stage_start: u8; } -unsafe fn partition_table() -> *const u8 { +unsafe fn mbr_start() -> *const u8 { + unsafe { &_mbr_start } +} + +unsafe fn partition_table_raw() -> *const u8 { unsafe { &_partition_table } } @@ -31,7 +36,7 @@ fn second_stage_start() -> *const () { pub extern "C" fn first_stage(disk_number: u16) { // read partition table and look for second stage partition print_char(b'1'); - let partition_table = &unsafe { slice::from_raw_parts(partition_table(), 16 * 4) }; + let partition_table = &unsafe { slice::from_raw_parts(partition_table_raw(), 16 * 4) }; let second_stage_partition = mbr::boot_partition(partition_table).unwrap_or_fail(NO_SECOND_STAGE_PARTITION); @@ -55,9 +60,12 @@ pub extern "C" fn first_stage(disk_number: u16) { // jump to second stage print_char(b'3'); - let second_stage_entry_point: extern "C" fn(disk_number: u16) = - unsafe { core::mem::transmute(target_addr as *const ()) }; - second_stage_entry_point(disk_number); + let second_stage_entry_point: extern "C" fn( + disk_number: u16, + partition_table_start: *const u8, + ) = unsafe { core::mem::transmute(target_addr as *const ()) }; + let mbr_start = unsafe { partition_table_raw() }; + second_stage_entry_point(disk_number, mbr_start); for _ in 0..10 { print_char(b'R'); } diff --git a/bios/second_stage/Cargo.toml b/bios/second_stage/Cargo.toml index 64852738..ad487e67 100644 --- a/bios/second_stage/Cargo.toml +++ b/bios/second_stage/Cargo.toml @@ -7,3 +7,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +fatfs = { git = "https://github.com/rafalh/rust-fatfs.git", default-features = false } +mbr-nostd = "0.1.0" +byteorder = { version = "1.4.3", default-features = false } diff --git a/bios/second_stage/build.rs b/bios/second_stage/build.rs new file mode 100644 index 00000000..945a419d --- /dev/null +++ b/bios/second_stage/build.rs @@ -0,0 +1,9 @@ +use std::path::Path; + +fn main() { + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); + println!( + "cargo:rustc-link-arg-bins=--script={}", + local_path.join("second-stage-link.ld").display() + ) +} diff --git a/bios/second_stage/second-stage-link.ld b/bios/second_stage/second-stage-link.ld new file mode 100644 index 00000000..7ecbc7f2 --- /dev/null +++ b/bios/second_stage/second-stage-link.ld @@ -0,0 +1,19 @@ +ENTRY(_start) + +SECTIONS { + . = 0x7c00 + 512; + + .start : { + *(.start) + } + .rodata : { + *(.rodata .rodata.*) + } + .text : { + *(.text .text.*) + } + + . = ALIGN(512); + + _second_stage_end = .; +} diff --git a/bios/second_stage/src/dap.rs b/bios/second_stage/src/dap.rs index 35ce6fe6..ec39015e 100644 --- a/bios/second_stage/src/dap.rs +++ b/bios/second_stage/src/dap.rs @@ -1,5 +1,6 @@ use core::arch::{asm, global_asm}; +#[derive(Debug, Clone, Copy)] #[repr(packed)] pub struct DiskAddressPacket { /// Size of the DAP structure diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs new file mode 100644 index 00000000..a9724286 --- /dev/null +++ b/bios/second_stage/src/disk.rs @@ -0,0 +1,68 @@ +use crate::{dap, screen, second_stage_end}; +use core::{fmt::Write as _, slice}; + +pub struct DiskAccess { + pub disk_number: u16, + pub base_offset: u64, + pub current_offset: u64, +} + +impl fatfs::IoBase for DiskAccess { + type Error = (); +} + +impl fatfs::Read for DiskAccess { + fn read(&mut self, buf: &mut [u8]) -> Result { + writeln!(screen::Writer, "read {} bytes", buf.len()).unwrap(); + + let end_addr = self.base_offset + self.current_offset + u64::try_from(buf.len()).unwrap(); + let start_lba = (self.base_offset + self.current_offset) / 512; + let end_lba = (end_addr - 1) / 512; + + let target_addr = u16::try_from(second_stage_end() as usize).unwrap(); + let dap = dap::DiskAddressPacket::from_lba( + target_addr, + start_lba, + u16::try_from(end_lba + 1 - start_lba).unwrap(), + ); + writeln!(screen::Writer, "dap: {dap:?}").unwrap(); + unsafe { + dap.perform_load(self.disk_number); + } + + let data = unsafe { slice::from_raw_parts(target_addr as *const u8, buf.len()) }; + buf.copy_from_slice(data); + + self.current_offset = end_addr; + Ok(buf.len()) + } +} + +impl fatfs::Seek for DiskAccess { + fn seek(&mut self, pos: fatfs::SeekFrom) -> Result { + writeln!(screen::Writer, "seek to {pos:?}").unwrap(); + match pos { + fatfs::SeekFrom::Start(offset) => { + self.current_offset = offset; + Ok(self.current_offset) + } + fatfs::SeekFrom::Current(offset) => { + self.current_offset = (i64::try_from(self.current_offset).unwrap() + offset) + .try_into() + .unwrap(); + Ok(self.current_offset) + } + fatfs::SeekFrom::End(_) => Err(()), + } + } +} + +impl fatfs::Write for DiskAccess { + fn write(&mut self, buf: &[u8]) -> Result { + unimplemented!() + } + + fn flush(&mut self) -> Result<(), Self::Error> { + unimplemented!() + } +} diff --git a/bios/second_stage/src/fail.rs b/bios/second_stage/src/fail.rs deleted file mode 100644 index 17fc1ee0..00000000 --- a/bios/second_stage/src/fail.rs +++ /dev/null @@ -1,59 +0,0 @@ -use core::arch::asm; - -pub trait UnwrapOrFail { - type Out; - - fn unwrap_or_fail(self, code: u8) -> Self::Out; -} - -impl UnwrapOrFail for Option { - type Out = T; - - fn unwrap_or_fail(self, code: u8) -> Self::Out { - match self { - Some(v) => v, - None => fail(code), - } - } -} - -impl UnwrapOrFail for Result { - type Out = T; - - fn unwrap_or_fail(self, code: u8) -> Self::Out { - match self { - Ok(v) => v, - Err(_) => fail(code), - } - } -} - -#[no_mangle] -pub extern "C" fn print_char(c: u8) { - let ax = u16::from(c) | 0x0e00; - unsafe { - asm!("int 0x10", in("ax") ax, in("bx") 0); - } -} - -#[cold] -#[inline(never)] -#[no_mangle] -pub extern "C" fn fail(code: u8) -> ! { - print_char(b'!'); - print_char(code); - loop { - hlt() - } -} - -fn hlt() { - unsafe { - asm!("hlt"); - } -} - -#[panic_handler] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - fail(b'P'); -} diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index b96388a7..a8701162 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -204,16 +204,11 @@ impl BiosParameterBlock { /// Returns a root directory object allowing for futher penetration of a filesystem structure. pub fn check_root_dir(&self) { - match self.fat_type() { - FatType::Fat12 | FatType::Fat16 => crate::fail(b'y'), - FatType::Fat32 => { - self.root_dir_first_cluster; - crate::fail(b'z'); - } - } + panic!("check_root_dir: fat type {:?}", self.fat_type()) } } +#[derive(Debug)] pub enum FatType { /// 12 bits per FAT entry Fat12, diff --git a/bios/second_stage/src/fat_bpb.rs b/bios/second_stage/src/fat_bpb.rs new file mode 100644 index 00000000..572e2f48 --- /dev/null +++ b/bios/second_stage/src/fat_bpb.rs @@ -0,0 +1,885 @@ +// based on https://github.com/rafalh/rust-fatfs/ + +use core::cmp; +use core::u16; +use core::u8; + +use fatfs::{FatType, FormatVolumeOptions, FsStatusFlags}; +use fatfs::{Read, Write}; + +const RESERVED_FAT_ENTRIES: u32 = 2; +// Size of single directory entry in bytes +pub(crate) const DIR_ENTRY_SIZE: u32 = 32; + +const BITS_PER_BYTE: u32 = 8; +const KB_32: u32 = 1024; +const KB_64: u64 = 1024; +const MB_64: u64 = KB_64 * 1024; +const GB_64: u64 = MB_64 * 1024; + +#[derive(Default, Debug, Clone)] +pub(crate) struct BiosParameterBlock { + pub(crate) bytes_per_sector: u16, + pub(crate) sectors_per_cluster: u8, + pub(crate) reserved_sectors: u16, + pub(crate) fats: u8, + pub(crate) root_entries: u16, + pub(crate) total_sectors_16: u16, + pub(crate) media: u8, + pub(crate) sectors_per_fat_16: u16, + pub(crate) sectors_per_track: u16, + pub(crate) heads: u16, + pub(crate) hidden_sectors: u32, + pub(crate) total_sectors_32: u32, + + // Extended BIOS Parameter Block + pub(crate) sectors_per_fat_32: u32, + pub(crate) extended_flags: u16, + pub(crate) fs_version: u16, + pub(crate) root_dir_first_cluster: u32, + pub(crate) fs_info_sector: u16, + pub(crate) backup_boot_sector: u16, + pub(crate) reserved_0: [u8; 12], + pub(crate) drive_num: u8, + pub(crate) reserved_1: u8, + pub(crate) ext_sig: u8, + pub(crate) volume_id: u32, + pub(crate) volume_label: [u8; 11], + pub(crate) fs_type_label: [u8; 8], +} + +impl BiosParameterBlock { + fn deserialize(rdr: &mut R) -> Result { + let mut bpb = Self { + bytes_per_sector: rdr.read_u16_le()?, + sectors_per_cluster: rdr.read_u8()?, + reserved_sectors: rdr.read_u16_le()?, + fats: rdr.read_u8()?, + root_entries: rdr.read_u16_le()?, + total_sectors_16: rdr.read_u16_le()?, + media: rdr.read_u8()?, + sectors_per_fat_16: rdr.read_u16_le()?, + sectors_per_track: rdr.read_u16_le()?, + heads: rdr.read_u16_le()?, + hidden_sectors: rdr.read_u32_le()?, + total_sectors_32: rdr.read_u32_le()?, + ..Self::default() + }; + + if bpb.is_fat32() { + bpb.sectors_per_fat_32 = rdr.read_u32_le()?; + bpb.extended_flags = rdr.read_u16_le()?; + bpb.fs_version = rdr.read_u16_le()?; + bpb.root_dir_first_cluster = rdr.read_u32_le()?; + bpb.fs_info_sector = rdr.read_u16_le()?; + bpb.backup_boot_sector = rdr.read_u16_le()?; + rdr.read_exact(&mut bpb.reserved_0)?; + } + + bpb.drive_num = rdr.read_u8()?; + bpb.reserved_1 = rdr.read_u8()?; + bpb.ext_sig = rdr.read_u8()?; // 0x29 + bpb.volume_id = rdr.read_u32_le()?; + rdr.read_exact(&mut bpb.volume_label)?; + rdr.read_exact(&mut bpb.fs_type_label)?; + + // when the extended boot signature is anything other than 0x29, the fields are invalid + if bpb.ext_sig != 0x29 { + // fields after ext_sig are not used - clean them + bpb.volume_id = 0; + bpb.volume_label = [0; 11]; + bpb.fs_type_label = [0; 8]; + } + + Ok(bpb) + } + + fn serialize(&self, wrt: &mut W) -> Result<(), W::Error> { + wrt.write_u16_le(self.bytes_per_sector)?; + wrt.write_u8(self.sectors_per_cluster)?; + wrt.write_u16_le(self.reserved_sectors)?; + wrt.write_u8(self.fats)?; + wrt.write_u16_le(self.root_entries)?; + wrt.write_u16_le(self.total_sectors_16)?; + wrt.write_u8(self.media)?; + wrt.write_u16_le(self.sectors_per_fat_16)?; + wrt.write_u16_le(self.sectors_per_track)?; + wrt.write_u16_le(self.heads)?; + wrt.write_u32_le(self.hidden_sectors)?; + wrt.write_u32_le(self.total_sectors_32)?; + + if self.is_fat32() { + wrt.write_u32_le(self.sectors_per_fat_32)?; + wrt.write_u16_le(self.extended_flags)?; + wrt.write_u16_le(self.fs_version)?; + wrt.write_u32_le(self.root_dir_first_cluster)?; + wrt.write_u16_le(self.fs_info_sector)?; + wrt.write_u16_le(self.backup_boot_sector)?; + wrt.write_all(&self.reserved_0)?; + } + + wrt.write_u8(self.drive_num)?; + wrt.write_u8(self.reserved_1)?; + wrt.write_u8(self.ext_sig)?; // 0x29 + wrt.write_u32_le(self.volume_id)?; + wrt.write_all(&self.volume_label)?; + wrt.write_all(&self.fs_type_label)?; + Ok(()) + } + + fn validate_bytes_per_sector(&self) -> Result<(), ()> { + if self.bytes_per_sector.count_ones() != 1 { + panic!( + "invalid bytes_per_sector value in BPB: expected a power of two but got {}", + self.bytes_per_sector + ); + return Err(Error::CorruptedFileSystem); + } + if self.bytes_per_sector < 512 || self.bytes_per_sector > 4096 { + panic!( + "invalid bytes_per_sector value in BPB: expected value in range [512, 4096] but got {}", + self.bytes_per_sector + ); + return Err(Error::CorruptedFileSystem); + } + Ok(()) + } + + fn validate_sectors_per_cluster(&self) -> Result<(), ()> { + if self.sectors_per_cluster.count_ones() != 1 { + panic!( + "invalid sectors_per_cluster value in BPB: expected a power of two but got {}", + self.sectors_per_cluster + ); + return Err(Error::CorruptedFileSystem); + } + if self.sectors_per_cluster < 1 || self.sectors_per_cluster > 128 { + panic!( + "invalid sectors_per_cluster value in BPB: expected value in range [1, 128] but got {}", + self.sectors_per_cluster + ); + return Err(Error::CorruptedFileSystem); + } + + // bytes per sector is u16, sectors per cluster is u8, so guaranteed no overflow in multiplication + let bytes_per_cluster = + u32::from(self.bytes_per_sector) * u32::from(self.sectors_per_cluster); + let maximum_compatibility_bytes_per_cluster: u32 = 32 * 1024; + + if bytes_per_cluster > maximum_compatibility_bytes_per_cluster { + // 32k is the largest value to maintain greatest compatibility + // Many implementations appear to support 64k per cluster, and some may support 128k or larger + // However, >32k is not as thoroughly tested... + // warn!("fs compatibility: bytes_per_cluster value '{}' in BPB exceeds '{}', and thus may be incompatible with some implementations", + // bytes_per_cluster, maximum_compatibility_bytes_per_cluster); + } + Ok(()) + } + + fn validate_reserved_sectors(&self) -> Result<(), ()> { + let is_fat32 = self.is_fat32(); + if self.reserved_sectors < 1 { + panic!( + "invalid reserved_sectors value in BPB: {}", + self.reserved_sectors + ); + return Err(Error::CorruptedFileSystem); + } + if !is_fat32 && self.reserved_sectors != 1 { + // Microsoft document indicates fat12 and fat16 code exists that presume this value is 1 + // warn!( + // "fs compatibility: reserved_sectors value '{}' in BPB is not '1', and thus is incompatible with some implementations", + // self.reserved_sectors + // ); + } + if is_fat32 && self.backup_boot_sector >= self.reserved_sectors { + panic!( + "Invalid BPB: expected backup boot-sector to be in the reserved region (sector < {}) but got sector {}", + self.reserved_sectors, self.backup_boot_sector + ); + return Err(Error::CorruptedFileSystem); + } + if is_fat32 && self.fs_info_sector >= self.reserved_sectors { + panic!( + "Invalid BPB: expected FSInfo sector to be in the reserved region (sector < {}) but got sector {}", + self.reserved_sectors, self.fs_info_sector + ); + return Err(Error::CorruptedFileSystem); + } + Ok(()) + } + + fn validate_fats(&self) -> Result<(), ()> { + if self.fats == 0 { + panic!("invalid fats value in BPB: {}", self.fats); + return Err(Error::CorruptedFileSystem); + } + if self.fats > 2 { + // Microsoft document indicates that few implementations support any values other than 1 or 2 + // warn!( + // "fs compatibility: numbers of FATs '{}' in BPB is greater than '2', and thus is incompatible with some implementations", + // self.fats + // ); + } + Ok(()) + } + + fn validate_root_entries(&self) -> Result<(), ()> { + let is_fat32 = self.is_fat32(); + if is_fat32 && self.root_entries != 0 { + panic!( + "Invalid root_entries value in FAT32 BPB: expected 0 but got {}", + self.root_entries + ); + return Err(Error::CorruptedFileSystem); + } + if !is_fat32 && self.root_entries == 0 { + panic!( + "Invalid root_entries value in FAT12/FAT16 BPB: expected non-zero value but got {}", + self.root_entries + ); + return Err(Error::CorruptedFileSystem); + } + if (u32::from(self.root_entries) * DIR_ENTRY_SIZE) % u32::from(self.bytes_per_sector) != 0 { + // warn!("Root entries should fill sectors fully"); + } + Ok(()) + } + + fn validate_total_sectors(&self) -> Result<(), ()> { + let is_fat32 = self.is_fat32(); + if is_fat32 && self.total_sectors_16 != 0 { + panic!( + "Invalid total_sectors_16 value in FAT32 BPB: expected 0 but got {}", + self.total_sectors_16 + ); + return Err(Error::CorruptedFileSystem); + } + if (self.total_sectors_16 == 0) == (self.total_sectors_32 == 0) { + panic!("Invalid BPB (total_sectors_16 or total_sectors_32 should be non-zero)"); + return Err(Error::CorruptedFileSystem); + } + let total_sectors = self.total_sectors(); + let first_data_sector = self.first_data_sector(); + if total_sectors <= first_data_sector { + panic!( + "Invalid total_sectors value in BPB: expected value > {} but got {}", + first_data_sector, total_sectors + ); + return Err(Error::CorruptedFileSystem); + } + Ok(()) + } + + fn validate_sectors_per_fat(&self) -> Result<(), ()> { + let is_fat32 = self.is_fat32(); + if is_fat32 && self.sectors_per_fat_32 == 0 { + panic!( + "Invalid sectors_per_fat_32 value in FAT32 BPB: expected non-zero value but got {}", + self.sectors_per_fat_32 + ); + return Err(Error::CorruptedFileSystem); + } + Ok(()) + } + + fn validate_total_clusters(&self) -> Result<(), ()> { + let is_fat32 = self.is_fat32(); + let total_clusters = self.total_clusters(); + let fat_type = FatType::from_clusters(total_clusters); + if is_fat32 != (fat_type == FatType::Fat32) { + panic!("Invalid BPB: result of FAT32 determination from total number of clusters and sectors_per_fat_16 field differs"); + return Err(Error::CorruptedFileSystem); + } + if fat_type == FatType::Fat32 && total_clusters > 0x0FFF_FFFF { + panic!("Invalid BPB: too many clusters {}", total_clusters); + return Err(Error::CorruptedFileSystem); + } + + let bits_per_fat_entry = fat_type.bits_per_fat_entry(); + let total_fat_entries = + self.sectors_per_fat() * u32::from(self.bytes_per_sector) * 8 / bits_per_fat_entry; + let usable_fat_entries = total_fat_entries - RESERVED_FAT_ENTRIES; + if usable_fat_entries < total_clusters { + // warn!( + // "FAT is too small (allows allocation of {} clusters) compared to the total number of clusters ({})", + // usable_fat_entries, total_clusters + // ); + } + Ok(()) + } + + fn validate(&self) -> Result<(), ()> { + if self.fs_version != 0 { + panic!( + "Unsupported filesystem version: expected 0 but got {}", + self.fs_version + ); + return Err(Error::CorruptedFileSystem); + } + self.validate_bytes_per_sector()?; + self.validate_sectors_per_cluster()?; + self.validate_reserved_sectors()?; + self.validate_fats()?; + self.validate_root_entries()?; + self.validate_total_sectors()?; + self.validate_sectors_per_fat()?; + self.validate_total_clusters()?; + Ok(()) + } + + pub(crate) fn mirroring_enabled(&self) -> bool { + self.extended_flags & 0x80 == 0 + } + + pub(crate) fn active_fat(&self) -> u16 { + // The zero-based number of the active FAT is only valid if mirroring is disabled. + if self.mirroring_enabled() { + 0 + } else { + self.extended_flags & 0x0F + } + } + + pub(crate) fn status_flags(&self) -> FsStatusFlags { + FsStatusFlags::decode(self.reserved_1) + } + + pub(crate) fn is_fat32(&self) -> bool { + // because this field must be zero on FAT32, and + // because it must be non-zero on FAT12/FAT16, + // this provides a simple way to detect FAT32 + self.sectors_per_fat_16 == 0 + } + + pub(crate) fn sectors_per_fat(&self) -> u32 { + if self.is_fat32() { + self.sectors_per_fat_32 + } else { + u32::from(self.sectors_per_fat_16) + } + } + + pub(crate) fn total_sectors(&self) -> u32 { + if self.total_sectors_16 == 0 { + self.total_sectors_32 + } else { + u32::from(self.total_sectors_16) + } + } + + pub(crate) fn reserved_sectors(&self) -> u32 { + u32::from(self.reserved_sectors) + } + + pub(crate) fn root_dir_sectors(&self) -> u32 { + let root_dir_bytes = u32::from(self.root_entries) * DIR_ENTRY_SIZE; + (root_dir_bytes + u32::from(self.bytes_per_sector) - 1) / u32::from(self.bytes_per_sector) + } + + pub(crate) fn sectors_per_all_fats(&self) -> u32 { + u32::from(self.fats) * self.sectors_per_fat() + } + + pub(crate) fn first_data_sector(&self) -> u32 { + let root_dir_sectors = self.root_dir_sectors(); + let fat_sectors = self.sectors_per_all_fats(); + self.reserved_sectors() + fat_sectors + root_dir_sectors + } + + pub(crate) fn total_clusters(&self) -> u32 { + let total_sectors = self.total_sectors(); + let first_data_sector = self.first_data_sector(); + let data_sectors = total_sectors - first_data_sector; + data_sectors / u32::from(self.sectors_per_cluster) + } + + pub(crate) fn bytes_from_sectors(&self, sectors: u32) -> u64 { + // Note: total number of sectors is a 32 bit number so offsets have to be 64 bit + u64::from(sectors) * u64::from(self.bytes_per_sector) + } + + pub(crate) fn sectors_from_clusters(&self, clusters: u32) -> u32 { + // Note: total number of sectors is a 32 bit number so it should not overflow + clusters * u32::from(self.sectors_per_cluster) + } + + pub(crate) fn cluster_size(&self) -> u32 { + u32::from(self.sectors_per_cluster) * u32::from(self.bytes_per_sector) + } + + pub(crate) fn clusters_from_bytes(&self, bytes: u64) -> u32 { + let cluster_size = u64::from(self.cluster_size()); + ((bytes + cluster_size - 1) / cluster_size) as u32 + } + + pub(crate) fn fs_info_sector(&self) -> u32 { + u32::from(self.fs_info_sector) + } + + pub(crate) fn backup_boot_sector(&self) -> u32 { + u32::from(self.backup_boot_sector) + } +} + +pub(crate) struct BootSector { + bootjmp: [u8; 3], + oem_name: [u8; 8], + pub(crate) bpb: BiosParameterBlock, + boot_code: [u8; 448], + boot_sig: [u8; 2], +} + +impl BootSector { + pub(crate) fn deserialize(rdr: &mut R) -> Result { + let mut boot = Self::default(); + rdr.read_exact(&mut boot.bootjmp)?; + rdr.read_exact(&mut boot.oem_name)?; + boot.bpb = BiosParameterBlock::deserialize(rdr)?; + + if boot.bpb.is_fat32() { + rdr.read_exact(&mut boot.boot_code[0..420])?; + } else { + rdr.read_exact(&mut boot.boot_code[0..448])?; + } + rdr.read_exact(&mut boot.boot_sig)?; + Ok(boot) + } + + pub(crate) fn serialize(&self, wrt: &mut W) -> Result<(), W::Error> { + wrt.write_all(&self.bootjmp)?; + wrt.write_all(&self.oem_name)?; + self.bpb.serialize(&mut *wrt)?; + + if self.bpb.is_fat32() { + wrt.write_all(&self.boot_code[0..420])?; + } else { + wrt.write_all(&self.boot_code[0..448])?; + } + wrt.write_all(&self.boot_sig)?; + Ok(()) + } + + pub(crate) fn validate(&self) -> Result<(), ()> { + if self.boot_sig != [0x55, 0xAA] { + panic!( + "Invalid boot sector signature: expected [0x55, 0xAA] but got {:?}", + self.boot_sig + ); + return Err(Error::CorruptedFileSystem); + } + if self.bootjmp[0] != 0xEB && self.bootjmp[0] != 0xE9 { + // warn!( + // "Unknown opcode {:x} in bootjmp boot sector field", + // self.bootjmp[0] + // ); + } + self.bpb.validate()?; + Ok(()) + } +} + +impl Default for BootSector { + fn default() -> Self { + Self { + bootjmp: Default::default(), + oem_name: Default::default(), + bpb: BiosParameterBlock::default(), + boot_code: [0; 448], + boot_sig: Default::default(), + } + } +} + +pub(crate) fn estimate_fat_type(total_bytes: u64) -> FatType { + // Used only to select cluster size if FAT type has not been overriden in options + if total_bytes < 4 * MB_64 { + FatType::Fat12 + } else if total_bytes < 512 * MB_64 { + FatType::Fat16 + } else { + FatType::Fat32 + } +} + +fn determine_bytes_per_cluster( + total_bytes: u64, + bytes_per_sector: u16, + fat_type: Option, +) -> u32 { + const MAX_CLUSTER_SIZE: u32 = 32 * KB_32; + + let fat_type = fat_type.unwrap_or_else(|| estimate_fat_type(total_bytes)); + let bytes_per_cluster = match fat_type { + FatType::Fat12 => (total_bytes.next_power_of_two() / MB_64 * 512) as u32, + FatType::Fat16 => { + if total_bytes <= 16 * MB_64 { + KB_32 + } else if total_bytes <= 128 * MB_64 { + 2 * KB_32 + } else { + ((total_bytes.next_power_of_two() / (64 * MB_64)) as u32) * KB_32 + } + } + FatType::Fat32 => { + if total_bytes <= 260 * MB_64 { + 512 + } else if total_bytes <= 8 * GB_64 { + 4 * KB_32 + } else { + ((total_bytes.next_power_of_two() / (2 * GB_64)) as u32) * KB_32 + } + } + }; + let bytes_per_cluster_clamped = cmp::min( + cmp::max(bytes_per_cluster, u32::from(bytes_per_sector)), + MAX_CLUSTER_SIZE, + ); + debug_assert!(bytes_per_cluster_clamped.is_power_of_two()); + bytes_per_cluster_clamped +} + +fn determine_sectors_per_fat( + total_sectors: u32, + bytes_per_sector: u16, + sectors_per_cluster: u8, + fat_type: FatType, + reserved_sectors: u16, + root_dir_sectors: u32, + fats: u8, +) -> u32 { + // + // FAT size formula transformations: + // + // Initial basic formula: + // size of FAT in bits >= (total number of clusters + 2) * bits per FAT entry + // + // Note: when computing number of clusters from number of sectors rounding down is used because partial clusters + // are not allowed + // Note: in those transformations '/' is a floating-point division (not a rounding towards zero division) + // + // data_sectors = total_sectors - reserved_sectors - fats * sectors_per_fat - root_dir_sectors + // total_clusters = floor(data_sectors / sectors_per_cluster) + // bits_per_sector = bytes_per_sector * 8 + // sectors_per_fat * bits_per_sector >= (total_clusters + 2) * bits_per_fat_entry + // sectors_per_fat * bits_per_sector >= (floor(data_sectors / sectors_per_cluster) + 2) * bits_per_fat_entry + // + // Note: omitting the floor function can cause the FAT to be bigger by 1 entry - negligible + // + // sectors_per_fat * bits_per_sector >= (data_sectors / sectors_per_cluster + 2) * bits_per_fat_entry + // t0 = total_sectors - reserved_sectors - root_dir_sectors + // sectors_per_fat * bits_per_sector >= ((t0 - fats * sectors_per_fat) / sectors_per_cluster + 2) * bits_per_fat_entry + // sectors_per_fat * bits_per_sector / bits_per_fat_entry >= (t0 - fats * sectors_per_fat) / sectors_per_cluster + 2 + // sectors_per_fat * bits_per_sector / bits_per_fat_entry >= t0 / sectors_per_cluster + 2 - fats * sectors_per_fat / sectors_per_cluster + // sectors_per_fat * bits_per_sector / bits_per_fat_entry + fats * sectors_per_fat / sectors_per_cluster >= t0 / sectors_per_cluster + 2 + // sectors_per_fat * (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) >= t0 / sectors_per_cluster + 2 + // sectors_per_fat >= (t0 / sectors_per_cluster + 2) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) + // + // Note: MS specification omits the constant 2 in calculations. This library is taking a better approach... + // + // sectors_per_fat >= ((t0 + 2 * sectors_per_cluster) / sectors_per_cluster) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) + // sectors_per_fat >= (t0 + 2 * sectors_per_cluster) / (sectors_per_cluster * bits_per_sector / bits_per_fat_entry + fats) + // + // Note: compared to MS formula this one can suffer from an overflow problem if u32 type is used + // + // When converting formula to integer types round towards a bigger FAT: + // * first division towards infinity + // * second division towards zero (it is in a denominator of the first division) + + let t0: u32 = total_sectors - u32::from(reserved_sectors) - root_dir_sectors; + let t1: u64 = u64::from(t0) + u64::from(2 * u32::from(sectors_per_cluster)); + let bits_per_cluster = + u32::from(sectors_per_cluster) * u32::from(bytes_per_sector) * BITS_PER_BYTE; + let t2 = u64::from(bits_per_cluster / fat_type.bits_per_fat_entry() + u32::from(fats)); + let sectors_per_fat = (t1 + t2 - 1) / t2; + // Note: casting is safe here because number of sectors per FAT cannot be bigger than total sectors number + sectors_per_fat as u32 +} + +fn try_fs_geometry( + total_sectors: u32, + bytes_per_sector: u16, + sectors_per_cluster: u8, + fat_type: FatType, + root_dir_sectors: u32, + fats: u8, +) -> Result<(u16, u32), Error<()>> { + // Note: most of implementations use 32 reserved sectors for FAT32 but it's wasting of space + // This implementation uses only 8. This is enough to fit in two boot sectors (main and backup) with additional + // bootstrap code and one FSInfo sector. It also makes FAT alligned to 4096 which is a nice number. + let reserved_sectors: u16 = if fat_type == FatType::Fat32 { 8 } else { 1 }; + + // Check if volume has enough space to accomodate reserved sectors, FAT, root directory and some data space + // Having less than 8 sectors for FAT and data would make a little sense + if total_sectors <= u32::from(reserved_sectors) + root_dir_sectors + 8 { + panic!("Volume is too small"); + return Err(Error::InvalidInput); + } + + // calculate File Allocation Table size + let sectors_per_fat = determine_sectors_per_fat( + total_sectors, + bytes_per_sector, + sectors_per_cluster, + fat_type, + reserved_sectors, + root_dir_sectors, + fats, + ); + + let data_sectors = total_sectors + - u32::from(reserved_sectors) + - root_dir_sectors + - sectors_per_fat * u32::from(fats); + let total_clusters = data_sectors / u32::from(sectors_per_cluster); + // if fat_type != FatType::from_clusters(total_clusters) { + // panic!("Invalid FAT type"); + // return Err(Error::InvalidInput); + // } + debug_assert!(total_clusters >= fat_type.min_clusters()); + if total_clusters > fat_type.max_clusters() { + // Note: it can happen for FAT32 + panic!("Too many clusters"); + return Err(Error::InvalidInput); + } + + Ok((reserved_sectors, sectors_per_fat)) +} + +fn determine_root_dir_sectors( + root_dir_entries: u16, + bytes_per_sector: u16, + fat_type: FatType, +) -> u32 { + if fat_type == FatType::Fat32 { + 0 + } else { + let root_dir_bytes = u32::from(root_dir_entries) * DIR_ENTRY_SIZE as u32; + (root_dir_bytes + u32::from(bytes_per_sector) - 1) / u32::from(bytes_per_sector) + } +} + +fn determine_fs_geometry( + total_sectors: u32, + bytes_per_sector: u16, + sectors_per_cluster: u8, + root_dir_entries: u16, + fats: u8, +) -> Result<(FatType, u16, u32), ()> { + for &fat_type in &[FatType::Fat32, FatType::Fat16, FatType::Fat12] { + let root_dir_sectors = + determine_root_dir_sectors(root_dir_entries, bytes_per_sector, fat_type); + let result = try_fs_geometry( + total_sectors, + bytes_per_sector, + sectors_per_cluster, + fat_type, + root_dir_sectors, + fats, + ); + if let Ok((reserved_sectors, sectors_per_fat)) = result { + return Ok((fat_type, reserved_sectors, sectors_per_fat)); + } + } + + panic!("Cannot select FAT type - unfortunate storage size"); + Err(Error::InvalidInput) +} + +fn format_bpb( + options: &FormatVolumeOptions, + total_sectors: u32, + bytes_per_sector: u16, +) -> Result<(BiosParameterBlock, FatType), ()> { + let bytes_per_cluster = options.bytes_per_cluster.unwrap_or_else(|| { + let total_bytes = u64::from(total_sectors) * u64::from(bytes_per_sector); + determine_bytes_per_cluster(total_bytes, bytes_per_sector, options.fat_type) + }); + + let sectors_per_cluster = bytes_per_cluster / u32::from(bytes_per_sector); + assert!(sectors_per_cluster <= u32::from(u8::MAX)); + let sectors_per_cluster = sectors_per_cluster as u8; + + let fats = options.fats.unwrap_or(2_u8); + let root_dir_entries = options.max_root_dir_entries.unwrap_or(512); + let (fat_type, reserved_sectors, sectors_per_fat) = determine_fs_geometry( + total_sectors, + bytes_per_sector, + sectors_per_cluster, + root_dir_entries, + fats, + )?; + + // drive_num should be 0 for floppy disks and 0x80 for hard disks - determine it using FAT type + let drive_num = + options + .drive_num + .unwrap_or_else(|| if fat_type == FatType::Fat12 { 0 } else { 0x80 }); + + // reserved_0 is always zero + let reserved_0 = [0_u8; 12]; + + // setup volume label + let mut volume_label = [0_u8; 11]; + if let Some(volume_label_from_opts) = options.volume_label { + volume_label.copy_from_slice(&volume_label_from_opts); + } else { + volume_label.copy_from_slice(b"NO NAME "); + } + + // setup fs_type_label field + let mut fs_type_label = [0_u8; 8]; + let fs_type_label_str = match fat_type { + FatType::Fat12 => b"FAT12 ", + FatType::Fat16 => b"FAT16 ", + FatType::Fat32 => b"FAT32 ", + }; + fs_type_label.copy_from_slice(fs_type_label_str); + + // create Bios Parameter Block struct + let is_fat32 = fat_type == FatType::Fat32; + let sectors_per_fat_16 = if is_fat32 { + 0 + } else { + debug_assert!(sectors_per_fat <= u32::from(u16::MAX)); + sectors_per_fat as u16 + }; + let bpb = BiosParameterBlock { + bytes_per_sector, + sectors_per_cluster, + reserved_sectors, + fats, + root_entries: if is_fat32 { 0 } else { root_dir_entries }, + total_sectors_16: if total_sectors < 0x10000 { + total_sectors as u16 + } else { + 0 + }, + media: options.media.unwrap_or(0xF8), + sectors_per_fat_16, + sectors_per_track: options.sectors_per_track.unwrap_or(0x20), + // heads: options.heads.unwrap_or(0x40), + heads: 0x40, + hidden_sectors: 0, + total_sectors_32: if total_sectors >= 0x10000 { + total_sectors + } else { + 0 + }, + // FAT32 fields start + sectors_per_fat_32: if is_fat32 { sectors_per_fat } else { 0 }, + extended_flags: 0, // mirroring enabled + fs_version: 0, + root_dir_first_cluster: if is_fat32 { 2 } else { 0 }, + fs_info_sector: if is_fat32 { 1 } else { 0 }, + backup_boot_sector: if is_fat32 { 6 } else { 0 }, + reserved_0, + // FAT32 fields end + drive_num, + reserved_1: 0, + ext_sig: 0x29, + volume_id: options.volume_id.unwrap_or(0x1234_5678), + volume_label, + fs_type_label, + }; + + // Check if number of clusters is proper for used FAT type + if FatType::from_clusters(bpb.total_clusters()) != fat_type { + panic!("Total number of clusters and FAT type does not match, please try a different volume size"); + return Err(Error::InvalidInput); + } + + Ok((bpb, fat_type)) +} + +pub(crate) fn format_boot_sector( + options: &FormatVolumeOptions, + total_sectors: u32, + bytes_per_sector: u16, +) -> Result<(BootSector, FatType), ()> { + let mut boot = BootSector::default(); + let (bpb, fat_type) = format_bpb(options, total_sectors, bytes_per_sector)?; + boot.bpb = bpb; + boot.oem_name.copy_from_slice(b"MSWIN4.1"); + // Boot code copied from FAT32 boot sector initialized by mkfs.fat + boot.bootjmp = [0xEB, 0x58, 0x90]; + let boot_code: [u8; 129] = [ + 0x0E, 0x1F, 0xBE, 0x77, 0x7C, 0xAC, 0x22, 0xC0, 0x74, 0x0B, 0x56, 0xB4, 0x0E, 0xBB, 0x07, + 0x00, 0xCD, 0x10, 0x5E, 0xEB, 0xF0, 0x32, 0xE4, 0xCD, 0x16, 0xCD, 0x19, 0xEB, 0xFE, 0x54, + 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, + 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x2E, 0x20, 0x20, 0x50, + 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6E, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, + 0x62, 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x66, 0x6C, 0x6F, 0x70, 0x70, 0x79, + 0x20, 0x61, 0x6E, 0x64, 0x0D, 0x0A, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6E, 0x79, + 0x20, 0x6B, 0x65, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x72, 0x79, 0x20, 0x61, 0x67, 0x61, + 0x69, 0x6E, 0x20, 0x2E, 0x2E, 0x2E, 0x20, 0x0D, 0x0A, + ]; + boot.boot_code[..boot_code.len()].copy_from_slice(&boot_code); + boot.boot_sig = [0x55, 0xAA]; + + // fix offsets in bootjmp and boot code for non-FAT32 filesystems (bootcode is on a different offset) + if fat_type != FatType::Fat32 { + // offset of boot code + const BOOT_CODE_OFFSET: u8 = 0x36 + 8; + // offset of message + const MESSAGE_OFFSET: u16 = 29; + boot.bootjmp[1] = BOOT_CODE_OFFSET - 2; + let message_offset_in_sector = u16::from(BOOT_CODE_OFFSET) + MESSAGE_OFFSET + 0x7c00; + boot.boot_code[3] = (message_offset_in_sector & 0xff) as u8; + boot.boot_code[4] = (message_offset_in_sector >> 8) as u8; + } + + Ok((boot, fat_type)) +} + +pub(crate) trait ReadLeExt { + type Error; + fn read_u8(&mut self) -> Result; + fn read_u16_le(&mut self) -> Result; + fn read_u32_le(&mut self) -> Result; +} + +impl ReadLeExt for T { + type Error = ::Error; + + fn read_u8(&mut self) -> Result { + let mut buf = [0_u8; 1]; + self.read_exact(&mut buf)?; + Ok(buf[0]) + } + + fn read_u16_le(&mut self) -> Result { + let mut buf = [0_u8; 2]; + self.read_exact(&mut buf)?; + Ok(u16::from_le_bytes(buf)) + } + + fn read_u32_le(&mut self) -> Result { + let mut buf = [0_u8; 4]; + self.read_exact(&mut buf)?; + Ok(u32::from_le_bytes(buf)) + } +} + +pub(crate) trait WriteLeExt { + type Error; + fn write_u8(&mut self, n: u8) -> Result<(), Self::Error>; + fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error>; + fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error>; +} + +impl WriteLeExt for T { + type Error = ::Error; + + fn write_u8(&mut self, n: u8) -> Result<(), Self::Error> { + self.write_all(&[n]) + } + + fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error> { + self.write_all(&n.to_le_bytes()) + } + + fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error> { + self.write_all(&n.to_le_bytes()) + } +} diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 161a22cf..948eb7cf 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -1,24 +1,87 @@ #![no_std] #![no_main] -use core::{ - arch::{asm, global_asm}, - slice, -}; +use byteorder::{ByteOrder, LittleEndian}; +use core::{fmt::Write as _, slice}; +use fatfs::{FsOptions, Read}; +use mbr_nostd::{MasterBootRecord, PartitionTable, PartitionTableEntry, PartitionType}; -// mod dap; -mod fail; -// mod fat; -// mod mbr; +mod dap; +mod disk; +mod fat; +// mod fat_bpb; +mod mini_fat; +mod screen; + +/// We use this partition type to store the second bootloader stage; +const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; + +extern "C" { + static _second_stage_end: u8; +} + +fn second_stage_end() -> *const u8 { + unsafe { &_second_stage_end } +} #[no_mangle] -pub extern "C" fn _start(disk_number: u16) { - fail::print_char(b'_'); - fail::print_char(b'_'); - fail::print_char(b'S'); - fail::print_char(b':'); +#[link_section = ".start"] +pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { + write!(screen::Writer, "\nSECOND STAGE: ").unwrap(); + + // parse partition table + let partitions = { + const MAX_ENTRIES: usize = 4; + const ENTRY_SIZE: usize = 16; + + let mut entries = [PartitionTableEntry::empty(); MAX_ENTRIES]; + let raw = unsafe { slice::from_raw_parts(partition_table_start, ENTRY_SIZE * MAX_ENTRIES) }; + for idx in 0..MAX_ENTRIES { + let offset = idx * ENTRY_SIZE; + let partition_type = PartitionType::from_mbr_tag_byte(raw[offset + 4]); + let lba = LittleEndian::read_u32(&raw[offset + 8..]); + let len = LittleEndian::read_u32(&raw[offset + 12..]); + entries[idx] = PartitionTableEntry::new(partition_type, lba, len); + } + entries + }; + // look for second stage partition + let second_stage_partition_idx = partitions + .iter() + .enumerate() + .find(|(_, e)| { + e.partition_type == PartitionType::Unknown(BOOTLOADER_SECOND_STAGE_PARTITION_TYPE) + }) + .unwrap() + .0; + let fat_partition = partitions.get(second_stage_partition_idx + 1).unwrap(); + assert!(matches!( + fat_partition.partition_type, + PartitionType::Fat12(_) | PartitionType::Fat16(_) | PartitionType::Fat32(_) + )); + screen::print_char(b'1'); + + // load fat partition + let mut disk = disk::DiskAccess { + disk_number, + base_offset: u64::from(fat_partition.logical_block_address) * 512, + current_offset: 0, + }; + + let mut buffer = [0u8; 512]; + disk.read_exact(&mut buffer).unwrap(); + screen::print_char(b'2'); + + let boot_sector = mini_fat::Bpb::parse(&buffer).unwrap(); + loop {} + writeln!(screen::Writer, "BPB: {boot_sector:?}").unwrap(); + + screen::print_char(b'3'); + + panic!("foo"); + let fat = fatfs::FileSystem::new(disk, FsOptions::new().update_accessed_date(false)).unwrap(); + screen::print_char(b'2'); - fail::print_char(b'1'); loop {} // try to parse FAT file system @@ -50,15 +113,22 @@ pub extern "C" fn _start(disk_number: u16) { loop {} } -// /// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 -// /// -// /// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, -// /// see https://github.com/rust-lang/rust/issues/90091 -// fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { -// if N > slice.len() { -// fail(b'S'); -// } -// let (a, b) = slice.split_at(N); -// // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) -// unsafe { (&*(a.as_ptr() as *const [T; N]), b) } -// } +/// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 +/// +/// TODO replace with `split_array` feature in stdlib as soon as it's stabilized, +/// see https://github.com/rust-lang/rust/issues/90091 +fn split_array_ref(slice: &[T]) -> (&[T; N], &[T]) { + if N > slice.len() { + fail(b'S'); + } + let (a, b) = slice.split_at(N); + // SAFETY: a points to [T; N]? Yes it's [T] of length N (checked by split_at) + unsafe { (&*(a.as_ptr() as *const [T; N]), b) } +} + +#[cold] +#[inline(never)] +#[no_mangle] +pub extern "C" fn fail(code: u8) -> ! { + panic!("fail: {}", code as char); +} diff --git a/bios/second_stage/src/mbr.rs b/bios/second_stage/src/mbr.rs deleted file mode 100644 index f7b00d6d..00000000 --- a/bios/second_stage/src/mbr.rs +++ /dev/null @@ -1,124 +0,0 @@ -// Based on https://docs.rs/mbr-nostd - -use super::fail::{fail, UnwrapOrFail}; - -/// We use this partition type to store the second bootloader stage; -const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; - -/// Returns the first bootable partition in the partition table. -pub fn boot_partition(partitions_raw: &[u8]) -> Option { - for index in 0..4 { - let entry = get_partition(partitions_raw, index); - if entry.partition_type == BOOTLOADER_SECOND_STAGE_PARTITION_TYPE { - return Some(entry); - } - } - None -} - -pub fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTableEntry { - if partitions_raw.len() < PARTITIONS_AREA_SIZE { - fail(b'a'); - } - - let offset = index * ENTRY_SIZE; - let buffer = partitions_raw.get(offset..).unwrap_or_fail(b'c'); - - let bootable_raw = *buffer.get(0).unwrap_or_fail(b'd'); - let bootable = bootable_raw == 0x80; - - let partition_type = *buffer.get(4).unwrap_or_fail(b'e'); - - let lba = u32::from_le_bytes( - buffer - .get(8..) - .and_then(|s| s.get(..4)) - .and_then(|s| s.try_into().ok()) - .unwrap_or_fail(b'e'), - ); - let len = u32::from_le_bytes( - buffer - .get(12..) - .and_then(|s| s.get(..4)) - .and_then(|s| s.try_into().ok()) - .unwrap_or_fail(b'f'), - ); - PartitionTableEntry::new(bootable, partition_type, lba, len) -} - -const PARTITIONS_AREA_SIZE: usize = 16 * 4; -const ENTRY_SIZE: usize = 16; - -/// The type of a particular partition. -#[derive(Copy, Clone, Eq, PartialEq)] -pub enum PartitionType { - BootloaderSecondStage, - Unused, - Unknown(u8), - Fat12(u8), - Fat16(u8), - Fat32(u8), - LinuxExt(u8), - HfsPlus(u8), - ISO9660(u8), - NtfsExfat(u8), -} - -impl PartitionType { - /// Parses a partition type from the type byte in the MBR's table. - pub fn from_mbr_tag_byte(tag: u8) -> PartitionType { - match tag { - // we use partition type 0x20 to store the second bootloader stage - 0x20 => PartitionType::BootloaderSecondStage, - 0x0 => PartitionType::Unused, - 0x01 => PartitionType::Fat12(tag), - 0x04 | 0x06 | 0x0e => PartitionType::Fat16(tag), - 0x0b | 0x0c | 0x1b | 0x1c => PartitionType::Fat32(tag), - 0x83 => PartitionType::LinuxExt(tag), - 0x07 => PartitionType::NtfsExfat(tag), - 0xaf => PartitionType::HfsPlus(tag), - _ => PartitionType::Unknown(tag), - } - } - - pub fn known_type(tag: u8) -> bool { - match tag { - 0x0 | 0x01 | 0x04 | 0x06 | 0x0e | 0x0b | 0x0c | 0x1b | 0x1c | 0x83 | 0x07 | 0xaf => { - true - } - _ => false, - } - } -} - -/// An entry in a partition table. -#[derive(Copy, Clone, Eq, PartialEq)] -pub struct PartitionTableEntry { - /// Whether this partition is a boot partition. - pub bootable: bool, - - /// The type of partition in this entry. - pub partition_type: u8, - - /// The index of the first block of this entry. - pub logical_block_address: u32, - - /// The total number of blocks in this entry. - pub sector_count: u32, -} - -impl PartitionTableEntry { - pub fn new( - bootable: bool, - partition_type: u8, - logical_block_address: u32, - sector_count: u32, - ) -> PartitionTableEntry { - PartitionTableEntry { - bootable, - partition_type, - logical_block_address, - sector_count, - } - } -} diff --git a/bios/second_stage/src/mini_fat.rs b/bios/second_stage/src/mini_fat.rs new file mode 100644 index 00000000..5f82ed9e --- /dev/null +++ b/bios/second_stage/src/mini_fat.rs @@ -0,0 +1,370 @@ +// based on https://crates.io/crates/mini_fat + +use core::ops::Range; + +#[derive(Debug)] +pub enum Error { + UnexpectedNonZero { + byte_index: usize, + }, + ExactlyOneTotalSectorsFieldMustBeZero { + total_sectors_16: u16, + total_sectors_32: u32, + }, + ExactlyOneFatSizeMustBeZero { + fat_size_16: u16, + fat_size_32: u32, + }, + InvalidSignature(u16), + InvalidFatEntry(u32), + FatLookup(FatLookupError), + NoSuchFile, + InvalidPath, + ExpectedFileFoundDirectory, +} + +#[derive(Debug)] +pub struct Bpb<'a> { + jmp_boot: [u8; 3], + oem_name: &'a [u8], + bytes_per_sector: u16, + sectors_per_cluster: u8, + reserved_sector_count: u16, + num_fats: u8, + root_entry_count: u16, + total_sectors_16: u16, + media: u8, + fat_size_16: u16, + sectors_per_track: u16, + num_heads: u16, + hidden_sectors: u32, + total_sectors_32: u32, + fat_size_32: u32, + ext_flags: u16, + fs_version: u16, + root_cluster: u32, + fs_info: u16, + bk_boot_sector: u16, + drive_number: u8, + boot_signature: u8, + volume_id: u32, + volume_label: &'a [u8], + file_system_type: &'a [u8], + signature: u16, +} + +const BPB_SIZE: usize = 512; +const REQUIRED_SIGNATURE: u16 = 0xAA55; + +impl<'a> Bpb<'a> { + pub fn parse(raw: &'a [u8]) -> Result { + let jmp_boot = [raw[0], raw[1], raw[2]]; + let oem_name = &raw[3..11]; + let bytes_per_sector = u16::from_le_bytes(raw[11..13].try_into().unwrap()); + let sectors_per_cluster = raw[13]; + let reserved_sector_count = u16::from_le_bytes(raw[14..16].try_into().unwrap()); + let num_fats = raw[16]; + let root_entry_count = u16::from_le_bytes(raw[17..19].try_into().unwrap()); + let total_sectors_16 = u16::from_le_bytes(raw[19..21].try_into().unwrap()); + let media = raw[21]; + let fat_size_16 = u16::from_le_bytes(raw[22..24].try_into().unwrap()); + panic!("baz"); + let sectors_per_track = u16::from_le_bytes(raw[24..26].try_into().unwrap()); + let num_heads = u16::from_le_bytes(raw[26..28].try_into().unwrap()); + let hidden_sectors = u32::from_le_bytes(raw[28..32].try_into().unwrap()); + let total_sectors_32 = u32::from_le_bytes(raw[32..36].try_into().unwrap()); + + let ( + fat_size_32, + ext_flags, + fs_version, + root_cluster, + fs_info, + bk_boot_sector, + drive_number, + boot_signature, + volume_id, + volume_label, + file_system_type, + ); + if (total_sectors_16 == 0) && (total_sectors_32 != 0) { + // FAT32 + fat_size_32 = u32::from_le_bytes(raw[36..40].try_into().unwrap()); + ext_flags = u16::from_le_bytes(raw[40..42].try_into().unwrap()); + fs_version = u16::from_le_bytes(raw[42..44].try_into().unwrap()); + root_cluster = u32::from_le_bytes(raw[44..48].try_into().unwrap()); + fs_info = u16::from_le_bytes(raw[48..50].try_into().unwrap()); + bk_boot_sector = u16::from_le_bytes(raw[50..52].try_into().unwrap()); + for i in 52..64 { + if raw[i] != 0 { + return Err(Error::UnexpectedNonZero { byte_index: i }); + } + } + drive_number = raw[64]; + if raw[65] != 0 { + return Err(Error::UnexpectedNonZero { byte_index: 65 }); + } + boot_signature = raw[66]; + volume_id = u32::from_le_bytes(raw[67..71].try_into().unwrap()); + volume_label = &raw[71..82]; + file_system_type = &raw[82..90]; + } else if (total_sectors_16 != 0) && (total_sectors_32 == 0) { + // FAT12 or FAT16 + fat_size_32 = 0; + ext_flags = 0; + fs_version = 0; + root_cluster = 0; + fs_info = 0; + bk_boot_sector = 0; + drive_number = raw[36]; + if raw[37] != 0 { + return Err(Error::UnexpectedNonZero { byte_index: 37 }); + } + boot_signature = raw[38]; + volume_id = u32::from_le_bytes(raw[39..43].try_into().unwrap()); + volume_label = &raw[43..54]; + file_system_type = &raw[54..62]; + } else { + return Err(Error::ExactlyOneTotalSectorsFieldMustBeZero { + total_sectors_16, + total_sectors_32, + }); + } + if (fat_size_16 == 0) == (fat_size_32 == 0) { + return Err(Error::ExactlyOneFatSizeMustBeZero { + fat_size_16, + fat_size_32, + }); + } + let signature = u16::from_le_bytes(raw[510..512].try_into().unwrap()); + if signature != REQUIRED_SIGNATURE { + return Err(Error::InvalidSignature(signature)); + } + Ok(Self { + jmp_boot, + oem_name, + bytes_per_sector, + sectors_per_cluster, + reserved_sector_count, + num_fats, + root_entry_count, + total_sectors_16, + media, + fat_size_16, + sectors_per_track, + num_heads, + hidden_sectors, + total_sectors_32, + fat_size_32, + ext_flags, + fs_version, + root_cluster, + fs_info, + bk_boot_sector, + drive_number, + boot_signature, + volume_id, + volume_label, + file_system_type, + signature, + }) + } + + fn fat_size_in_sectors(&self) -> u32 { + if self.fat_size_16 != 0 && self.fat_size_32 == 0 { + self.fat_size_16 as u32 + } else { + debug_assert!(self.fat_size_16 == 0 && self.fat_size_32 != 0); + self.fat_size_32 + } + } + + fn count_of_clusters(&self) -> u32 { + let root_dir_sectors = ((self.root_entry_count as u32 * 32) + + (self.bytes_per_sector as u32 - 1)) + / self.bytes_per_sector as u32; + let total_sectors = if self.total_sectors_16 != 0 { + self.total_sectors_16 as u32 + } else { + self.total_sectors_32 + }; + let data_sectors = total_sectors + - (self.reserved_sector_count as u32 + + (self.num_fats as u32 * self.fat_size_in_sectors()) + + root_dir_sectors); + data_sectors / self.sectors_per_cluster as u32 + } + + fn fat_type(&self) -> FatType { + let count_of_clusters = self.count_of_clusters(); + if count_of_clusters < 4085 { + FatType::Fat12 + } else if count_of_clusters < 65525 { + FatType::Fat16 + } else { + FatType::Fat32 + } + } + + fn maximum_valid_cluster(&self) -> u32 { + self.count_of_clusters() + 1 + } + + fn root_directory_size(&self) -> usize { + debug_assert!((self.fat_type() == FatType::Fat32) == (self.root_entry_count == 0)); + self.root_entry_count as usize * DIRECTORY_ENTRY_BYTES + } + + fn root_directory_offset(&self) -> u64 { + (self.reserved_sector_count as u64 + (self.num_fats as u64 * self.fat_size_16 as u64)) + * self.bytes_per_sector as u64 + } + + fn fat_offset(&self) -> u64 { + self.reserved_sector_count as u64 * self.bytes_per_sector as u64 + } + + fn data_offset(&self) -> u64 { + self.root_directory_size() as u64 + + ((self.reserved_sector_count as u64 + + self.fat_size_in_sectors() as u64 * self.num_fats as u64) + * self.bytes_per_sector as u64) + } + + pub fn bytes_per_cluster(&self) -> u32 { + self.bytes_per_sector as u32 * self.sectors_per_cluster as u32 + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FatType { + Fat12, + Fat16, + Fat32, +} + +impl FatType { + fn fat_entry_defective(self) -> u32 { + match self { + Self::Fat12 => 0xFF7, + Self::Fat16 => 0xFFF7, + Self::Fat32 => 0x0FFFFFF7, + } + } +} + +mod directory_attributes { + pub const READ_ONLY: u8 = 0x01; + pub const HIDDEN: u8 = 0x02; + pub const SYSTEM: u8 = 0x04; + pub const VOLUME_ID: u8 = 0x08; + pub const DIRECTORY: u8 = 0x10; + + pub const LONG_NAME: u8 = READ_ONLY | HIDDEN | SYSTEM | VOLUME_ID; +} + +#[derive(Debug)] +pub enum FatLookupError { + FreeCluster, + DefectiveCluster, + UnspecifiedEntryOne, + ReservedEntry, +} + +enum FileFatEntry { + AllocatedCluster(u32), + EndOfFile, +} + +fn classify_fat_entry( + fat_type: FatType, + entry: u32, + maximum_valid_cluster: u32, +) -> Result { + match entry { + 0 => Err(FatLookupError::FreeCluster), + 1 => Err(FatLookupError::UnspecifiedEntryOne), + entry => { + if entry <= maximum_valid_cluster { + Ok(FileFatEntry::AllocatedCluster(entry)) + } else if entry < fat_type.fat_entry_defective() { + Err(FatLookupError::ReservedEntry) + } else if entry == fat_type.fat_entry_defective() { + Err(FatLookupError::DefectiveCluster) + } else { + Ok(FileFatEntry::EndOfFile) + } + } + } +} + +fn handle_read(handle: &mut H, offset: u64, size: usize, buf: &mut [u8]) -> Result<(), Error> +where + H: fatfs::Seek + fatfs::Read, +{ + handle.seek(fatfs::SeekFrom::Start(offset)).unwrap(); + handle.read_exact(buf).unwrap(); + Ok(()) +} + +fn fat_entry_of_nth_cluster( + handle: &mut H, + fat_type: FatType, + fat_start: u64, + n: u32, +) -> Result +where + H: fatfs::Seek + fatfs::Read, +{ + debug_assert!(n >= 2); + match fat_type { + FatType::Fat32 => { + let base = n as u64 * 4; + handle + .seek(fatfs::SeekFrom::Start(fat_start + base)) + .unwrap(); + let mut buf = [0; 4]; + handle.read_exact(&mut buf).unwrap(); + Ok(u32::from_le_bytes(buf) & 0x0FFFFFFF) + } + FatType::Fat16 => { + let base = n as u64 * 2; + handle + .seek(fatfs::SeekFrom::Start(fat_start + base)) + .unwrap(); + let mut buf = [0; 2]; + handle.read_exact(&mut buf).unwrap(); + Ok(u16::from_le_bytes(buf) as u32) + } + FatType::Fat12 => { + let base = n as u64 + (n as u64 / 2); + handle + .seek(fatfs::SeekFrom::Start(fat_start + base)) + .unwrap(); + let mut buf = [0; 2]; + handle.read_exact(&mut buf).unwrap(); + let entry16 = u16::from_le_bytes(buf); + if n & 1 == 0 { + Ok((entry16 & 0xFFF) as u32) + } else { + Ok((entry16 >> 4) as u32) + } + } + } +} + +fn read_bpb<'a, H>( + handle: &mut H, + partition_byte_start: u64, + buf: &'a mut [u8], +) -> Result, Error> +where + H: fatfs::Seek + fatfs::Read, +{ + handle_read(handle, partition_byte_start, BPB_SIZE, buf)?; + Bpb::parse(buf) +} + +const DIRECTORY_ENTRY_BYTES: usize = 32; +const UNUSED_ENTRY_PREFIX: u8 = 0xE5; +const END_OF_DIRECTORY_PREFIX: u8 = 0; diff --git a/bios/second_stage/src/screen.rs b/bios/second_stage/src/screen.rs new file mode 100644 index 00000000..165d6424 --- /dev/null +++ b/bios/second_stage/src/screen.rs @@ -0,0 +1,45 @@ +use core::{arch::asm, fmt::Write as _}; + +pub fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("int 0x10", in("ax") ax, in("bx") 0); + } +} + +pub fn print_str(s: &str) { + for c in s.chars() { + if c.is_ascii() { + print_char(c as u8); + if c == '\n' { + print_char(b'\r'); + } + } else { + print_char(b'X'); + } + } +} + +fn hlt() { + unsafe { + asm!("hlt"); + } +} + +pub struct Writer; + +impl core::fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + print_str(s); + Ok(()) + } +} + +#[panic_handler] +pub fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(Writer, "\nPANIC: {}", info); + + loop { + hlt(); + } +} diff --git a/build.rs b/build.rs index 771e66ad..02da99c3 100644 --- a/build.rs +++ b/build.rs @@ -9,6 +9,9 @@ const BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION: &str = "0.1.0-alpha.0"; fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-changed=Cargo.toml"); + println!("cargo:rerun-if-changed=Cargo.lock"); let uefi_path = build_uefi_bootloader(&out_dir); println!( @@ -35,6 +38,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { if Path::new("uefi").exists() { // local build cmd.arg("--path").arg("uefi"); + println!("cargo:rerun-if-changed=uefi"); } else { cmd.arg("--version").arg(BOOTLOADER_X86_64_UEFI_VERSION); } @@ -69,6 +73,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { if local_path.exists() { // local build cmd.arg("--path").arg(&local_path); + println!("cargo:rerun-if-changed={}", local_path.display()); } else { cmd.arg("--version") .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); @@ -98,6 +103,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { }; convert_elf_to_bin(elf_path) } + fn build_bios_second_stage(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); @@ -109,6 +115,7 @@ fn build_bios_second_stage(out_dir: &Path) -> PathBuf { if local_path.exists() { // local build cmd.arg("--path").arg(&local_path); + println!("cargo:rerun-if-changed={}", local_path.display()); } else { cmd.arg("--version") .arg(BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION); diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index c6096bb2..aa36abf6 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -5,26 +5,41 @@ const QEMU_ARGS: &[&str] = &[ "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", - "-display", - "none", + // "-display", + // "none", "--no-reboot", + // "-d", + // "int", + "-s", + // "-S", ]; pub fn run_test_kernel(kernel_binary_path: &str) { - run_test_kernel_on_uefi(kernel_binary_path); - run_test_kernel_on_uefi_pxe(kernel_binary_path); - // TODO: run tests with BIOS bootloader too -} - -pub fn run_test_kernel_on_uefi(kernel_binary_path: &str) { let kernel_path = Path::new(kernel_binary_path); + + // create FAT filesystem with kernel executable and UEFI bootloader let out_fat_path = kernel_path.with_extension("fat"); bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap(); + + // wrap the created filesystem in an GPT disk image for UEFI booting let out_gpt_path = kernel_path.with_extension("gpt"); bootloader::create_uefi_disk_image(&out_fat_path, &out_gpt_path).unwrap(); + + // wrap the created filesystem in an MBR disk image for legacy BIOS booting let out_mbr_path = kernel_path.with_extension("mbr"); bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap(); + // create a TFTP folder with the kernel executable and UEFI bootloader for + // UEFI PXE booting + let out_tftp_path = kernel_path.with_extension(".tftp"); + bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap(); + + // run_test_kernel_on_uefi(&out_gpt_path); + run_test_kernel_on_bios(&out_mbr_path); + // run_test_kernel_on_uefi_pxe(&out_tftp_path); +} + +pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd .arg("-drive") @@ -47,12 +62,29 @@ pub fn run_test_kernel_on_uefi(kernel_binary_path: &str) { } } -pub fn run_test_kernel_on_uefi_pxe(kernel_binary_path: &str) { - let kernel_path = Path::new(kernel_binary_path); - let out_tftp_path = kernel_path.with_extension(".tftp"); +pub fn run_test_kernel_on_bios(out_mbr_path: &Path) { + let mut run_cmd = Command::new("qemu-system-x86_64"); + run_cmd + .arg("-drive") + .arg(format!("format=raw,file={}", out_mbr_path.display())); + run_cmd.args(QEMU_ARGS); - bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap(); + let child_output = run_cmd.output().unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stderr) + .unwrap(); + strip_ansi_escapes::Writer::new(std::io::stderr()) + .write_all(&child_output.stdout) + .unwrap(); + + match child_output.status.code() { + Some(33) => {} // success + Some(35) => panic!("Test failed"), // success + other => panic!("Test failed with unexpected exit code `{:?}`", other), + } +} +pub fn run_test_kernel_on_uefi_pxe(out_tftp_path: &Path) { let mut run_cmd = Command::new("qemu-system-x86_64"); run_cmd.arg("-netdev").arg(format!( "user,id=net0,net=192.168.17.0/24,tftp={},bootfile=bootloader,id=net0", diff --git a/x86-16bit-second-stage.json b/x86-16bit-second-stage.json index ee6f67ad..f5ed774e 100644 --- a/x86-16bit-second-stage.json +++ b/x86-16bit-second-stage.json @@ -16,5 +16,5 @@ "panic-strategy": "abort", "os": "none", "vendor": "unknown", - "relocation-model": "pie" + "relocation-model": "static" } From abfdba522e990fcaf865302b7b7d91f0abef04a7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 12 Jun 2022 17:54:51 +0200 Subject: [PATCH 108/226] Start custom FAT implementation bysed on `mini_fat` crate --- Cargo.lock | 12 +- Cargo.toml | 6 + bios/second_stage/Cargo.toml | 1 - bios/second_stage/src/dap.rs | 2 +- bios/second_stage/src/disk.rs | 41 +++-- bios/second_stage/src/fat.rs | 316 +++++++++++++--------------------- bios/second_stage/src/main.rs | 24 ++- build.rs | 1 + 8 files changed, 161 insertions(+), 242 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4ab9d637..4662e750 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -60,7 +60,7 @@ dependencies = [ "anyhow", "bootloader-x86_64-bios", "bootloader_test_runner", - "fatfs 0.3.5", + "fatfs", "gpt", "llvm-tools", "mbrman", @@ -91,7 +91,6 @@ name = "bootloader-x86_64-bios-second-stage" version = "0.1.0" dependencies = [ "byteorder", - "fatfs 0.4.0", "mbr-nostd", ] @@ -206,15 +205,6 @@ dependencies = [ "log", ] -[[package]] -name = "fatfs" -version = "0.4.0" -source = "git+https://github.com/rafalh/rust-fatfs.git#4892fb1791b29616d8573235eb83644003d13791" -dependencies = [ - "bitflags", - "log", -] - [[package]] name = "funty" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 14f927fe..31dff3cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,6 +62,12 @@ codegen-units = 1 debug = false overflow-checks = false +[profile.second-stage] +inherits = "release" +opt-level = "s" +codegen-units = 1 +debug = false + [profile.test.package.test_kernel_higher_half] rustflags = [ "-C", diff --git a/bios/second_stage/Cargo.toml b/bios/second_stage/Cargo.toml index ad487e67..dec11e9d 100644 --- a/bios/second_stage/Cargo.toml +++ b/bios/second_stage/Cargo.toml @@ -7,6 +7,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -fatfs = { git = "https://github.com/rafalh/rust-fatfs.git", default-features = false } mbr-nostd = "0.1.0" byteorder = { version = "1.4.3", default-features = false } diff --git a/bios/second_stage/src/dap.rs b/bios/second_stage/src/dap.rs index ec39015e..40717d80 100644 --- a/bios/second_stage/src/dap.rs +++ b/bios/second_stage/src/dap.rs @@ -1,4 +1,4 @@ -use core::arch::{asm, global_asm}; +use core::arch::asm; #[derive(Debug, Clone, Copy)] #[repr(packed)] diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index a9724286..c2f7d423 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -1,18 +1,15 @@ use crate::{dap, screen, second_stage_end}; use core::{fmt::Write as _, slice}; +#[derive(Clone)] pub struct DiskAccess { pub disk_number: u16, pub base_offset: u64, pub current_offset: u64, } -impl fatfs::IoBase for DiskAccess { - type Error = (); -} - -impl fatfs::Read for DiskAccess { - fn read(&mut self, buf: &mut [u8]) -> Result { +impl Read for DiskAccess { + fn read_exact(&mut self, buf: &mut [u8]) { writeln!(screen::Writer, "read {} bytes", buf.len()).unwrap(); let end_addr = self.base_offset + self.current_offset + u64::try_from(buf.len()).unwrap(); @@ -34,35 +31,37 @@ impl fatfs::Read for DiskAccess { buf.copy_from_slice(data); self.current_offset = end_addr; - Ok(buf.len()) } } -impl fatfs::Seek for DiskAccess { - fn seek(&mut self, pos: fatfs::SeekFrom) -> Result { +impl Seek for DiskAccess { + fn seek(&mut self, pos: SeekFrom) -> u64 { writeln!(screen::Writer, "seek to {pos:?}").unwrap(); match pos { - fatfs::SeekFrom::Start(offset) => { + SeekFrom::Start(offset) => { self.current_offset = offset; - Ok(self.current_offset) + self.current_offset } - fatfs::SeekFrom::Current(offset) => { + SeekFrom::Current(offset) => { self.current_offset = (i64::try_from(self.current_offset).unwrap() + offset) .try_into() .unwrap(); - Ok(self.current_offset) + self.current_offset } - fatfs::SeekFrom::End(_) => Err(()), } } } -impl fatfs::Write for DiskAccess { - fn write(&mut self, buf: &[u8]) -> Result { - unimplemented!() - } +pub trait Read { + fn read_exact(&mut self, buf: &mut [u8]); +} - fn flush(&mut self) -> Result<(), Self::Error> { - unimplemented!() - } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SeekFrom { + Start(u64), + Current(i64), +} + +pub trait Seek { + fn seek(&mut self, pos: SeekFrom) -> u64; } diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index a8701162..bf485af1 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -1,235 +1,161 @@ -// based on https://github.com/rafalh/rust-fatfs/ +// based on https://crates.io/crates/mini_fat -use super::split_array_ref; +use crate::disk::Read; -// Size of single directory entry in bytes -const DIR_ENTRY_SIZE: u32 = 32; +const DIRECTORY_ENTRY_BYTES: usize = 32; +const UNUSED_ENTRY_PREFIX: u8 = 0xE5; +const END_OF_DIRECTORY_PREFIX: u8 = 0; -pub(crate) struct BootSector { - pub(crate) bpb: BiosParameterBlock, +pub struct File { + first_cluster: u32, + file_size: u32, } -impl BootSector { - pub(crate) fn deserialize(bytes: &[u8]) -> Self { - let mut boot = Self::default(); - // let (&bootjmp, bytes) = split_array_ref(bytes); - // let (&oem_name, bytes) = split_array_ref(bytes); - - let bytes = &bytes[3 + 8..]; - - // boot.bootjmp = bootjmp; - // boot.oem_name = oem_name; - boot.bpb = BiosParameterBlock::deserialize(bytes); - - // let bytes = if boot.bpb.is_fat32() { - // let (boot_code, bytes): (&[_; 420], _) = split_array_ref(bytes); - // boot.boot_code[0..420].copy_from_slice(&boot_code[..]); - // bytes - // } else { - // let (&boot_code, bytes) = split_array_ref(bytes); - // boot.boot_code = boot_code; - // bytes - // }; - // let (&boot_sig, bytes) = split_array_ref(bytes); - // boot.boot_sig = boot_sig; - boot +impl File { + pub fn file_size(&self) -> u32 { + self.file_size } } -impl Default for BootSector { - fn default() -> Self { - Self { - bpb: BiosParameterBlock::default(), - } - } -} - -#[derive(Default, Debug, Clone)] -pub(crate) struct BiosParameterBlock { - pub(crate) bytes_per_sector: u16, - pub(crate) sectors_per_cluster: u8, - pub(crate) reserved_sectors: u16, - pub(crate) fats: u8, - pub(crate) root_entries: u16, - pub(crate) total_sectors_16: u16, - pub(crate) media: u8, - pub(crate) sectors_per_fat_16: u16, - pub(crate) sectors_per_track: u16, - pub(crate) heads: u16, - pub(crate) hidden_sectors: u32, - pub(crate) total_sectors_32: u32, - - // Extended BIOS Parameter Block - pub(crate) sectors_per_fat_32: u32, - pub(crate) extended_flags: u16, - pub(crate) fs_version: u16, - pub(crate) root_dir_first_cluster: u32, - pub(crate) fs_info_sector: u16, - pub(crate) backup_boot_sector: u16, - pub(crate) reserved_0: [u8; 12], - pub(crate) drive_num: u8, - pub(crate) reserved_1: u8, - pub(crate) ext_sig: u8, - pub(crate) volume_id: u32, - pub(crate) volume_label: [u8; 11], - pub(crate) fs_type_label: [u8; 8], +struct Bpb { + bytes_per_sector: u16, + sectors_per_cluster: u8, + reserved_sector_count: u16, + num_fats: u8, + root_entry_count: u16, + total_sectors_16: u16, + fat_size_16: u16, + total_sectors_32: u32, + fat_size_32: u32, + root_cluster: u32, } -impl BiosParameterBlock { - pub fn deserialize(bytes: &[u8]) -> Self { - let (&bytes_per_sector, bytes) = split_array_ref(bytes); - let (&[sectors_per_cluster], bytes) = split_array_ref(bytes); - let (&reserved_sectors, bytes) = split_array_ref(bytes); - let (&[fats], bytes) = split_array_ref(bytes); - let (&root_entries, bytes) = split_array_ref(bytes); - let (&total_sectors_16, bytes) = split_array_ref(bytes); - let (&[media], bytes) = split_array_ref(bytes); - let (§ors_per_fat_16, bytes) = split_array_ref(bytes); - let (§ors_per_track, bytes) = split_array_ref(bytes); - let (&heads, bytes) = split_array_ref(bytes); - let (&hidden_sectors, bytes) = split_array_ref(bytes); - let (&total_sectors_32, bytes) = split_array_ref(bytes); - - let mut bpb = Self { - bytes_per_sector: u16::from_le_bytes(bytes_per_sector), - sectors_per_cluster, - reserved_sectors: u16::from_le_bytes(reserved_sectors), - fats, - root_entries: u16::from_le_bytes(root_entries), - total_sectors_16: u16::from_le_bytes(total_sectors_16), - media, - sectors_per_fat_16: u16::from_le_bytes(sectors_per_fat_16), - sectors_per_track: u16::from_le_bytes(sectors_per_track), - heads: u16::from_le_bytes(heads), - hidden_sectors: u32::from_le_bytes(hidden_sectors), - total_sectors_32: u32::from_le_bytes(total_sectors_32), - ..Self::default() - }; - - let (§ors_per_fat_32, bytes) = split_array_ref(bytes); - let (&extended_flags, bytes) = split_array_ref(bytes); - let (&fs_version, bytes) = split_array_ref(bytes); - let (&root_dir_first_cluster, bytes) = split_array_ref(bytes); - let (&fs_info_sector, bytes) = split_array_ref(bytes); - let (&backup_boot_sector, bytes) = split_array_ref(bytes); - let (&reserved_0, bytes) = split_array_ref(bytes); - - if bpb.is_fat32() { - bpb.sectors_per_fat_32 = u32::from_le_bytes(sectors_per_fat_32); - bpb.extended_flags = u16::from_le_bytes(extended_flags); - bpb.fs_version = u16::from_le_bytes(fs_version); - bpb.root_dir_first_cluster = u32::from_le_bytes(root_dir_first_cluster); - bpb.fs_info_sector = u16::from_le_bytes(fs_info_sector); - bpb.backup_boot_sector = u16::from_le_bytes(backup_boot_sector); - bpb.reserved_0 = reserved_0; +impl Bpb { + fn parse(disk: &mut D) -> Self { + let mut raw = [0u8; 512]; + disk.read_exact(&mut raw); + + let bytes_per_sector = u16::from_le_bytes(raw[11..13].try_into().unwrap()); + let sectors_per_cluster = raw[13]; + let reserved_sector_count = u16::from_le_bytes(raw[14..16].try_into().unwrap()); + let num_fats = raw[16]; + let root_entry_count = u16::from_le_bytes(raw[17..19].try_into().unwrap()); + let fat_size_16 = u16::from_le_bytes(raw[22..24].try_into().unwrap()); + + let total_sectors_16 = u16::from_le_bytes(raw[19..21].try_into().unwrap()); + let total_sectors_32 = u32::from_le_bytes(raw[32..36].try_into().unwrap()); + + let root_cluster; + let fat_size_32; + + if (total_sectors_16 == 0) && (total_sectors_32 != 0) { + // FAT32 + fat_size_32 = u32::from_le_bytes(raw[36..40].try_into().unwrap()); + root_cluster = u32::from_le_bytes(raw[44..48].try_into().unwrap()); + } else if (total_sectors_16 != 0) && (total_sectors_32 == 0) { + // FAT12 or FAT16 + fat_size_32 = 0; + root_cluster = 0; + } else { + panic!("ExactlyOneTotalSectorsFieldMustBeZero"); } - let (&[drive_num], bytes) = split_array_ref(bytes); - let (&[reserved_1], bytes) = split_array_ref(bytes); - let (&[ext_sig], bytes) = split_array_ref(bytes); - let (&volume_id, bytes) = split_array_ref(bytes); - let (&volume_label, bytes) = split_array_ref(bytes); - let (&fs_type_label, bytes) = split_array_ref(bytes); - - bpb.drive_num = drive_num; - bpb.reserved_1 = reserved_1; - bpb.ext_sig = ext_sig; // 0x29 - bpb.volume_id = u32::from_le_bytes(volume_id); - bpb.volume_label = volume_label; - bpb.fs_type_label = fs_type_label; - - // when the extended boot signature is anything other than 0x29, the fields are invalid - if bpb.ext_sig != 0x29 { - // fields after ext_sig are not used - clean them - bpb.volume_id = 0; - bpb.volume_label = [0; 11]; - bpb.fs_type_label = [0; 8]; + Self { + bytes_per_sector, + sectors_per_cluster, + reserved_sector_count, + num_fats, + root_entry_count, + total_sectors_16, + fat_size_16, + total_sectors_32, + fat_size_32, + root_cluster, } - - bpb } - pub(crate) fn is_fat32(&self) -> bool { - // because this field must be zero on FAT32, and - // because it must be non-zero on FAT12/FAT16, - // this provides a simple way to detect FAT32 - self.sectors_per_fat_16 == 0 - } - - pub(crate) fn sectors_per_fat(&self) -> u32 { - if self.is_fat32() { - self.sectors_per_fat_32 + fn fat_size_in_sectors(&self) -> u32 { + if self.fat_size_16 != 0 && self.fat_size_32 == 0 { + self.fat_size_16 as u32 } else { - u32::from(self.sectors_per_fat_16) + debug_assert!(self.fat_size_16 == 0 && self.fat_size_32 != 0); + self.fat_size_32 } } - pub(crate) fn total_sectors(&self) -> u32 { - if self.total_sectors_16 == 0 { - self.total_sectors_32 + fn count_of_clusters(&self) -> u32 { + let root_dir_sectors = ((self.root_entry_count as u32 * 32) + + (self.bytes_per_sector as u32 - 1)) + / self.bytes_per_sector as u32; + let total_sectors = if self.total_sectors_16 != 0 { + self.total_sectors_16 as u32 } else { - u32::from(self.total_sectors_16) - } + self.total_sectors_32 + }; + let data_sectors = total_sectors + - (self.reserved_sector_count as u32 + + (self.num_fats as u32 * self.fat_size_in_sectors()) + + root_dir_sectors); + data_sectors / self.sectors_per_cluster as u32 } - pub(crate) fn reserved_sectors(&self) -> u32 { - u32::from(self.reserved_sectors) + fn fat_type(&self) -> FatType { + let count_of_clusters = self.count_of_clusters(); + if count_of_clusters < 4085 { + FatType::Fat12 + } else if count_of_clusters < 65525 { + FatType::Fat16 + } else { + FatType::Fat32 + } } - pub(crate) fn root_dir_sectors(&self) -> u32 { - let root_dir_bytes = u32::from(self.root_entries) * DIR_ENTRY_SIZE; - (root_dir_bytes + u32::from(self.bytes_per_sector) - 1) / u32::from(self.bytes_per_sector) + fn root_directory_size(&self) -> usize { + if self.fat_type() == FatType::Fat32 { + debug_assert_eq!(self.root_entry_count, 0); + } + self.root_entry_count as usize * DIRECTORY_ENTRY_BYTES } - pub(crate) fn sectors_per_all_fats(&self) -> u32 { - u32::from(self.fats) * self.sectors_per_fat() + fn root_directory_offset(&self) -> u64 { + (self.reserved_sector_count as u64 + (self.num_fats as u64 * self.fat_size_16 as u64)) + * self.bytes_per_sector as u64 } +} - pub(crate) fn first_data_sector(&self) -> u32 { - let root_dir_sectors = self.root_dir_sectors(); - let fat_sectors = self.sectors_per_all_fats(); - self.reserved_sectors() + fat_sectors + root_dir_sectors - } +pub struct FileSystem { + disk: D, + bpb: Bpb, +} - pub(crate) fn total_clusters(&self) -> u32 { - let total_sectors = self.total_sectors(); - let first_data_sector = self.first_data_sector(); - let data_sectors = total_sectors - first_data_sector; - data_sectors / u32::from(self.sectors_per_cluster) +impl FileSystem { + pub fn parse(mut disk: D) -> Self { + Self { + bpb: Bpb::parse(&mut disk), + disk, + } } - pub fn fat_type(&self) -> FatType { - FatType::from_clusters(self.total_clusters()) + pub fn lookup_file(&mut self, path: &str) -> Option { + todo!(); } - /// Returns a root directory object allowing for futher penetration of a filesystem structure. - pub fn check_root_dir(&self) { - panic!("check_root_dir: fat type {:?}", self.fat_type()) + fn read_root_dir(&mut self) { + match self.bpb.fat_type() { + FatType::Fat32 => { + self.bpb.root_cluster; + } + FatType::Fat12 | FatType::Fat16 => { + self.bpb.root_directory_offset(); + self.bpb.root_directory_size(); + } + } } } -#[derive(Debug)] -pub enum FatType { - /// 12 bits per FAT entry +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum FatType { Fat12, - /// 16 bits per FAT entry Fat16, - /// 32 bits per FAT entry Fat32, } - -impl FatType { - const FAT16_MIN_CLUSTERS: u32 = 4085; - const FAT32_MIN_CLUSTERS: u32 = 65525; - const FAT32_MAX_CLUSTERS: u32 = 0x0FFF_FFF4; - - pub(crate) fn from_clusters(total_clusters: u32) -> Self { - if total_clusters < Self::FAT16_MIN_CLUSTERS { - FatType::Fat12 - } else if total_clusters < Self::FAT32_MIN_CLUSTERS { - FatType::Fat16 - } else { - FatType::Fat32 - } - } -} diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 948eb7cf..1df81c1c 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -3,14 +3,15 @@ use byteorder::{ByteOrder, LittleEndian}; use core::{fmt::Write as _, slice}; -use fatfs::{FsOptions, Read}; -use mbr_nostd::{MasterBootRecord, PartitionTable, PartitionTableEntry, PartitionType}; +use disk::Read; +use mbr_nostd::{PartitionTableEntry, PartitionType}; mod dap; mod disk; mod fat; +// mod fat_old; // mod fat_bpb; -mod mini_fat; +// mod mini_fat; mod screen; /// We use this partition type to store the second bootloader stage; @@ -68,20 +69,17 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { current_offset: 0, }; - let mut buffer = [0u8; 512]; - disk.read_exact(&mut buffer).unwrap(); + let mut fs = fat::FileSystem::parse(disk.clone()); + let kernel = fs + .lookup_file("kernel-x86_64") + .expect("no `kernel-x86_64` file found"); screen::print_char(b'2'); - let boot_sector = mini_fat::Bpb::parse(&buffer).unwrap(); - loop {} - writeln!(screen::Writer, "BPB: {boot_sector:?}").unwrap(); - + let mut buffer = [0u8; 512]; + disk.read_exact(&mut buffer); screen::print_char(b'3'); - panic!("foo"); - let fat = fatfs::FileSystem::new(disk, FsOptions::new().update_accessed_date(false)).unwrap(); - screen::print_char(b'2'); - + let kernel_first_cluster = todo!(); loop {} // try to parse FAT file system diff --git a/build.rs b/build.rs index 02da99c3..eebd797b 100644 --- a/build.rs +++ b/build.rs @@ -122,6 +122,7 @@ fn build_bios_second_stage(out_dir: &Path) -> PathBuf { } cmd.arg("--locked"); cmd.arg("--target").arg("x86-16bit-second-stage.json"); + cmd.arg("--profile").arg("second-stage"); cmd.arg("-Zbuild-std=core") .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); From 96d2bdbae6fdc91c8df958fd09d4207a3ab20020 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 12 Jun 2022 20:44:17 +0200 Subject: [PATCH 109/226] Read root dir entries from FAT partition --- bios/second_stage/src/fat.rs | 141 +++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 6 deletions(-) diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index bf485af1..6bf81d1e 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -1,11 +1,17 @@ // based on https://crates.io/crates/mini_fat -use crate::disk::Read; +use crate::{ + disk::{Read, Seek, SeekFrom}, + screen, +}; +use core::{char::DecodeUtf16Error, fmt::Write as _}; const DIRECTORY_ENTRY_BYTES: usize = 32; const UNUSED_ENTRY_PREFIX: u8 = 0xE5; const END_OF_DIRECTORY_PREFIX: u8 = 0; +static mut BUFFER: [u8; 0x4000] = [0; 0x4000]; + pub struct File { first_cluster: u32, file_size: u32, @@ -31,7 +37,7 @@ struct Bpb { } impl Bpb { - fn parse(disk: &mut D) -> Self { + fn parse(disk: &mut D) -> Self { let mut raw = [0u8; 512]; disk.read_exact(&mut raw); @@ -128,7 +134,7 @@ pub struct FileSystem { bpb: Bpb, } -impl FileSystem { +impl FileSystem { pub fn parse(mut disk: D) -> Self { Self { bpb: Bpb::parse(&mut disk), @@ -137,17 +143,48 @@ impl FileSystem { } pub fn lookup_file(&mut self, path: &str) -> Option { + let root = self.read_root_dir(); + for entry in root { + write!(screen::Writer, "entry: ").unwrap(); + match entry { + Ok(RawDirectoryEntry::Normal(entry)) => { + writeln!(screen::Writer, "{}", entry.short_filename_main).unwrap(); + } + Ok(RawDirectoryEntry::LongName(entry)) => { + for c in entry.name() { + match c { + Ok(c) => write!(screen::Writer, "{c}").unwrap(), + Err(_) => write!(screen::Writer, "X").unwrap(), + } + } + writeln!(screen::Writer).unwrap(); + } + Err(()) => writeln!(screen::Writer, "").unwrap(), + } + } todo!(); } - fn read_root_dir(&mut self) { + fn read_root_dir<'a>(&'a mut self) -> impl Iterator> + 'a { match self.bpb.fat_type() { FatType::Fat32 => { self.bpb.root_cluster; + unimplemented!(); } FatType::Fat12 | FatType::Fat16 => { - self.bpb.root_directory_offset(); - self.bpb.root_directory_size(); + let root_directory_size = self.bpb.root_directory_size(); + let buffer = unsafe { &mut BUFFER[..] }; + assert!(root_directory_size <= buffer.len()); + let raw = &mut buffer[..root_directory_size]; + + self.disk + .seek(SeekFrom::Start(self.bpb.root_directory_offset())); + self.disk.read_exact(raw); + + raw.chunks(DIRECTORY_ENTRY_BYTES) + .take_while(|raw_entry| raw_entry[0] != END_OF_DIRECTORY_PREFIX) + .filter(|raw_entry| raw_entry[0] != UNUSED_ENTRY_PREFIX) + .map(RawDirectoryEntry::parse) } } } @@ -159,3 +196,95 @@ enum FatType { Fat16, Fat32, } + +#[derive(Debug)] +struct RawDirectoryEntryNormal<'a> { + short_filename_main: &'a str, + short_filename_extension: &'a str, + attributes: u8, + first_cluster: u32, + file_size: u32, +} + +#[derive(Debug)] +struct RawDirectoryEntryLongName<'a> { + order: u8, + name_1: &'a [u8], + name_2: &'a [u8], + name_3: &'a [u8], + attributes: u8, + checksum: u8, +} + +impl<'a> RawDirectoryEntryLongName<'a> { + pub fn name(&self) -> impl Iterator> + 'a { + let iter = self + .name_1 + .chunks(2) + .chain(self.name_2.chunks(2)) + .chain(self.name_3.chunks(2)) + .map(|c| u16::from_le_bytes(c.try_into().unwrap())) + .take_while(|&c| c != 0); + char::decode_utf16(iter) + } +} + +#[derive(Debug)] +enum RawDirectoryEntry<'a> { + Normal(RawDirectoryEntryNormal<'a>), + LongName(RawDirectoryEntryLongName<'a>), +} + +impl<'a> RawDirectoryEntry<'a> { + fn parse(raw: &'a [u8]) -> Result { + let attributes = raw[11]; + if attributes == directory_attributes::LONG_NAME { + let order = raw[0]; + let name_1 = &raw[1..11]; + let checksum = raw[13]; + let name_2 = &raw[14..26]; + let name_3 = &raw[28..32]; + + Ok(Self::LongName(RawDirectoryEntryLongName { + order, + name_1, + name_2, + name_3, + attributes, + checksum, + })) + } else { + fn slice_to_string(slice: &[u8]) -> Result<&str, ()> { + const SKIP_SPACE: u8 = 0x20; + let mut iter = slice.into_iter().copied(); + let start_idx = iter.position(|c| c != SKIP_SPACE).ok_or(())?; + let end_idx = start_idx + iter.position(|c| c == SKIP_SPACE).unwrap_or(slice.len()); + + core::str::from_utf8(&slice[start_idx..end_idx]).map_err(|_| ()) + } + let short_filename_main = slice_to_string(&raw[0..8])?; + let short_filename_extension = slice_to_string(&raw[8..11])?; + let first_cluster_hi = u16::from_le_bytes(raw[20..22].try_into().unwrap()); + let first_cluster_lo = u16::from_le_bytes(raw[26..28].try_into().unwrap()); + let first_cluster = ((first_cluster_hi as u32) << 16) | (first_cluster_lo as u32); + let file_size = u32::from_le_bytes(raw[28..32].try_into().unwrap()); + Ok(Self::Normal(RawDirectoryEntryNormal { + short_filename_main, + short_filename_extension, + attributes, + first_cluster, + file_size, + })) + } + } +} + +mod directory_attributes { + pub const READ_ONLY: u8 = 0x01; + pub const HIDDEN: u8 = 0x02; + pub const SYSTEM: u8 = 0x04; + pub const VOLUME_ID: u8 = 0x08; + pub const DIRECTORY: u8 = 0x10; + + pub const LONG_NAME: u8 = READ_ONLY | HIDDEN | SYSTEM | VOLUME_ID; +} From eef2109861d718e0da82b3c5e4c2d9308d34da56 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 12 Jun 2022 22:53:23 +0200 Subject: [PATCH 110/226] Use segment-based addressing for DAP to support loading larger second stages --- bios/boot_sector/src/dap.rs | 11 ++++++-- bios/boot_sector/src/main.rs | 55 ++++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/bios/boot_sector/src/dap.rs b/bios/boot_sector/src/dap.rs index 7f0b583a..4db49971 100644 --- a/bios/boot_sector/src/dap.rs +++ b/bios/boot_sector/src/dap.rs @@ -18,13 +18,18 @@ pub struct DiskAddressPacket { } impl DiskAddressPacket { - pub fn from_lba(memory_buffer_start: u16, start_lba: u64, number_of_sectors: u16) -> Self { + pub fn from_lba( + start_lba: u64, + number_of_sectors: u16, + target_offset: u16, + target_segment: u16, + ) -> Self { Self { packet_size: 0x10, zero: 0, number_of_sectors, - offset: memory_buffer_start, - segment: 0, + offset: target_offset, + segment: target_segment, start_lba, } } diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 8570c75a..2743bf14 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -4,7 +4,7 @@ use core::{arch::global_asm, slice}; use error::NO_SECOND_STAGE_PARTITION; -use fail::{fail, print_char, UnwrapOrFail}; +use fail::{print_char, UnwrapOrFail}; global_asm!(include_str!("boot.s")); @@ -14,15 +14,10 @@ mod fail; mod mbr; extern "C" { - static _mbr_start: u8; static _partition_table: u8; static _second_stage_start: u8; } -unsafe fn mbr_start() -> *const u8 { - unsafe { &_mbr_start } -} - unsafe fn partition_table_raw() -> *const u8 { unsafe { &_partition_table } } @@ -38,24 +33,36 @@ pub extern "C" fn first_stage(disk_number: u16) { print_char(b'1'); let partition_table = &unsafe { slice::from_raw_parts(partition_table_raw(), 16 * 4) }; let second_stage_partition = - mbr::boot_partition(partition_table).unwrap_or_fail(NO_SECOND_STAGE_PARTITION); + // mbr::boot_partition(partition_table).unwrap_or_fail(NO_SECOND_STAGE_PARTITION); + mbr::get_partition(partition_table, 0); // load second stage partition into memory print_char(b'2'); - let target_addr = u16::try_from(second_stage_start() as usize).unwrap_or_fail(b'a'); - let dap = dap::DiskAddressPacket::from_lba( - target_addr, - second_stage_partition.logical_block_address.into(), - second_stage_partition - .sector_count - .try_into() - .unwrap_or_fail(b'b'), - ); - unsafe { - dap.perform_load(disk_number); - } - if second_stage_partition.sector_count == 0 { - fail(b'c'); + let entry_point_address = second_stage_start() as u32; + + let mut start_lba = second_stage_partition.logical_block_address.into(); + let mut number_of_sectors = second_stage_partition.sector_count; + let mut target_addr = entry_point_address; + + loop { + let sectors = u32::min(number_of_sectors, 32) as u16; + let dap = dap::DiskAddressPacket::from_lba( + start_lba, + sectors, + (target_addr & 0b1111) as u16, + (target_addr >> 4).try_into().unwrap_or_fail(b'a'), + ); + unsafe { + dap.perform_load(disk_number); + } + + start_lba += u64::from(sectors); + number_of_sectors -= u32::from(sectors); + target_addr = target_addr + u32::from(sectors) * 512; + + if number_of_sectors == 0 { + break; + } } // jump to second stage @@ -63,9 +70,9 @@ pub extern "C" fn first_stage(disk_number: u16) { let second_stage_entry_point: extern "C" fn( disk_number: u16, partition_table_start: *const u8, - ) = unsafe { core::mem::transmute(target_addr as *const ()) }; - let mbr_start = unsafe { partition_table_raw() }; - second_stage_entry_point(disk_number, mbr_start); + ) = unsafe { core::mem::transmute(entry_point_address as *const ()) }; + let partition_table_start = unsafe { partition_table_raw() }; + second_stage_entry_point(disk_number, partition_table_start); for _ in 0..10 { print_char(b'R'); } From 3de1a39115ec5fdba884318d21165462d559019c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 12 Jun 2022 22:53:46 +0200 Subject: [PATCH 111/226] Reorder second stage sections --- bios/second_stage/second-stage-link.ld | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/bios/second_stage/second-stage-link.ld b/bios/second_stage/second-stage-link.ld index 7ecbc7f2..0d6371c2 100644 --- a/bios/second_stage/second-stage-link.ld +++ b/bios/second_stage/second-stage-link.ld @@ -6,11 +6,17 @@ SECTIONS { .start : { *(.start) } - .rodata : { + .text : ALIGN(512) { + *(.text .text.*) + } + .rodata : ALIGN(512) { *(.rodata .rodata.*) } - .text : { - *(.text .text.*) + .bss : ALIGN(512) { + *(.bss .bss.*) + } + .data : ALIGN(512) { + *(.data .data.*) } . = ALIGN(512); From a183966d8d00a7ff6e23905e4b7feef725acaf12 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 12 Jun 2022 22:53:59 +0200 Subject: [PATCH 112/226] Use pie relocation model for second stage --- x86-16bit-second-stage.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x86-16bit-second-stage.json b/x86-16bit-second-stage.json index f5ed774e..ee6f67ad 100644 --- a/x86-16bit-second-stage.json +++ b/x86-16bit-second-stage.json @@ -16,5 +16,5 @@ "panic-strategy": "abort", "os": "none", "vendor": "unknown", - "relocation-model": "static" + "relocation-model": "pie" } From 1d7ff1f0627fa33dab3f811af150a547e1f48172 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 12 Jun 2022 22:54:47 +0200 Subject: [PATCH 113/226] Implement looking up `kernel-x86_64` file on FAT partition --- bios/second_stage/src/dap.rs | 22 ++--- bios/second_stage/src/disk.rs | 42 +++++---- bios/second_stage/src/fat.rs | 159 +++++++++++++++++++++++++++++----- bios/second_stage/src/main.rs | 41 ++------- 4 files changed, 175 insertions(+), 89 deletions(-) diff --git a/bios/second_stage/src/dap.rs b/bios/second_stage/src/dap.rs index 40717d80..43d08f0e 100644 --- a/bios/second_stage/src/dap.rs +++ b/bios/second_stage/src/dap.rs @@ -18,24 +18,18 @@ pub struct DiskAddressPacket { } impl DiskAddressPacket { - pub fn new(memory_buffer_start: u16, file_offset: u64, bytes: u32) -> Self { - Self { - packet_size: 0x10, - zero: 0, - number_of_sectors: (bytes / 512) as u16, - offset: memory_buffer_start, - segment: 0, - start_lba: file_offset / 512, - } - } - - pub fn from_lba(memory_buffer_start: u16, start_lba: u64, number_of_sectors: u16) -> Self { + pub fn from_lba( + start_lba: u64, + number_of_sectors: u16, + target_addr: u16, + target_addr_segment: u16, + ) -> Self { Self { packet_size: 0x10, zero: 0, number_of_sectors, - offset: memory_buffer_start, - segment: 0, + offset: target_addr, + segment: target_addr_segment, start_lba, } } diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index c2f7d423..db448a7d 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -1,5 +1,5 @@ -use crate::{dap, screen, second_stage_end}; -use core::{fmt::Write as _, slice}; +use crate::{dap, screen}; +use core::fmt::Write as _; #[derive(Clone)] pub struct DiskAccess { @@ -11,24 +11,36 @@ pub struct DiskAccess { impl Read for DiskAccess { fn read_exact(&mut self, buf: &mut [u8]) { writeln!(screen::Writer, "read {} bytes", buf.len()).unwrap(); + assert_eq!(buf.len() % 512, 0); let end_addr = self.base_offset + self.current_offset + u64::try_from(buf.len()).unwrap(); - let start_lba = (self.base_offset + self.current_offset) / 512; + let mut start_lba = (self.base_offset + self.current_offset) / 512; let end_lba = (end_addr - 1) / 512; - let target_addr = u16::try_from(second_stage_end() as usize).unwrap(); - let dap = dap::DiskAddressPacket::from_lba( - target_addr, - start_lba, - u16::try_from(end_lba + 1 - start_lba).unwrap(), - ); - writeln!(screen::Writer, "dap: {dap:?}").unwrap(); - unsafe { - dap.perform_load(self.disk_number); - } + let mut number_of_sectors = end_lba + 1 - start_lba; + let mut target_addr = buf.as_ptr_range().start as u32; + + loop { + let sectors = u64::min(number_of_sectors, 32) as u16; + let dap = dap::DiskAddressPacket::from_lba( + start_lba, + sectors, + (target_addr & 0b1111) as u16, + (target_addr >> 4).try_into().unwrap(), + ); + writeln!(screen::Writer, "dap: {dap:?}").unwrap(); + unsafe { + dap.perform_load(self.disk_number); + } - let data = unsafe { slice::from_raw_parts(target_addr as *const u8, buf.len()) }; - buf.copy_from_slice(data); + start_lba += u64::from(sectors); + number_of_sectors -= u64::from(sectors); + target_addr = target_addr + u32::from(sectors) * 512; + + if number_of_sectors == 0 { + break; + } + } self.current_offset = end_addr; } diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index 6bf81d1e..188ee3f5 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -38,7 +38,10 @@ struct Bpb { impl Bpb { fn parse(disk: &mut D) -> Self { - let mut raw = [0u8; 512]; + let mut raw = { + let buffer = unsafe { &mut BUFFER[..] }; + &mut buffer[..512] + }; disk.read_exact(&mut raw); let bytes_per_sector = u16::from_le_bytes(raw[11..13].try_into().unwrap()); @@ -142,27 +145,50 @@ impl FileSystem { } } - pub fn lookup_file(&mut self, path: &str) -> Option { - let root = self.read_root_dir(); - for entry in root { - write!(screen::Writer, "entry: ").unwrap(); - match entry { - Ok(RawDirectoryEntry::Normal(entry)) => { - writeln!(screen::Writer, "{}", entry.short_filename_main).unwrap(); - } - Ok(RawDirectoryEntry::LongName(entry)) => { - for c in entry.name() { - match c { - Ok(c) => write!(screen::Writer, "{c}").unwrap(), - Err(_) => write!(screen::Writer, "X").unwrap(), - } - } - writeln!(screen::Writer).unwrap(); + pub fn find_file_in_root_dir(&mut self, name: &str) -> Option { + let mut root_entries = self.read_root_dir().filter_map(|e| e.ok()); + let raw_entry = root_entries.find(|e| e.eq_name(name))?; + + let entry = match raw_entry { + RawDirectoryEntry::Normal(entry) => DirectoryEntry { + short_name: entry.short_filename_main, + short_name_extension: entry.short_filename_extension, + long_name_1: &[], + long_name_2: &[], + long_name_3: &[], + file_size: entry.file_size, + first_cluster: entry.first_cluster, + attributes: entry.attributes, + }, + RawDirectoryEntry::LongName(long_name) => match root_entries.next() { + Some(RawDirectoryEntry::LongName(_)) => unimplemented!(), + Some(RawDirectoryEntry::Normal(entry)) => DirectoryEntry { + short_name: entry.short_filename_main, + short_name_extension: entry.short_filename_extension, + long_name_1: long_name.name_1, + long_name_2: long_name.name_2, + long_name_3: long_name.name_3, + file_size: entry.file_size, + first_cluster: entry.first_cluster, + attributes: entry.attributes, + }, + None => { + panic!("next none"); + return None; } - Err(()) => writeln!(screen::Writer, "").unwrap(), - } + }, + }; + + writeln!(screen::Writer, "entry: {entry:?}").unwrap(); + + if entry.is_directory() { + None + } else { + Some(File { + first_cluster: entry.first_cluster, + file_size: entry.file_size, + }) } - todo!(); } fn read_root_dir<'a>(&'a mut self) -> impl Iterator> + 'a { @@ -197,6 +223,76 @@ enum FatType { Fat32, } +#[derive(Clone)] +pub struct DirectoryEntry<'a> { + short_name: &'a str, + short_name_extension: &'a str, + long_name_1: &'a [u8], + long_name_2: &'a [u8], + long_name_3: &'a [u8], + file_size: u32, + first_cluster: u32, + attributes: u8, +} + +impl<'a> DirectoryEntry<'a> { + pub fn name(&self) -> impl Iterator> + 'a { + let mut long_name = { + let iter = self + .long_name_1 + .chunks(2) + .chain(self.long_name_2.chunks(2)) + .chain(self.long_name_3.chunks(2)) + .map(|c| u16::from_le_bytes(c.try_into().unwrap())) + .take_while(|&c| c != 0); + char::decode_utf16(iter).peekable() + }; + let short_name = { + let iter = self.short_name.chars(); + let extension_iter = { + let raw = ".".chars().chain(self.short_name_extension.chars()); + raw.take(if self.short_name_extension.is_empty() { + 0 + } else { + self.short_name_extension.len() + 1 + }) + }; + iter.chain(extension_iter).map(Ok) + }; + + if long_name.peek().is_some() { + long_name.chain(short_name.take(0)) + } else { + long_name.chain(short_name.take(usize::MAX)) + } + } + + pub fn is_directory(&self) -> bool { + self.attributes & directory_attributes::DIRECTORY != 0 + } +} + +impl core::fmt::Debug for DirectoryEntry<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + struct NamePrinter<'a>(&'a DirectoryEntry<'a>); + impl core::fmt::Debug for NamePrinter<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + for char in self.0.name().filter_map(|e| e.ok()) { + write!(f, "{char}")?; + } + Ok(()) + } + } + + f.debug_struct("DirectoryEntry") + .field("name", &NamePrinter(self)) + .field("file_size", &self.file_size) + .field("first_cluster", &self.first_cluster) + .field("attributes", &self.attributes) + .finish() + } +} + #[derive(Debug)] struct RawDirectoryEntryNormal<'a> { short_filename_main: &'a str, @@ -257,10 +353,14 @@ impl<'a> RawDirectoryEntry<'a> { fn slice_to_string(slice: &[u8]) -> Result<&str, ()> { const SKIP_SPACE: u8 = 0x20; let mut iter = slice.into_iter().copied(); - let start_idx = iter.position(|c| c != SKIP_SPACE).ok_or(())?; - let end_idx = start_idx + iter.position(|c| c == SKIP_SPACE).unwrap_or(slice.len()); - - core::str::from_utf8(&slice[start_idx..end_idx]).map_err(|_| ()) + match iter.position(|c| c != SKIP_SPACE) { + Some(start_idx) => { + let end_idx = + start_idx + iter.position(|c| c == SKIP_SPACE).unwrap_or(slice.len()); + core::str::from_utf8(&slice[start_idx..end_idx]).map_err(|_| ()) + } + None => Ok(""), + } } let short_filename_main = slice_to_string(&raw[0..8])?; let short_filename_extension = slice_to_string(&raw[8..11])?; @@ -277,6 +377,17 @@ impl<'a> RawDirectoryEntry<'a> { })) } } + + pub fn eq_name(&self, name: &str) -> bool { + match self { + RawDirectoryEntry::Normal(entry) => entry + .short_filename_main + .chars() + .chain(entry.short_filename_extension.chars()) + .eq(name.chars()), + RawDirectoryEntry::LongName(entry) => entry.name().eq(name.chars().map(Ok)), + } + } } mod directory_attributes { diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 1df81c1c..9947a225 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -3,7 +3,6 @@ use byteorder::{ByteOrder, LittleEndian}; use core::{fmt::Write as _, slice}; -use disk::Read; use mbr_nostd::{PartitionTableEntry, PartitionType}; mod dap; @@ -68,45 +67,15 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { base_offset: u64::from(fat_partition.logical_block_address) * 512, current_offset: 0, }; - - let mut fs = fat::FileSystem::parse(disk.clone()); - let kernel = fs - .lookup_file("kernel-x86_64") - .expect("no `kernel-x86_64` file found"); screen::print_char(b'2'); - let mut buffer = [0u8; 512]; - disk.read_exact(&mut buffer); + let mut fs = fat::FileSystem::parse(disk.clone()); screen::print_char(b'3'); - let kernel_first_cluster = todo!(); - loop {} - - // try to parse FAT file system - // let fat_slice = unsafe { - // slice::from_raw_parts( - // partition_buf as *const u8, - // usize::try_from(second_stage_partition.sector_count).unwrap_or_else(|_| fail(b'a')) - // * 512, - // ) - // }; - - // print_char(b'4'); - // let boot_sector = fat::BootSector::deserialize(fat_slice); - // let root_dir = boot_sector.bpb.root_dir_first_cluster; - // boot_sector.bpb.check_root_dir(); - - // print_char(b'5'); - - // TODO: get root dir - - // TODO: get offset of `second_stage` file - - // TODO: get offset of `kernel-x86_64` file - - // TODO: load `second_stage` file into memory - - // TODO: jump to `second_stage`, pass offset of `kernel-x86_64` and disk number as arguments + let kernel = fs + .find_file_in_root_dir("kernel-x86_64") + .expect("no `kernel-x86_64` file found"); + screen::print_char(b'4'); loop {} } From 3e7f6cf5be2b736def15fcb52d47eb0805dc77de Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 12 Jun 2022 22:57:16 +0200 Subject: [PATCH 114/226] Add TODOs for next steps --- bios/second_stage/src/main.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 9947a225..334ce811 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -77,6 +77,16 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { .expect("no `kernel-x86_64` file found"); screen::print_char(b'4'); + // TODO: Retrieve memory map + + // TODO: Set up protected mode, or unreal mode + + // TODO: Load `kernel` to protected mode address + + // TODO: Set up long mode with identity-mapping + + // TODO: Load third stage that uses `bootloader-common` crate and jump to it + loop {} } From b12a3cf24f96f42bb5c7092d80ace1d1b78cdc07 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 25 Jun 2022 12:25:18 +0200 Subject: [PATCH 115/226] Also remove `CARGO_ENCODED_RUSTFLAGS` env variable from build script commands --- build.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.rs b/build.rs index eebd797b..fa4ddec3 100644 --- a/build.rs +++ b/build.rs @@ -48,6 +48,7 @@ fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); let status = cmd .status() .expect("failed to run cargo install for uefi bootloader"); @@ -85,6 +86,7 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); // used by clippy let status = cmd .status() @@ -127,6 +129,7 @@ fn build_bios_second_stage(out_dir: &Path) -> PathBuf { .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); // used by clippy let status = cmd .status() From 06d3b9521cc8bac981068bafe0ab84b5c4a7e8c0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 25 Jun 2022 13:45:08 +0200 Subject: [PATCH 116/226] Read FAT clusters of kernel file --- bios/second_stage/src/disk.rs | 20 +++- bios/second_stage/src/fat.rs | 171 +++++++++++++++++++++++++++++++++- bios/second_stage/src/main.rs | 12 ++- 3 files changed, 192 insertions(+), 11 deletions(-) diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index db448a7d..8e83967c 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -9,8 +9,15 @@ pub struct DiskAccess { } impl Read for DiskAccess { - fn read_exact(&mut self, buf: &mut [u8]) { - writeln!(screen::Writer, "read {} bytes", buf.len()).unwrap(); + fn read_exact(&mut self, input_buf: &mut [u8]) { + writeln!(screen::Writer, "read {} bytes", input_buf.len()).unwrap(); + static mut TMP_BUF: [u8; 512] = [0; 512]; + let tmp_buf = unsafe { &mut TMP_BUF[..] }; + let (buf, copy_needed) = if input_buf.len() >= tmp_buf.len() { + (&mut input_buf[..], false) + } else { + (&mut tmp_buf[..], true) + }; assert_eq!(buf.len() % 512, 0); let end_addr = self.base_offset + self.current_offset + u64::try_from(buf.len()).unwrap(); @@ -43,6 +50,15 @@ impl Read for DiskAccess { } self.current_offset = end_addr; + + if copy_needed { + let len = input_buf.len(); + for i in 0..len { + input_buf[i] = tmp_buf[i]; + } + } + + writeln!(screen::Writer, "read {} bytes done", input_buf.len()).unwrap(); } } diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index 188ee3f5..f4c58b9e 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -1,4 +1,4 @@ -// based on https://crates.io/crates/mini_fat +// based on https://crates.io/crates/mini_fat by https://github.com/gridbugs use crate::{ disk::{Read, Seek, SeekFrom}, @@ -10,8 +10,6 @@ const DIRECTORY_ENTRY_BYTES: usize = 32; const UNUSED_ENTRY_PREFIX: u8 = 0xE5; const END_OF_DIRECTORY_PREFIX: u8 = 0; -static mut BUFFER: [u8; 0x4000] = [0; 0x4000]; - pub struct File { first_cluster: u32, file_size: u32, @@ -38,8 +36,9 @@ struct Bpb { impl Bpb { fn parse(disk: &mut D) -> Self { + static mut BPB_BUFFER: [u8; 512] = [0; 512]; let mut raw = { - let buffer = unsafe { &mut BUFFER[..] }; + let buffer = unsafe { &mut BPB_BUFFER[..] }; &mut buffer[..512] }; disk.read_exact(&mut raw); @@ -130,6 +129,25 @@ impl Bpb { (self.reserved_sector_count as u64 + (self.num_fats as u64 * self.fat_size_16 as u64)) * self.bytes_per_sector as u64 } + + fn maximum_valid_cluster(&self) -> u32 { + self.count_of_clusters() + 1 + } + + fn fat_offset(&self) -> u64 { + self.reserved_sector_count as u64 * self.bytes_per_sector as u64 + } + + fn data_offset(&self) -> u64 { + self.root_directory_size() as u64 + + ((self.reserved_sector_count as u64 + + self.fat_size_in_sectors() as u64 * self.num_fats as u64) + * self.bytes_per_sector as u64) + } + + pub fn bytes_per_cluster(&self) -> u32 { + self.bytes_per_sector as u32 * self.sectors_per_cluster as u32 + } } pub struct FileSystem { @@ -199,7 +217,8 @@ impl FileSystem { } FatType::Fat12 | FatType::Fat16 => { let root_directory_size = self.bpb.root_directory_size(); - let buffer = unsafe { &mut BUFFER[..] }; + static mut ROOT_DIR_BUFFER: [u8; 0x4000] = [0; 0x4000]; + let buffer = unsafe { &mut ROOT_DIR_BUFFER[..] }; assert!(root_directory_size <= buffer.len()); let raw = &mut buffer[..root_directory_size]; @@ -214,6 +233,68 @@ impl FileSystem { } } } + + pub fn file_clusters<'a>( + &'a mut self, + file: &File, + ) -> impl Iterator> + 'a { + Traverser { + current_entry: file.first_cluster, + bpb: &self.bpb, + disk: &mut self.disk, + } + } +} + +struct Traverser<'a, D> { + disk: &'a mut D, + current_entry: u32, + bpb: &'a Bpb, +} + +impl Traverser<'_, D> +where + D: Read + Seek, +{ + fn next_cluster(&mut self) -> Result, ()> { + let entry = classify_fat_entry( + self.bpb.fat_type(), + self.current_entry, + self.bpb.maximum_valid_cluster(), + ) + .map_err(|_| ())?; + let entry = match entry { + FileFatEntry::AllocatedCluster(cluster) => cluster, + FileFatEntry::EndOfFile => return Ok(None), + }; + let cluster_start = + self.bpb.data_offset() + (u64::from(entry) - 2) * self.bpb.bytes_per_cluster() as u64; + // handle_read( + // self.traverser.handle, + // cluster_start, + // self.traverser.bpb.bytes_per_cluster() as usize, + // &mut self.traverser.buf, + // )?; + // if let Some(t) = f(&self.traverser.buf) { + // break Ok(Some(t)); + // } + let next_entry = + fat_entry_of_nth_cluster(self.disk, self.bpb.fat_type(), self.bpb.fat_offset(), entry); + self.current_entry = next_entry; + + Ok(Some(entry)) + } +} + +impl Iterator for Traverser<'_, D> +where + D: Read + Seek, +{ + type Item = Result; + + fn next(&mut self) -> Option { + self.next_cluster().transpose() + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -223,6 +304,16 @@ enum FatType { Fat32, } +impl FatType { + fn fat_entry_defective(self) -> u32 { + match self { + Self::Fat12 => 0xFF7, + Self::Fat16 => 0xFFF7, + Self::Fat32 => 0x0FFFFFF7, + } + } +} + #[derive(Clone)] pub struct DirectoryEntry<'a> { short_name: &'a str, @@ -399,3 +490,73 @@ mod directory_attributes { pub const LONG_NAME: u8 = READ_ONLY | HIDDEN | SYSTEM | VOLUME_ID; } + +fn classify_fat_entry( + fat_type: FatType, + entry: u32, + maximum_valid_cluster: u32, +) -> Result { + match entry { + 0 => Err(FatLookupError::FreeCluster), + 1 => Err(FatLookupError::UnspecifiedEntryOne), + entry => { + if entry <= maximum_valid_cluster { + Ok(FileFatEntry::AllocatedCluster(entry)) + } else if entry < fat_type.fat_entry_defective() { + Err(FatLookupError::ReservedEntry) + } else if entry == fat_type.fat_entry_defective() { + Err(FatLookupError::DefectiveCluster) + } else { + Ok(FileFatEntry::EndOfFile) + } + } + } +} + +#[derive(Debug)] +pub enum FatLookupError { + FreeCluster, + DefectiveCluster, + UnspecifiedEntryOne, + ReservedEntry, +} + +enum FileFatEntry { + AllocatedCluster(u32), + EndOfFile, +} + +fn fat_entry_of_nth_cluster(disk: &mut D, fat_type: FatType, fat_start: u64, n: u32) -> u32 +where + D: Seek + Read, +{ + debug_assert!(n >= 2); + match fat_type { + FatType::Fat32 => { + let base = n as u64 * 4; + disk.seek(SeekFrom::Start(fat_start + base)); + let mut buf = [0; 4]; + disk.read_exact(&mut buf); + u32::from_le_bytes(buf) & 0x0FFFFFFF + } + FatType::Fat16 => { + let base = n as u64 * 2; + disk.seek(SeekFrom::Start(fat_start + base)); + let mut buf = [0; 2]; + disk.read_exact(&mut buf); + u16::from_le_bytes(buf) as u32 + } + FatType::Fat12 => { + let base = n as u64 + (n as u64 / 2); + disk.seek(SeekFrom::Start(fat_start + base)); + let mut buf = [0; 2]; + disk.read_exact(&mut buf); + let entry16 = u16::from_le_bytes(buf); + if n & 1 == 0 { + (entry16 & 0xFFF) as u32 + } else { + (entry16 >> 4) as u32 + } + } + } +} diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 334ce811..c333eb19 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -59,7 +59,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { fat_partition.partition_type, PartitionType::Fat12(_) | PartitionType::Fat16(_) | PartitionType::Fat32(_) )); - screen::print_char(b'1'); + writeln!(screen::Writer, "1").unwrap(); // load fat partition let mut disk = disk::DiskAccess { @@ -67,15 +67,19 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { base_offset: u64::from(fat_partition.logical_block_address) * 512, current_offset: 0, }; - screen::print_char(b'2'); + writeln!(screen::Writer, "2").unwrap(); let mut fs = fat::FileSystem::parse(disk.clone()); - screen::print_char(b'3'); + writeln!(screen::Writer, "3").unwrap(); let kernel = fs .find_file_in_root_dir("kernel-x86_64") .expect("no `kernel-x86_64` file found"); - screen::print_char(b'4'); + writeln!(screen::Writer, "4").unwrap(); + + for cluster in fs.file_clusters(&kernel) { + writeln!(screen::Writer, "kernel cluster: {cluster:?}").unwrap(); + } // TODO: Retrieve memory map From a958a2be7979feddd48ca8294f9b9c0869868316 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 25 Jun 2022 14:42:08 +0200 Subject: [PATCH 117/226] Return cluster start address and size --- bios/second_stage/src/fat.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index f4c58b9e..0b715c7f 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -237,7 +237,7 @@ impl FileSystem { pub fn file_clusters<'a>( &'a mut self, file: &File, - ) -> impl Iterator> + 'a { + ) -> impl Iterator> + 'a { Traverser { current_entry: file.first_cluster, bpb: &self.bpb, @@ -246,6 +246,12 @@ impl FileSystem { } } +#[derive(Debug)] +pub struct Cluster { + pub start_offset: u64, + pub len_bytes: u32, +} + struct Traverser<'a, D> { disk: &'a mut D, current_entry: u32, @@ -256,7 +262,7 @@ impl Traverser<'_, D> where D: Read + Seek, { - fn next_cluster(&mut self) -> Result, ()> { + fn next_cluster(&mut self) -> Result, ()> { let entry = classify_fat_entry( self.bpb.fat_type(), self.current_entry, @@ -269,20 +275,14 @@ where }; let cluster_start = self.bpb.data_offset() + (u64::from(entry) - 2) * self.bpb.bytes_per_cluster() as u64; - // handle_read( - // self.traverser.handle, - // cluster_start, - // self.traverser.bpb.bytes_per_cluster() as usize, - // &mut self.traverser.buf, - // )?; - // if let Some(t) = f(&self.traverser.buf) { - // break Ok(Some(t)); - // } let next_entry = fat_entry_of_nth_cluster(self.disk, self.bpb.fat_type(), self.bpb.fat_offset(), entry); self.current_entry = next_entry; - Ok(Some(entry)) + Ok(Some(Cluster { + start_offset: cluster_start, + len_bytes: self.bpb.bytes_per_cluster(), + })) } } @@ -290,7 +290,7 @@ impl Iterator for Traverser<'_, D> where D: Read + Seek, { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { self.next_cluster().transpose() From c8a0c04f4075f9fee54f803d8ef9a2d4ef8477fe Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 25 Jun 2022 14:42:30 +0200 Subject: [PATCH 118/226] Remove some printlns --- bios/second_stage/src/disk.rs | 5 ----- bios/second_stage/src/main.rs | 17 +++++++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index 8e83967c..83c502e8 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -10,7 +10,6 @@ pub struct DiskAccess { impl Read for DiskAccess { fn read_exact(&mut self, input_buf: &mut [u8]) { - writeln!(screen::Writer, "read {} bytes", input_buf.len()).unwrap(); static mut TMP_BUF: [u8; 512] = [0; 512]; let tmp_buf = unsafe { &mut TMP_BUF[..] }; let (buf, copy_needed) = if input_buf.len() >= tmp_buf.len() { @@ -35,7 +34,6 @@ impl Read for DiskAccess { (target_addr & 0b1111) as u16, (target_addr >> 4).try_into().unwrap(), ); - writeln!(screen::Writer, "dap: {dap:?}").unwrap(); unsafe { dap.perform_load(self.disk_number); } @@ -57,14 +55,11 @@ impl Read for DiskAccess { input_buf[i] = tmp_buf[i]; } } - - writeln!(screen::Writer, "read {} bytes done", input_buf.len()).unwrap(); } } impl Seek for DiskAccess { fn seek(&mut self, pos: SeekFrom) -> u64 { - writeln!(screen::Writer, "seek to {pos:?}").unwrap(); match pos { SeekFrom::Start(offset) => { self.current_offset = offset; diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index c333eb19..c46fd019 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -27,7 +27,7 @@ fn second_stage_end() -> *const u8 { #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { - write!(screen::Writer, "\nSECOND STAGE: ").unwrap(); + writeln!(screen::Writer, " -> SECOND STAGE").unwrap(); // parse partition table let partitions = { @@ -59,7 +59,6 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { fat_partition.partition_type, PartitionType::Fat12(_) | PartitionType::Fat16(_) | PartitionType::Fat32(_) )); - writeln!(screen::Writer, "1").unwrap(); // load fat partition let mut disk = disk::DiskAccess { @@ -67,20 +66,26 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { base_offset: u64::from(fat_partition.logical_block_address) * 512, current_offset: 0, }; - writeln!(screen::Writer, "2").unwrap(); let mut fs = fat::FileSystem::parse(disk.clone()); - writeln!(screen::Writer, "3").unwrap(); let kernel = fs .find_file_in_root_dir("kernel-x86_64") .expect("no `kernel-x86_64` file found"); - writeln!(screen::Writer, "4").unwrap(); for cluster in fs.file_clusters(&kernel) { - writeln!(screen::Writer, "kernel cluster: {cluster:?}").unwrap(); + let cluster = cluster.unwrap(); + writeln!( + screen::Writer, + "kernel cluster: start: {:#x}, len: {}", + cluster.start_offset, + cluster.len_bytes + ) + .unwrap(); } + writeln!(screen::Writer, "DONE").unwrap(); + // TODO: Retrieve memory map // TODO: Set up protected mode, or unreal mode From f1f54576431f4ec7d9de30b280bb24515a2595b6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:42:59 +0200 Subject: [PATCH 119/226] Remove alignment and assert maximum executable size Only 480.5 KiB of memory are usable for the second stage in real mode. If the executable becomes larger than that, we override the Extended BIOS Data Area. --- bios/second_stage/second-stage-link.ld | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/bios/second_stage/second-stage-link.ld b/bios/second_stage/second-stage-link.ld index 0d6371c2..ab91f9eb 100644 --- a/bios/second_stage/second-stage-link.ld +++ b/bios/second_stage/second-stage-link.ld @@ -6,20 +6,33 @@ SECTIONS { .start : { *(.start) } - .text : ALIGN(512) { + .text : { *(.text .text.*) } - .rodata : ALIGN(512) { + .rodata : { *(.rodata .rodata.*) } - .bss : ALIGN(512) { + .data : { + *(.data .data.*) + } + .bss : { *(.bss .bss.*) } - .data : ALIGN(512) { - *(.data .data.*) + .eh_frame : { + *(.eh_frame .eh_frame.*) + } + .eh_frame_hdr : { + *(.eh_frame_hdr .eh_frame_hdr.*) } . = ALIGN(512); _second_stage_end = .; + + . = 0x0007FFFF - 2; + .end_marker : + { + SHORT(0xdead) /* magic number for bootable disk */ + } + . = 0x7c00 + 512; } From 6e8f9fa964bfcd23b95fe902fe3a5f2c55065e27 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:43:53 +0200 Subject: [PATCH 120/226] Ensure proper alignment of DAP target buffer --- bios/second_stage/src/disk.rs | 51 ++++++++++++++++++++++++----------- bios/second_stage/src/fat.rs | 37 +++++++++++++------------ 2 files changed, 54 insertions(+), 34 deletions(-) diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index 83c502e8..268461cd 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -9,14 +9,21 @@ pub struct DiskAccess { } impl Read for DiskAccess { - fn read_exact(&mut self, input_buf: &mut [u8]) { - static mut TMP_BUF: [u8; 512] = [0; 512]; - let tmp_buf = unsafe { &mut TMP_BUF[..] }; - let (buf, copy_needed) = if input_buf.len() >= tmp_buf.len() { - (&mut input_buf[..], false) - } else { - (&mut tmp_buf[..], true) + fn read_exact(&mut self, len: usize) -> &[u8] { + static mut TMP_BUF: AlignedBuffer<512> = AlignedBuffer { + buffer: [0; 512], + limit: 512, }; + let buf = unsafe { &mut TMP_BUF }; + assert!(len <= buf.buffer.len()); + + self.read_exact_into(buf); + + &buf.buffer[..len] + } + + fn read_exact_into(&mut self, buf: &mut dyn AlignedSlice) { + let buf = buf.slice_mut(); assert_eq!(buf.len() % 512, 0); let end_addr = self.base_offset + self.current_offset + u64::try_from(buf.len()).unwrap(); @@ -48,13 +55,6 @@ impl Read for DiskAccess { } self.current_offset = end_addr; - - if copy_needed { - let len = input_buf.len(); - for i in 0..len { - input_buf[i] = tmp_buf[i]; - } - } } } @@ -76,7 +76,8 @@ impl Seek for DiskAccess { } pub trait Read { - fn read_exact(&mut self, buf: &mut [u8]); + fn read_exact(&mut self, len: usize) -> &[u8]; + fn read_exact_into(&mut self, buf: &mut dyn AlignedSlice); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -88,3 +89,23 @@ pub enum SeekFrom { pub trait Seek { fn seek(&mut self, pos: SeekFrom) -> u64; } + +#[repr(align(2))] +pub struct AlignedBuffer { + pub buffer: [u8; LEN], + pub limit: usize, +} + +pub trait AlignedSlice { + fn slice(&self) -> &[u8]; + fn slice_mut(&mut self) -> &mut [u8]; +} + +impl AlignedSlice for AlignedBuffer { + fn slice(&self) -> &[u8] { + &self.buffer[..self.limit] + } + fn slice_mut(&mut self) -> &mut [u8] { + &mut self.buffer[..self.limit] + } +} diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index 0b715c7f..308c623d 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -1,7 +1,7 @@ // based on https://crates.io/crates/mini_fat by https://github.com/gridbugs use crate::{ - disk::{Read, Seek, SeekFrom}, + disk::{AlignedBuffer, AlignedSlice, Read, Seek, SeekFrom}, screen, }; use core::{char::DecodeUtf16Error, fmt::Write as _}; @@ -36,12 +36,7 @@ struct Bpb { impl Bpb { fn parse(disk: &mut D) -> Self { - static mut BPB_BUFFER: [u8; 512] = [0; 512]; - let mut raw = { - let buffer = unsafe { &mut BPB_BUFFER[..] }; - &mut buffer[..512] - }; - disk.read_exact(&mut raw); + let raw = disk.read_exact(512); let bytes_per_sector = u16::from_le_bytes(raw[11..13].try_into().unwrap()); let sectors_per_cluster = raw[13]; @@ -217,16 +212,20 @@ impl FileSystem { } FatType::Fat12 | FatType::Fat16 => { let root_directory_size = self.bpb.root_directory_size(); - static mut ROOT_DIR_BUFFER: [u8; 0x4000] = [0; 0x4000]; - let buffer = unsafe { &mut ROOT_DIR_BUFFER[..] }; - assert!(root_directory_size <= buffer.len()); - let raw = &mut buffer[..root_directory_size]; + static mut ROOT_DIR_BUFFER: AlignedBuffer<0x4000> = AlignedBuffer { + buffer: [0; 0x4000], + limit: 0x4000, + }; + let buffer = unsafe { &mut ROOT_DIR_BUFFER }; + buffer.limit = root_directory_size; self.disk .seek(SeekFrom::Start(self.bpb.root_directory_offset())); - self.disk.read_exact(raw); + self.disk.read_exact_into(buffer); - raw.chunks(DIRECTORY_ENTRY_BYTES) + buffer + .slice() + .chunks(DIRECTORY_ENTRY_BYTES) .take_while(|raw_entry| raw_entry[0] != END_OF_DIRECTORY_PREFIX) .filter(|raw_entry| raw_entry[0] != UNUSED_ENTRY_PREFIX) .map(RawDirectoryEntry::parse) @@ -535,22 +534,22 @@ where FatType::Fat32 => { let base = n as u64 * 4; disk.seek(SeekFrom::Start(fat_start + base)); - let mut buf = [0; 4]; - disk.read_exact(&mut buf); + let buf = disk.read_exact(4); + let buf: [u8; 4] = buf.try_into().unwrap(); u32::from_le_bytes(buf) & 0x0FFFFFFF } FatType::Fat16 => { let base = n as u64 * 2; disk.seek(SeekFrom::Start(fat_start + base)); - let mut buf = [0; 2]; - disk.read_exact(&mut buf); + let buf = disk.read_exact(2); + let buf: [u8; 2] = buf.try_into().unwrap(); u16::from_le_bytes(buf) as u32 } FatType::Fat12 => { let base = n as u64 + (n as u64 / 2); disk.seek(SeekFrom::Start(fat_start + base)); - let mut buf = [0; 2]; - disk.read_exact(&mut buf); + let buf = disk.read_exact(2); + let buf: [u8; 2] = buf.try_into().unwrap(); let entry16 = u16::from_le_bytes(buf); if n & 1 == 0 { (entry16 & 0xFFF) as u32 From de014a9d036033ea7c0f948f66dab585fe668953 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:44:31 +0200 Subject: [PATCH 121/226] Remove some printlns to keep binary size down --- bios/second_stage/src/fat.rs | 23 ----------------------- bios/second_stage/src/main.rs | 2 +- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index 308c623d..f7915bcc 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -192,8 +192,6 @@ impl FileSystem { }, }; - writeln!(screen::Writer, "entry: {entry:?}").unwrap(); - if entry.is_directory() { None } else { @@ -362,27 +360,6 @@ impl<'a> DirectoryEntry<'a> { } } -impl core::fmt::Debug for DirectoryEntry<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - struct NamePrinter<'a>(&'a DirectoryEntry<'a>); - impl core::fmt::Debug for NamePrinter<'_> { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - for char in self.0.name().filter_map(|e| e.ok()) { - write!(f, "{char}")?; - } - Ok(()) - } - } - - f.debug_struct("DirectoryEntry") - .field("name", &NamePrinter(self)) - .field("file_size", &self.file_size) - .field("first_cluster", &self.first_cluster) - .field("attributes", &self.attributes) - .finish() - } -} - #[derive(Debug)] struct RawDirectoryEntryNormal<'a> { short_filename_main: &'a str, diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index c46fd019..c80dd8dc 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -27,7 +27,7 @@ fn second_stage_end() -> *const u8 { #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { - writeln!(screen::Writer, " -> SECOND STAGE").unwrap(); + screen::Writer.write_str(" -> SECOND STAGE").unwrap(); // parse partition table let partitions = { From 80de514a12fb4ef1ebddfbd823c60cbe836ea9aa Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:45:13 +0200 Subject: [PATCH 122/226] Remove some old FAT modules --- bios/second_stage/src/fat_bpb.rs | 885 ------------------------------ bios/second_stage/src/main.rs | 5 +- bios/second_stage/src/mini_fat.rs | 370 ------------- 3 files changed, 1 insertion(+), 1259 deletions(-) delete mode 100644 bios/second_stage/src/fat_bpb.rs delete mode 100644 bios/second_stage/src/mini_fat.rs diff --git a/bios/second_stage/src/fat_bpb.rs b/bios/second_stage/src/fat_bpb.rs deleted file mode 100644 index 572e2f48..00000000 --- a/bios/second_stage/src/fat_bpb.rs +++ /dev/null @@ -1,885 +0,0 @@ -// based on https://github.com/rafalh/rust-fatfs/ - -use core::cmp; -use core::u16; -use core::u8; - -use fatfs::{FatType, FormatVolumeOptions, FsStatusFlags}; -use fatfs::{Read, Write}; - -const RESERVED_FAT_ENTRIES: u32 = 2; -// Size of single directory entry in bytes -pub(crate) const DIR_ENTRY_SIZE: u32 = 32; - -const BITS_PER_BYTE: u32 = 8; -const KB_32: u32 = 1024; -const KB_64: u64 = 1024; -const MB_64: u64 = KB_64 * 1024; -const GB_64: u64 = MB_64 * 1024; - -#[derive(Default, Debug, Clone)] -pub(crate) struct BiosParameterBlock { - pub(crate) bytes_per_sector: u16, - pub(crate) sectors_per_cluster: u8, - pub(crate) reserved_sectors: u16, - pub(crate) fats: u8, - pub(crate) root_entries: u16, - pub(crate) total_sectors_16: u16, - pub(crate) media: u8, - pub(crate) sectors_per_fat_16: u16, - pub(crate) sectors_per_track: u16, - pub(crate) heads: u16, - pub(crate) hidden_sectors: u32, - pub(crate) total_sectors_32: u32, - - // Extended BIOS Parameter Block - pub(crate) sectors_per_fat_32: u32, - pub(crate) extended_flags: u16, - pub(crate) fs_version: u16, - pub(crate) root_dir_first_cluster: u32, - pub(crate) fs_info_sector: u16, - pub(crate) backup_boot_sector: u16, - pub(crate) reserved_0: [u8; 12], - pub(crate) drive_num: u8, - pub(crate) reserved_1: u8, - pub(crate) ext_sig: u8, - pub(crate) volume_id: u32, - pub(crate) volume_label: [u8; 11], - pub(crate) fs_type_label: [u8; 8], -} - -impl BiosParameterBlock { - fn deserialize(rdr: &mut R) -> Result { - let mut bpb = Self { - bytes_per_sector: rdr.read_u16_le()?, - sectors_per_cluster: rdr.read_u8()?, - reserved_sectors: rdr.read_u16_le()?, - fats: rdr.read_u8()?, - root_entries: rdr.read_u16_le()?, - total_sectors_16: rdr.read_u16_le()?, - media: rdr.read_u8()?, - sectors_per_fat_16: rdr.read_u16_le()?, - sectors_per_track: rdr.read_u16_le()?, - heads: rdr.read_u16_le()?, - hidden_sectors: rdr.read_u32_le()?, - total_sectors_32: rdr.read_u32_le()?, - ..Self::default() - }; - - if bpb.is_fat32() { - bpb.sectors_per_fat_32 = rdr.read_u32_le()?; - bpb.extended_flags = rdr.read_u16_le()?; - bpb.fs_version = rdr.read_u16_le()?; - bpb.root_dir_first_cluster = rdr.read_u32_le()?; - bpb.fs_info_sector = rdr.read_u16_le()?; - bpb.backup_boot_sector = rdr.read_u16_le()?; - rdr.read_exact(&mut bpb.reserved_0)?; - } - - bpb.drive_num = rdr.read_u8()?; - bpb.reserved_1 = rdr.read_u8()?; - bpb.ext_sig = rdr.read_u8()?; // 0x29 - bpb.volume_id = rdr.read_u32_le()?; - rdr.read_exact(&mut bpb.volume_label)?; - rdr.read_exact(&mut bpb.fs_type_label)?; - - // when the extended boot signature is anything other than 0x29, the fields are invalid - if bpb.ext_sig != 0x29 { - // fields after ext_sig are not used - clean them - bpb.volume_id = 0; - bpb.volume_label = [0; 11]; - bpb.fs_type_label = [0; 8]; - } - - Ok(bpb) - } - - fn serialize(&self, wrt: &mut W) -> Result<(), W::Error> { - wrt.write_u16_le(self.bytes_per_sector)?; - wrt.write_u8(self.sectors_per_cluster)?; - wrt.write_u16_le(self.reserved_sectors)?; - wrt.write_u8(self.fats)?; - wrt.write_u16_le(self.root_entries)?; - wrt.write_u16_le(self.total_sectors_16)?; - wrt.write_u8(self.media)?; - wrt.write_u16_le(self.sectors_per_fat_16)?; - wrt.write_u16_le(self.sectors_per_track)?; - wrt.write_u16_le(self.heads)?; - wrt.write_u32_le(self.hidden_sectors)?; - wrt.write_u32_le(self.total_sectors_32)?; - - if self.is_fat32() { - wrt.write_u32_le(self.sectors_per_fat_32)?; - wrt.write_u16_le(self.extended_flags)?; - wrt.write_u16_le(self.fs_version)?; - wrt.write_u32_le(self.root_dir_first_cluster)?; - wrt.write_u16_le(self.fs_info_sector)?; - wrt.write_u16_le(self.backup_boot_sector)?; - wrt.write_all(&self.reserved_0)?; - } - - wrt.write_u8(self.drive_num)?; - wrt.write_u8(self.reserved_1)?; - wrt.write_u8(self.ext_sig)?; // 0x29 - wrt.write_u32_le(self.volume_id)?; - wrt.write_all(&self.volume_label)?; - wrt.write_all(&self.fs_type_label)?; - Ok(()) - } - - fn validate_bytes_per_sector(&self) -> Result<(), ()> { - if self.bytes_per_sector.count_ones() != 1 { - panic!( - "invalid bytes_per_sector value in BPB: expected a power of two but got {}", - self.bytes_per_sector - ); - return Err(Error::CorruptedFileSystem); - } - if self.bytes_per_sector < 512 || self.bytes_per_sector > 4096 { - panic!( - "invalid bytes_per_sector value in BPB: expected value in range [512, 4096] but got {}", - self.bytes_per_sector - ); - return Err(Error::CorruptedFileSystem); - } - Ok(()) - } - - fn validate_sectors_per_cluster(&self) -> Result<(), ()> { - if self.sectors_per_cluster.count_ones() != 1 { - panic!( - "invalid sectors_per_cluster value in BPB: expected a power of two but got {}", - self.sectors_per_cluster - ); - return Err(Error::CorruptedFileSystem); - } - if self.sectors_per_cluster < 1 || self.sectors_per_cluster > 128 { - panic!( - "invalid sectors_per_cluster value in BPB: expected value in range [1, 128] but got {}", - self.sectors_per_cluster - ); - return Err(Error::CorruptedFileSystem); - } - - // bytes per sector is u16, sectors per cluster is u8, so guaranteed no overflow in multiplication - let bytes_per_cluster = - u32::from(self.bytes_per_sector) * u32::from(self.sectors_per_cluster); - let maximum_compatibility_bytes_per_cluster: u32 = 32 * 1024; - - if bytes_per_cluster > maximum_compatibility_bytes_per_cluster { - // 32k is the largest value to maintain greatest compatibility - // Many implementations appear to support 64k per cluster, and some may support 128k or larger - // However, >32k is not as thoroughly tested... - // warn!("fs compatibility: bytes_per_cluster value '{}' in BPB exceeds '{}', and thus may be incompatible with some implementations", - // bytes_per_cluster, maximum_compatibility_bytes_per_cluster); - } - Ok(()) - } - - fn validate_reserved_sectors(&self) -> Result<(), ()> { - let is_fat32 = self.is_fat32(); - if self.reserved_sectors < 1 { - panic!( - "invalid reserved_sectors value in BPB: {}", - self.reserved_sectors - ); - return Err(Error::CorruptedFileSystem); - } - if !is_fat32 && self.reserved_sectors != 1 { - // Microsoft document indicates fat12 and fat16 code exists that presume this value is 1 - // warn!( - // "fs compatibility: reserved_sectors value '{}' in BPB is not '1', and thus is incompatible with some implementations", - // self.reserved_sectors - // ); - } - if is_fat32 && self.backup_boot_sector >= self.reserved_sectors { - panic!( - "Invalid BPB: expected backup boot-sector to be in the reserved region (sector < {}) but got sector {}", - self.reserved_sectors, self.backup_boot_sector - ); - return Err(Error::CorruptedFileSystem); - } - if is_fat32 && self.fs_info_sector >= self.reserved_sectors { - panic!( - "Invalid BPB: expected FSInfo sector to be in the reserved region (sector < {}) but got sector {}", - self.reserved_sectors, self.fs_info_sector - ); - return Err(Error::CorruptedFileSystem); - } - Ok(()) - } - - fn validate_fats(&self) -> Result<(), ()> { - if self.fats == 0 { - panic!("invalid fats value in BPB: {}", self.fats); - return Err(Error::CorruptedFileSystem); - } - if self.fats > 2 { - // Microsoft document indicates that few implementations support any values other than 1 or 2 - // warn!( - // "fs compatibility: numbers of FATs '{}' in BPB is greater than '2', and thus is incompatible with some implementations", - // self.fats - // ); - } - Ok(()) - } - - fn validate_root_entries(&self) -> Result<(), ()> { - let is_fat32 = self.is_fat32(); - if is_fat32 && self.root_entries != 0 { - panic!( - "Invalid root_entries value in FAT32 BPB: expected 0 but got {}", - self.root_entries - ); - return Err(Error::CorruptedFileSystem); - } - if !is_fat32 && self.root_entries == 0 { - panic!( - "Invalid root_entries value in FAT12/FAT16 BPB: expected non-zero value but got {}", - self.root_entries - ); - return Err(Error::CorruptedFileSystem); - } - if (u32::from(self.root_entries) * DIR_ENTRY_SIZE) % u32::from(self.bytes_per_sector) != 0 { - // warn!("Root entries should fill sectors fully"); - } - Ok(()) - } - - fn validate_total_sectors(&self) -> Result<(), ()> { - let is_fat32 = self.is_fat32(); - if is_fat32 && self.total_sectors_16 != 0 { - panic!( - "Invalid total_sectors_16 value in FAT32 BPB: expected 0 but got {}", - self.total_sectors_16 - ); - return Err(Error::CorruptedFileSystem); - } - if (self.total_sectors_16 == 0) == (self.total_sectors_32 == 0) { - panic!("Invalid BPB (total_sectors_16 or total_sectors_32 should be non-zero)"); - return Err(Error::CorruptedFileSystem); - } - let total_sectors = self.total_sectors(); - let first_data_sector = self.first_data_sector(); - if total_sectors <= first_data_sector { - panic!( - "Invalid total_sectors value in BPB: expected value > {} but got {}", - first_data_sector, total_sectors - ); - return Err(Error::CorruptedFileSystem); - } - Ok(()) - } - - fn validate_sectors_per_fat(&self) -> Result<(), ()> { - let is_fat32 = self.is_fat32(); - if is_fat32 && self.sectors_per_fat_32 == 0 { - panic!( - "Invalid sectors_per_fat_32 value in FAT32 BPB: expected non-zero value but got {}", - self.sectors_per_fat_32 - ); - return Err(Error::CorruptedFileSystem); - } - Ok(()) - } - - fn validate_total_clusters(&self) -> Result<(), ()> { - let is_fat32 = self.is_fat32(); - let total_clusters = self.total_clusters(); - let fat_type = FatType::from_clusters(total_clusters); - if is_fat32 != (fat_type == FatType::Fat32) { - panic!("Invalid BPB: result of FAT32 determination from total number of clusters and sectors_per_fat_16 field differs"); - return Err(Error::CorruptedFileSystem); - } - if fat_type == FatType::Fat32 && total_clusters > 0x0FFF_FFFF { - panic!("Invalid BPB: too many clusters {}", total_clusters); - return Err(Error::CorruptedFileSystem); - } - - let bits_per_fat_entry = fat_type.bits_per_fat_entry(); - let total_fat_entries = - self.sectors_per_fat() * u32::from(self.bytes_per_sector) * 8 / bits_per_fat_entry; - let usable_fat_entries = total_fat_entries - RESERVED_FAT_ENTRIES; - if usable_fat_entries < total_clusters { - // warn!( - // "FAT is too small (allows allocation of {} clusters) compared to the total number of clusters ({})", - // usable_fat_entries, total_clusters - // ); - } - Ok(()) - } - - fn validate(&self) -> Result<(), ()> { - if self.fs_version != 0 { - panic!( - "Unsupported filesystem version: expected 0 but got {}", - self.fs_version - ); - return Err(Error::CorruptedFileSystem); - } - self.validate_bytes_per_sector()?; - self.validate_sectors_per_cluster()?; - self.validate_reserved_sectors()?; - self.validate_fats()?; - self.validate_root_entries()?; - self.validate_total_sectors()?; - self.validate_sectors_per_fat()?; - self.validate_total_clusters()?; - Ok(()) - } - - pub(crate) fn mirroring_enabled(&self) -> bool { - self.extended_flags & 0x80 == 0 - } - - pub(crate) fn active_fat(&self) -> u16 { - // The zero-based number of the active FAT is only valid if mirroring is disabled. - if self.mirroring_enabled() { - 0 - } else { - self.extended_flags & 0x0F - } - } - - pub(crate) fn status_flags(&self) -> FsStatusFlags { - FsStatusFlags::decode(self.reserved_1) - } - - pub(crate) fn is_fat32(&self) -> bool { - // because this field must be zero on FAT32, and - // because it must be non-zero on FAT12/FAT16, - // this provides a simple way to detect FAT32 - self.sectors_per_fat_16 == 0 - } - - pub(crate) fn sectors_per_fat(&self) -> u32 { - if self.is_fat32() { - self.sectors_per_fat_32 - } else { - u32::from(self.sectors_per_fat_16) - } - } - - pub(crate) fn total_sectors(&self) -> u32 { - if self.total_sectors_16 == 0 { - self.total_sectors_32 - } else { - u32::from(self.total_sectors_16) - } - } - - pub(crate) fn reserved_sectors(&self) -> u32 { - u32::from(self.reserved_sectors) - } - - pub(crate) fn root_dir_sectors(&self) -> u32 { - let root_dir_bytes = u32::from(self.root_entries) * DIR_ENTRY_SIZE; - (root_dir_bytes + u32::from(self.bytes_per_sector) - 1) / u32::from(self.bytes_per_sector) - } - - pub(crate) fn sectors_per_all_fats(&self) -> u32 { - u32::from(self.fats) * self.sectors_per_fat() - } - - pub(crate) fn first_data_sector(&self) -> u32 { - let root_dir_sectors = self.root_dir_sectors(); - let fat_sectors = self.sectors_per_all_fats(); - self.reserved_sectors() + fat_sectors + root_dir_sectors - } - - pub(crate) fn total_clusters(&self) -> u32 { - let total_sectors = self.total_sectors(); - let first_data_sector = self.first_data_sector(); - let data_sectors = total_sectors - first_data_sector; - data_sectors / u32::from(self.sectors_per_cluster) - } - - pub(crate) fn bytes_from_sectors(&self, sectors: u32) -> u64 { - // Note: total number of sectors is a 32 bit number so offsets have to be 64 bit - u64::from(sectors) * u64::from(self.bytes_per_sector) - } - - pub(crate) fn sectors_from_clusters(&self, clusters: u32) -> u32 { - // Note: total number of sectors is a 32 bit number so it should not overflow - clusters * u32::from(self.sectors_per_cluster) - } - - pub(crate) fn cluster_size(&self) -> u32 { - u32::from(self.sectors_per_cluster) * u32::from(self.bytes_per_sector) - } - - pub(crate) fn clusters_from_bytes(&self, bytes: u64) -> u32 { - let cluster_size = u64::from(self.cluster_size()); - ((bytes + cluster_size - 1) / cluster_size) as u32 - } - - pub(crate) fn fs_info_sector(&self) -> u32 { - u32::from(self.fs_info_sector) - } - - pub(crate) fn backup_boot_sector(&self) -> u32 { - u32::from(self.backup_boot_sector) - } -} - -pub(crate) struct BootSector { - bootjmp: [u8; 3], - oem_name: [u8; 8], - pub(crate) bpb: BiosParameterBlock, - boot_code: [u8; 448], - boot_sig: [u8; 2], -} - -impl BootSector { - pub(crate) fn deserialize(rdr: &mut R) -> Result { - let mut boot = Self::default(); - rdr.read_exact(&mut boot.bootjmp)?; - rdr.read_exact(&mut boot.oem_name)?; - boot.bpb = BiosParameterBlock::deserialize(rdr)?; - - if boot.bpb.is_fat32() { - rdr.read_exact(&mut boot.boot_code[0..420])?; - } else { - rdr.read_exact(&mut boot.boot_code[0..448])?; - } - rdr.read_exact(&mut boot.boot_sig)?; - Ok(boot) - } - - pub(crate) fn serialize(&self, wrt: &mut W) -> Result<(), W::Error> { - wrt.write_all(&self.bootjmp)?; - wrt.write_all(&self.oem_name)?; - self.bpb.serialize(&mut *wrt)?; - - if self.bpb.is_fat32() { - wrt.write_all(&self.boot_code[0..420])?; - } else { - wrt.write_all(&self.boot_code[0..448])?; - } - wrt.write_all(&self.boot_sig)?; - Ok(()) - } - - pub(crate) fn validate(&self) -> Result<(), ()> { - if self.boot_sig != [0x55, 0xAA] { - panic!( - "Invalid boot sector signature: expected [0x55, 0xAA] but got {:?}", - self.boot_sig - ); - return Err(Error::CorruptedFileSystem); - } - if self.bootjmp[0] != 0xEB && self.bootjmp[0] != 0xE9 { - // warn!( - // "Unknown opcode {:x} in bootjmp boot sector field", - // self.bootjmp[0] - // ); - } - self.bpb.validate()?; - Ok(()) - } -} - -impl Default for BootSector { - fn default() -> Self { - Self { - bootjmp: Default::default(), - oem_name: Default::default(), - bpb: BiosParameterBlock::default(), - boot_code: [0; 448], - boot_sig: Default::default(), - } - } -} - -pub(crate) fn estimate_fat_type(total_bytes: u64) -> FatType { - // Used only to select cluster size if FAT type has not been overriden in options - if total_bytes < 4 * MB_64 { - FatType::Fat12 - } else if total_bytes < 512 * MB_64 { - FatType::Fat16 - } else { - FatType::Fat32 - } -} - -fn determine_bytes_per_cluster( - total_bytes: u64, - bytes_per_sector: u16, - fat_type: Option, -) -> u32 { - const MAX_CLUSTER_SIZE: u32 = 32 * KB_32; - - let fat_type = fat_type.unwrap_or_else(|| estimate_fat_type(total_bytes)); - let bytes_per_cluster = match fat_type { - FatType::Fat12 => (total_bytes.next_power_of_two() / MB_64 * 512) as u32, - FatType::Fat16 => { - if total_bytes <= 16 * MB_64 { - KB_32 - } else if total_bytes <= 128 * MB_64 { - 2 * KB_32 - } else { - ((total_bytes.next_power_of_two() / (64 * MB_64)) as u32) * KB_32 - } - } - FatType::Fat32 => { - if total_bytes <= 260 * MB_64 { - 512 - } else if total_bytes <= 8 * GB_64 { - 4 * KB_32 - } else { - ((total_bytes.next_power_of_two() / (2 * GB_64)) as u32) * KB_32 - } - } - }; - let bytes_per_cluster_clamped = cmp::min( - cmp::max(bytes_per_cluster, u32::from(bytes_per_sector)), - MAX_CLUSTER_SIZE, - ); - debug_assert!(bytes_per_cluster_clamped.is_power_of_two()); - bytes_per_cluster_clamped -} - -fn determine_sectors_per_fat( - total_sectors: u32, - bytes_per_sector: u16, - sectors_per_cluster: u8, - fat_type: FatType, - reserved_sectors: u16, - root_dir_sectors: u32, - fats: u8, -) -> u32 { - // - // FAT size formula transformations: - // - // Initial basic formula: - // size of FAT in bits >= (total number of clusters + 2) * bits per FAT entry - // - // Note: when computing number of clusters from number of sectors rounding down is used because partial clusters - // are not allowed - // Note: in those transformations '/' is a floating-point division (not a rounding towards zero division) - // - // data_sectors = total_sectors - reserved_sectors - fats * sectors_per_fat - root_dir_sectors - // total_clusters = floor(data_sectors / sectors_per_cluster) - // bits_per_sector = bytes_per_sector * 8 - // sectors_per_fat * bits_per_sector >= (total_clusters + 2) * bits_per_fat_entry - // sectors_per_fat * bits_per_sector >= (floor(data_sectors / sectors_per_cluster) + 2) * bits_per_fat_entry - // - // Note: omitting the floor function can cause the FAT to be bigger by 1 entry - negligible - // - // sectors_per_fat * bits_per_sector >= (data_sectors / sectors_per_cluster + 2) * bits_per_fat_entry - // t0 = total_sectors - reserved_sectors - root_dir_sectors - // sectors_per_fat * bits_per_sector >= ((t0 - fats * sectors_per_fat) / sectors_per_cluster + 2) * bits_per_fat_entry - // sectors_per_fat * bits_per_sector / bits_per_fat_entry >= (t0 - fats * sectors_per_fat) / sectors_per_cluster + 2 - // sectors_per_fat * bits_per_sector / bits_per_fat_entry >= t0 / sectors_per_cluster + 2 - fats * sectors_per_fat / sectors_per_cluster - // sectors_per_fat * bits_per_sector / bits_per_fat_entry + fats * sectors_per_fat / sectors_per_cluster >= t0 / sectors_per_cluster + 2 - // sectors_per_fat * (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) >= t0 / sectors_per_cluster + 2 - // sectors_per_fat >= (t0 / sectors_per_cluster + 2) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) - // - // Note: MS specification omits the constant 2 in calculations. This library is taking a better approach... - // - // sectors_per_fat >= ((t0 + 2 * sectors_per_cluster) / sectors_per_cluster) / (bits_per_sector / bits_per_fat_entry + fats / sectors_per_cluster) - // sectors_per_fat >= (t0 + 2 * sectors_per_cluster) / (sectors_per_cluster * bits_per_sector / bits_per_fat_entry + fats) - // - // Note: compared to MS formula this one can suffer from an overflow problem if u32 type is used - // - // When converting formula to integer types round towards a bigger FAT: - // * first division towards infinity - // * second division towards zero (it is in a denominator of the first division) - - let t0: u32 = total_sectors - u32::from(reserved_sectors) - root_dir_sectors; - let t1: u64 = u64::from(t0) + u64::from(2 * u32::from(sectors_per_cluster)); - let bits_per_cluster = - u32::from(sectors_per_cluster) * u32::from(bytes_per_sector) * BITS_PER_BYTE; - let t2 = u64::from(bits_per_cluster / fat_type.bits_per_fat_entry() + u32::from(fats)); - let sectors_per_fat = (t1 + t2 - 1) / t2; - // Note: casting is safe here because number of sectors per FAT cannot be bigger than total sectors number - sectors_per_fat as u32 -} - -fn try_fs_geometry( - total_sectors: u32, - bytes_per_sector: u16, - sectors_per_cluster: u8, - fat_type: FatType, - root_dir_sectors: u32, - fats: u8, -) -> Result<(u16, u32), Error<()>> { - // Note: most of implementations use 32 reserved sectors for FAT32 but it's wasting of space - // This implementation uses only 8. This is enough to fit in two boot sectors (main and backup) with additional - // bootstrap code and one FSInfo sector. It also makes FAT alligned to 4096 which is a nice number. - let reserved_sectors: u16 = if fat_type == FatType::Fat32 { 8 } else { 1 }; - - // Check if volume has enough space to accomodate reserved sectors, FAT, root directory and some data space - // Having less than 8 sectors for FAT and data would make a little sense - if total_sectors <= u32::from(reserved_sectors) + root_dir_sectors + 8 { - panic!("Volume is too small"); - return Err(Error::InvalidInput); - } - - // calculate File Allocation Table size - let sectors_per_fat = determine_sectors_per_fat( - total_sectors, - bytes_per_sector, - sectors_per_cluster, - fat_type, - reserved_sectors, - root_dir_sectors, - fats, - ); - - let data_sectors = total_sectors - - u32::from(reserved_sectors) - - root_dir_sectors - - sectors_per_fat * u32::from(fats); - let total_clusters = data_sectors / u32::from(sectors_per_cluster); - // if fat_type != FatType::from_clusters(total_clusters) { - // panic!("Invalid FAT type"); - // return Err(Error::InvalidInput); - // } - debug_assert!(total_clusters >= fat_type.min_clusters()); - if total_clusters > fat_type.max_clusters() { - // Note: it can happen for FAT32 - panic!("Too many clusters"); - return Err(Error::InvalidInput); - } - - Ok((reserved_sectors, sectors_per_fat)) -} - -fn determine_root_dir_sectors( - root_dir_entries: u16, - bytes_per_sector: u16, - fat_type: FatType, -) -> u32 { - if fat_type == FatType::Fat32 { - 0 - } else { - let root_dir_bytes = u32::from(root_dir_entries) * DIR_ENTRY_SIZE as u32; - (root_dir_bytes + u32::from(bytes_per_sector) - 1) / u32::from(bytes_per_sector) - } -} - -fn determine_fs_geometry( - total_sectors: u32, - bytes_per_sector: u16, - sectors_per_cluster: u8, - root_dir_entries: u16, - fats: u8, -) -> Result<(FatType, u16, u32), ()> { - for &fat_type in &[FatType::Fat32, FatType::Fat16, FatType::Fat12] { - let root_dir_sectors = - determine_root_dir_sectors(root_dir_entries, bytes_per_sector, fat_type); - let result = try_fs_geometry( - total_sectors, - bytes_per_sector, - sectors_per_cluster, - fat_type, - root_dir_sectors, - fats, - ); - if let Ok((reserved_sectors, sectors_per_fat)) = result { - return Ok((fat_type, reserved_sectors, sectors_per_fat)); - } - } - - panic!("Cannot select FAT type - unfortunate storage size"); - Err(Error::InvalidInput) -} - -fn format_bpb( - options: &FormatVolumeOptions, - total_sectors: u32, - bytes_per_sector: u16, -) -> Result<(BiosParameterBlock, FatType), ()> { - let bytes_per_cluster = options.bytes_per_cluster.unwrap_or_else(|| { - let total_bytes = u64::from(total_sectors) * u64::from(bytes_per_sector); - determine_bytes_per_cluster(total_bytes, bytes_per_sector, options.fat_type) - }); - - let sectors_per_cluster = bytes_per_cluster / u32::from(bytes_per_sector); - assert!(sectors_per_cluster <= u32::from(u8::MAX)); - let sectors_per_cluster = sectors_per_cluster as u8; - - let fats = options.fats.unwrap_or(2_u8); - let root_dir_entries = options.max_root_dir_entries.unwrap_or(512); - let (fat_type, reserved_sectors, sectors_per_fat) = determine_fs_geometry( - total_sectors, - bytes_per_sector, - sectors_per_cluster, - root_dir_entries, - fats, - )?; - - // drive_num should be 0 for floppy disks and 0x80 for hard disks - determine it using FAT type - let drive_num = - options - .drive_num - .unwrap_or_else(|| if fat_type == FatType::Fat12 { 0 } else { 0x80 }); - - // reserved_0 is always zero - let reserved_0 = [0_u8; 12]; - - // setup volume label - let mut volume_label = [0_u8; 11]; - if let Some(volume_label_from_opts) = options.volume_label { - volume_label.copy_from_slice(&volume_label_from_opts); - } else { - volume_label.copy_from_slice(b"NO NAME "); - } - - // setup fs_type_label field - let mut fs_type_label = [0_u8; 8]; - let fs_type_label_str = match fat_type { - FatType::Fat12 => b"FAT12 ", - FatType::Fat16 => b"FAT16 ", - FatType::Fat32 => b"FAT32 ", - }; - fs_type_label.copy_from_slice(fs_type_label_str); - - // create Bios Parameter Block struct - let is_fat32 = fat_type == FatType::Fat32; - let sectors_per_fat_16 = if is_fat32 { - 0 - } else { - debug_assert!(sectors_per_fat <= u32::from(u16::MAX)); - sectors_per_fat as u16 - }; - let bpb = BiosParameterBlock { - bytes_per_sector, - sectors_per_cluster, - reserved_sectors, - fats, - root_entries: if is_fat32 { 0 } else { root_dir_entries }, - total_sectors_16: if total_sectors < 0x10000 { - total_sectors as u16 - } else { - 0 - }, - media: options.media.unwrap_or(0xF8), - sectors_per_fat_16, - sectors_per_track: options.sectors_per_track.unwrap_or(0x20), - // heads: options.heads.unwrap_or(0x40), - heads: 0x40, - hidden_sectors: 0, - total_sectors_32: if total_sectors >= 0x10000 { - total_sectors - } else { - 0 - }, - // FAT32 fields start - sectors_per_fat_32: if is_fat32 { sectors_per_fat } else { 0 }, - extended_flags: 0, // mirroring enabled - fs_version: 0, - root_dir_first_cluster: if is_fat32 { 2 } else { 0 }, - fs_info_sector: if is_fat32 { 1 } else { 0 }, - backup_boot_sector: if is_fat32 { 6 } else { 0 }, - reserved_0, - // FAT32 fields end - drive_num, - reserved_1: 0, - ext_sig: 0x29, - volume_id: options.volume_id.unwrap_or(0x1234_5678), - volume_label, - fs_type_label, - }; - - // Check if number of clusters is proper for used FAT type - if FatType::from_clusters(bpb.total_clusters()) != fat_type { - panic!("Total number of clusters and FAT type does not match, please try a different volume size"); - return Err(Error::InvalidInput); - } - - Ok((bpb, fat_type)) -} - -pub(crate) fn format_boot_sector( - options: &FormatVolumeOptions, - total_sectors: u32, - bytes_per_sector: u16, -) -> Result<(BootSector, FatType), ()> { - let mut boot = BootSector::default(); - let (bpb, fat_type) = format_bpb(options, total_sectors, bytes_per_sector)?; - boot.bpb = bpb; - boot.oem_name.copy_from_slice(b"MSWIN4.1"); - // Boot code copied from FAT32 boot sector initialized by mkfs.fat - boot.bootjmp = [0xEB, 0x58, 0x90]; - let boot_code: [u8; 129] = [ - 0x0E, 0x1F, 0xBE, 0x77, 0x7C, 0xAC, 0x22, 0xC0, 0x74, 0x0B, 0x56, 0xB4, 0x0E, 0xBB, 0x07, - 0x00, 0xCD, 0x10, 0x5E, 0xEB, 0xF0, 0x32, 0xE4, 0xCD, 0x16, 0xCD, 0x19, 0xEB, 0xFE, 0x54, - 0x68, 0x69, 0x73, 0x20, 0x69, 0x73, 0x20, 0x6E, 0x6F, 0x74, 0x20, 0x61, 0x20, 0x62, 0x6F, - 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x64, 0x69, 0x73, 0x6B, 0x2E, 0x20, 0x20, 0x50, - 0x6C, 0x65, 0x61, 0x73, 0x65, 0x20, 0x69, 0x6E, 0x73, 0x65, 0x72, 0x74, 0x20, 0x61, 0x20, - 0x62, 0x6F, 0x6F, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x20, 0x66, 0x6C, 0x6F, 0x70, 0x70, 0x79, - 0x20, 0x61, 0x6E, 0x64, 0x0D, 0x0A, 0x70, 0x72, 0x65, 0x73, 0x73, 0x20, 0x61, 0x6E, 0x79, - 0x20, 0x6B, 0x65, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x74, 0x72, 0x79, 0x20, 0x61, 0x67, 0x61, - 0x69, 0x6E, 0x20, 0x2E, 0x2E, 0x2E, 0x20, 0x0D, 0x0A, - ]; - boot.boot_code[..boot_code.len()].copy_from_slice(&boot_code); - boot.boot_sig = [0x55, 0xAA]; - - // fix offsets in bootjmp and boot code for non-FAT32 filesystems (bootcode is on a different offset) - if fat_type != FatType::Fat32 { - // offset of boot code - const BOOT_CODE_OFFSET: u8 = 0x36 + 8; - // offset of message - const MESSAGE_OFFSET: u16 = 29; - boot.bootjmp[1] = BOOT_CODE_OFFSET - 2; - let message_offset_in_sector = u16::from(BOOT_CODE_OFFSET) + MESSAGE_OFFSET + 0x7c00; - boot.boot_code[3] = (message_offset_in_sector & 0xff) as u8; - boot.boot_code[4] = (message_offset_in_sector >> 8) as u8; - } - - Ok((boot, fat_type)) -} - -pub(crate) trait ReadLeExt { - type Error; - fn read_u8(&mut self) -> Result; - fn read_u16_le(&mut self) -> Result; - fn read_u32_le(&mut self) -> Result; -} - -impl ReadLeExt for T { - type Error = ::Error; - - fn read_u8(&mut self) -> Result { - let mut buf = [0_u8; 1]; - self.read_exact(&mut buf)?; - Ok(buf[0]) - } - - fn read_u16_le(&mut self) -> Result { - let mut buf = [0_u8; 2]; - self.read_exact(&mut buf)?; - Ok(u16::from_le_bytes(buf)) - } - - fn read_u32_le(&mut self) -> Result { - let mut buf = [0_u8; 4]; - self.read_exact(&mut buf)?; - Ok(u32::from_le_bytes(buf)) - } -} - -pub(crate) trait WriteLeExt { - type Error; - fn write_u8(&mut self, n: u8) -> Result<(), Self::Error>; - fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error>; - fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error>; -} - -impl WriteLeExt for T { - type Error = ::Error; - - fn write_u8(&mut self, n: u8) -> Result<(), Self::Error> { - self.write_all(&[n]) - } - - fn write_u16_le(&mut self, n: u16) -> Result<(), Self::Error> { - self.write_all(&n.to_le_bytes()) - } - - fn write_u32_le(&mut self, n: u32) -> Result<(), Self::Error> { - self.write_all(&n.to_le_bytes()) - } -} diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index c80dd8dc..cde60450 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -2,15 +2,12 @@ #![no_main] use byteorder::{ByteOrder, LittleEndian}; -use core::{fmt::Write as _, slice}; +use core::{arch::asm, fmt::Write as _, mem::size_of, slice}; use mbr_nostd::{PartitionTableEntry, PartitionType}; mod dap; mod disk; mod fat; -// mod fat_old; -// mod fat_bpb; -// mod mini_fat; mod screen; /// We use this partition type to store the second bootloader stage; diff --git a/bios/second_stage/src/mini_fat.rs b/bios/second_stage/src/mini_fat.rs deleted file mode 100644 index 5f82ed9e..00000000 --- a/bios/second_stage/src/mini_fat.rs +++ /dev/null @@ -1,370 +0,0 @@ -// based on https://crates.io/crates/mini_fat - -use core::ops::Range; - -#[derive(Debug)] -pub enum Error { - UnexpectedNonZero { - byte_index: usize, - }, - ExactlyOneTotalSectorsFieldMustBeZero { - total_sectors_16: u16, - total_sectors_32: u32, - }, - ExactlyOneFatSizeMustBeZero { - fat_size_16: u16, - fat_size_32: u32, - }, - InvalidSignature(u16), - InvalidFatEntry(u32), - FatLookup(FatLookupError), - NoSuchFile, - InvalidPath, - ExpectedFileFoundDirectory, -} - -#[derive(Debug)] -pub struct Bpb<'a> { - jmp_boot: [u8; 3], - oem_name: &'a [u8], - bytes_per_sector: u16, - sectors_per_cluster: u8, - reserved_sector_count: u16, - num_fats: u8, - root_entry_count: u16, - total_sectors_16: u16, - media: u8, - fat_size_16: u16, - sectors_per_track: u16, - num_heads: u16, - hidden_sectors: u32, - total_sectors_32: u32, - fat_size_32: u32, - ext_flags: u16, - fs_version: u16, - root_cluster: u32, - fs_info: u16, - bk_boot_sector: u16, - drive_number: u8, - boot_signature: u8, - volume_id: u32, - volume_label: &'a [u8], - file_system_type: &'a [u8], - signature: u16, -} - -const BPB_SIZE: usize = 512; -const REQUIRED_SIGNATURE: u16 = 0xAA55; - -impl<'a> Bpb<'a> { - pub fn parse(raw: &'a [u8]) -> Result { - let jmp_boot = [raw[0], raw[1], raw[2]]; - let oem_name = &raw[3..11]; - let bytes_per_sector = u16::from_le_bytes(raw[11..13].try_into().unwrap()); - let sectors_per_cluster = raw[13]; - let reserved_sector_count = u16::from_le_bytes(raw[14..16].try_into().unwrap()); - let num_fats = raw[16]; - let root_entry_count = u16::from_le_bytes(raw[17..19].try_into().unwrap()); - let total_sectors_16 = u16::from_le_bytes(raw[19..21].try_into().unwrap()); - let media = raw[21]; - let fat_size_16 = u16::from_le_bytes(raw[22..24].try_into().unwrap()); - panic!("baz"); - let sectors_per_track = u16::from_le_bytes(raw[24..26].try_into().unwrap()); - let num_heads = u16::from_le_bytes(raw[26..28].try_into().unwrap()); - let hidden_sectors = u32::from_le_bytes(raw[28..32].try_into().unwrap()); - let total_sectors_32 = u32::from_le_bytes(raw[32..36].try_into().unwrap()); - - let ( - fat_size_32, - ext_flags, - fs_version, - root_cluster, - fs_info, - bk_boot_sector, - drive_number, - boot_signature, - volume_id, - volume_label, - file_system_type, - ); - if (total_sectors_16 == 0) && (total_sectors_32 != 0) { - // FAT32 - fat_size_32 = u32::from_le_bytes(raw[36..40].try_into().unwrap()); - ext_flags = u16::from_le_bytes(raw[40..42].try_into().unwrap()); - fs_version = u16::from_le_bytes(raw[42..44].try_into().unwrap()); - root_cluster = u32::from_le_bytes(raw[44..48].try_into().unwrap()); - fs_info = u16::from_le_bytes(raw[48..50].try_into().unwrap()); - bk_boot_sector = u16::from_le_bytes(raw[50..52].try_into().unwrap()); - for i in 52..64 { - if raw[i] != 0 { - return Err(Error::UnexpectedNonZero { byte_index: i }); - } - } - drive_number = raw[64]; - if raw[65] != 0 { - return Err(Error::UnexpectedNonZero { byte_index: 65 }); - } - boot_signature = raw[66]; - volume_id = u32::from_le_bytes(raw[67..71].try_into().unwrap()); - volume_label = &raw[71..82]; - file_system_type = &raw[82..90]; - } else if (total_sectors_16 != 0) && (total_sectors_32 == 0) { - // FAT12 or FAT16 - fat_size_32 = 0; - ext_flags = 0; - fs_version = 0; - root_cluster = 0; - fs_info = 0; - bk_boot_sector = 0; - drive_number = raw[36]; - if raw[37] != 0 { - return Err(Error::UnexpectedNonZero { byte_index: 37 }); - } - boot_signature = raw[38]; - volume_id = u32::from_le_bytes(raw[39..43].try_into().unwrap()); - volume_label = &raw[43..54]; - file_system_type = &raw[54..62]; - } else { - return Err(Error::ExactlyOneTotalSectorsFieldMustBeZero { - total_sectors_16, - total_sectors_32, - }); - } - if (fat_size_16 == 0) == (fat_size_32 == 0) { - return Err(Error::ExactlyOneFatSizeMustBeZero { - fat_size_16, - fat_size_32, - }); - } - let signature = u16::from_le_bytes(raw[510..512].try_into().unwrap()); - if signature != REQUIRED_SIGNATURE { - return Err(Error::InvalidSignature(signature)); - } - Ok(Self { - jmp_boot, - oem_name, - bytes_per_sector, - sectors_per_cluster, - reserved_sector_count, - num_fats, - root_entry_count, - total_sectors_16, - media, - fat_size_16, - sectors_per_track, - num_heads, - hidden_sectors, - total_sectors_32, - fat_size_32, - ext_flags, - fs_version, - root_cluster, - fs_info, - bk_boot_sector, - drive_number, - boot_signature, - volume_id, - volume_label, - file_system_type, - signature, - }) - } - - fn fat_size_in_sectors(&self) -> u32 { - if self.fat_size_16 != 0 && self.fat_size_32 == 0 { - self.fat_size_16 as u32 - } else { - debug_assert!(self.fat_size_16 == 0 && self.fat_size_32 != 0); - self.fat_size_32 - } - } - - fn count_of_clusters(&self) -> u32 { - let root_dir_sectors = ((self.root_entry_count as u32 * 32) - + (self.bytes_per_sector as u32 - 1)) - / self.bytes_per_sector as u32; - let total_sectors = if self.total_sectors_16 != 0 { - self.total_sectors_16 as u32 - } else { - self.total_sectors_32 - }; - let data_sectors = total_sectors - - (self.reserved_sector_count as u32 - + (self.num_fats as u32 * self.fat_size_in_sectors()) - + root_dir_sectors); - data_sectors / self.sectors_per_cluster as u32 - } - - fn fat_type(&self) -> FatType { - let count_of_clusters = self.count_of_clusters(); - if count_of_clusters < 4085 { - FatType::Fat12 - } else if count_of_clusters < 65525 { - FatType::Fat16 - } else { - FatType::Fat32 - } - } - - fn maximum_valid_cluster(&self) -> u32 { - self.count_of_clusters() + 1 - } - - fn root_directory_size(&self) -> usize { - debug_assert!((self.fat_type() == FatType::Fat32) == (self.root_entry_count == 0)); - self.root_entry_count as usize * DIRECTORY_ENTRY_BYTES - } - - fn root_directory_offset(&self) -> u64 { - (self.reserved_sector_count as u64 + (self.num_fats as u64 * self.fat_size_16 as u64)) - * self.bytes_per_sector as u64 - } - - fn fat_offset(&self) -> u64 { - self.reserved_sector_count as u64 * self.bytes_per_sector as u64 - } - - fn data_offset(&self) -> u64 { - self.root_directory_size() as u64 - + ((self.reserved_sector_count as u64 - + self.fat_size_in_sectors() as u64 * self.num_fats as u64) - * self.bytes_per_sector as u64) - } - - pub fn bytes_per_cluster(&self) -> u32 { - self.bytes_per_sector as u32 * self.sectors_per_cluster as u32 - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum FatType { - Fat12, - Fat16, - Fat32, -} - -impl FatType { - fn fat_entry_defective(self) -> u32 { - match self { - Self::Fat12 => 0xFF7, - Self::Fat16 => 0xFFF7, - Self::Fat32 => 0x0FFFFFF7, - } - } -} - -mod directory_attributes { - pub const READ_ONLY: u8 = 0x01; - pub const HIDDEN: u8 = 0x02; - pub const SYSTEM: u8 = 0x04; - pub const VOLUME_ID: u8 = 0x08; - pub const DIRECTORY: u8 = 0x10; - - pub const LONG_NAME: u8 = READ_ONLY | HIDDEN | SYSTEM | VOLUME_ID; -} - -#[derive(Debug)] -pub enum FatLookupError { - FreeCluster, - DefectiveCluster, - UnspecifiedEntryOne, - ReservedEntry, -} - -enum FileFatEntry { - AllocatedCluster(u32), - EndOfFile, -} - -fn classify_fat_entry( - fat_type: FatType, - entry: u32, - maximum_valid_cluster: u32, -) -> Result { - match entry { - 0 => Err(FatLookupError::FreeCluster), - 1 => Err(FatLookupError::UnspecifiedEntryOne), - entry => { - if entry <= maximum_valid_cluster { - Ok(FileFatEntry::AllocatedCluster(entry)) - } else if entry < fat_type.fat_entry_defective() { - Err(FatLookupError::ReservedEntry) - } else if entry == fat_type.fat_entry_defective() { - Err(FatLookupError::DefectiveCluster) - } else { - Ok(FileFatEntry::EndOfFile) - } - } - } -} - -fn handle_read(handle: &mut H, offset: u64, size: usize, buf: &mut [u8]) -> Result<(), Error> -where - H: fatfs::Seek + fatfs::Read, -{ - handle.seek(fatfs::SeekFrom::Start(offset)).unwrap(); - handle.read_exact(buf).unwrap(); - Ok(()) -} - -fn fat_entry_of_nth_cluster( - handle: &mut H, - fat_type: FatType, - fat_start: u64, - n: u32, -) -> Result -where - H: fatfs::Seek + fatfs::Read, -{ - debug_assert!(n >= 2); - match fat_type { - FatType::Fat32 => { - let base = n as u64 * 4; - handle - .seek(fatfs::SeekFrom::Start(fat_start + base)) - .unwrap(); - let mut buf = [0; 4]; - handle.read_exact(&mut buf).unwrap(); - Ok(u32::from_le_bytes(buf) & 0x0FFFFFFF) - } - FatType::Fat16 => { - let base = n as u64 * 2; - handle - .seek(fatfs::SeekFrom::Start(fat_start + base)) - .unwrap(); - let mut buf = [0; 2]; - handle.read_exact(&mut buf).unwrap(); - Ok(u16::from_le_bytes(buf) as u32) - } - FatType::Fat12 => { - let base = n as u64 + (n as u64 / 2); - handle - .seek(fatfs::SeekFrom::Start(fat_start + base)) - .unwrap(); - let mut buf = [0; 2]; - handle.read_exact(&mut buf).unwrap(); - let entry16 = u16::from_le_bytes(buf); - if n & 1 == 0 { - Ok((entry16 & 0xFFF) as u32) - } else { - Ok((entry16 >> 4) as u32) - } - } - } -} - -fn read_bpb<'a, H>( - handle: &mut H, - partition_byte_start: u64, - buf: &'a mut [u8], -) -> Result, Error> -where - H: fatfs::Seek + fatfs::Read, -{ - handle_read(handle, partition_byte_start, BPB_SIZE, buf)?; - Bpb::parse(buf) -} - -const DIRECTORY_ENTRY_BYTES: usize = 32; -const UNUSED_ENTRY_PREFIX: u8 = 0xE5; -const END_OF_DIRECTORY_PREFIX: u8 = 0; From cdfc0c3b3173e512e4c6005ffbb96b3b13a167c7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:45:40 +0200 Subject: [PATCH 123/226] Use static relocation model in an attempt to bring binary size down --- x86-16bit-second-stage.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x86-16bit-second-stage.json b/x86-16bit-second-stage.json index ee6f67ad..f5ed774e 100644 --- a/x86-16bit-second-stage.json +++ b/x86-16bit-second-stage.json @@ -16,5 +16,5 @@ "panic-strategy": "abort", "os": "none", "vendor": "unknown", - "relocation-model": "pie" + "relocation-model": "static" } From 7a24837692d96c8e9b04377fa2e7646648637dcb Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:45:58 +0200 Subject: [PATCH 124/226] Enter unreal mode --- bios/second_stage/src/main.rs | 5 ++ bios/second_stage/src/protected_mode.rs | 91 +++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 bios/second_stage/src/protected_mode.rs diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index cde60450..afa88fff 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -5,9 +5,12 @@ use byteorder::{ByteOrder, LittleEndian}; use core::{arch::asm, fmt::Write as _, mem::size_of, slice}; use mbr_nostd::{PartitionTableEntry, PartitionType}; +use crate::protected_mode::enter_unreal_mode; + mod dap; mod disk; mod fat; +mod protected_mode; mod screen; /// We use this partition type to store the second bootloader stage; @@ -26,6 +29,8 @@ fn second_stage_end() -> *const u8 { pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { screen::Writer.write_str(" -> SECOND STAGE").unwrap(); + enter_unreal_mode(); + // parse partition table let partitions = { const MAX_ENTRIES: usize = 4; diff --git a/bios/second_stage/src/protected_mode.rs b/bios/second_stage/src/protected_mode.rs new file mode 100644 index 00000000..1cd251ea --- /dev/null +++ b/bios/second_stage/src/protected_mode.rs @@ -0,0 +1,91 @@ +use core::{arch::asm, mem::size_of}; + +static GDT: GdtProtectedMode = GdtProtectedMode::new(); + +#[repr(C)] +pub struct GdtProtectedMode { + zero: u64, + code: u64, + data: u64, +} + +impl GdtProtectedMode { + const fn new() -> Self { + let limit = { + let limit_low = 0xffff; + let limit_high = 0xf << 48; + limit_high | limit_low + }; + let access_common = { + let present = 1 << 47; + let user_segment = 1 << 44; + let read_write = 1 << 41; + present | user_segment | read_write + }; + let protected_mode = 1 << 54; + let granularity = 1 << 55; + let base_flags = protected_mode | granularity | access_common | limit; + let executable = 1 << 43; + Self { + zero: 0, + code: base_flags | executable, + data: base_flags, + } + } + + fn clear_interrupts_and_load(&'static self) { + let pointer = GdtPointer { + base: &GDT, + limit: (3 * size_of::() - 1) as u16, + }; + + unsafe { + asm!("cli", "lgdt [{}]", in(reg) &pointer, options(readonly, nostack, preserves_flags)); + } + } +} + +#[repr(C, packed(2))] +pub struct GdtPointer { + /// Size of the DT. + pub limit: u16, + /// Pointer to the memory region containing the DT. + pub base: *const GdtProtectedMode, +} + +unsafe impl Send for GdtPointer {} +unsafe impl Sync for GdtPointer {} + +pub fn enter_unreal_mode() { + let ds: u16; + unsafe { + asm!("mov {0:x}, ds", out(reg) ds, options(nomem, nostack, preserves_flags)); + } + + GDT.clear_interrupts_and_load(); + + // set protected mode bit + let mut cr0: u32; + unsafe { + asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack, preserves_flags)); + } + let cr0_protected = cr0 | 1; + write_cr0(cr0_protected); + + unsafe { + asm!("mov bx, 0x10", "mov ds, bx"); + } + + write_cr0(cr0); + + unsafe { + asm!("mov ds, {0:x}", in(reg) ds, options(nostack, preserves_flags)); + asm!("sti"); + + asm!("mov bx, 0x0f01", "mov eax, 0xb8000", "mov [eax], bx"); + } +} + +fn write_cr0(val: u32) { + unsafe { asm!("mov cr0, {}", in(reg) val, options(nostack, preserves_flags)) }; +} From 3b83a0872289fd0fab7c519277169b69a5bc88ff Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:52:28 +0200 Subject: [PATCH 125/226] Remove unused module --- bios/boot_sector/src/error.rs | 4 ---- bios/boot_sector/src/main.rs | 2 -- 2 files changed, 6 deletions(-) delete mode 100644 bios/boot_sector/src/error.rs diff --git a/bios/boot_sector/src/error.rs b/bios/boot_sector/src/error.rs deleted file mode 100644 index 318aaafa..00000000 --- a/bios/boot_sector/src/error.rs +++ /dev/null @@ -1,4 +0,0 @@ -/// The boot sector did not find the second stage partition. -/// -/// The BIOS bootloader requires a special second stage partition with partition type 0x20. -pub const NO_SECOND_STAGE_PARTITION: u8 = b'x'; diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 2743bf14..46df6694 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -3,13 +3,11 @@ #![warn(unsafe_op_in_unsafe_fn)] use core::{arch::global_asm, slice}; -use error::NO_SECOND_STAGE_PARTITION; use fail::{print_char, UnwrapOrFail}; global_asm!(include_str!("boot.s")); mod dap; -mod error; mod fail; mod mbr; From 0df7c07ff16c5b7c0261f0e4645c29e8b51a18f7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:53:16 +0200 Subject: [PATCH 126/226] Move root dir buffer into main.rs to reuse it later We can reuse it to load the kernel. --- bios/second_stage/src/disk.rs | 20 ++++++++++++-------- bios/second_stage/src/fat.rs | 25 ++++++++++++------------- bios/second_stage/src/main.rs | 12 ++++++++++-- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index 268461cd..6334c47a 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -1,5 +1,4 @@ -use crate::{dap, screen}; -use core::fmt::Write as _; +use crate::dap; #[derive(Clone)] pub struct DiskAccess { @@ -10,7 +9,7 @@ pub struct DiskAccess { impl Read for DiskAccess { fn read_exact(&mut self, len: usize) -> &[u8] { - static mut TMP_BUF: AlignedBuffer<512> = AlignedBuffer { + static mut TMP_BUF: AlignedArrayBuffer<512> = AlignedArrayBuffer { buffer: [0; 512], limit: 512, }; @@ -22,7 +21,7 @@ impl Read for DiskAccess { &buf.buffer[..len] } - fn read_exact_into(&mut self, buf: &mut dyn AlignedSlice) { + fn read_exact_into(&mut self, buf: &mut dyn AlignedBuffer) { let buf = buf.slice_mut(); assert_eq!(buf.len() % 512, 0); @@ -77,7 +76,7 @@ impl Seek for DiskAccess { pub trait Read { fn read_exact(&mut self, len: usize) -> &[u8]; - fn read_exact_into(&mut self, buf: &mut dyn AlignedSlice); + fn read_exact_into(&mut self, buf: &mut dyn AlignedBuffer); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -91,21 +90,26 @@ pub trait Seek { } #[repr(align(2))] -pub struct AlignedBuffer { +pub struct AlignedArrayBuffer { pub buffer: [u8; LEN], pub limit: usize, } -pub trait AlignedSlice { +pub trait AlignedBuffer { fn slice(&self) -> &[u8]; fn slice_mut(&mut self) -> &mut [u8]; + fn set_limit(&mut self, limit: usize); } -impl AlignedSlice for AlignedBuffer { +impl AlignedBuffer for AlignedArrayBuffer { fn slice(&self) -> &[u8] { &self.buffer[..self.limit] } fn slice_mut(&mut self) -> &mut [u8] { &mut self.buffer[..self.limit] } + fn set_limit(&mut self, limit: usize) { + assert!(limit <= LEN); + self.limit = limit; + } } diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index f7915bcc..21c358d8 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -1,9 +1,6 @@ // based on https://crates.io/crates/mini_fat by https://github.com/gridbugs -use crate::{ - disk::{AlignedBuffer, AlignedSlice, Read, Seek, SeekFrom}, - screen, -}; +use crate::disk::{AlignedBuffer, Read, Seek, SeekFrom}; use core::{char::DecodeUtf16Error, fmt::Write as _}; const DIRECTORY_ENTRY_BYTES: usize = 32; @@ -158,8 +155,12 @@ impl FileSystem { } } - pub fn find_file_in_root_dir(&mut self, name: &str) -> Option { - let mut root_entries = self.read_root_dir().filter_map(|e| e.ok()); + pub fn find_file_in_root_dir( + &mut self, + name: &str, + buffer: &mut dyn AlignedBuffer, + ) -> Option { + let mut root_entries = self.read_root_dir(buffer).filter_map(|e| e.ok()); let raw_entry = root_entries.find(|e| e.eq_name(name))?; let entry = match raw_entry { @@ -202,7 +203,10 @@ impl FileSystem { } } - fn read_root_dir<'a>(&'a mut self) -> impl Iterator> + 'a { + fn read_root_dir<'a>( + &'a mut self, + buffer: &'a mut (dyn AlignedBuffer + 'a), + ) -> impl Iterator> + 'a { match self.bpb.fat_type() { FatType::Fat32 => { self.bpb.root_cluster; @@ -210,12 +214,7 @@ impl FileSystem { } FatType::Fat12 | FatType::Fat16 => { let root_directory_size = self.bpb.root_directory_size(); - static mut ROOT_DIR_BUFFER: AlignedBuffer<0x4000> = AlignedBuffer { - buffer: [0; 0x4000], - limit: 0x4000, - }; - let buffer = unsafe { &mut ROOT_DIR_BUFFER }; - buffer.limit = root_directory_size; + buffer.set_limit(root_directory_size); self.disk .seek(SeekFrom::Start(self.bpb.root_directory_offset())); diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index afa88fff..d93f7974 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -2,7 +2,8 @@ #![no_main] use byteorder::{ByteOrder, LittleEndian}; -use core::{arch::asm, fmt::Write as _, mem::size_of, slice}; +use core::{fmt::Write as _, slice}; +use disk::AlignedArrayBuffer; use mbr_nostd::{PartitionTableEntry, PartitionType}; use crate::protected_mode::enter_unreal_mode; @@ -24,6 +25,11 @@ fn second_stage_end() -> *const u8 { unsafe { &_second_stage_end } } +static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { + buffer: [0; 0x4000], + limit: 0x4000, +}; + #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { @@ -71,8 +77,10 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let mut fs = fat::FileSystem::parse(disk.clone()); + let disk_buffer = unsafe { &mut DISK_BUFFER }; + let kernel = fs - .find_file_in_root_dir("kernel-x86_64") + .find_file_in_root_dir("kernel-x86_64", disk_buffer) .expect("no `kernel-x86_64` file found"); for cluster in fs.file_clusters(&kernel) { From 5b257be13448378d9e0aba68c63dcafd5bb7a1b6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:58:31 +0200 Subject: [PATCH 127/226] Reset buffer limit after reading root dir --- bios/second_stage/src/disk.rs | 6 ++++++ bios/second_stage/src/main.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index 6334c47a..e282d95c 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -95,6 +95,12 @@ pub struct AlignedArrayBuffer { pub limit: usize, } +impl AlignedArrayBuffer { + pub fn reset_limit(&mut self) { + self.limit = LEN; + } +} + pub trait AlignedBuffer { fn slice(&self) -> &[u8]; fn slice_mut(&mut self) -> &mut [u8]; diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index d93f7974..54735037 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -82,6 +82,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let kernel = fs .find_file_in_root_dir("kernel-x86_64", disk_buffer) .expect("no `kernel-x86_64` file found"); + disk_buffer.reset_limit(); for cluster in fs.file_clusters(&kernel) { let cluster = cluster.unwrap(); From 785e82a119d359edd93ef7fc9fc72a7e00f7c9da Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 27 Jun 2022 09:58:38 +0200 Subject: [PATCH 128/226] Update TODOs --- bios/second_stage/src/main.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 54735037..ee3eb63d 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -97,15 +97,15 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { writeln!(screen::Writer, "DONE").unwrap(); - // TODO: Retrieve memory map - - // TODO: Set up protected mode, or unreal mode + // TODO: Load `kernel` into DISK_BUFFER, then copy it to protected mode + // address (might require multiple iterations for large kernels) - // TODO: Load `kernel` to protected mode address - - // TODO: Set up long mode with identity-mapping + // TODO: Retrieve memory map + // TODO: VESA config - // TODO: Load third stage that uses `bootloader-common` crate and jump to it + // TODO: Load third stage using DISK_BUFFER, then copy it to protected mode addr + // TODO: Set up long mode with identity-mapping, then jump to third stage (passing + // kernel, memory map, and vesa info as arguments) loop {} } From d16b84bcf8f25db3d26711c4aa1897feed878e74 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 12 Aug 2022 19:29:03 +0200 Subject: [PATCH 129/226] Fix reuse of bootloader memory areas The previous implementation ignored all descriptors whose start address was smaller than the last allocation. --- api/src/info.rs | 4 +++- bios/src/memory_descriptor.rs | 4 ++++ common/src/legacy_memory_region.rs | 15 +++++++++++---- uefi/src/memory_descriptor.rs | 11 ++++++----- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/api/src/info.rs b/api/src/info.rs index 555ad0ca..46facd5b 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -141,10 +141,12 @@ impl MemoryRegion { pub enum MemoryRegionKind { /// Unused conventional memory, can be used by the kernel. Usable, - /// Memory mappings created by the bootloader, including the kernel and boot info mappings. + /// Memory mappings created by the bootloader, including the page table and boot info mappings. /// /// This memory should _not_ be used by the kernel. Bootloader, + /// Memory mapping of the kernel. + Kernel, /// An unknown memory region reported by the UEFI firmware. /// /// Contains the UEFI memory type tag. diff --git a/bios/src/memory_descriptor.rs b/bios/src/memory_descriptor.rs index 1bb14859..624f0110 100644 --- a/bios/src/memory_descriptor.rs +++ b/bios/src/memory_descriptor.rs @@ -17,6 +17,10 @@ impl LegacyMemoryRegion for E820MemoryRegion { other => MemoryRegionKind::UnknownBios(other), } } + + fn usable_after_bootloader_exit(&self) -> bool { + matches!(self.kind(), MemoryRegionKind::Usable) + } } /// A physical memory region returned by an `e820` BIOS call. diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index 1801199b..dd6f223a 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -14,8 +14,8 @@ pub trait LegacyMemoryRegion: Copy + core::fmt::Debug { /// Returns the type of the region, e.g. whether it is usable or reserved. fn kind(&self) -> MemoryRegionKind; - /// Mark additional regions as usable just before the bootloader jumps to the kernel. - fn on_bootloader_exit(&mut self) {} + /// Some regions become usable when the bootloader jumps to the kernel. + fn usable_after_bootloader_exit(&self) -> bool; } /// A physical frame allocator based on a BIOS or UEFI provided memory map. @@ -104,11 +104,10 @@ where ) -> &mut [MemoryRegion] { let mut next_index = 0; - for mut descriptor in self.original { + for descriptor in self.original { let mut start = descriptor.start(); let end = start + descriptor.len(); let next_free = self.next_frame.start_address(); - descriptor.on_bootloader_exit(); let kind = match descriptor.kind() { MemoryRegionKind::Usable => { if end <= next_free { @@ -130,6 +129,14 @@ where MemoryRegionKind::Usable } } + _ if descriptor.usable_after_bootloader_exit() => { + // Region was not usable before, but it will be as soon as + // the bootloader passes control to the kernel. We don't + // need to check against `next_free` because the + // LegacyFrameAllocator only allocates memory from usable + // descriptors. + MemoryRegionKind::Usable + } other => other, }; diff --git a/uefi/src/memory_descriptor.rs b/uefi/src/memory_descriptor.rs index 8fe8bf1c..dee9f272 100644 --- a/uefi/src/memory_descriptor.rs +++ b/uefi/src/memory_descriptor.rs @@ -24,19 +24,20 @@ impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { } } - fn on_bootloader_exit(&mut self) { + fn usable_after_bootloader_exit(&self) -> bool { match self.0.ty { - // the bootloader is about to exit, so we can reallocate its data + MemoryType::CONVENTIONAL => true, MemoryType::LOADER_CODE | MemoryType::LOADER_DATA | MemoryType::BOOT_SERVICES_CODE | MemoryType::BOOT_SERVICES_DATA | MemoryType::RUNTIME_SERVICES_CODE | MemoryType::RUNTIME_SERVICES_DATA => { - // we don't need this data anymore - self.0.ty = MemoryType::CONVENTIONAL; + // we don't need this data anymore after the bootloader + // passes control to the kernel + true } - _ => {} + _ => false, } } } From 73ae143c4395776ce32def52ba94d87a7671b967 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 12 Aug 2022 19:30:08 +0200 Subject: [PATCH 130/226] Allocate UEFI kernel memory using custom memory type To avoid that the descriptor is interpreted as usable again in `usable_after_bootloader_exit`. --- uefi/src/main.rs | 7 ++++--- uefi/src/memory_descriptor.rs | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 094df8af..954b3e3d 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -7,6 +7,7 @@ use crate::memory_descriptor::UefiMemoryDescriptor; use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; use bootloader_x86_64_common::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; use core::{arch::asm, cell::UnsafeCell, fmt::Write, mem, panic::PanicInfo, ptr, slice}; +use memory_descriptor::KERNEL_MEMORY_TYPE; use uefi::{ prelude::{entry, Boot, Handle, Status, SystemTable}, proto::{ @@ -60,7 +61,7 @@ fn efi_main(image: Handle, st: SystemTable) -> Status { } fn main_inner(image: Handle, mut st: SystemTable) -> Status { - // temporarily clone the system table for printing panics + // temporarily clone the y table for printing panics unsafe { *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); } @@ -202,7 +203,7 @@ fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<& .boot_services() .allocate_pages( AllocateType::AnyPages, - MemoryType::LOADER_DATA, + KERNEL_MEMORY_TYPE, ((kernel_size - 1) / 4096) + 1, ) .unwrap() as *mut u8; @@ -282,7 +283,7 @@ fn load_kernel_file_from_tftp_boot_server( .boot_services() .allocate_pages( AllocateType::AnyPages, - MemoryType::LOADER_DATA, + KERNEL_MEMORY_TYPE, ((kernel_size - 1) / 4096) + 1, ) .expect("Failed to allocate memory for the kernel file") as *mut u8; diff --git a/uefi/src/memory_descriptor.rs b/uefi/src/memory_descriptor.rs index dee9f272..5a7b850a 100644 --- a/uefi/src/memory_descriptor.rs +++ b/uefi/src/memory_descriptor.rs @@ -7,6 +7,7 @@ use x86_64::PhysAddr; pub struct UefiMemoryDescriptor(pub MemoryDescriptor); const PAGE_SIZE: u64 = 4096; +pub const KERNEL_MEMORY_TYPE: MemoryType = MemoryType::custom(0x80000000); impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { fn start(&self) -> PhysAddr { @@ -20,6 +21,7 @@ impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { fn kind(&self) -> MemoryRegionKind { match self.0.ty { MemoryType::CONVENTIONAL => MemoryRegionKind::Usable, + other if other == KERNEL_MEMORY_TYPE => MemoryRegionKind::Kernel, other => MemoryRegionKind::UnknownUefi(other.0), } } From e0a083617d7d8a00bce14677dcb2e129f2c963aa Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 14:58:03 +0200 Subject: [PATCH 131/226] Remove copy&pasted comments from second stage linker script --- bios/second_stage/second-stage-link.ld | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bios/second_stage/second-stage-link.ld b/bios/second_stage/second-stage-link.ld index ab91f9eb..db1ebe1a 100644 --- a/bios/second_stage/second-stage-link.ld +++ b/bios/second_stage/second-stage-link.ld @@ -32,7 +32,6 @@ SECTIONS { . = 0x0007FFFF - 2; .end_marker : { - SHORT(0xdead) /* magic number for bootable disk */ + SHORT(0xdead) } - . = 0x7c00 + 512; } From 84eea29b69ac75c0d7ce9c36e0d2a1369052dc2b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 14:58:28 +0200 Subject: [PATCH 132/226] Load the kernel into buffer memory --- bios/second_stage/src/main.rs | 49 +++++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index ee3eb63d..26a0aaa8 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -6,7 +6,10 @@ use core::{fmt::Write as _, slice}; use disk::AlignedArrayBuffer; use mbr_nostd::{PartitionTableEntry, PartitionType}; -use crate::protected_mode::enter_unreal_mode; +use crate::{ + disk::{AlignedBuffer, Read, Seek, SeekFrom}, + protected_mode::enter_unreal_mode, +}; mod dap; mod disk; @@ -33,7 +36,7 @@ static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { - screen::Writer.write_str(" -> SECOND STAGE").unwrap(); + screen::Writer.write_str(" -> SECOND STAGE\n").unwrap(); enter_unreal_mode(); @@ -78,27 +81,45 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let mut fs = fat::FileSystem::parse(disk.clone()); let disk_buffer = unsafe { &mut DISK_BUFFER }; + let disk_buffer_size = u64::try_from(disk_buffer.buffer.len()).unwrap(); + disk_buffer.reset_limit(); let kernel = fs .find_file_in_root_dir("kernel-x86_64", disk_buffer) .expect("no `kernel-x86_64` file found"); - disk_buffer.reset_limit(); for cluster in fs.file_clusters(&kernel) { let cluster = cluster.unwrap(); - writeln!( - screen::Writer, - "kernel cluster: start: {:#x}, len: {}", - cluster.start_offset, - cluster.len_bytes - ) - .unwrap(); + let cluster_start = cluster.start_offset; + let cluster_end = cluster_start + u64::from(cluster.len_bytes); + + let mut offset = 0; + loop { + let range_start = cluster_start + offset; + if range_start >= cluster_end { + break; + } + let range_end = u64::min(range_start + disk_buffer_size, cluster_end); + let len = range_end - range_start; + + writeln!( + screen::Writer, + "loading kernel bytes {range_start:#x}..{range_end:#x}" + ) + .unwrap(); + + disk.seek(SeekFrom::Start(range_start)); + disk_buffer.reset_limit(); + disk.read_exact_into(disk_buffer); + + let slice = &disk_buffer.buffer[..usize::try_from(len).unwrap()]; + // TODO: copy slice to protected mode address + + offset += len; + } } - writeln!(screen::Writer, "DONE").unwrap(); - - // TODO: Load `kernel` into DISK_BUFFER, then copy it to protected mode - // address (might require multiple iterations for large kernels) + writeln!(screen::Writer, "kernel loaded").unwrap(); // TODO: Retrieve memory map // TODO: VESA config From 52ad3e2afab4f902ef5acb3241d7bc34c2a16e94 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 15:40:18 +0200 Subject: [PATCH 133/226] Copy kernel to protected mode --- bios/second_stage/src/main.rs | 11 +++++++++-- bios/second_stage/src/protected_mode.rs | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 26a0aaa8..847adf8f 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -8,7 +8,7 @@ use mbr_nostd::{PartitionTableEntry, PartitionType}; use crate::{ disk::{AlignedBuffer, Read, Seek, SeekFrom}, - protected_mode::enter_unreal_mode, + protected_mode::{copy_to_protected_mode, enter_unreal_mode}, }; mod dap; @@ -20,6 +20,8 @@ mod screen; /// We use this partition type to store the second bootloader stage; const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; +const KERNEL_DST: *mut u8 = (2 * 1024 * 1024) as *mut u8; + extern "C" { static _second_stage_end: u8; } @@ -113,7 +115,12 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { disk.read_exact_into(disk_buffer); let slice = &disk_buffer.buffer[..usize::try_from(len).unwrap()]; - // TODO: copy slice to protected mode address + unsafe { + copy_to_protected_mode( + KERNEL_DST.wrapping_add(usize::try_from(offset).unwrap()), + slice, + ) + }; offset += len; } diff --git a/bios/second_stage/src/protected_mode.rs b/bios/second_stage/src/protected_mode.rs index 1cd251ea..8404ae55 100644 --- a/bios/second_stage/src/protected_mode.rs +++ b/bios/second_stage/src/protected_mode.rs @@ -86,6 +86,13 @@ pub fn enter_unreal_mode() { } } +pub unsafe fn copy_to_protected_mode(target: *mut u8, bytes: &[u8]) { + for (offset, byte) in bytes.iter().enumerate() { + let dst = target.wrapping_add(offset); + unsafe { asm!("mov [{}], {}", in(reg) dst, in(reg) byte) }; + } +} + fn write_cr0(val: u32) { unsafe { asm!("mov cr0, {}", in(reg) val, options(nostack, preserves_flags)) }; } From 397a2d0ede795d790cd4d7c161dfeacae6c2d64e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 15:40:28 +0200 Subject: [PATCH 134/226] Enable overflow checks for second stage --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 31dff3cf..064eb26e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ inherits = "release" opt-level = "s" codegen-units = 1 debug = false +overflow-checks = true [profile.test.package.test_kernel_higher_half] rustflags = [ From 955643daefe30562f6fd4a726e8ded44c42603fa Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 16:24:54 +0200 Subject: [PATCH 135/226] Pass read size directly in `read_exact_into` --- bios/second_stage/src/disk.rs | 31 ++++++++----------------------- bios/second_stage/src/fat.rs | 3 +-- bios/second_stage/src/main.rs | 12 ++++++------ 3 files changed, 15 insertions(+), 31 deletions(-) diff --git a/bios/second_stage/src/disk.rs b/bios/second_stage/src/disk.rs index e282d95c..88b2b990 100644 --- a/bios/second_stage/src/disk.rs +++ b/bios/second_stage/src/disk.rs @@ -9,21 +9,18 @@ pub struct DiskAccess { impl Read for DiskAccess { fn read_exact(&mut self, len: usize) -> &[u8] { - static mut TMP_BUF: AlignedArrayBuffer<512> = AlignedArrayBuffer { - buffer: [0; 512], - limit: 512, - }; + static mut TMP_BUF: AlignedArrayBuffer<512> = AlignedArrayBuffer { buffer: [0; 512] }; let buf = unsafe { &mut TMP_BUF }; assert!(len <= buf.buffer.len()); - self.read_exact_into(buf); + self.read_exact_into(512, buf); &buf.buffer[..len] } - fn read_exact_into(&mut self, buf: &mut dyn AlignedBuffer) { - let buf = buf.slice_mut(); - assert_eq!(buf.len() % 512, 0); + fn read_exact_into(&mut self, len: usize, buf: &mut dyn AlignedBuffer) { + assert_eq!(len % 512, 0); + let buf = &mut buf.slice_mut()[..len]; let end_addr = self.base_offset + self.current_offset + u64::try_from(buf.len()).unwrap(); let mut start_lba = (self.base_offset + self.current_offset) / 512; @@ -76,7 +73,7 @@ impl Seek for DiskAccess { pub trait Read { fn read_exact(&mut self, len: usize) -> &[u8]; - fn read_exact_into(&mut self, buf: &mut dyn AlignedBuffer); + fn read_exact_into(&mut self, len: usize, buf: &mut dyn AlignedBuffer); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -92,30 +89,18 @@ pub trait Seek { #[repr(align(2))] pub struct AlignedArrayBuffer { pub buffer: [u8; LEN], - pub limit: usize, -} - -impl AlignedArrayBuffer { - pub fn reset_limit(&mut self) { - self.limit = LEN; - } } pub trait AlignedBuffer { fn slice(&self) -> &[u8]; fn slice_mut(&mut self) -> &mut [u8]; - fn set_limit(&mut self, limit: usize); } impl AlignedBuffer for AlignedArrayBuffer { fn slice(&self) -> &[u8] { - &self.buffer[..self.limit] + &self.buffer[..] } fn slice_mut(&mut self) -> &mut [u8] { - &mut self.buffer[..self.limit] - } - fn set_limit(&mut self, limit: usize) { - assert!(limit <= LEN); - self.limit = limit; + &mut self.buffer[..] } } diff --git a/bios/second_stage/src/fat.rs b/bios/second_stage/src/fat.rs index 21c358d8..73236328 100644 --- a/bios/second_stage/src/fat.rs +++ b/bios/second_stage/src/fat.rs @@ -214,11 +214,10 @@ impl FileSystem { } FatType::Fat12 | FatType::Fat16 => { let root_directory_size = self.bpb.root_directory_size(); - buffer.set_limit(root_directory_size); self.disk .seek(SeekFrom::Start(self.bpb.root_directory_offset())); - self.disk.read_exact_into(buffer); + self.disk.read_exact_into(root_directory_size, buffer); buffer .slice() diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 847adf8f..613f6ef6 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -32,7 +32,6 @@ fn second_stage_end() -> *const u8 { static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { buffer: [0; 0x4000], - limit: 0x4000, }; #[no_mangle] @@ -83,9 +82,8 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let mut fs = fat::FileSystem::parse(disk.clone()); let disk_buffer = unsafe { &mut DISK_BUFFER }; - let disk_buffer_size = u64::try_from(disk_buffer.buffer.len()).unwrap(); + let disk_buffer_size = disk_buffer.buffer.len(); - disk_buffer.reset_limit(); let kernel = fs .find_file_in_root_dir("kernel-x86_64", disk_buffer) .expect("no `kernel-x86_64` file found"); @@ -101,7 +99,10 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { if range_start >= cluster_end { break; } - let range_end = u64::min(range_start + disk_buffer_size, cluster_end); + let range_end = u64::min( + range_start + u64::try_from(disk_buffer_size).unwrap(), + cluster_end, + ); let len = range_end - range_start; writeln!( @@ -111,8 +112,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { .unwrap(); disk.seek(SeekFrom::Start(range_start)); - disk_buffer.reset_limit(); - disk.read_exact_into(disk_buffer); + disk.read_exact_into(disk_buffer_size, disk_buffer); let slice = &disk_buffer.buffer[..usize::try_from(len).unwrap()]; unsafe { From 66b16a3d91fc098945d705539adef5d4c5f3bc60 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 16:27:49 +0200 Subject: [PATCH 136/226] Create `load_file` function --- bios/second_stage/src/main.rs | 46 ++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 613f6ef6..0bc7f79e 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -82,12 +82,32 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let mut fs = fat::FileSystem::parse(disk.clone()); let disk_buffer = unsafe { &mut DISK_BUFFER }; - let disk_buffer_size = disk_buffer.buffer.len(); - let kernel = fs - .find_file_in_root_dir("kernel-x86_64", disk_buffer) - .expect("no `kernel-x86_64` file found"); + load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); + + writeln!(screen::Writer, "kernel loaded").unwrap(); + + // TODO: Retrieve memory map + // TODO: VESA config + + // TODO: Load third stage using DISK_BUFFER, then copy it to protected mode addr + // TODO: Set up long mode with identity-mapping, then jump to third stage (passing + // kernel, memory map, and vesa info as arguments) + + loop {} +} +fn load_file( + file_name: &str, + dst: *mut u8, + fs: &mut fat::FileSystem, + disk: &mut disk::DiskAccess, + disk_buffer: &mut AlignedArrayBuffer<16384>, +) { + let disk_buffer_size = disk_buffer.buffer.len(); + let kernel = fs + .find_file_in_root_dir(file_name, disk_buffer) + .expect("file not found"); for cluster in fs.file_clusters(&kernel) { let cluster = cluster.unwrap(); let cluster_start = cluster.start_offset; @@ -107,7 +127,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { writeln!( screen::Writer, - "loading kernel bytes {range_start:#x}..{range_end:#x}" + "loading bytes {range_start:#x}..{range_end:#x}" ) .unwrap(); @@ -116,26 +136,12 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let slice = &disk_buffer.buffer[..usize::try_from(len).unwrap()]; unsafe { - copy_to_protected_mode( - KERNEL_DST.wrapping_add(usize::try_from(offset).unwrap()), - slice, - ) + copy_to_protected_mode(dst.wrapping_add(usize::try_from(offset).unwrap()), slice) }; offset += len; } } - - writeln!(screen::Writer, "kernel loaded").unwrap(); - - // TODO: Retrieve memory map - // TODO: VESA config - - // TODO: Load third stage using DISK_BUFFER, then copy it to protected mode addr - // TODO: Set up long mode with identity-mapping, then jump to third stage (passing - // kernel, memory map, and vesa info as arguments) - - loop {} } /// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 From 5b5c5e68a0d0ad2b9c3fedf32d37c0cfc1f2ef6f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 16:41:34 +0200 Subject: [PATCH 137/226] Fix some warnings --- src/lib.rs | 7 +------ src/mbr.rs | 5 +++-- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 86e1ecf4..2495d53c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -68,12 +68,7 @@ for all possible configuration options. #![warn(missing_docs)] use anyhow::Context; -use std::{ - collections::BTreeMap, - fs::{self, File}, - io::{self, Seek}, - path::Path, -}; +use std::{collections::BTreeMap, path::Path}; mod fat; mod gpt; diff --git a/src/mbr.rs b/src/mbr.rs index 9b44b9b4..8f7dfade 100644 --- a/src/mbr.rs +++ b/src/mbr.rs @@ -1,7 +1,7 @@ use anyhow::Context; use std::{ fs::{self, File}, - io::{self, Read, Seek, SeekFrom}, + io::{self, Seek, SeekFrom}, path::Path, }; const SECTOR_SIZE: u32 = 512; @@ -91,7 +91,8 @@ pub fn create_mbr_disk( // fat partition disk.seek(SeekFrom::Start( (boot_partition_start_sector * SECTOR_SIZE).into(), - )); + )) + .context("seek failed")?; io::copy(&mut boot_partition, &mut disk) .context("failed to copy FAT image to MBR disk image")?; From 6492eab051b71a19b58a4f70185e7898fabb2c46 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 16:58:26 +0200 Subject: [PATCH 138/226] Load a third stage --- Cargo.lock | 4 +++ Cargo.toml | 7 ++++ bios/second_stage/src/main.rs | 12 +++++-- bios/third_stage/.gitignore | 1 + bios/third_stage/Cargo.toml | 9 +++++ bios/third_stage/build.rs | 9 +++++ bios/third_stage/src/main.rs | 13 ++++++++ bios/third_stage/third-stage-link.ld | 36 ++++++++++++++++++++ build.rs | 49 ++++++++++++++++++++++++++++ i686-third-stage.json | 21 ++++++++++++ src/lib.rs | 3 ++ 11 files changed, 162 insertions(+), 2 deletions(-) create mode 100644 bios/third_stage/.gitignore create mode 100644 bios/third_stage/Cargo.toml create mode 100644 bios/third_stage/build.rs create mode 100644 bios/third_stage/src/main.rs create mode 100644 bios/third_stage/third-stage-link.ld create mode 100644 i686-third-stage.json diff --git a/Cargo.lock b/Cargo.lock index 4662e750..ec88046e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -94,6 +94,10 @@ dependencies = [ "mbr-nostd", ] +[[package]] +name = "bootloader-x86_64-bios-third-stage" +version = "0.1.0" + [[package]] name = "bootloader-x86_64-common" version = "0.1.0-alpha.0" diff --git a/Cargo.toml b/Cargo.toml index 064eb26e..206696a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ members = [ "bios", "bios/boot_sector", "bios/second_stage", + "bios/third_stage", "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", @@ -69,6 +70,12 @@ codegen-units = 1 debug = false overflow-checks = true +[profile.third-stage] +inherits = "release" +codegen-units = 1 +debug = false +overflow-checks = true + [profile.test.package.test_kernel_higher_half] rustflags = [ "-C", diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index 0bc7f79e..e86f8885 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -20,7 +20,8 @@ mod screen; /// We use this partition type to store the second bootloader stage; const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; -const KERNEL_DST: *mut u8 = (2 * 1024 * 1024) as *mut u8; +const THIRD_STAGE_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB accessible here) +const KERNEL_DST: *mut u8 = 0x0100_0000 as *mut u8; // 16MiB extern "C" { static _second_stage_end: u8; @@ -83,8 +84,15 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let disk_buffer = unsafe { &mut DISK_BUFFER }; + load_file( + "boot-stage-3", + THIRD_STAGE_DST, + &mut fs, + &mut disk, + disk_buffer, + ); + writeln!(screen::Writer, "third stage loaded").unwrap(); load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); - writeln!(screen::Writer, "kernel loaded").unwrap(); // TODO: Retrieve memory map diff --git a/bios/third_stage/.gitignore b/bios/third_stage/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/bios/third_stage/.gitignore @@ -0,0 +1 @@ +/target diff --git a/bios/third_stage/Cargo.toml b/bios/third_stage/Cargo.toml new file mode 100644 index 00000000..5661c5e9 --- /dev/null +++ b/bios/third_stage/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "bootloader-x86_64-bios-third-stage" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/bios/third_stage/build.rs b/bios/third_stage/build.rs new file mode 100644 index 00000000..73bc965e --- /dev/null +++ b/bios/third_stage/build.rs @@ -0,0 +1,9 @@ +use std::path::Path; + +fn main() { + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); + println!( + "cargo:rustc-link-arg-bins=--script={}", + local_path.join("third-stage-link.ld").display() + ) +} diff --git a/bios/third_stage/src/main.rs b/bios/third_stage/src/main.rs new file mode 100644 index 00000000..ab19acd6 --- /dev/null +++ b/bios/third_stage/src/main.rs @@ -0,0 +1,13 @@ +#![no_std] +#![no_main] + +#[no_mangle] +#[link_section = ".start"] +pub extern "C" fn _start() { + loop {} +} + +#[panic_handler] +pub fn panic(info: &core::panic::PanicInfo) -> ! { + loop {} +} diff --git a/bios/third_stage/third-stage-link.ld b/bios/third_stage/third-stage-link.ld new file mode 100644 index 00000000..c82e6f52 --- /dev/null +++ b/bios/third_stage/third-stage-link.ld @@ -0,0 +1,36 @@ +ENTRY(_start) + +SECTIONS { + . = 0x00100000; + + .start : { + *(.start) + } + .text : { + *(.text .text.*) + } + .rodata : { + *(.rodata .rodata.*) + } + .data : { + *(.data .data.*) + } + .bss : { + *(.bss .bss.*) + } + .eh_frame : { + *(.eh_frame .eh_frame.*) + } + .eh_frame_hdr : { + *(.eh_frame_hdr .eh_frame_hdr.*) + } + + . = ALIGN(512); + + _third_stage_end = .; + + .end_marker : + { + SHORT(0xdead) + } +} diff --git a/build.rs b/build.rs index fa4ddec3..bc5cedb9 100644 --- a/build.rs +++ b/build.rs @@ -6,6 +6,7 @@ use std::{ const BOOTLOADER_X86_64_UEFI_VERSION: &str = "0.1.0-alpha.0"; const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = "0.1.0-alpha.0"; const BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION: &str = "0.1.0-alpha.0"; +const BOOTLOADER_X86_64_BIOS_THIRD_STAGE_VERSION: &str = "0.1.0-alpha.0"; fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); @@ -29,6 +30,12 @@ fn main() { "cargo:rustc-env=BIOS_SECOND_STAGE_PATH={}", bios_second_stage_path.display() ); + + let bios_third_stage_path = build_bios_third_stage(&out_dir); + println!( + "cargo:rustc-env=BIOS_THIRD_STAGE_PATH={}", + bios_third_stage_path.display() + ); } fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { @@ -149,6 +156,48 @@ fn build_bios_second_stage(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } +fn build_bios_third_stage(out_dir: &Path) -> PathBuf { + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); + let mut cmd = Command::new(cargo); + cmd.arg("install").arg("bootloader-x86_64-bios-third-stage"); + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("bios") + .join("third_stage"); + if local_path.exists() { + // local build + cmd.arg("--path").arg(&local_path); + println!("cargo:rerun-if-changed={}", local_path.display()); + } else { + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_THIRD_STAGE_VERSION); + } + cmd.arg("--locked"); + cmd.arg("--target").arg("i686-third-stage.json"); + cmd.arg("--profile").arg("third-stage"); + cmd.arg("-Zbuild-std=core") + .arg("-Zbuild-std-features=compiler-builtins-mem"); + cmd.arg("--root").arg(out_dir); + cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); + cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); // used by clippy + let status = cmd + .status() + .expect("failed to run cargo install for bios third stage"); + let elf_path = if status.success() { + let path = out_dir + .join("bin") + .join("bootloader-x86_64-bios-third-stage"); + assert!( + path.exists(), + "bios third stage executable does not exist after building" + ); + path + } else { + panic!("failed to build bios third stage"); + }; + convert_elf_to_bin(elf_path) +} + fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { let flat_binary_path = elf_path.with_extension("bin"); diff --git a/i686-third-stage.json b/i686-third-stage.json new file mode 100644 index 00000000..eca1387f --- /dev/null +++ b/i686-third-stage.json @@ -0,0 +1,21 @@ +{ + "arch": "x86", + "cpu": "i386", + "data-layout": "e-m:e-i32:32-f80:128-n8:16:32-S128-p:32:32", + "dynamic-linking": false, + "executables": true, + "linker-flavor": "ld.lld", + "linker": "rust-lld", + "llvm-target": "i686-unknown-none", + "max-atomic-width": 64, + "position-independent-executables": false, + "disable-redzone": true, + "target-c-int-width": "32", + "target-pointer-width": "32", + "target-endian": "little", + "panic-strategy": "abort", + "os": "none", + "vendor": "unknown", + "relocation-model": "static", + "features": "+soft-float,+sse" +} diff --git a/src/lib.rs b/src/lib.rs index 2495d53c..89799204 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,14 +76,17 @@ mod mbr; mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; +const BIOS_THIRD_STAGE: &str = "boot-stage-3"; /// Creates a bootable FAT partition at the given path. pub fn create_boot_partition(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + let third_stage_path = Path::new(env!("BIOS_THIRD_STAGE_PATH")); let mut files = BTreeMap::new(); files.insert("efi/boot/bootx64.efi", bootloader_path); files.insert(KERNEL_FILE_NAME, kernel_binary); + files.insert(BIOS_THIRD_STAGE, third_stage_path); fat::create_fat_filesystem(files, &out_path).context("failed to create UEFI FAT filesystem")?; From 7cf073eae6a5c6e30684a67cdac66016c7dcfdf2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 18:42:55 +0200 Subject: [PATCH 139/226] Jump to third stage --- bios/second_stage/src/main.rs | 8 ++-- bios/second_stage/src/protected_mode.rs | 63 +++++++++++++++++++++---- bios/third_stage/src/main.rs | 9 ++++ 3 files changed, 68 insertions(+), 12 deletions(-) diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index e86f8885..e6f61429 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -8,7 +8,9 @@ use mbr_nostd::{PartitionTableEntry, PartitionType}; use crate::{ disk::{AlignedBuffer, Read, Seek, SeekFrom}, - protected_mode::{copy_to_protected_mode, enter_unreal_mode}, + protected_mode::{ + copy_to_protected_mode, enter_protected_mode_and_jump_to_third_stage, enter_unreal_mode, + }, }; mod dap; @@ -98,9 +100,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { // TODO: Retrieve memory map // TODO: VESA config - // TODO: Load third stage using DISK_BUFFER, then copy it to protected mode addr - // TODO: Set up long mode with identity-mapping, then jump to third stage (passing - // kernel, memory map, and vesa info as arguments) + enter_protected_mode_and_jump_to_third_stage(THIRD_STAGE_DST); loop {} } diff --git a/bios/second_stage/src/protected_mode.rs b/bios/second_stage/src/protected_mode.rs index 8404ae55..3f7653c4 100644 --- a/bios/second_stage/src/protected_mode.rs +++ b/bios/second_stage/src/protected_mode.rs @@ -1,4 +1,7 @@ -use core::{arch::asm, mem::size_of}; +use core::{ + arch::{asm, global_asm}, + mem::size_of, +}; static GDT: GdtProtectedMode = GdtProtectedMode::new(); @@ -65,17 +68,14 @@ pub fn enter_unreal_mode() { GDT.clear_interrupts_and_load(); // set protected mode bit - let mut cr0: u32; - unsafe { - asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack, preserves_flags)); - } - let cr0_protected = cr0 | 1; - write_cr0(cr0_protected); + let cr0 = set_protected_mode_bit(); + // load GDT unsafe { asm!("mov bx, 0x10", "mov ds, bx"); } + // unset protected mode bit again write_cr0(cr0); unsafe { @@ -86,13 +86,60 @@ pub fn enter_unreal_mode() { } } +#[no_mangle] pub unsafe fn copy_to_protected_mode(target: *mut u8, bytes: &[u8]) { for (offset, byte) in bytes.iter().enumerate() { let dst = target.wrapping_add(offset); - unsafe { asm!("mov [{}], {}", in(reg) dst, in(reg) byte) }; + unsafe { asm!("nop", "nop", "mov [{}], {}", in(reg) dst, in(reg_byte) *byte) }; + assert_eq!(read_from_protected_mode(dst), *byte); } } +#[no_mangle] +pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { + let res; + unsafe { asm!("nop", "nop", "mov {}, [{}]", out(reg_byte) res, in(reg) ptr) }; + res +} + +pub fn enter_protected_mode_and_jump_to_third_stage(entry_point: *const u8) { + let (entry_point_high, entry_point_low) = { + let addr = entry_point as u32; + (addr >> 16, addr & u32::from(u16::MAX)) + }; + + unsafe { asm!("cli") }; + set_protected_mode_bit(); + unsafe { + // asm!("ljmp $0x8, $protected_mode", options(att_syntax)); + asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); + asm!( + " + .code32 + mov bx, 0x10 + mov ds, bx + mov es, bx + mov ss, bx + shl eax, 16 + or eax, {0} + jmp eax + 2: + jmp 2b + " + , in(reg) entry_point_low, in("ax") entry_point_high, out("ebx") _); + } +} + +fn set_protected_mode_bit() -> u32 { + let mut cr0: u32; + unsafe { + asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack, preserves_flags)); + } + let cr0_protected = cr0 | 1; + write_cr0(cr0_protected); + cr0 +} + fn write_cr0(val: u32) { unsafe { asm!("mov cr0, {}", in(reg) val, options(nostack, preserves_flags)) }; } diff --git a/bios/third_stage/src/main.rs b/bios/third_stage/src/main.rs index ab19acd6..392c11d3 100644 --- a/bios/third_stage/src/main.rs +++ b/bios/third_stage/src/main.rs @@ -4,6 +4,15 @@ #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start() { + // TODO: Set up long mode with identity-mapping, then jump to 4th stage (passing + // kernel, memory map, and vesa info as arguments) + + let vga = 0xb8000 as *mut u16; + + for i in 0..(80 * 25) { + unsafe { vga.wrapping_add(i).write_volatile(0x0f01) }; + } + loop {} } From 6369fae1adb9a0789ff9ebf60352487a8467d3a8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 18:43:26 +0200 Subject: [PATCH 140/226] Verify bytes written to protected mode memory --- bios/second_stage/src/main.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bios/second_stage/src/main.rs b/bios/second_stage/src/main.rs index e6f61429..23f9a049 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/second_stage/src/main.rs @@ -146,6 +146,12 @@ fn load_file( unsafe { copy_to_protected_mode(dst.wrapping_add(usize::try_from(offset).unwrap()), slice) }; + let written = unsafe { + protected_mode::read_from_protected_mode( + dst.wrapping_add(usize::try_from(offset).unwrap()), + ) + }; + assert_eq!(slice[0], written); offset += len; } From af6d1920420b93ae9469bad99334226df06bb2ef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 19:49:00 +0200 Subject: [PATCH 141/226] Rename bios stages --- Cargo.lock | 4 +- Cargo.toml | 9 ++- bios/{second_stage => stage-2}/.gitignore | 0 bios/{second_stage => stage-2}/Cargo.toml | 2 +- bios/{third_stage => stage-2}/build.rs | 2 +- .../src/boot unreal.s | 0 bios/{second_stage => stage-2}/src/dap.rs | 0 bios/{second_stage => stage-2}/src/disk.rs | 0 bios/{second_stage => stage-2}/src/fat.rs | 0 bios/{second_stage => stage-2}/src/main.rs | 16 ++--- .../src/protected_mode.rs | 2 +- bios/{second_stage => stage-2}/src/screen.rs | 0 .../stage-2-link.ld} | 0 bios/{third_stage => stage-3}/.gitignore | 0 bios/{third_stage => stage-3}/Cargo.toml | 2 +- bios/{second_stage => stage-3}/build.rs | 2 +- bios/{third_stage => stage-3}/src/main.rs | 3 + .../stage-3-link.ld} | 0 build.rs | 59 +++++++++---------- ...stage.json => i386-code16-boot-sector.json | 0 x86-16bit.json => i386-code16-stage-2.json | 0 i686-third-stage.json => i686-stage-3.json | 0 src/lib.rs | 10 ++-- 23 files changed, 51 insertions(+), 60 deletions(-) rename bios/{second_stage => stage-2}/.gitignore (100%) rename bios/{second_stage => stage-2}/Cargo.toml (86%) rename bios/{third_stage => stage-2}/build.rs (74%) rename bios/{second_stage => stage-2}/src/boot unreal.s (100%) rename bios/{second_stage => stage-2}/src/dap.rs (100%) rename bios/{second_stage => stage-2}/src/disk.rs (100%) rename bios/{second_stage => stage-2}/src/fat.rs (100%) rename bios/{second_stage => stage-2}/src/main.rs (93%) rename bios/{second_stage => stage-2}/src/protected_mode.rs (97%) rename bios/{second_stage => stage-2}/src/screen.rs (100%) rename bios/{second_stage/second-stage-link.ld => stage-2/stage-2-link.ld} (100%) rename bios/{third_stage => stage-3}/.gitignore (100%) rename bios/{third_stage => stage-3}/Cargo.toml (82%) rename bios/{second_stage => stage-3}/build.rs (73%) rename bios/{third_stage => stage-3}/src/main.rs (90%) rename bios/{third_stage/third-stage-link.ld => stage-3/stage-3-link.ld} (100%) rename x86-16bit-second-stage.json => i386-code16-boot-sector.json (100%) rename x86-16bit.json => i386-code16-stage-2.json (100%) rename i686-third-stage.json => i686-stage-3.json (100%) diff --git a/Cargo.lock b/Cargo.lock index ec88046e..05d4cd8d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,7 +87,7 @@ name = "bootloader-x86_64-bios-boot-sector" version = "0.1.0" [[package]] -name = "bootloader-x86_64-bios-second-stage" +name = "bootloader-x86_64-bios-stage-2" version = "0.1.0" dependencies = [ "byteorder", @@ -95,7 +95,7 @@ dependencies = [ ] [[package]] -name = "bootloader-x86_64-bios-third-stage" +name = "bootloader-x86_64-bios-stage-3" version = "0.1.0" [[package]] diff --git a/Cargo.toml b/Cargo.toml index 206696a3..9fd33207 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,8 +16,7 @@ members = [ "uefi", "bios", "bios/boot_sector", - "bios/second_stage", - "bios/third_stage", + "bios/stage-*", "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", @@ -55,7 +54,7 @@ lto = false debug = true overflow-checks = true -[profile.first-stage] +[profile.stage-1] inherits = "release" opt-level = "s" lto = true @@ -63,14 +62,14 @@ codegen-units = 1 debug = false overflow-checks = false -[profile.second-stage] +[profile.stage-2] inherits = "release" opt-level = "s" codegen-units = 1 debug = false overflow-checks = true -[profile.third-stage] +[profile.stage-3] inherits = "release" codegen-units = 1 debug = false diff --git a/bios/second_stage/.gitignore b/bios/stage-2/.gitignore similarity index 100% rename from bios/second_stage/.gitignore rename to bios/stage-2/.gitignore diff --git a/bios/second_stage/Cargo.toml b/bios/stage-2/Cargo.toml similarity index 86% rename from bios/second_stage/Cargo.toml rename to bios/stage-2/Cargo.toml index dec11e9d..e6978b1d 100644 --- a/bios/second_stage/Cargo.toml +++ b/bios/stage-2/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bootloader-x86_64-bios-second-stage" +name = "bootloader-x86_64-bios-stage-2" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2021" diff --git a/bios/third_stage/build.rs b/bios/stage-2/build.rs similarity index 74% rename from bios/third_stage/build.rs rename to bios/stage-2/build.rs index 73bc965e..674be6e9 100644 --- a/bios/third_stage/build.rs +++ b/bios/stage-2/build.rs @@ -4,6 +4,6 @@ fn main() { let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); println!( "cargo:rustc-link-arg-bins=--script={}", - local_path.join("third-stage-link.ld").display() + local_path.join("stage-2-link.ld").display() ) } diff --git a/bios/second_stage/src/boot unreal.s b/bios/stage-2/src/boot unreal.s similarity index 100% rename from bios/second_stage/src/boot unreal.s rename to bios/stage-2/src/boot unreal.s diff --git a/bios/second_stage/src/dap.rs b/bios/stage-2/src/dap.rs similarity index 100% rename from bios/second_stage/src/dap.rs rename to bios/stage-2/src/dap.rs diff --git a/bios/second_stage/src/disk.rs b/bios/stage-2/src/disk.rs similarity index 100% rename from bios/second_stage/src/disk.rs rename to bios/stage-2/src/disk.rs diff --git a/bios/second_stage/src/fat.rs b/bios/stage-2/src/fat.rs similarity index 100% rename from bios/second_stage/src/fat.rs rename to bios/stage-2/src/fat.rs diff --git a/bios/second_stage/src/main.rs b/bios/stage-2/src/main.rs similarity index 93% rename from bios/second_stage/src/main.rs rename to bios/stage-2/src/main.rs index 23f9a049..6acd33e3 100644 --- a/bios/second_stage/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -9,7 +9,7 @@ use mbr_nostd::{PartitionTableEntry, PartitionType}; use crate::{ disk::{AlignedBuffer, Read, Seek, SeekFrom}, protected_mode::{ - copy_to_protected_mode, enter_protected_mode_and_jump_to_third_stage, enter_unreal_mode, + copy_to_protected_mode, enter_protected_mode_and_jump_to_stage_3, enter_unreal_mode, }, }; @@ -22,7 +22,7 @@ mod screen; /// We use this partition type to store the second bootloader stage; const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; -const THIRD_STAGE_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB accessible here) +const STAGE_3_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB accessible here) const KERNEL_DST: *mut u8 = 0x0100_0000 as *mut u8; // 16MiB extern "C" { @@ -86,21 +86,15 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let disk_buffer = unsafe { &mut DISK_BUFFER }; - load_file( - "boot-stage-3", - THIRD_STAGE_DST, - &mut fs, - &mut disk, - disk_buffer, - ); - writeln!(screen::Writer, "third stage loaded").unwrap(); + load_file("boot-stage-3", STAGE_3_DST, &mut fs, &mut disk, disk_buffer); + writeln!(screen::Writer, "stage 3 loaded").unwrap(); load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded").unwrap(); // TODO: Retrieve memory map // TODO: VESA config - enter_protected_mode_and_jump_to_third_stage(THIRD_STAGE_DST); + enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST); loop {} } diff --git a/bios/second_stage/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs similarity index 97% rename from bios/second_stage/src/protected_mode.rs rename to bios/stage-2/src/protected_mode.rs index 3f7653c4..3428331e 100644 --- a/bios/second_stage/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -102,7 +102,7 @@ pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { res } -pub fn enter_protected_mode_and_jump_to_third_stage(entry_point: *const u8) { +pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8) { let (entry_point_high, entry_point_low) = { let addr = entry_point as u32; (addr >> 16, addr & u32::from(u16::MAX)) diff --git a/bios/second_stage/src/screen.rs b/bios/stage-2/src/screen.rs similarity index 100% rename from bios/second_stage/src/screen.rs rename to bios/stage-2/src/screen.rs diff --git a/bios/second_stage/second-stage-link.ld b/bios/stage-2/stage-2-link.ld similarity index 100% rename from bios/second_stage/second-stage-link.ld rename to bios/stage-2/stage-2-link.ld diff --git a/bios/third_stage/.gitignore b/bios/stage-3/.gitignore similarity index 100% rename from bios/third_stage/.gitignore rename to bios/stage-3/.gitignore diff --git a/bios/third_stage/Cargo.toml b/bios/stage-3/Cargo.toml similarity index 82% rename from bios/third_stage/Cargo.toml rename to bios/stage-3/Cargo.toml index 5661c5e9..ed8f3b63 100644 --- a/bios/third_stage/Cargo.toml +++ b/bios/stage-3/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bootloader-x86_64-bios-third-stage" +name = "bootloader-x86_64-bios-stage-3" version = "0.1.0" authors = ["Philipp Oppermann "] edition = "2021" diff --git a/bios/second_stage/build.rs b/bios/stage-3/build.rs similarity index 73% rename from bios/second_stage/build.rs rename to bios/stage-3/build.rs index 945a419d..3aefe234 100644 --- a/bios/second_stage/build.rs +++ b/bios/stage-3/build.rs @@ -4,6 +4,6 @@ fn main() { let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); println!( "cargo:rustc-link-arg-bins=--script={}", - local_path.join("second-stage-link.ld").display() + local_path.join("stage-3-link.ld").display() ) } diff --git a/bios/third_stage/src/main.rs b/bios/stage-3/src/main.rs similarity index 90% rename from bios/third_stage/src/main.rs rename to bios/stage-3/src/main.rs index 392c11d3..767ff6d4 100644 --- a/bios/third_stage/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -1,5 +1,8 @@ #![no_std] #![no_main] +#![deny(unsafe_op_in_unsafe_fn)] + +mod paging; #[no_mangle] #[link_section = ".start"] diff --git a/bios/third_stage/third-stage-link.ld b/bios/stage-3/stage-3-link.ld similarity index 100% rename from bios/third_stage/third-stage-link.ld rename to bios/stage-3/stage-3-link.ld diff --git a/build.rs b/build.rs index bc5cedb9..e8f7b43d 100644 --- a/build.rs +++ b/build.rs @@ -5,8 +5,8 @@ use std::{ const BOOTLOADER_X86_64_UEFI_VERSION: &str = "0.1.0-alpha.0"; const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = "0.1.0-alpha.0"; -const BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION: &str = "0.1.0-alpha.0"; -const BOOTLOADER_X86_64_BIOS_THIRD_STAGE_VERSION: &str = "0.1.0-alpha.0"; +const BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION: &str = "0.1.0-alpha.0"; +const BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION: &str = "0.1.0-alpha.0"; fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); @@ -25,16 +25,16 @@ fn main() { "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", bios_boot_sector_path.display() ); - let bios_second_stage_path = build_bios_second_stage(&out_dir); + let bios_stage_2_path = build_bios_stage_2(&out_dir); println!( - "cargo:rustc-env=BIOS_SECOND_STAGE_PATH={}", - bios_second_stage_path.display() + "cargo:rustc-env=BIOS_STAGE_2_PATH={}", + bios_stage_2_path.display() ); - let bios_third_stage_path = build_bios_third_stage(&out_dir); + let bios_stage_3_path = build_bios_stage_3(&out_dir); println!( - "cargo:rustc-env=BIOS_THIRD_STAGE_PATH={}", - bios_third_stage_path.display() + "cargo:rustc-env=BIOS_STAGE_3_PATH={}", + bios_stage_3_path.display() ); } @@ -87,8 +87,8 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); } cmd.arg("--locked"); - cmd.arg("--target").arg("x86-16bit.json"); - cmd.arg("--profile").arg("first-stage"); + cmd.arg("--target").arg("i386-code16-boot-sector.json"); + cmd.arg("--profile").arg("stage-1"); cmd.arg("-Zbuild-std=core") .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); @@ -113,25 +113,24 @@ fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -fn build_bios_second_stage(out_dir: &Path) -> PathBuf { +fn build_bios_stage_2(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); - cmd.arg("install") - .arg("bootloader-x86_64-bios-second-stage"); + cmd.arg("install").arg("bootloader-x86_64-bios-stage-2"); let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) .join("bios") - .join("second_stage"); + .join("stage-2"); if local_path.exists() { // local build cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { cmd.arg("--version") - .arg(BOOTLOADER_X86_64_BIOS_SECOND_STAGE_VERSION); + .arg(BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION); } cmd.arg("--locked"); - cmd.arg("--target").arg("x86-16bit-second-stage.json"); - cmd.arg("--profile").arg("second-stage"); + cmd.arg("--target").arg("i386-code16-stage-2.json"); + cmd.arg("--profile").arg("stage-2"); cmd.arg("-Zbuild-std=core") .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); @@ -142,9 +141,7 @@ fn build_bios_second_stage(out_dir: &Path) -> PathBuf { .status() .expect("failed to run cargo install for bios second stage"); let elf_path = if status.success() { - let path = out_dir - .join("bin") - .join("bootloader-x86_64-bios-second-stage"); + let path = out_dir.join("bin").join("bootloader-x86_64-bios-stage-2"); assert!( path.exists(), "bios second stage executable does not exist after building" @@ -156,24 +153,24 @@ fn build_bios_second_stage(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } -fn build_bios_third_stage(out_dir: &Path) -> PathBuf { +fn build_bios_stage_3(out_dir: &Path) -> PathBuf { let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); - cmd.arg("install").arg("bootloader-x86_64-bios-third-stage"); + cmd.arg("install").arg("bootloader-x86_64-bios-stage-3"); let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) .join("bios") - .join("third_stage"); + .join("stage-3"); if local_path.exists() { // local build cmd.arg("--path").arg(&local_path); println!("cargo:rerun-if-changed={}", local_path.display()); } else { cmd.arg("--version") - .arg(BOOTLOADER_X86_64_BIOS_THIRD_STAGE_VERSION); + .arg(BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION); } cmd.arg("--locked"); - cmd.arg("--target").arg("i686-third-stage.json"); - cmd.arg("--profile").arg("third-stage"); + cmd.arg("--target").arg("i686-stage-3.json"); + cmd.arg("--profile").arg("stage-3"); cmd.arg("-Zbuild-std=core") .arg("-Zbuild-std-features=compiler-builtins-mem"); cmd.arg("--root").arg(out_dir); @@ -182,18 +179,16 @@ fn build_bios_third_stage(out_dir: &Path) -> PathBuf { cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); // used by clippy let status = cmd .status() - .expect("failed to run cargo install for bios third stage"); + .expect("failed to run cargo install for bios stage-3"); let elf_path = if status.success() { - let path = out_dir - .join("bin") - .join("bootloader-x86_64-bios-third-stage"); + let path = out_dir.join("bin").join("bootloader-x86_64-bios-stage-3"); assert!( path.exists(), - "bios third stage executable does not exist after building" + "bios stage-3 executable does not exist after building" ); path } else { - panic!("failed to build bios third stage"); + panic!("failed to build bios stage-3"); }; convert_elf_to_bin(elf_path) } diff --git a/x86-16bit-second-stage.json b/i386-code16-boot-sector.json similarity index 100% rename from x86-16bit-second-stage.json rename to i386-code16-boot-sector.json diff --git a/x86-16bit.json b/i386-code16-stage-2.json similarity index 100% rename from x86-16bit.json rename to i386-code16-stage-2.json diff --git a/i686-third-stage.json b/i686-stage-3.json similarity index 100% rename from i686-third-stage.json rename to i686-stage-3.json diff --git a/src/lib.rs b/src/lib.rs index 89799204..082f15c4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,17 +76,17 @@ mod mbr; mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; -const BIOS_THIRD_STAGE: &str = "boot-stage-3"; +const BIOS_STAGE_3: &str = "boot-stage-3"; /// Creates a bootable FAT partition at the given path. pub fn create_boot_partition(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - let third_stage_path = Path::new(env!("BIOS_THIRD_STAGE_PATH")); + let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); let mut files = BTreeMap::new(); files.insert("efi/boot/bootx64.efi", bootloader_path); files.insert(KERNEL_FILE_NAME, kernel_binary); - files.insert(BIOS_THIRD_STAGE, third_stage_path); + files.insert(BIOS_STAGE_3, stage_3_path); fat::create_fat_filesystem(files, &out_path).context("failed to create UEFI FAT filesystem")?; @@ -108,11 +108,11 @@ pub fn create_bios_disk_image( out_mbr_path: &Path, ) -> anyhow::Result<()> { let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); - let second_stage_path = Path::new(env!("BIOS_SECOND_STAGE_PATH")); + let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); mbr::create_mbr_disk( bootsector_path, - second_stage_path, + stage_2_path, boot_partition_path, out_mbr_path, ) From 824786b0498a8f02e4d79ca8c8477ed68dae0068 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 19:49:28 +0200 Subject: [PATCH 142/226] Set up paging and enter long mode (compatibility mode) --- bios/stage-3/src/main.rs | 4 +++ bios/stage-3/src/paging.rs | 66 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 bios/stage-3/src/paging.rs diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 767ff6d4..1111e627 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -7,6 +7,10 @@ mod paging; #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start() { + // set up identity mapping, enable paging, and switch CPU into long + // mode (32-bit compatibility mode) + paging::init(); + // TODO: Set up long mode with identity-mapping, then jump to 4th stage (passing // kernel, memory map, and vesa info as arguments) diff --git a/bios/stage-3/src/paging.rs b/bios/stage-3/src/paging.rs new file mode 100644 index 00000000..a00530db --- /dev/null +++ b/bios/stage-3/src/paging.rs @@ -0,0 +1,66 @@ +use core::{arch::asm, cell::UnsafeCell}; + +static LEVEL_4: RacyCell = RacyCell::new(PageTable::empty()); +static LEVEL_3: RacyCell = RacyCell::new(PageTable::empty()); +static LEVEL_2: RacyCell = RacyCell::new(PageTable::empty()); + +pub struct RacyCell(UnsafeCell); + +impl RacyCell { + const fn new(v: T) -> Self { + Self(UnsafeCell::new(v)) + } + + pub unsafe fn get_mut(&self) -> &mut T { + unsafe { &mut *self.0.get() } + } +} + +unsafe impl Send for RacyCell where T: Send {} +unsafe impl Sync for RacyCell {} + +pub fn init() { + create_mappings(); + + enable_paging(); +} + +fn create_mappings() { + let l4 = unsafe { LEVEL_4.get_mut() }; + let l3 = unsafe { LEVEL_3.get_mut() }; + let l2 = unsafe { LEVEL_2.get_mut() }; + let common_flags = 0b11; + l4.entries[0] = (l3 as *mut PageTable as u64) | common_flags; + l3.entries[0] = (l2 as *mut PageTable as u64) | common_flags; + for i in 0..512 { + l2.entries[i] = (u64::try_from(i).unwrap() * (2 * 1024 * 1024)) | common_flags | (1 << 7); + } +} + +fn enable_paging() { + // load level 4 table pointer into cr3 register + let l4 = unsafe { LEVEL_4.get_mut() } as *mut PageTable; + unsafe { asm!("mov cr3, {0}", in(reg) l4) }; + + // enable PAE-flag in cr4 (Physical Address Extension) + unsafe { asm!("mov eax, cr4", "or eax, 1<<5", "mov cr4, eax", out("eax")_) }; + + // set the long mode bit in the EFER MSR (model specific register) + unsafe { + asm!("mov ecx, 0xC0000080", "rdmsr", "or eax, 1 << 8", "wrmsr", out("eax") _, out("ecx")_) + }; + + // enable paging in the cr0 register + unsafe { asm!("mov eax, cr0", "or eax, 1 << 31", "mov cr0, eax", out("eax")_) }; +} + +#[repr(align(4096))] +struct PageTable { + pub entries: [u64; 512], +} + +impl PageTable { + pub const fn empty() -> Self { + Self { entries: [0; 512] } + } +} From 7c0df39a98aa29054a835ffe2ad8fa969726f55c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 19:50:44 +0200 Subject: [PATCH 143/226] Delete old real mode test code --- real_mode/.cargo/config | 5 -- real_mode/.gitignore | 4 -- real_mode/Cargo.lock | 14 ------ real_mode/Cargo.toml | 18 ------- real_mode/README.md | 35 ------------- real_mode/build.rs | 57 --------------------- real_mode/linker.ld | 68 -------------------------- real_mode/real_mode/.gitignore | 1 - real_mode/real_mode/Cargo.lock | 6 --- real_mode/real_mode/Cargo.toml | 20 -------- real_mode/real_mode/src/lib.rs | 45 ----------------- real_mode/real_mode/src/second_stage.s | 8 --- real_mode/real_mode/x86-16bit.json | 21 -------- real_mode/src/main.rs | 12 ----- real_mode/x86-32bit.json | 26 ---------- 15 files changed, 340 deletions(-) delete mode 100644 real_mode/.cargo/config delete mode 100644 real_mode/.gitignore delete mode 100644 real_mode/Cargo.lock delete mode 100644 real_mode/Cargo.toml delete mode 100644 real_mode/README.md delete mode 100644 real_mode/build.rs delete mode 100644 real_mode/linker.ld delete mode 100644 real_mode/real_mode/.gitignore delete mode 100644 real_mode/real_mode/Cargo.lock delete mode 100644 real_mode/real_mode/Cargo.toml delete mode 100644 real_mode/real_mode/src/lib.rs delete mode 100644 real_mode/real_mode/src/second_stage.s delete mode 100644 real_mode/real_mode/x86-16bit.json delete mode 100644 real_mode/src/main.rs delete mode 100644 real_mode/x86-32bit.json diff --git a/real_mode/.cargo/config b/real_mode/.cargo/config deleted file mode 100644 index 168bd90a..00000000 --- a/real_mode/.cargo/config +++ /dev/null @@ -1,5 +0,0 @@ -[build] -target = "x86-32bit.json" - -[alias] -xbuild = "build -Zbuild-std=core" diff --git a/real_mode/.gitignore b/real_mode/.gitignore deleted file mode 100644 index bae8924d..00000000 --- a/real_mode/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/target/ -**/*.rs.bk -/test.bin - diff --git a/real_mode/Cargo.lock b/real_mode/Cargo.lock deleted file mode 100644 index fd402fe0..00000000 --- a/real_mode/Cargo.lock +++ /dev/null @@ -1,14 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "bootloader" -version = "0.1.0" -dependencies = [ - "llvm-tools", -] - -[[package]] -name = "llvm-tools" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" diff --git a/real_mode/Cargo.toml b/real_mode/Cargo.toml deleted file mode 100644 index da95f3ea..00000000 --- a/real_mode/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "bootloader" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] - -[build-dependencies] -llvm-tools = "0.1.1" - -[profile.release] -opt-level = "s" -lto = true -debug = true -codegen-units = 1 diff --git a/real_mode/README.md b/real_mode/README.md deleted file mode 100644 index 29a85726..00000000 --- a/real_mode/README.md +++ /dev/null @@ -1,35 +0,0 @@ -# 16-bit Rust (Experiment) - -This is an experiment to translate the 16-bit code of the bootloader from assembly to Rust. - -## Building - -To build the project, use cargo-xbuild: - -``` -cargo xbuild --release -``` - -The BIOS only loads the first 512 bytes of our executable into memory, so the amount of code that this binary can contain is very limited. This is also the reason why this can only be built in release mode. - -If the code does not fit into 512 bytes, the linker will throw the following error: - -> rust-lld: error: linker.ld:16: unable to move location counter backward for: .bootloader - -## Creating a Disk Image - -The output of `cargo xbuild` is an ELF binary, which can't be loaded directly by the BIOS. To boot our project, we must therefore convert it into a flat binary first. This works with the following `objcopy` command: - -``` -objcopy -I elf32-i386 -O binary target/x86-32bit/release/bootloader image.bin -``` - -This creates a file named `image.bin` in the root folder of the project, which is a bootable disk image. - -## Running it in QEMU - -To run the disk image in QEMU, execute the following command: - -``` -qemu-system-x86_64 -drive format=raw,file=image.bin -``` diff --git a/real_mode/build.rs b/real_mode/build.rs deleted file mode 100644 index 0617a56a..00000000 --- a/real_mode/build.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::process::Command; -use std::env; -use std::path::Path; -use llvm_tools::{LlvmTools, exe}; - -fn main() { - let out_dir = env::var("OUT_DIR").unwrap(); - let llvm_tools = LlvmTools::new().expect("LLVM tools not found"); - let objcopy = llvm_tools.tool(&exe("llvm-objcopy")).expect("llvm-objcopy not found"); - - build_subproject(Path::new("first_stage"), &["_start", "print_char"], "x86-16bit.json", &out_dir, &objcopy); - build_subproject(Path::new("real_mode"), &["second_stage"], "x86-16bit.json", &out_dir, &objcopy); -} - -fn build_subproject(dir: &Path, global_symbols: &[&str], target: &str, out_dir: &str, objcopy: &Path) { - let dir_name = dir.file_name().unwrap().to_str().unwrap(); - let manifest_path = dir.join("Cargo.toml"); - let out_path = Path::new(&out_dir); - assert!(global_symbols.len() > 0, "must have at least one global symbol"); - - // build - let mut cmd = Command::new("cargo"); - cmd.arg("xbuild").arg("--release"); - cmd.arg("--verbose"); - cmd.arg(format!("--manifest-path={}", manifest_path.display())); - cmd.arg(format!("--target={}", dir.join(target).display())); - cmd.arg("-Z").arg("unstable-options"); - cmd.arg("--out-dir").arg(&out_dir); - cmd.arg("--target-dir").arg(out_path.join("target").join(dir_name)); - cmd.env_remove("RUSTFLAGS"); - cmd.env("XBUILD_SYSROOT_PATH", out_path.join("target").join(dir_name).join("sysroot")); - let status = cmd.status().unwrap(); - assert!(status.success()); - - // localize symbols - let mut cmd = Command::new(objcopy); - for symbol in global_symbols { - cmd.arg("-G").arg(symbol); - } - cmd.arg(out_path.join(format!("lib{}.a", dir_name))); - let status = cmd.status().unwrap(); - assert!(status.success()); - - /* - // FIXME: it seems like this messes up relocations - // convert to ELF64 - let mut cmd = Command::new(objcopy); - cmd.arg("-I").arg("elf32-i386").arg("-O").arg("elf64-x86-64"); - cmd.arg(out_path.join(format!("lib{}.a", dir_name))); - let status = cmd.status().unwrap(); - assert!(status.success()); - */ - - // emit linker flags - println!("cargo:rustc-link-search=native={}", out_dir); - println!("cargo:rustc-link-lib=static={}", dir_name); -} \ No newline at end of file diff --git a/real_mode/linker.ld b/real_mode/linker.ld deleted file mode 100644 index 64f42333..00000000 --- a/real_mode/linker.ld +++ /dev/null @@ -1,68 +0,0 @@ -ENTRY(_start) - -SECTIONS { - . = 0x500; - _stack_start = .; - . = 0x7c00; - _stack_end = .; - - _bootloader_start = .; - .boot : - { - *first_stage*(.boot) - } - .first_stage_text : - { - *first_stage*(.text .text.*) - } - .first_stage_data : - { - *first_stage*(.rodata .rodata.*) - *first_stage*(.data .data.*) - *first_stage*(.got .got.*) - } - - . = 0x7c00 + 510; - .magic_number : - { - SHORT(0xaa55) /* magic number for bootable disk */ - } - - _second_stage_start = .; - - .real_mode_text : - { - *real_mode*(.text .text.*) - } - .real_mode_data : - { - *real_mode*(.rodata .rodata.*) - *real_mode*(.data .data.*) - *real_mode*(.got .got.*) - } - - .text : - { - *(.text .text.*) - } - - .data : - { - *(.rodata .rodata.*) - *(.data .data.*) - *(.got .got.*) - } - - .eh_frame : - { - _EH_FRAME = .; - *(.eh_frame) - } - - .asm : - { - *(.second_stage) - } - . = ALIGN(512); - _second_stage_end = .; -} diff --git a/real_mode/real_mode/.gitignore b/real_mode/real_mode/.gitignore deleted file mode 100644 index ea8c4bf7..00000000 --- a/real_mode/real_mode/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/target diff --git a/real_mode/real_mode/Cargo.lock b/real_mode/real_mode/Cargo.lock deleted file mode 100644 index f7dc4c93..00000000 --- a/real_mode/real_mode/Cargo.lock +++ /dev/null @@ -1,6 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "real_mode" -version = "0.1.0" - diff --git a/real_mode/real_mode/Cargo.toml b/real_mode/real_mode/Cargo.toml deleted file mode 100644 index f798ceb8..00000000 --- a/real_mode/real_mode/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[package] -name = "real_mode" -version = "0.1.0" -authors = ["Philipp Oppermann "] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[lib] -name = "real_mode" -crate-type = ["staticlib"] - -[dependencies] - - -[profile.release] -opt-level = "s" -lto = true -codegen-units = 1 -debug = true diff --git a/real_mode/real_mode/src/lib.rs b/real_mode/real_mode/src/lib.rs deleted file mode 100644 index ec78a355..00000000 --- a/real_mode/real_mode/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -#![feature(asm, global_asm)] -#![no_std] - -global_asm!(include_str!("second_stage.s")); - -extern "C" { - fn print_char(c: u8); - fn second_stage_asm() -> u32; -} - -#[no_mangle] -pub extern "C" fn second_stage(_disk_number: u16) { - let val = unsafe { second_stage_asm() }; - if val == 12345 { - println(b"match"); - } else { - println(b"no match"); - } -} - -#[panic_handler] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - println(b"PANIC!"); - loop { - hlt() - } -} - -fn hlt() { - unsafe { - asm!("hlt":::: "intel","volatile"); - } -} - -#[inline(never)] -fn println(s: &[u8]) { - print(s); - unsafe { print_char(b'\n') }; -} - -fn print(s: &[u8]) { - for &c in s { - unsafe { print_char(c) }; - } -} diff --git a/real_mode/real_mode/src/second_stage.s b/real_mode/real_mode/src/second_stage.s deleted file mode 100644 index 200b3b08..00000000 --- a/real_mode/real_mode/src/second_stage.s +++ /dev/null @@ -1,8 +0,0 @@ -.section .second_stage, "awx" -.global second_stage -.intel_syntax noprefix -.code16 - -second_stage_asm: - mov eax, 12345 - ret diff --git a/real_mode/real_mode/x86-16bit.json b/real_mode/real_mode/x86-16bit.json deleted file mode 100644 index 35bd2784..00000000 --- a/real_mode/real_mode/x86-16bit.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "arch": "x86", - "cpu": "i386", - "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", - "dynamic-linking": false, - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "llvm-target": "i386-unknown-none-code16", - "max-atomic-width": 64, - "position-independent-executables": false, - "disable-redzone": true, - "target-c-int-width": "32", - "target-pointer-width": "32", - "target-endian": "little", - "panic-strategy": "abort", - "os": "none", - "vendor": "unknown", - "relocation_model": "static", - "eliminate_frame_pointer": true -} \ No newline at end of file diff --git a/real_mode/src/main.rs b/real_mode/src/main.rs deleted file mode 100644 index 39944883..00000000 --- a/real_mode/src/main.rs +++ /dev/null @@ -1,12 +0,0 @@ -#![no_std] -#![no_main] - -#[no_mangle] -pub extern "C" fn rust_main() -> u32 { - 54321 -} - -#[panic_handler] -pub fn panic(_info: &core::panic::PanicInfo) -> ! { - loop{} -} diff --git a/real_mode/x86-32bit.json b/real_mode/x86-32bit.json deleted file mode 100644 index 7a2d0eaa..00000000 --- a/real_mode/x86-32bit.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "arch": "x86", - "cpu": "i386", - "data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128", - "dynamic-linking": false, - "executables": true, - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "pre-link-args": { - "ld.lld": [ - "--script=linker.ld" - ] - }, - "llvm-target": "i386-unknown-none", - "max-atomic-width": 64, - "position-independent-executables": false, - "disable-redzone": true, - "target-c-int-width": "32", - "target-pointer-width": "32", - "target-endian": "little", - "panic-strategy": "abort", - "os": "none", - "vendor": "unknown", - "relocation_model": "static", - "eliminate_frame_pointer": true -} \ No newline at end of file From 83ed2c82578dacd066080c3ce7ee940af513a406 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 19:51:27 +0200 Subject: [PATCH 144/226] Remove old linker script and target file --- linker.ld | 51 ------------------------------------------ x86_64-bootloader.json | 22 ------------------ 2 files changed, 73 deletions(-) delete mode 100644 linker.ld delete mode 100644 x86_64-bootloader.json diff --git a/linker.ld b/linker.ld deleted file mode 100644 index 33570c2e..00000000 --- a/linker.ld +++ /dev/null @@ -1,51 +0,0 @@ -ENTRY(_start) - -SECTIONS { - . = 0x500; - /* buffer for loading the kernel */ - _kernel_buffer = .; - . += 512; - /* page tables */ - . = ALIGN(0x1000); - __page_table_start = .; - _p4 = .; - . += 0x1000; - _p3 = .; - . += 0x1000; - _p2 = .; - . += 0x1000; - _p1 = .; - . += 0x1000; - __page_table_end = .; - __bootloader_start = .; - _memory_map = .; - . += 0x1000; - - _stack_start = .; - . = 0x7c00; - _stack_end = .; - - .bootloader : - { - /* first stage */ - *(.boot-first-stage) - - /* rest of bootloader */ - _rest_of_bootloader_start_addr = .; - *(.boot) - *(.context_switch) - *(.text .text.*) - *(.rodata .rodata.*) - *(.data .data.*) - *(.bss .bss.*) - *(.got) - . = ALIGN(512); - _rest_of_bootloader_end_addr = .; - __bootloader_end = .; - } - - .kernel : - { - KEEP(*(.kernel)) - } -} diff --git a/x86_64-bootloader.json b/x86_64-bootloader.json deleted file mode 100644 index ceb196e9..00000000 --- a/x86_64-bootloader.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "llvm-target": "x86_64-unknown-none-gnu", - "data-layout": "e-m:e-i64:64-f80:128-n8:16:32:64-S128", - "linker-flavor": "ld.lld", - "linker": "rust-lld", - "pre-link-args": { - "ld.lld": [ - "--script=linker.ld", - "--gc-sections" - ] - }, - "target-endian": "little", - "target-pointer-width": "64", - "target-c-int-width": "32", - "arch": "x86_64", - "os": "none", - "features": "-mmx,-sse,+soft-float", - "disable-redzone": true, - "panic-strategy": "abort", - "executables": true, - "relocation-model": "static" -} From 39ba5269eada8ad40963d2ec7c92c4a6410060ab Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 14 Aug 2022 20:16:04 +0200 Subject: [PATCH 145/226] Create prototype for long mode stage 4 and load it --- Cargo.lock | 26 +- Cargo.toml | 5 +- bios/src/asm/e820.s | 54 ---- bios/src/asm/stage_1.s | 262 ---------------- bios/src/asm/stage_2.s | 171 ---------- bios/src/asm/stage_3.s | 168 ---------- bios/src/asm/vesa.s | 325 -------------------- bios/src/main_bak.rs | 276 ----------------- bios/stage-2/src/main.rs | 14 +- bios/{ => stage-4}/Cargo.toml | 8 +- bios/{ => stage-4}/src/main.rs | 0 bios/{ => stage-4}/src/memory_descriptor.rs | 0 build.rs | 14 + src/lib.rs | 3 + 14 files changed, 48 insertions(+), 1278 deletions(-) delete mode 100644 bios/src/asm/e820.s delete mode 100644 bios/src/asm/stage_1.s delete mode 100644 bios/src/asm/stage_2.s delete mode 100644 bios/src/asm/stage_3.s delete mode 100644 bios/src/asm/vesa.s delete mode 100644 bios/src/main_bak.rs rename bios/{ => stage-4}/Cargo.toml (71%) rename bios/{ => stage-4}/src/main.rs (100%) rename bios/{ => stage-4}/src/memory_descriptor.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 05d4cd8d..732a5152 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,7 @@ name = "bootloader" version = "0.11.0-alpha.0" dependencies = [ "anyhow", - "bootloader-x86_64-bios", + "bootloader-x86_64-bios-stage-4", "bootloader_test_runner", "fatfs", "gpt", @@ -70,18 +70,6 @@ dependencies = [ "test_kernel_pie", ] -[[package]] -name = "bootloader-x86_64-bios" -version = "0.1.0-alpha.0" -dependencies = [ - "bootloader-x86_64-common", - "bootloader_api", - "log", - "rsdp", - "usize_conversions", - "x86_64", -] - [[package]] name = "bootloader-x86_64-bios-boot-sector" version = "0.1.0" @@ -98,6 +86,18 @@ dependencies = [ name = "bootloader-x86_64-bios-stage-3" version = "0.1.0" +[[package]] +name = "bootloader-x86_64-bios-stage-4" +version = "0.1.0-alpha.0" +dependencies = [ + "bootloader-x86_64-common", + "bootloader_api", + "log", + "rsdp", + "usize_conversions", + "x86_64", +] + [[package]] name = "bootloader-x86_64-common" version = "0.1.0-alpha.0" diff --git a/Cargo.toml b/Cargo.toml index 9fd33207..cdcd32fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ members = [ "api", "common", "uefi", - "bios", "bios/boot_sector", "bios/stage-*", "tests/runner", @@ -32,9 +31,9 @@ fatfs = "0.3.4" gpt = "3.0.0" mbrman = "0.4.2" -[dependencies.bootloader-x86_64-bios] +[build-dependencies.bootloader-x86_64-bios-stage-4] version = "0.1.0-alpha.0" -path = "bios" +path = "bios/stage-4" artifact = ["bin"] target = "x86_64-unknown-none" diff --git a/bios/src/asm/e820.s b/bios/src/asm/e820.s deleted file mode 100644 index 59d9c78b..00000000 --- a/bios/src/asm/e820.s +++ /dev/null @@ -1,54 +0,0 @@ -.section .boot, "awx" -.code16 - -# From http://wiki.osdev.org/Detecting_Memory_(x86)#Getting_an_E820_Memory_Map - -# use the INT 0x15, eax= 0xE820 BIOS function to get a memory map -# inputs: es:di -> destination buffer for 24 byte entries -# outputs: bp = entry count, trashes all registers except esi -do_e820: - xor ebx, ebx # ebx must be 0 to start - xor bp, bp # keep an entry count in bp - mov edx, 0x0534D4150 # Place "SMAP" into edx - mov eax, 0xe820 - mov dword ptr es:[di + 20], 1 # force a valid ACPI 3.X entry - mov ecx, 24 # ask for 24 bytes - int 0x15 - jc .failed # carry set on first call means "unsupported function" - mov edx, 0x0534D4150 # Some BIOSes apparently trash this register? - cmp eax, edx # on success, eax must have been reset to "SMAP" - jne .failed - test ebx, ebx # ebx = 0 implies list is only 1 entry long (worthless) - je .failed - jmp .jmpin -.e820lp: - mov eax, 0xe820 # eax, ecx get trashed on every int 0x15 call - mov dword ptr es:[di + 20], 1 # force a valid ACPI 3.X entry - mov ecx, 24 # ask for 24 bytes again - int 0x15 - jc .e820f # carry set means "end of list already reached" - mov edx, 0x0534D4150 # repair potentially trashed register -.jmpin: - jcxz .skipent # skip any 0 length entries - cmp cl, 20 # got a 24 byte ACPI 3.X response? - jbe .notext - test byte ptr es:[di + 20], 1 # if so: is the "ignore this data" bit clear? - je .skipent -.notext: - mov ecx, es:[di + 8] # get lower uint32_t of memory region length - or ecx, es:[di + 12] # "or" it with upper uint32_t to test for zero - jz .skipent # if length uint64_t is 0, skip entry - inc bp # got a good entry: ++count, move to next storage spot - add di, 24 -.skipent: - test ebx, ebx # if ebx resets to 0, list is complete - jne .e820lp -.e820f: - mov [mmap_ent], bp # store the entry count - clc # there is "jc" on end of list to this point, so the carry must be cleared - ret -.failed: - stc # "function unsupported" error exit - ret - -mmap_ent: .word 0 diff --git a/bios/src/asm/stage_1.s b/bios/src/asm/stage_1.s deleted file mode 100644 index f065564e..00000000 --- a/bios/src/asm/stage_1.s +++ /dev/null @@ -1,262 +0,0 @@ -.section .boot-first-stage, "awx" -.global _start -.code16 - -# This stage initializes the stack, enables the A20 line, loads the rest of -# the bootloader from disk, and jumps to stage_2. - -_start: - # zero segment registers - xor ax, ax - mov ds, ax - mov es, ax - mov ss, ax - mov fs, ax - mov gs, ax - - # clear the direction flag (e.g. go forward in memory when using - # instructions like lodsb) - cld - - # initialize stack - mov sp, 0x7c00 - - mov si, offset boot_start_str - call real_mode_println - -enable_a20: - # enable A20-Line via IO-Port 92, might not work on all motherboards - in al, 0x92 - test al, 2 - jnz enable_a20_after - or al, 2 - and al, 0xFE - out 0x92, al -enable_a20_after: - - -enter_protected_mode: - # clear interrupts - cli - push ds - push es - - lgdt [gdt32info] - - mov eax, cr0 - or al, 1 # set protected mode bit - mov cr0, eax - - jmp protected_mode # tell 386/486 to not crash - -protected_mode: - mov bx, 0x10 - mov ds, bx # set data segment - mov es, bx # set extra segment - - and al, 0xfe # clear protected mode bit - mov cr0, eax - -unreal_mode: - pop es # get back old extra segment - pop ds # get back old data segment - sti - - # back to real mode, but internal data segment register is still loaded - # with gdt segment -> we can access the full 4GiB of memory - - mov bx, 0x0f01 # attrib/char of smiley - mov eax, 0xb8f00 # note 32 bit offset - mov word ptr ds:[eax], bx - -check_int13h_extensions: - mov ah, 0x41 - mov bx, 0x55aa - # dl contains drive number - int 0x13 - jc no_int13h_extensions - -load_rest_of_bootloader_from_disk: - mov eax, offset _rest_of_bootloader_start_addr - - mov ecx, 0 - -load_from_disk: - lea eax, _rest_of_bootloader_start_addr - add eax, ecx # add offset - - # dap buffer segment - mov ebx, eax - shr ebx, 4 # divide by 16 - mov [dap_buffer_seg], bx - - # buffer offset - shl ebx, 4 # multiply by 16 - sub eax, ebx - mov [dap_buffer_addr], ax - - mov eax, offset _rest_of_bootloader_start_addr - add eax, ecx # add offset - - # number of disk blocks to load - mov ebx, offset _rest_of_bootloader_end_addr - sub ebx, eax # end - start - jz load_from_disk_done - shr ebx, 9 # divide by 512 (block size) - cmp ebx, 127 - jle .continue_loading_from_disk - mov ebx, 127 -.continue_loading_from_disk: - mov [dap_blocks], bx - # increase offset - shl ebx, 9 - add ecx, ebx - - # number of start block - mov ebx, offset _start - sub eax, ebx - shr eax, 9 # divide by 512 (block size) - mov [dap_start_lba], eax - - mov si, offset dap - mov ah, 0x42 - int 0x13 - jc rest_of_bootloader_load_failed - - jmp load_from_disk - -load_from_disk_done: - # reset segment to 0 - mov word ptr [dap_buffer_seg], 0 - -jump_to_second_stage: - mov eax, offset stage_2 - jmp eax - -spin: - jmp spin - -# print a string and a newline -# IN -# si: points at zero-terminated String -# CLOBBER -# ax -real_mode_println: - call real_mode_print - mov al, 13 # \r - call real_mode_print_char - mov al, 10 # \n - jmp real_mode_print_char - -# print a string -# IN -# si: points at zero-terminated String -# CLOBBER -# ax -real_mode_print: - cld -real_mode_print_loop: - # note: if direction flag is set (via std) - # this will DECREMENT the ptr, effectively - # reading/printing in reverse. - lodsb al, BYTE PTR [si] - test al, al - jz real_mode_print_done - call real_mode_print_char - jmp real_mode_print_loop -real_mode_print_done: - ret - -# print a character -# IN -# al: character to print -# CLOBBER -# ah -real_mode_print_char: - mov ah, 0x0e - int 0x10 - ret - -# print a number in hex -# IN -# bx: the number -# CLOBBER -# al, cx -real_mode_print_hex: - mov cx, 4 -.lp: - mov al, bh - shr al, 4 - - cmp al, 0xA - jb .below_0xA - - add al, 'A' - 0xA - '0' -.below_0xA: - add al, '0' - - call real_mode_print_char - - shl bx, 4 - loop .lp - - ret - -real_mode_error: - call real_mode_println - jmp spin - -no_int13h_extensions: - mov si, offset no_int13h_extensions_str - jmp real_mode_error - -rest_of_bootloader_load_failed: - mov si, offset rest_of_bootloader_load_failed_str - jmp real_mode_error - -boot_start_str: .asciz "Booting (first stage)..." -error_str: .asciz "Error: " -no_int13h_extensions_str: .asciz "No support for int13h extensions" -rest_of_bootloader_load_failed_str: .asciz "Failed to load rest of bootloader" - -gdt32info: - .word gdt32_end - gdt32 - 1 # last byte in table - .word gdt32 # start of table - -gdt32: - # entry 0 is always unused - .quad 0 -codedesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x9a - .byte 0xcf - .byte 0 -datadesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x92 - .byte 0xcf - .byte 0 -gdt32_end: - -dap: # disk access packet - .byte 0x10 # size of dap - .byte 0 # unused -dap_blocks: - .word 0 # number of sectors -dap_buffer_addr: - .word 0 # offset to memory buffer -dap_buffer_seg: - .word 0 # segment of memory buffer -dap_start_lba: - .quad 0 # start logical block address - -.org 510 -.word 0xaa55 # magic number for bootable disk diff --git a/bios/src/asm/stage_2.s b/bios/src/asm/stage_2.s deleted file mode 100644 index 2e4645bb..00000000 --- a/bios/src/asm/stage_2.s +++ /dev/null @@ -1,171 +0,0 @@ -.section .boot, "awx" -.code16 - -# This stage sets the target operating mode, loads the kernel from disk, -# creates an e820 memory map, enters protected mode, and jumps to the -# third stage. - -second_stage_start_str: .asciz "Booting (second stage)..." -kernel_load_failed_str: .asciz "Failed to load kernel from disk" - -kernel_load_failed: - mov si, offset kernel_load_failed_str - call real_mode_println -kernel_load_failed_spin: - jmp kernel_load_failed_spin - -stage_2: - mov si, offset second_stage_start_str - call real_mode_println - -set_target_operating_mode: - # Some BIOSs assume the processor will only operate in Legacy Mode. We change the Target - # Operating Mode to "Long Mode Target Only", so the firmware expects each CPU to enter Long Mode - # once and then stay in it. This allows the firmware to enable mode-specifc optimizations. - # We save the flags, because CF is set if the callback is not supported (in which case, this is - # a NOP) - pushf - mov ax, 0xec00 - mov bl, 0x2 - int 0x15 - popf - -load_kernel_from_disk: - # start of memory buffer - mov eax, offset _kernel_buffer - mov [dap_buffer_addr], ax - - # number of disk blocks to load - mov word ptr [dap_blocks], 1 - - # number of start block - mov eax, offset _kernel_start_addr - mov ebx, offset _start - sub eax, ebx - shr eax, 9 # divide by 512 (block size) - mov [dap_start_lba], eax - - # destination address - mov edi, 0x400000 - - # block count - mov ecx, offset _kernel_size - add ecx, 511 # align up - shr ecx, 9 - -load_next_kernel_block_from_disk: - # load block from disk - mov si, offset dap - mov ah, 0x42 - int 0x13 - jc kernel_load_failed - - # copy block to 2MiB - push ecx - push esi - mov ecx, 512 / 4 - # move with zero extension - # because we are moving a word ptr - # to esi, a 32-bit register. - movzx esi, word ptr [dap_buffer_addr] - # move from esi to edi ecx times. - rep movsd [edi], [esi] - pop esi - pop ecx - - # next block - mov eax, [dap_start_lba] - add eax, 1 - mov [dap_start_lba], eax - - sub ecx, 1 - jnz load_next_kernel_block_from_disk - -create_memory_map: - lea di, es:[_memory_map] - call do_e820 - -video_mode_config: - call vesa - -enter_protected_mode_again: - cli - lgdt [gdt32info] - mov eax, cr0 - or al, 1 # set protected mode bit - mov cr0, eax - - push 0x8 - mov eax, offset stage_3 - push eax - retf - -spin32: - jmp spin32 - - - -# print a string and a newline -# IN -# esi: points at zero-terminated String -vga_println: - push eax - push ebx - push ecx - push edx - - call vga_print - - # newline - mov edx, 0 - mov eax, vga_position - mov ecx, 80 * 2 - div ecx - add eax, 1 - mul ecx - mov vga_position, eax - - pop edx - pop ecx - pop ebx - pop eax - - ret - -# print a string -# IN -# esi: points at zero-terminated String -# CLOBBER -# ah, ebx -vga_print: - cld -vga_print_loop: - # note: if direction flag is set (via std) - # this will DECREMENT the ptr, effectively - # reading/printing in reverse. - lodsb al, BYTE PTR [esi] - test al, al - jz vga_print_done - call vga_print_char - jmp vga_print_loop -vga_print_done: - ret - - -# print a character -# IN -# al: character to print -# CLOBBER -# ah, ebx -vga_print_char: - mov ebx, vga_position - mov ah, 0x0f - mov [ebx + 0xa0000], ax - - add ebx, 2 - mov [vga_position], ebx - - ret - -vga_position: - .double 0 diff --git a/bios/src/asm/stage_3.s b/bios/src/asm/stage_3.s deleted file mode 100644 index d9bcd102..00000000 --- a/bios/src/asm/stage_3.s +++ /dev/null @@ -1,168 +0,0 @@ -.section .boot, "awx" -.code32 - -# This stage performs some checks on the CPU (cpuid, long mode), sets up an -# initial page table mapping (identity map the bootloader, map the P4 -# recursively, map the kernel blob to 4MB), enables paging, switches to long -# mode, and jumps to stage_4. - -stage_3: - mov bx, 0x10 - mov ds, bx # set data segment - mov es, bx # set extra segment - mov ss, bx # set stack segment - -check_cpu: - call check_cpuid - call check_long_mode - - cli # disable interrupts - - lidt zero_idt # Load a zero length IDT so that any NMI causes a triple fault. - -# enter long mode - -set_up_page_tables: - # zero out buffer for page tables - mov edi, offset __page_table_start - mov ecx, offset __page_table_end - sub ecx, edi - shr ecx, 2 # one stosd zeros 4 bytes -> divide by 4 - xor eax, eax - rep stosd - - # p4 - mov eax, offset _p3 - or eax, (1 | 2) - mov [_p4], eax - # p3 - mov eax, offset _p2 - or eax, (1 | 2) - mov [_p3], eax - # p2 - mov eax, (1 | 2 | (1 << 7)) - mov ecx, 0 - map_p2_table: - mov [_p2 + ecx * 8], eax - add eax, 0x200000 - add ecx, 1 - cmp ecx, 512 - jb map_p2_table - -enable_paging: - # Write back cache and add a memory fence. I'm not sure if this is - # necessary, but better be on the safe side. - wbinvd - mfence - - # load P4 to cr3 register (cpu uses this to access the P4 table) - mov eax, offset _p4 - mov cr3, eax - - # enable PAE-flag in cr4 (Physical Address Extension) - mov eax, cr4 - or eax, (1 << 5) - mov cr4, eax - - # set the long mode bit in the EFER MSR (model specific register) - mov ecx, 0xC0000080 - rdmsr - or eax, (1 << 8) - wrmsr - - # enable paging in the cr0 register - mov eax, cr0 - or eax, (1 << 31) - mov cr0, eax - -load_64bit_gdt: - lgdt gdt_64_pointer # Load GDT.Pointer defined below. - -jump_to_long_mode: - push 0x8 - mov eax, offset stage_4 - push eax - retf # Load CS with 64 bit segment and flush the instruction cache - -spin_here: - jmp spin_here - -check_cpuid: - # Check if CPUID is supported by attempting to flip the ID bit (bit 21) - # in the FLAGS register. If we can flip it, CPUID is available. - - # Copy FLAGS in to EAX via stack - pushfd - pop eax - - # Copy to ECX as well for comparing later on - mov ecx, eax - - # Flip the ID bit - xor eax, (1 << 21) - - # Copy EAX to FLAGS via the stack - push eax - popfd - - # Copy FLAGS back to EAX (with the flipped bit if CPUID is supported) - pushfd - pop eax - - # Restore FLAGS from the old version stored in ECX (i.e. flipping the - # ID bit back if it was ever flipped). - push ecx - popfd - - # Compare EAX and ECX. If they are equal then that means the bit - # wasn't flipped, and CPUID isn't supported. - cmp eax, ecx - je no_cpuid - ret -no_cpuid: - mov esi, offset no_cpuid_str - call vga_println -no_cpuid_spin: - hlt - jmp no_cpuid_spin - -check_long_mode: - # test if extended processor info in available - mov eax, 0x80000000 # implicit argument for cpuid - cpuid # get highest supported argument - cmp eax, 0x80000001 # it needs to be at least 0x80000001 - jb no_long_mode # if it's less, the CPU is too old for long mode - - # use extended info to test if long mode is available - mov eax, 0x80000001 # argument for extended processor info - cpuid # returns various feature bits in ecx and edx - test edx, (1 << 29) # test if the LM-bit is set in the D-register - jz no_long_mode # If it's not set, there is no long mode - ret -no_long_mode: - mov esi, offset no_long_mode_str - call vga_println -no_long_mode_spin: - hlt - jmp no_long_mode_spin - - -.align 4 -zero_idt: - .word 0 - .byte 0 - -gdt_64: - .quad 0x0000000000000000 # Null Descriptor - should be present. - .quad 0x00209A0000000000 # 64-bit code descriptor (exec/read). - .quad 0x0000920000000000 # 64-bit data descriptor (read/write). - -.align 4 - .word 0 # Padding to make the "address of the GDT" field aligned on a 4-byte boundary - -gdt_64_pointer: - .word gdt_64_pointer - gdt_64 - 1 # 16-bit Size (Limit) of GDT. - .long gdt_64 # 32-bit Base Address of GDT. (CPU will zero extend to 64-bit) - -no_cpuid_str: .asciz "Error: CPU does not support CPUID" -no_long_mode_str: .asciz "Error: CPU does not support long mode" diff --git a/bios/src/asm/vesa.s b/bios/src/asm/vesa.s deleted file mode 100644 index adeaf4fd..00000000 --- a/bios/src/asm/vesa.s +++ /dev/null @@ -1,325 +0,0 @@ -# Code originally taken from https://gitlab.redox-os.org/redox-os/bootloader -# -# Copyright (c) 2017 Redox OS, licensed under MIT License - -.section .boot, "awx" -.code16 - -vesa: -vesa_getcardinfo: - mov ax, 0x4F00 - mov di, offset VBECardInfo - int 0x10 - cmp ax, 0x4F - je vesa_findmode - mov eax, 1 - ret -vesa_resetlist: - # if needed, reset mins/maxes/stuff - xor cx, cx - mov [vesa_minx], cx - mov [vesa_miny], cx - mov [config_xres], cx - mov [config_yres], cx -vesa_findmode: - mov si, [VBECardInfo_videomodeptr] - mov ax, [VBECardInfo_videomodeptr+2] - mov fs, ax - sub si, 2 -vesa_searchmodes: - add si, 2 - mov cx, fs:[si] - cmp cx, 0xFFFF - jne vesa_getmodeinfo - cmp word ptr [vesa_goodmode], 0 - je vesa_resetlist - jmp vesa_findmode -vesa_getmodeinfo: - push esi - mov [vesa_currentmode], cx - mov ax, 0x4F01 - mov di, offset VBEModeInfo - int 0x10 - pop esi - cmp ax, 0x4F - je vesa_foundmode - mov eax, 1 - ret -vesa_foundmode: - # check minimum values, really not minimums from an OS perspective but ugly for users - cmp byte ptr [VBEModeInfo_bitsperpixel], 32 - jb vesa_searchmodes -vesa_testx: - mov cx, [VBEModeInfo_xresolution] - cmp word ptr [config_xres], 0 - je vesa_notrequiredx - cmp cx, [config_xres] - je vesa_testy - jmp vesa_searchmodes -vesa_notrequiredx: - cmp cx, [vesa_minx] - jb vesa_searchmodes -vesa_testy: - mov cx, [VBEModeInfo_yresolution] - cmp word ptr [config_yres], 0 - je vesa_notrequiredy - cmp cx, [config_yres] - jne vesa_searchmodes # as if there weren't enough warnings, USE WITH CAUTION - cmp word ptr [config_xres], 0 - jnz vesa_setmode - jmp vesa_testgood -vesa_notrequiredy: - cmp cx, [vesa_miny] - jb vesa_searchmodes -vesa_testgood: - mov al, 13 - call print_char - mov cx, [vesa_currentmode] - mov [vesa_goodmode], cx - push esi - # call print_dec - # mov al, ':' - # call print_char - mov cx, [VBEModeInfo_xresolution] - call print_dec - mov al, 'x' - call print_char - mov cx, [VBEModeInfo_yresolution] - call print_dec - mov al, '@' - call print_char - xor ch, ch - mov cl, [VBEModeInfo_bitsperpixel] - call print_dec -vesa_confirm_mode: - mov si, offset vesa_modeok - call print - # xor ax, ax - # int 0x16 # read key press - pop esi - cmp al, al # originally `cmp al, 'y'` to compare key press - je vesa_setmode - cmp al, 's' - je vesa_savemode - jmp vesa_searchmodes -vesa_savemode: - mov cx, [VBEModeInfo_xresolution] - mov [config_xres], cx - mov cx, [VBEModeInfo_yresolution] - mov [config_yres], cx - # call save_config -vesa_setmode: - mov bx, [vesa_currentmode] - cmp bx, 0 - je vesa_nomode - or bx, 0x4000 - mov ax, 0x4F02 - int 0x10 -vesa_nomode: - cmp ax, 0x4F - je vesa_returngood - mov eax, 1 - ret -vesa_returngood: - xor eax, eax - ret - -vesa_modeok: - .ascii ": Is this OK? (s)ave/(y)es/(n)o " - .byte 8,8,8,8,0 - -vesa_goodmode: .2byte 0 -vesa_currentmode: .2byte 0 -# useful functions - -# print a number in decimal -# IN -# cx: the number -# CLOBBER -# al, cx, si -print_dec: - mov si, offset print_dec_number -print_dec_clear: - mov al, '0' - mov [si], al - inc si - cmp si, offset print_dec_numberend - jb print_dec_clear - dec si - call convert_dec - mov si, offset print_dec_number -print_dec_lp: - lodsb - cmp si, offset print_dec_numberend - jae print_dec_end - cmp al, '0' - jbe print_dec_lp -print_dec_end: - dec si - call print - ret - -print_dec_number: .skip 7, 0 -print_dec_numberend: .skip 1, 0 - -convert_dec: - dec si - mov bx, si # place to convert into must be in si, number to convert must be in cx -convert_dec_cnvrt: - mov si, bx - sub si, 4 -convert_dec_ten4: inc si - cmp cx, 10000 - jb convert_dec_ten3 - sub cx, 10000 - inc byte ptr [si] - jmp convert_dec_cnvrt -convert_dec_ten3: inc si - cmp cx, 1000 - jb convert_dec_ten2 - sub cx, 1000 - inc byte ptr [si] - jmp convert_dec_cnvrt -convert_dec_ten2: inc si - cmp cx, 100 - jb convert_dec_ten1 - sub cx, 100 - inc byte ptr [si] - jmp convert_dec_cnvrt -convert_dec_ten1: inc si - cmp cx, 10 - jb convert_dec_ten0 - sub cx, 10 - inc byte ptr [si] - jmp convert_dec_cnvrt -convert_dec_ten0: inc si - cmp cx, 1 - jb convert_dec_return - sub cx, 1 - inc byte ptr [si] - jmp convert_dec_cnvrt -convert_dec_return: - ret - - -VBECardInfo: - VBECardInfo_signature: .skip 4, 0 - VBECardInfo_version: .skip 2, 0 - VBECardInfo_oemstring: .skip 4, 0 - VBECardInfo_capabilities: .skip 4, 0 - VBECardInfo_videomodeptr: .skip 4, 0 - VBECardInfo_totalmemory: .skip 2, 0 - VBECardInfo_oemsoftwarerev: .skip 2, 0 - VBECardInfo_oemvendornameptr: .skip 4, 0 - VBECardInfo_oemproductnameptr: .skip 4, 0 - VBECardInfo_oemproductrevptr: .skip 4, 0 - VBECardInfo_reserved: .skip 222, 0 - VBECardInfo_oemdata: .skip 256, 0 - -VBEModeInfo: - VBEModeInfo_attributes: .skip 2, 0 - VBEModeInfo_winA: .skip 1, 0 - VBEModeInfo_winB: .skip 1, 0 - VBEModeInfo_granularity: .skip 2, 0 - VBEModeInfo_winsize: .skip 2, 0 - VBEModeInfo_segmentA: .skip 2, 0 - VBEModeInfo_segmentB: .skip 2, 0 - VBEModeInfo_winfuncptr: .skip 4, 0 - VBEModeInfo_bytesperscanline: .skip 2, 0 - VBEModeInfo_xresolution: .skip 2, 0 - VBEModeInfo_yresolution: .skip 2, 0 - VBEModeInfo_xcharsize: .skip 1, 0 - VBEModeInfo_ycharsize: .skip 1, 0 - VBEModeInfo_numberofplanes: .skip 1, 0 - VBEModeInfo_bitsperpixel: .skip 1, 0 - VBEModeInfo_numberofbanks: .skip 1, 0 - VBEModeInfo_memorymodel: .skip 1, 0 - VBEModeInfo_banksize: .skip 1, 0 - VBEModeInfo_numberofimagepages: .skip 1, 0 - VBEModeInfo_unused: .skip 1, 0 - VBEModeInfo_redmasksize: .skip 1, 0 - VBEModeInfo_redfieldposition: .skip 1, 0 - VBEModeInfo_greenmasksize: .skip 1, 0 - VBEModeInfo_greenfieldposition: .skip 1, 0 - VBEModeInfo_bluemasksize: .skip 1, 0 - VBEModeInfo_bluefieldposition: .skip 1, 0 - VBEModeInfo_rsvdmasksize: .skip 1, 0 - VBEModeInfo_rsvdfieldposition: .skip 1, 0 - VBEModeInfo_directcolormodeinfo: .skip 1, 0 - VBEModeInfo_physbaseptr: .skip 4, 0 - VBEModeInfo_offscreenmemoryoffset: .skip 4, 0 - VBEModeInfo_offscreenmemsize: .skip 2, 0 - VBEModeInfo_reserved: .skip 206, 0 - -# VBE.ModeAttributes: -# ModeAttributes_available equ 1 << 0 -# ModeAttributes_bios equ 1 << 2 -# ModeAttributes_color equ 1 << 3 -# ModeAttributes_graphics equ 1 << 4 -# ModeAttributes_vgacompatible equ 1 << 5 -# ModeAttributes_notbankable equ 1 << 6 -# ModeAttributes_linearframebuffer equ 1 << 7 - -VBEEDID: - VBEEDID_header: .skip 8, 0 - VBEEDID_manufacturer: .skip 2, 0 - VBEEDID_productid: .skip 2, 0 - VBEEDID_serial: .skip 4, 0 - VBEEDID_manufactureweek: .skip 1, 0 - VBEEDID_manufactureyear: .skip 1, 0 - VBEEDID_version: .skip 1, 0 - VBEEDID_revision: .skip 1, 0 - VBEEDID_input: .skip 1, 0 - VBEEDID_horizontalsize: .skip 1, 0 - VBEEDID_verticalsize: .skip 1, 0 - VBEEDID_gamma: .skip 1, 0 - VBEEDID_displaytype: .skip 1, 0 - VBEEDID_chromaticity: .skip 10, 0 - VBEEDID_timingI: .skip 1, 0 - VBEEDID_timingII: .skip 1, 0 - VBEEDID_timingreserved: .skip 1, 0 - VBEEDID_standardtiming: .skip 16, 0 # format: db (horizontal-248)/8, aspectratio | verticalfrequency - 60 - # VBEEDID_standardtiming_aspect.16.10 equ 0 # mul horizontal by 10, shr 4 to get vertical resolution - # VBEEDID_standardtiming_aspect.4.3 equ 1 << 6 # mul horizontal by 3, shr 2 to get vertical resolution - # VBEEDID_standardtiming_aspect.5.4 equ 2 << 6 # shl horizontal by 2, div by 5 to get vertical resolution - # VBEEDID_standardtiming_aspect.16.9 equ 3 << 6 # mul horizontal by 9, shr by 4 to get vertical resolution - VBEEDID_descriptorblock1: .skip 18, 0 - VBEEDID_descriptorblock2: .skip 18, 0 - VBEEDID_descriptorblock3: .skip 18, 0 - VBEEDID_descriptorblock4: .skip 18, 0 - VBEEDID_extensionflag: .skip 1, 0 - VBEEDID_checksum: .skip 1, 0 - -config: - config_xres: .2byte 0 - config_yres: .2byte 0 - -# print a string -# IN -# si: points at zero-terminated String -# CLOBBER -# si, ax -print: - pushf - cld -print_loop: - lodsb - test al, al - jz print_done - call print_char - jmp print_loop -print_done: - popf - ret - - -# print a character -# IN -# al: character to print -print_char: - pusha - mov bx, 7 - mov ah, 0x0e - int 0x10 - popa - ret diff --git a/bios/src/main_bak.rs b/bios/src/main_bak.rs deleted file mode 100644 index 09573303..00000000 --- a/bios/src/main_bak.rs +++ /dev/null @@ -1,276 +0,0 @@ -#![no_std] -#![no_main] - -use crate::memory_descriptor::E820MemoryRegion; -use bootloader_api::info::{FrameBufferInfo, PixelFormat}; -use bootloader_x86_64_common::{ - load_and_switch_to_kernel, logger::LOGGER, Kernel, PageTables, SystemInfo, -}; -use core::{ - arch::{asm, global_asm}, - panic::PanicInfo, - slice, -}; -use usize_conversions::usize_from; -use x86_64::structures::paging::{FrameAllocator, OffsetPageTable}; -use x86_64::structures::paging::{ - Mapper, PageTable, PageTableFlags, PhysFrame, Size2MiB, Size4KiB, -}; -use x86_64::{PhysAddr, VirtAddr}; - -mod memory_descriptor; - -global_asm!(include_str!("asm/stage_1.s")); -global_asm!(include_str!("asm/stage_2.s")); -global_asm!(include_str!("asm/vesa.s")); -global_asm!(include_str!("asm/e820.s")); -global_asm!(include_str!("asm/stage_3.s")); - -// values defined in `vesa.s` -extern "C" { - static VBEModeInfo_physbaseptr: u32; - static VBEModeInfo_bytesperscanline: u16; - static VBEModeInfo_xresolution: u16; - static VBEModeInfo_yresolution: u16; - static VBEModeInfo_bitsperpixel: u8; - static VBEModeInfo_redfieldposition: u8; - static VBEModeInfo_greenfieldposition: u8; - static VBEModeInfo_bluefieldposition: u8; -} - -// Symbols defined in `linker.ld` -extern "C" { - static mmap_ent: usize; - static _memory_map: usize; - static _kernel_start_addr: usize; - static _kernel_end_addr: usize; - static _kernel_size: usize; -} - -#[no_mangle] -pub unsafe extern "C" fn stage_4() -> ! { - // Set stack segment - asm!( - "mov ax, 0x0; mov ss, ax", - out("ax") _, - ); - - let kernel_start = 0x400000; - let kernel_size = &_kernel_size as *const _ as u64; - let memory_map_addr = &_memory_map as *const _ as u64; - let memory_map_entry_count = (mmap_ent & 0xff) as u64; // Extract lower 8 bits - - bootloader_main( - PhysAddr::new(kernel_start), - kernel_size, - VirtAddr::new(memory_map_addr), - memory_map_entry_count, - ) -} - -fn bootloader_main( - kernel_start: PhysAddr, - kernel_size: u64, - memory_map_addr: VirtAddr, - memory_map_entry_count: u64, -) -> ! { - use bootloader_x86_64_common::legacy_memory_region::LegacyFrameAllocator; - - let e820_memory_map = { - let ptr = usize_from(memory_map_addr.as_u64()) as *const E820MemoryRegion; - unsafe { slice::from_raw_parts(ptr, usize_from(memory_map_entry_count)) } - }; - let max_phys_addr = e820_memory_map - .iter() - .map(|r| r.start_addr + r.len) - .max() - .expect("no physical memory regions found"); - - let mut frame_allocator = { - let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); - let next_free = kernel_end + 1; - LegacyFrameAllocator::new_starting_at(next_free, e820_memory_map.iter().copied()) - }; - - // We identity-map all memory, so the offset between physical and virtual addresses is 0 - let phys_offset = VirtAddr::new(0); - - let mut bootloader_page_table = { - let frame = x86_64::registers::control::Cr3::read().0; - let table: *mut PageTable = (phys_offset + frame.start_address().as_u64()).as_mut_ptr(); - unsafe { OffsetPageTable::new(&mut *table, phys_offset) } - }; - // identity-map remaining physical memory (first gigabyte is already identity-mapped) - { - let start_frame: PhysFrame = - PhysFrame::containing_address(PhysAddr::new(4096 * 512 * 512)); - let end_frame = PhysFrame::containing_address(PhysAddr::new(max_phys_addr - 1)); - for frame in PhysFrame::range_inclusive(start_frame, end_frame) { - unsafe { - bootloader_page_table - .identity_map( - frame, - PageTableFlags::PRESENT | PageTableFlags::WRITABLE, - &mut frame_allocator, - ) - .unwrap() - .flush() - }; - } - } - - let framebuffer_addr = PhysAddr::new(unsafe { VBEModeInfo_physbaseptr }.into()); - let mut error = None; - let framebuffer_info = unsafe { - let framebuffer_size = - usize::from(VBEModeInfo_yresolution) * usize::from(VBEModeInfo_bytesperscanline); - let bytes_per_pixel = VBEModeInfo_bitsperpixel / 8; - init_logger( - framebuffer_addr, - framebuffer_size.into(), - VBEModeInfo_xresolution.into(), - VBEModeInfo_yresolution.into(), - bytes_per_pixel.into(), - (VBEModeInfo_bytesperscanline / u16::from(bytes_per_pixel)).into(), - match ( - VBEModeInfo_redfieldposition, - VBEModeInfo_greenfieldposition, - VBEModeInfo_bluefieldposition, - ) { - (0, 8, 16) => PixelFormat::Rgb, - (16, 8, 0) => PixelFormat::Bgr, - (r, g, b) => { - error = Some(("invalid rgb field positions", r, g, b)); - PixelFormat::Rgb // default to RBG so that we can print something - } - }, - ) - }; - - log::info!("BIOS boot"); - - if let Some((msg, r, g, b)) = error { - panic!("{}: r: {}, g: {}, b: {}", msg, r, g, b); - } - - let page_tables = create_page_tables(&mut frame_allocator); - - let kernel_slice = { - let ptr = kernel_start.as_u64() as *const u8; - unsafe { slice::from_raw_parts(ptr, usize_from(kernel_size)) } - }; - let kernel = Kernel::parse(kernel_slice); - - let system_info = SystemInfo { - framebuffer_addr, - framebuffer_info, - rsdp_addr: detect_rsdp(), - }; - - load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info); -} - -fn init_logger( - framebuffer_start: PhysAddr, - framebuffer_size: usize, - horizontal_resolution: usize, - vertical_resolution: usize, - bytes_per_pixel: usize, - stride: usize, - pixel_format: PixelFormat, -) -> FrameBufferInfo { - let ptr = framebuffer_start.as_u64() as *mut u8; - let slice = unsafe { slice::from_raw_parts_mut(ptr, framebuffer_size) }; - - let info = FrameBufferInfo { - byte_len: framebuffer_size, - horizontal_resolution, - vertical_resolution, - bytes_per_pixel, - stride, - pixel_format, - }; - - bootloader_x86_64_common::init_logger(slice, info); - - info -} - -/// Creates page table abstraction types for both the bootloader and kernel page tables. -fn create_page_tables(frame_allocator: &mut impl FrameAllocator) -> PageTables { - // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 - let phys_offset = VirtAddr::new(0); - - // copy the currently active level 4 page table, because it might be read-only - let bootloader_page_table = { - let frame = x86_64::registers::control::Cr3::read().0; - let table: *mut PageTable = (phys_offset + frame.start_address().as_u64()).as_mut_ptr(); - unsafe { OffsetPageTable::new(&mut *table, phys_offset) } - }; - - // create a new page table hierarchy for the kernel - let (kernel_page_table, kernel_level_4_frame) = { - // get an unused frame for new level 4 page table - let frame: PhysFrame = frame_allocator.allocate_frame().expect("no unused frames"); - log::info!("New page table at: {:#?}", &frame); - // get the corresponding virtual address - let addr = phys_offset + frame.start_address().as_u64(); - // initialize a new page table - let ptr = addr.as_mut_ptr(); - unsafe { *ptr = PageTable::new() }; - let level_4_table = unsafe { &mut *ptr }; - ( - unsafe { OffsetPageTable::new(level_4_table, phys_offset) }, - frame, - ) - }; - - PageTables { - bootloader: bootloader_page_table, - kernel: kernel_page_table, - kernel_level_4_frame, - } -} - -fn detect_rsdp() -> Option { - use core::ptr::NonNull; - use rsdp::{ - handler::{AcpiHandler, PhysicalMapping}, - Rsdp, - }; - - #[derive(Clone)] - struct IdentityMapped; - impl AcpiHandler for IdentityMapped { - unsafe fn map_physical_region( - &self, - physical_address: usize, - size: usize, - ) -> PhysicalMapping { - PhysicalMapping::new( - physical_address, - NonNull::new(physical_address as *mut _).unwrap(), - size, - size, - Self, - ) - } - - fn unmap_physical_region(_region: &PhysicalMapping) {} - } - - unsafe { - Rsdp::search_for_on_bios(IdentityMapped) - .ok() - .map(|mapping| PhysAddr::new(mapping.physical_start() as u64)) - } -} - -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - unsafe { LOGGER.get().map(|l| l.force_unlock()) }; - log::error!("{}", info); - loop { - unsafe { asm!("cli; hlt") }; - } -} diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 6acd33e3..35cfe031 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -86,8 +86,15 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let disk_buffer = unsafe { &mut DISK_BUFFER }; - load_file("boot-stage-3", STAGE_3_DST, &mut fs, &mut disk, disk_buffer); + let stage_3_len = load_file("boot-stage-3", STAGE_3_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "stage 3 loaded").unwrap(); + let stage_4_dst = { + let stage_3_end = STAGE_3_DST.wrapping_add(usize::try_from(stage_3_len).unwrap()); + let align_offset = stage_3_end.align_offset(512); + stage_3_end.wrapping_add(align_offset) + }; + load_file("boot-stage-4", stage_4_dst, &mut fs, &mut disk, disk_buffer); + writeln!(screen::Writer, "stage 4 loaded").unwrap(); load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded").unwrap(); @@ -105,15 +112,17 @@ fn load_file( fs: &mut fat::FileSystem, disk: &mut disk::DiskAccess, disk_buffer: &mut AlignedArrayBuffer<16384>, -) { +) -> u64 { let disk_buffer_size = disk_buffer.buffer.len(); let kernel = fs .find_file_in_root_dir(file_name, disk_buffer) .expect("file not found"); + let mut total_size = 0; for cluster in fs.file_clusters(&kernel) { let cluster = cluster.unwrap(); let cluster_start = cluster.start_offset; let cluster_end = cluster_start + u64::from(cluster.len_bytes); + total_size += u64::from(cluster.len_bytes); let mut offset = 0; loop { @@ -150,6 +159,7 @@ fn load_file( offset += len; } } + total_size } /// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 diff --git a/bios/Cargo.toml b/bios/stage-4/Cargo.toml similarity index 71% rename from bios/Cargo.toml rename to bios/stage-4/Cargo.toml index 9b6798da..b6a572e0 100644 --- a/bios/Cargo.toml +++ b/bios/stage-4/Cargo.toml @@ -1,14 +1,14 @@ [package] -name = "bootloader-x86_64-bios" +name = "bootloader-x86_64-bios-stage-4" version = "0.1.0-alpha.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -bootloader_api = { version = "0.1.0-alpha.0", path = "../api" } -bootloader-x86_64-common = { version = "0.1.0-alpha.0", path = "../common" } +bootloader_api = { version = "0.1.0-alpha.0", path = "../../api" } +bootloader-x86_64-common = { version = "0.1.0-alpha.0", path = "../../common" } log = "0.4.14" x86_64 = "0.14.8" -usize_conversions = "0.2.0" rsdp = "2.0.0" +usize_conversions = "0.2.0" diff --git a/bios/src/main.rs b/bios/stage-4/src/main.rs similarity index 100% rename from bios/src/main.rs rename to bios/stage-4/src/main.rs diff --git a/bios/src/memory_descriptor.rs b/bios/stage-4/src/memory_descriptor.rs similarity index 100% rename from bios/src/memory_descriptor.rs rename to bios/stage-4/src/memory_descriptor.rs diff --git a/build.rs b/build.rs index e8f7b43d..01deace2 100644 --- a/build.rs +++ b/build.rs @@ -4,9 +4,11 @@ use std::{ }; const BOOTLOADER_X86_64_UEFI_VERSION: &str = "0.1.0-alpha.0"; + const BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION: &str = "0.1.0-alpha.0"; const BOOTLOADER_X86_64_BIOS_STAGE_2_VERSION: &str = "0.1.0-alpha.0"; const BOOTLOADER_X86_64_BIOS_STAGE_3_VERSION: &str = "0.1.0-alpha.0"; +const BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION: &str = "0.1.0-alpha.0"; fn main() { let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); @@ -36,6 +38,12 @@ fn main() { "cargo:rustc-env=BIOS_STAGE_3_PATH={}", bios_stage_3_path.display() ); + + let bios_stage_4_path = build_bios_stage_4(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_4_PATH={}", + bios_stage_4_path.display() + ); } fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { @@ -193,6 +201,12 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf { convert_elf_to_bin(elf_path) } +fn build_bios_stage_4(out_dir: &Path) -> PathBuf { + let elf_path = + PathBuf::from(std::env::var("CARGO_BIN_FILE_BOOTLOADER_X86_64_BIOS_STAGE_4").unwrap()); + convert_elf_to_bin(elf_path) +} + fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { let flat_binary_path = elf_path.with_extension("bin"); diff --git a/src/lib.rs b/src/lib.rs index 082f15c4..be3cd01a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -77,16 +77,19 @@ mod pxe; const KERNEL_FILE_NAME: &str = "kernel-x86_64"; const BIOS_STAGE_3: &str = "boot-stage-3"; +const BIOS_STAGE_4: &str = "boot-stage-4"; /// Creates a bootable FAT partition at the given path. pub fn create_boot_partition(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); + let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); let mut files = BTreeMap::new(); files.insert("efi/boot/bootx64.efi", bootloader_path); files.insert(KERNEL_FILE_NAME, kernel_binary); files.insert(BIOS_STAGE_3, stage_3_path); + files.insert(BIOS_STAGE_4, stage_4_path); fat::create_fat_filesystem(files, &out_path).context("failed to create UEFI FAT filesystem")?; From 478ea856c0e565b82a35fe9b6afb3ac119809f9a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:40:18 +0200 Subject: [PATCH 146/226] Enable debug symbols for stage 3 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index cdcd32fd..19c3c90d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -71,7 +71,7 @@ overflow-checks = true [profile.stage-3] inherits = "release" codegen-units = 1 -debug = false +debug = true overflow-checks = true [profile.test.package.test_kernel_higher_half] From 18afbb10babf05e42c62508eff90c3c3323e969a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:41:47 +0200 Subject: [PATCH 147/226] Fix: Return correct offset from `read_exact` The method calls `read_exact_into`, which can only read whole sectors. So the resulting slice does not start with the desired bytes when the offset is not aligned on a sector boundary. --- bios/stage-2/src/disk.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/bios/stage-2/src/disk.rs b/bios/stage-2/src/disk.rs index 88b2b990..4dbb3ba1 100644 --- a/bios/stage-2/src/disk.rs +++ b/bios/stage-2/src/disk.rs @@ -9,13 +9,17 @@ pub struct DiskAccess { impl Read for DiskAccess { fn read_exact(&mut self, len: usize) -> &[u8] { - static mut TMP_BUF: AlignedArrayBuffer<512> = AlignedArrayBuffer { buffer: [0; 512] }; + let current_sector_offset = usize::try_from(self.current_offset % 512).unwrap(); + + static mut TMP_BUF: AlignedArrayBuffer<1024> = AlignedArrayBuffer { + buffer: [0; 512 * 2], + }; let buf = unsafe { &mut TMP_BUF }; - assert!(len <= buf.buffer.len()); + assert!(current_sector_offset + len <= buf.buffer.len()); - self.read_exact_into(512, buf); + self.read_exact_into(buf.buffer.len(), buf); - &buf.buffer[..len] + &buf.buffer[current_sector_offset..][..len] } fn read_exact_into(&mut self, len: usize, buf: &mut dyn AlignedBuffer) { From 830b8366f1e64dbd80ceeecfa11aefccb6eb1146 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:42:05 +0200 Subject: [PATCH 148/226] Fix: Seek to start before loading bpb --- bios/stage-2/src/fat.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/bios/stage-2/src/fat.rs b/bios/stage-2/src/fat.rs index 73236328..024eefc5 100644 --- a/bios/stage-2/src/fat.rs +++ b/bios/stage-2/src/fat.rs @@ -33,6 +33,7 @@ struct Bpb { impl Bpb { fn parse(disk: &mut D) -> Self { + disk.seek(SeekFrom::Start(0)); let raw = disk.read_exact(512); let bytes_per_sector = u16::from_le_bytes(raw[11..13].try_into().unwrap()); From bfffdd3809a2554e7aefb74cec6655ada6388bf8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:42:28 +0200 Subject: [PATCH 149/226] Add cluster index number to cluster struct --- bios/stage-2/src/fat.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bios/stage-2/src/fat.rs b/bios/stage-2/src/fat.rs index 024eefc5..82453b58 100644 --- a/bios/stage-2/src/fat.rs +++ b/bios/stage-2/src/fat.rs @@ -244,6 +244,7 @@ impl FileSystem { #[derive(Debug)] pub struct Cluster { + pub index: u32, pub start_offset: u64, pub len_bytes: u32, } @@ -273,9 +274,11 @@ where self.bpb.data_offset() + (u64::from(entry) - 2) * self.bpb.bytes_per_cluster() as u64; let next_entry = fat_entry_of_nth_cluster(self.disk, self.bpb.fat_type(), self.bpb.fat_offset(), entry); + let index = self.current_entry; self.current_entry = next_entry; Ok(Some(Cluster { + index, start_offset: cluster_start, len_bytes: self.bpb.bytes_per_cluster(), })) From 694f81b97c7dd7a02833342561d0045d6a782973 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:42:49 +0200 Subject: [PATCH 150/226] Print load addresses in stage 2 --- bios/stage-2/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 35cfe031..5e7a8600 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -87,16 +87,16 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let disk_buffer = unsafe { &mut DISK_BUFFER }; let stage_3_len = load_file("boot-stage-3", STAGE_3_DST, &mut fs, &mut disk, disk_buffer); - writeln!(screen::Writer, "stage 3 loaded").unwrap(); + writeln!(screen::Writer, "stage 3 loaded at {STAGE_3_DST:#p}").unwrap(); let stage_4_dst = { let stage_3_end = STAGE_3_DST.wrapping_add(usize::try_from(stage_3_len).unwrap()); let align_offset = stage_3_end.align_offset(512); stage_3_end.wrapping_add(align_offset) }; load_file("boot-stage-4", stage_4_dst, &mut fs, &mut disk, disk_buffer); - writeln!(screen::Writer, "stage 4 loaded").unwrap(); + writeln!(screen::Writer, "stage 4 loaded at {stage_4_dst:#p}").unwrap(); load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); - writeln!(screen::Writer, "kernel loaded").unwrap(); + writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); // TODO: Retrieve memory map // TODO: VESA config From 29e3200f674515e5adda6355653006c604005edd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:44:12 +0200 Subject: [PATCH 151/226] Read file size from file metadata instead of counting it --- bios/stage-2/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 5e7a8600..ed00dc83 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -114,11 +114,12 @@ fn load_file( disk_buffer: &mut AlignedArrayBuffer<16384>, ) -> u64 { let disk_buffer_size = disk_buffer.buffer.len(); - let kernel = fs + let file = fs .find_file_in_root_dir(file_name, disk_buffer) .expect("file not found"); - let mut total_size = 0; - for cluster in fs.file_clusters(&kernel) { + let file_size = file.file_size().into(); + + for cluster in fs.file_clusters(&file) { let cluster = cluster.unwrap(); let cluster_start = cluster.start_offset; let cluster_end = cluster_start + u64::from(cluster.len_bytes); @@ -159,7 +160,7 @@ fn load_file( offset += len; } } - total_size + file_size } /// Taken from https://github.com/rust-lang/rust/blob/e100ec5bc7cd768ec17d75448b29c9ab4a39272b/library/core/src/slice/mod.rs#L1673-L1677 From 0f0eb88ff3718738771a8582d78e7f2f005abb5b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:45:16 +0200 Subject: [PATCH 152/226] Fix: Keep track of total offset when copying to protected mode Before we always overwrote the first cluster. --- bios/stage-2/src/main.rs | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index ed00dc83..91bd1c54 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -119,11 +119,11 @@ fn load_file( .expect("file not found"); let file_size = file.file_size().into(); + let mut total_offset = 0; for cluster in fs.file_clusters(&file) { let cluster = cluster.unwrap(); let cluster_start = cluster.start_offset; let cluster_end = cluster_start + u64::from(cluster.len_bytes); - total_size += u64::from(cluster.len_bytes); let mut offset = 0; loop { @@ -137,27 +137,17 @@ fn load_file( ); let len = range_end - range_start; - writeln!( - screen::Writer, - "loading bytes {range_start:#x}..{range_end:#x}" - ) - .unwrap(); - disk.seek(SeekFrom::Start(range_start)); disk.read_exact_into(disk_buffer_size, disk_buffer); let slice = &disk_buffer.buffer[..usize::try_from(len).unwrap()]; - unsafe { - copy_to_protected_mode(dst.wrapping_add(usize::try_from(offset).unwrap()), slice) - }; - let written = unsafe { - protected_mode::read_from_protected_mode( - dst.wrapping_add(usize::try_from(offset).unwrap()), - ) - }; + unsafe { copy_to_protected_mode(dst.wrapping_add(total_offset), slice) }; + let written = + unsafe { protected_mode::read_from_protected_mode(dst.wrapping_add(total_offset)) }; assert_eq!(slice[0], written); offset += len; + total_offset += usize::try_from(len).unwrap(); } } file_size From 3ce77c5fac3a69768629bddc666994a1ed4105d6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:46:11 +0200 Subject: [PATCH 153/226] Pass kernel and stage-4 addresses to stage-3 --- bios/stage-2/src/main.rs | 2 +- bios/stage-2/src/protected_mode.rs | 57 +++++++++++++++++++----------- bios/stage-3/src/main.rs | 2 +- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 91bd1c54..429e2c4d 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -101,7 +101,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { // TODO: Retrieve memory map // TODO: VESA config - enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST); + enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, stage_4_dst, KERNEL_DST); loop {} } diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 3428331e..9d42bedf 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -1,5 +1,6 @@ use core::{ arch::{asm, global_asm}, + fmt::Write as _, mem::size_of, }; @@ -102,31 +103,47 @@ pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { res } -pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8) { - let (entry_point_high, entry_point_low) = { - let addr = entry_point as u32; - (addr >> 16, addr & u32::from(u16::MAX)) - }; - +pub fn enter_protected_mode_and_jump_to_stage_3( + entry_point: *const u8, + stage_4: *const u8, + kernel: *const u8, +) { unsafe { asm!("cli") }; set_protected_mode_bit(); unsafe { - // asm!("ljmp $0x8, $protected_mode", options(att_syntax)); + asm!( + // align the stack + "and esp, 0xffffff00", + // push arguments + "push {kernel_addr}", + "push {stage_4_addr}", + // push entry point address + "mov ebx, {entry_point}", + "push {entry_point}", + stage_4_addr = in(reg) stage_4 as u32, + kernel_addr = in(reg) kernel as u32, + entry_point = in(reg) entry_point as u32, + out("ebx") _ + ); asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); asm!( - " - .code32 - mov bx, 0x10 - mov ds, bx - mov es, bx - mov ss, bx - shl eax, 16 - or eax, {0} - jmp eax - 2: - jmp 2b - " - , in(reg) entry_point_low, in("ax") entry_point_high, out("ebx") _); + ".code32", + + // reload segment registers + "mov bx, 0x10", + "mov ds, bx", + "mov es, bx", + "mov ss, bx", + + // jump to third stage + "pop eax", + "call eax", + + // enter endless loop in case third stage returns + "2:", + "jmp 2b", + out("eax") _ + ); } } diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 1111e627..79dc4dd7 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -6,7 +6,7 @@ mod paging; #[no_mangle] #[link_section = ".start"] -pub extern "C" fn _start() { +pub extern "C" fn _start(stage_4_addr: u32, kernel_addr: u32) { // set up identity mapping, enable paging, and switch CPU into long // mode (32-bit compatibility mode) paging::init(); From b69f8a71d9d654a4f1a51d0c043e08f747a4a678 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:47:00 +0200 Subject: [PATCH 154/226] Add a vga buffer writer to stage 3 --- bios/stage-3/src/main.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 79dc4dd7..d6f23e74 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -2,11 +2,22 @@ #![no_main] #![deny(unsafe_op_in_unsafe_fn)] +use core::fmt::Write as _; + +use crate::vga_buffer::Writer; + mod paging; +mod vga_buffer; #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(stage_4_addr: u32, kernel_addr: u32) { + // Writer.clear_screen(); + writeln!( + Writer, + "Third Stage (stage_4_addr: {stage_4_addr:#x}, kernel_addr: {kernel_addr:#x})" + ); + // set up identity mapping, enable paging, and switch CPU into long // mode (32-bit compatibility mode) paging::init(); @@ -14,16 +25,13 @@ pub extern "C" fn _start(stage_4_addr: u32, kernel_addr: u32) { // TODO: Set up long mode with identity-mapping, then jump to 4th stage (passing // kernel, memory map, and vesa info as arguments) - let vga = 0xb8000 as *mut u16; - - for i in 0..(80 * 25) { - unsafe { vga.wrapping_add(i).write_volatile(0x0f01) }; - } + writeln!(Writer, "Paging init done"); loop {} } #[panic_handler] pub fn panic(info: &core::panic::PanicInfo) -> ! { + writeln!(Writer, "PANIC: {info}"); loop {} } From 5eb826465b5f73cd5161305fd8ea07eed0299ffd Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:47:15 +0200 Subject: [PATCH 155/226] Minor target tweaks --- i686-stage-3.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/i686-stage-3.json b/i686-stage-3.json index eca1387f..b444faec 100644 --- a/i686-stage-3.json +++ b/i686-stage-3.json @@ -6,7 +6,7 @@ "executables": true, "linker-flavor": "ld.lld", "linker": "rust-lld", - "llvm-target": "i686-unknown-none", + "llvm-target": "i386-unknown-none", "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, @@ -17,5 +17,5 @@ "os": "none", "vendor": "unknown", "relocation-model": "static", - "features": "+soft-float,+sse" + "features": "+soft-float,-sse,-mmx" } From 9b80050b4174f7f48c9c07a39ee50f81792c903b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:47:34 +0200 Subject: [PATCH 156/226] Minor error message fix --- src/fat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fat.rs b/src/fat.rs index 4e474a25..c0d9bcc7 100644 --- a/src/fat.rs +++ b/src/fat.rs @@ -45,7 +45,7 @@ pub fn create_fat_filesystem( // format the file system and open it let format_options = fatfs::FormatVolumeOptions::new().volume_label(label); - fatfs::format_volume(&fat_file, format_options).context("Failed to format UEFI FAT file")?; + fatfs::format_volume(&fat_file, format_options).context("Failed to format FAT file")?; let filesystem = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new()) .context("Failed to open FAT file system of UEFI FAT file")?; From cd4ec8039b2b7657c174e60848b2c175976676cc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Mon, 15 Aug 2022 11:53:48 +0200 Subject: [PATCH 157/226] Add vga buffer implementation --- bios/stage-3/src/vga_buffer.rs | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 bios/stage-3/src/vga_buffer.rs diff --git a/bios/stage-3/src/vga_buffer.rs b/bios/stage-3/src/vga_buffer.rs new file mode 100644 index 00000000..b16cf3b2 --- /dev/null +++ b/bios/stage-3/src/vga_buffer.rs @@ -0,0 +1,44 @@ +use core::fmt::{Result, Write}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +const VGA_BUFFER: *mut u8 = 0xb8000 as *mut _; +const SCREEN_WIDTH: usize = 80; +const SCREEN_SIZE: usize = SCREEN_WIDTH * 25; + +pub static CURRENT_OFFSET: AtomicUsize = AtomicUsize::new(0); + +pub struct Writer; + +impl Writer { + pub fn clear_screen(&mut self) { + for i in 0..(SCREEN_SIZE * 2) { + unsafe { + VGA_BUFFER.offset(i as isize).write_volatile(0); + } + } + + CURRENT_OFFSET.store(0, Ordering::Relaxed); + } +} + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> Result { + for byte in s.bytes() { + if byte == b'\n' { + let current = CURRENT_OFFSET.load(Ordering::Relaxed); + let bytes_per_line = SCREEN_WIDTH * 2; + let offset = current % bytes_per_line; + CURRENT_OFFSET.fetch_add(bytes_per_line - offset, Ordering::Relaxed) as isize; + continue; + } + let index = CURRENT_OFFSET.fetch_add(2, Ordering::Relaxed) as isize; + + unsafe { + VGA_BUFFER.offset(index).write_volatile(byte); + VGA_BUFFER.offset(index + 1).write_volatile(0x2f); + } + } + + Ok(()) + } +} From a167ef2d9ca349cb9460baa031d39c92f754861f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 27 Aug 2022 12:27:20 +0200 Subject: [PATCH 158/226] Reorder stage 2 sections to fix relocation out of range error --- bios/stage-2/stage-2-link.ld | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bios/stage-2/stage-2-link.ld b/bios/stage-2/stage-2-link.ld index db1ebe1a..b80c7473 100644 --- a/bios/stage-2/stage-2-link.ld +++ b/bios/stage-2/stage-2-link.ld @@ -9,15 +9,15 @@ SECTIONS { .text : { *(.text .text.*) } + .bss : { + *(.bss .bss.*) + } .rodata : { *(.rodata .rodata.*) } .data : { *(.data .data.*) } - .bss : { - *(.bss .bss.*) - } .eh_frame : { *(.eh_frame .eh_frame.*) } From 6a57e015284cc5edbb7b25e19720e4dc15291234 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 27 Aug 2022 15:47:47 +0200 Subject: [PATCH 159/226] Create `Addresses` struct to pass the various memory addresses between stages --- Cargo.lock | 9 ++++++++ Cargo.toml | 1 + bios/common/Cargo.toml | 12 ++++++++++ bios/common/src/lib.rs | 15 +++++++++++++ bios/stage-2/Cargo.toml | 1 + bios/stage-2/src/main.rs | 35 +++++++++++++++++++++--------- bios/stage-2/src/protected_mode.rs | 14 ++++-------- bios/stage-3/Cargo.toml | 1 + bios/stage-3/src/main.rs | 11 ++++------ 9 files changed, 72 insertions(+), 27 deletions(-) create mode 100644 bios/common/Cargo.toml create mode 100644 bios/common/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 732a5152..4c75ba38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -74,10 +74,15 @@ dependencies = [ name = "bootloader-x86_64-bios-boot-sector" version = "0.1.0" +[[package]] +name = "bootloader-x86_64-bios-common" +version = "0.1.0" + [[package]] name = "bootloader-x86_64-bios-stage-2" version = "0.1.0" dependencies = [ + "bootloader-x86_64-bios-common", "byteorder", "mbr-nostd", ] @@ -85,11 +90,15 @@ dependencies = [ [[package]] name = "bootloader-x86_64-bios-stage-3" version = "0.1.0" +dependencies = [ + "bootloader-x86_64-bios-common", +] [[package]] name = "bootloader-x86_64-bios-stage-4" version = "0.1.0-alpha.0" dependencies = [ + "bootloader-x86_64-bios-common", "bootloader-x86_64-common", "bootloader_api", "log", diff --git a/Cargo.toml b/Cargo.toml index 19c3c90d..dc4a40cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "uefi", "bios/boot_sector", "bios/stage-*", + "bios/common", "tests/runner", "tests/test_kernels/default_settings", "tests/test_kernels/map_phys_mem", diff --git a/bios/common/Cargo.toml b/bios/common/Cargo.toml new file mode 100644 index 00000000..fedddfc8 --- /dev/null +++ b/bios/common/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bootloader-x86_64-bios-common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + +[features] +default = ["debug"] +debug = [] diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs new file mode 100644 index 00000000..345fb391 --- /dev/null +++ b/bios/common/src/lib.rs @@ -0,0 +1,15 @@ +#![no_std] + +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct Addresses { + pub stage_4: Region, + pub kernel: Region, + pub memory_map: Region, + pub framebuffer: Region, +} + +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct Region { + pub start: u64, + pub len: u64, +} diff --git a/bios/stage-2/Cargo.toml b/bios/stage-2/Cargo.toml index e6978b1d..f70bf2b4 100644 --- a/bios/stage-2/Cargo.toml +++ b/bios/stage-2/Cargo.toml @@ -9,3 +9,4 @@ edition = "2021" [dependencies] mbr-nostd = "0.1.0" byteorder = { version = "1.4.3", default-features = false } +bootloader-x86_64-bios-common = { version = "0.1.0", path = "../common" } diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 429e2c4d..3af6221c 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -1,17 +1,17 @@ #![no_std] #![no_main] -use byteorder::{ByteOrder, LittleEndian}; -use core::{fmt::Write as _, slice}; -use disk::AlignedArrayBuffer; -use mbr_nostd::{PartitionTableEntry, PartitionType}; - use crate::{ disk::{AlignedBuffer, Read, Seek, SeekFrom}, protected_mode::{ copy_to_protected_mode, enter_protected_mode_and_jump_to_stage_3, enter_unreal_mode, }, }; +use bootloader_x86_64_bios_common::{Addresses, Region}; +use byteorder::{ByteOrder, LittleEndian}; +use core::{fmt::Write as _, slice}; +use disk::AlignedArrayBuffer; +use mbr_nostd::{PartitionTableEntry, PartitionType}; mod dap; mod disk; @@ -90,18 +90,33 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { writeln!(screen::Writer, "stage 3 loaded at {STAGE_3_DST:#p}").unwrap(); let stage_4_dst = { let stage_3_end = STAGE_3_DST.wrapping_add(usize::try_from(stage_3_len).unwrap()); - let align_offset = stage_3_end.align_offset(512); - stage_3_end.wrapping_add(align_offset) + assert!(STAGE_4_DST > stage_3_end); + STAGE_4_DST }; - load_file("boot-stage-4", stage_4_dst, &mut fs, &mut disk, disk_buffer); + let stage_4_len = load_file("boot-stage-4", stage_4_dst, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "stage 4 loaded at {stage_4_dst:#p}").unwrap(); - load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); + let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); // TODO: Retrieve memory map // TODO: VESA config - enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, stage_4_dst, KERNEL_DST); + let addresses = Addresses { + stage_4: Region { + start: stage_4_dst as u64, + len: stage_4_len, + }, + kernel: Region { + start: KERNEL_DST as u64, + len: kernel_len, + }, + // TODO + memory_map: Region { start: 0, len: 0 }, + // TODO + framebuffer: Region { start: 0, len: 0 }, + }; + + enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, &addresses); loop {} } diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 9d42bedf..b69ff378 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -1,3 +1,4 @@ +use bootloader_x86_64_bios_common::Addresses; use core::{ arch::{asm, global_asm}, fmt::Write as _, @@ -103,11 +104,7 @@ pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { res } -pub fn enter_protected_mode_and_jump_to_stage_3( - entry_point: *const u8, - stage_4: *const u8, - kernel: *const u8, -) { +pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, addresses: &Addresses) { unsafe { asm!("cli") }; set_protected_mode_bit(); unsafe { @@ -115,13 +112,10 @@ pub fn enter_protected_mode_and_jump_to_stage_3( // align the stack "and esp, 0xffffff00", // push arguments - "push {kernel_addr}", - "push {stage_4_addr}", + "push {addr}", // push entry point address - "mov ebx, {entry_point}", "push {entry_point}", - stage_4_addr = in(reg) stage_4 as u32, - kernel_addr = in(reg) kernel as u32, + addr = in(reg) addresses as *const _ as u32, entry_point = in(reg) entry_point as u32, out("ebx") _ ); diff --git a/bios/stage-3/Cargo.toml b/bios/stage-3/Cargo.toml index ed8f3b63..42e233ce 100644 --- a/bios/stage-3/Cargo.toml +++ b/bios/stage-3/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +bootloader-x86_64-bios-common = { version = "0.1.0", path = "../common" } diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index d6f23e74..091e2d8f 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -2,21 +2,18 @@ #![no_main] #![deny(unsafe_op_in_unsafe_fn)] -use core::fmt::Write as _; - use crate::vga_buffer::Writer; +use bootloader_x86_64_bios_common::Addresses; +use core::{arch::asm, fmt::Write as _}; mod paging; mod vga_buffer; #[no_mangle] #[link_section = ".start"] -pub extern "C" fn _start(stage_4_addr: u32, kernel_addr: u32) { +pub extern "C" fn _start(addresses: &Addresses) { // Writer.clear_screen(); - writeln!( - Writer, - "Third Stage (stage_4_addr: {stage_4_addr:#x}, kernel_addr: {kernel_addr:#x})" - ); + writeln!(Writer, "Third Stage ({addresses:#x?})").unwrap(); // set up identity mapping, enable paging, and switch CPU into long // mode (32-bit compatibility mode) From 900327d5b52bd43e900de339b0b055d3b8de6ff8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 27 Aug 2022 15:49:53 +0200 Subject: [PATCH 160/226] Link stage-4 statically at predefined address We cannot do any relocations on loading. --- Cargo.lock | 1 - Cargo.toml | 12 +++++------ bios/stage-2/src/main.rs | 1 + bios/stage-4/build.rs | 9 +++++++++ bios/stage-4/stage-4-link.ld | 21 +++++++++++++++++++ build.rs | 39 ++++++++++++++++++++++++++++++++++-- x86_64-stage-4.json | 21 +++++++++++++++++++ 7 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 bios/stage-4/build.rs create mode 100644 bios/stage-4/stage-4-link.ld create mode 100644 x86_64-stage-4.json diff --git a/Cargo.lock b/Cargo.lock index 4c75ba38..8916f00c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,7 +58,6 @@ name = "bootloader" version = "0.11.0-alpha.0" dependencies = [ "anyhow", - "bootloader-x86_64-bios-stage-4", "bootloader_test_runner", "fatfs", "gpt", diff --git a/Cargo.toml b/Cargo.toml index dc4a40cc..f23ed5a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,12 +32,6 @@ fatfs = "0.3.4" gpt = "3.0.0" mbrman = "0.4.2" -[build-dependencies.bootloader-x86_64-bios-stage-4] -version = "0.1.0-alpha.0" -path = "bios/stage-4" -artifact = ["bin"] -target = "x86_64-unknown-none" - [dev-dependencies] bootloader_test_runner = { path = "tests/runner" } test_kernel_default_settings = { path = "tests/test_kernels/default_settings", artifact = "bin", target = "x86_64-unknown-none" } @@ -71,7 +65,11 @@ overflow-checks = true [profile.stage-3] inherits = "release" -codegen-units = 1 +debug = true +overflow-checks = true + +[profile.stage-4] +inherits = "release" debug = true overflow-checks = true diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 3af6221c..302ff8d3 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -23,6 +23,7 @@ mod screen; const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; const STAGE_3_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB accessible here) +const STAGE_4_DST: *mut u8 = 0x0020_0000 as *mut u8; // 2MiB (typically still 13MiB accessible here) const KERNEL_DST: *mut u8 = 0x0100_0000 as *mut u8; // 16MiB extern "C" { diff --git a/bios/stage-4/build.rs b/bios/stage-4/build.rs new file mode 100644 index 00000000..1fcfb947 --- /dev/null +++ b/bios/stage-4/build.rs @@ -0,0 +1,9 @@ +use std::path::Path; + +fn main() { + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")); + println!( + "cargo:rustc-link-arg-bins=--script={}", + local_path.join("stage-4-link.ld").display() + ) +} diff --git a/bios/stage-4/stage-4-link.ld b/bios/stage-4/stage-4-link.ld new file mode 100644 index 00000000..6977f2d3 --- /dev/null +++ b/bios/stage-4/stage-4-link.ld @@ -0,0 +1,21 @@ +ENTRY(_start) + +SECTIONS { + . = 0x00200000; + + .start : { + *(.start) + } + .text : { + *(.text .text.*) + } + .rodata : { + *(.rodata .rodata.*) + } + .data : { + *(.data .data.*) + } + .bss : { + *(.bss .bss.*) + } +} diff --git a/build.rs b/build.rs index 01deace2..979050ed 100644 --- a/build.rs +++ b/build.rs @@ -202,8 +202,43 @@ fn build_bios_stage_3(out_dir: &Path) -> PathBuf { } fn build_bios_stage_4(out_dir: &Path) -> PathBuf { - let elf_path = - PathBuf::from(std::env::var("CARGO_BIN_FILE_BOOTLOADER_X86_64_BIOS_STAGE_4").unwrap()); + let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); + let mut cmd = Command::new(cargo); + cmd.arg("install").arg("bootloader-x86_64-bios-stage-4"); + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("bios") + .join("stage-4"); + if local_path.exists() { + // local build + cmd.arg("--path").arg(&local_path); + println!("cargo:rerun-if-changed={}", local_path.display()); + } else { + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_STAGE_4_VERSION); + } + cmd.arg("--locked"); + cmd.arg("--target").arg("x86_64-stage-4.json"); + cmd.arg("--profile").arg("stage-4"); + cmd.arg("-Zbuild-std=core") + .arg("-Zbuild-std-features=compiler-builtins-mem"); + cmd.arg("--root").arg(out_dir); + cmd.env_remove("RUSTFLAGS"); + cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); + cmd.env_remove("RUSTC_WORKSPACE_WRAPPER"); // used by clippy + let status = cmd + .status() + .expect("failed to run cargo install for bios stage-4"); + let elf_path = if status.success() { + let path = out_dir.join("bin").join("bootloader-x86_64-bios-stage-4"); + assert!( + path.exists(), + "bios stage-4 executable does not exist after building" + ); + path + } else { + panic!("failed to build bios stage-4"); + }; + convert_elf_to_bin(elf_path) } diff --git a/x86_64-stage-4.json b/x86_64-stage-4.json new file mode 100644 index 00000000..5ea3e7e5 --- /dev/null +++ b/x86_64-stage-4.json @@ -0,0 +1,21 @@ +{ + "arch": "x86_64", + "code-model": "kernel", + "cpu": "x86-64", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", + "disable-redzone": true, + "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-3dnow,-3dnowa,-avx,-avx2,+soft-float", + "linker": "rust-lld", + "linker-flavor": "ld.lld", + "llvm-target": "x86_64-unknown-none-elf", + "max-atomic-width": 64, + "panic-strategy": "abort", + "position-independent-executables": true, + "relro-level": "off", + "stack-probes": { + "kind": "call" + }, + "static-position-independent-executables": true, + "target-pointer-width": "64", + "relocation-model": "static" +} From 899cec69e16d27f097805f25da0f436f46c8ba90 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 27 Aug 2022 15:50:20 +0200 Subject: [PATCH 161/226] Fix: actually use passed `self` pointer in GDT load function --- bios/stage-2/src/protected_mode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index b69ff378..868650cf 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -40,7 +40,7 @@ impl GdtProtectedMode { fn clear_interrupts_and_load(&'static self) { let pointer = GdtPointer { - base: &GDT, + base: self, limit: (3 * size_of::() - 1) as u16, }; From 05130d1d356e7e1566f7e576245580fc542184e6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 27 Aug 2022 15:50:53 +0200 Subject: [PATCH 162/226] Load long mode GDT and jump to 4th stage --- bios/stage-3/src/gdt.rs | 48 +++++++++++++++++++++++++++++++++++++++ bios/stage-3/src/main.rs | 49 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 bios/stage-3/src/gdt.rs diff --git a/bios/stage-3/src/gdt.rs b/bios/stage-3/src/gdt.rs new file mode 100644 index 00000000..c577c00e --- /dev/null +++ b/bios/stage-3/src/gdt.rs @@ -0,0 +1,48 @@ +use core::{arch::asm, mem::size_of}; + +pub static LONG_MODE_GDT: GdtLongMode = GdtLongMode::new(); + +#[repr(C)] +pub struct GdtLongMode { + zero: u64, + code: u64, + data: u64, +} + +impl GdtLongMode { + const fn new() -> Self { + let common_flags = { + (1 << 44) // user segment + | (1 << 47) // present + | (1 << 41) // writable + | (1 << 40) // accessed (to avoid changes by the CPU) + }; + Self { + zero: 0, + code: common_flags | (1 << 43) | (1 << 53), // executable and long mode + data: common_flags, + } + } + + pub fn load(&'static self) { + let pointer = GdtPointer { + base: self, + limit: (3 * size_of::() - 1) as u16, + }; + + unsafe { + asm!("lgdt [{}]", in(reg) &pointer, options(readonly, nostack, preserves_flags)); + } + } +} + +#[repr(C, packed(2))] +pub struct GdtPointer { + /// Size of the DT. + pub limit: u16, + /// Pointer to the memory region containing the DT. + pub base: *const GdtLongMode, +} + +unsafe impl Send for GdtPointer {} +unsafe impl Sync for GdtPointer {} diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 091e2d8f..6b067e7d 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -6,6 +6,7 @@ use crate::vga_buffer::Writer; use bootloader_x86_64_bios_common::Addresses; use core::{arch::asm, fmt::Write as _}; +mod gdt; mod paging; mod vga_buffer; @@ -19,16 +20,54 @@ pub extern "C" fn _start(addresses: &Addresses) { // mode (32-bit compatibility mode) paging::init(); - // TODO: Set up long mode with identity-mapping, then jump to 4th stage (passing - // kernel, memory map, and vesa info as arguments) - - writeln!(Writer, "Paging init done"); + gdt::LONG_MODE_GDT.load(); + enter_long_mode_and_jump_to_stage_4(addresses); loop {} } +#[no_mangle] +pub fn enter_long_mode_and_jump_to_stage_4(addresses: &Addresses) { + let _ = writeln!(Writer, "Paging init done, jumping to stage 4"); + unsafe { + asm!( + // align the stack + "and esp, 0xffffff00", + // push arguments (extended to 64 bit) + "push 0", + "push {addr}", + // push entry point address (extended to 64 bit) + "push 0", + "push {entry_point}", + addr = in(reg) addresses as *const _ as u32, + entry_point = in(reg) addresses.stage_4.start as u32, + out("ebx") _ + ); + asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); + asm!( + ".code64", + + // reload segment registers + "mov bx, 0x10", + "mov ds, bx", + "mov es, bx", + "mov ss, bx", + + // jump to 4th stage + "pop rax", + "pop rdi", + "call rax", + + // enter endless loop in case 4th stage returns + "2:", + "jmp 2b", + out("eax") _ + ); + } +} + #[panic_handler] pub fn panic(info: &core::panic::PanicInfo) -> ! { - writeln!(Writer, "PANIC: {info}"); + let _ = writeln!(Writer, "PANIC: {info}"); loop {} } From e5dddcf43f0e1875cd9b3bd347914f4a74644c2d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 27 Aug 2022 15:51:42 +0200 Subject: [PATCH 163/226] Update stage-4 with VGA printing and handling of Addresses struct --- bios/stage-4/Cargo.toml | 1 + bios/stage-4/src/main.rs | 47 ++++++++++++++++++++++++++-------- bios/stage-4/src/vga_buffer.rs | 44 +++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 11 deletions(-) create mode 100644 bios/stage-4/src/vga_buffer.rs diff --git a/bios/stage-4/Cargo.toml b/bios/stage-4/Cargo.toml index b6a572e0..ad3aac08 100644 --- a/bios/stage-4/Cargo.toml +++ b/bios/stage-4/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" [dependencies] bootloader_api = { version = "0.1.0-alpha.0", path = "../../api" } +bootloader-x86_64-bios-common = { version = "0.1.0", path = "../common" } bootloader-x86_64-common = { version = "0.1.0-alpha.0", path = "../../common" } log = "0.4.14" x86_64 = "0.14.8" diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 50516e52..7134ca2f 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -2,12 +2,17 @@ #![no_main] use crate::memory_descriptor::E820MemoryRegion; +use crate::vga_buffer::Writer; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; +use bootloader_x86_64_bios_common::Addresses; use bootloader_x86_64_common::{ - load_and_switch_to_kernel, logger::LOGGER, Kernel, PageTables, SystemInfo, + legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, logger::LOGGER, Kernel, + PageTables, SystemInfo, }; use core::{ arch::{asm, global_asm}, + fmt::Write, + mem::size_of, panic::PanicInfo, slice, }; @@ -19,18 +24,27 @@ use x86_64::structures::paging::{ use x86_64::{PhysAddr, VirtAddr}; mod memory_descriptor; +mod vga_buffer; -fn bootloader_main( - kernel_start: PhysAddr, - kernel_size: u64, - memory_map_addr: VirtAddr, - memory_map_entry_count: u64, -) -> ! { - use bootloader_x86_64_common::legacy_memory_region::LegacyFrameAllocator; +#[no_mangle] +#[link_section = ".start"] +pub extern "C" fn _start(addresses: &Addresses) -> ! { + Writer.clear_screen(); + writeln!(Writer, "4th Stage").unwrap(); + writeln!(Writer, "{addresses:#x?}").unwrap(); let e820_memory_map = { - let ptr = usize_from(memory_map_addr.as_u64()) as *const E820MemoryRegion; - unsafe { slice::from_raw_parts(ptr, usize_from(memory_map_entry_count)) } + assert!( + addresses.memory_map.start != 0, + "memory map address must be set" + ); + let ptr = usize_from(addresses.memory_map.start) as *const E820MemoryRegion; + unsafe { + slice::from_raw_parts( + ptr, + usize_from(addresses.memory_map.len / size_of::() as u64), + ) + } }; let max_phys_addr = e820_memory_map .iter() @@ -38,6 +52,14 @@ fn bootloader_main( .max() .expect("no physical memory regions found"); + let kernel_start = { + assert!( + addresses.kernel.start != 0, + "kernel start address must be set" + ); + PhysAddr::new(addresses.kernel.start) + }; + let kernel_size = addresses.kernel.len; let mut frame_allocator = { let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); let next_free = kernel_end + 1; @@ -71,7 +93,7 @@ fn bootloader_main( } } - let framebuffer_addr = todo!(); + let framebuffer_addr = PhysAddr::new(addresses.framebuffer.start); let framebuffer_info = todo!(); log::info!("BIOS boot"); @@ -191,6 +213,9 @@ fn detect_rsdp() -> Option { #[panic_handler] fn panic(info: &PanicInfo) -> ! { + // TODO remove + let _ = writeln!(Writer, "{info}"); + unsafe { LOGGER.get().map(|l| l.force_unlock()) }; log::error!("{}", info); loop { diff --git a/bios/stage-4/src/vga_buffer.rs b/bios/stage-4/src/vga_buffer.rs new file mode 100644 index 00000000..ac7c8cc8 --- /dev/null +++ b/bios/stage-4/src/vga_buffer.rs @@ -0,0 +1,44 @@ +use core::fmt::{Result, Write}; +use core::sync::atomic::{AtomicUsize, Ordering}; + +const VGA_BUFFER: *mut u8 = 0xb8000 as *mut _; +const SCREEN_WIDTH: usize = 80; +const SCREEN_SIZE: usize = SCREEN_WIDTH * 25; + +pub static CURRENT_OFFSET: AtomicUsize = AtomicUsize::new(0); + +pub struct Writer; + +impl Writer { + pub fn clear_screen(&mut self) { + for i in 0..(SCREEN_SIZE * 2) { + unsafe { + VGA_BUFFER.offset(i as isize).write_volatile(0); + } + } + + CURRENT_OFFSET.store(0, Ordering::Relaxed); + } +} + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> Result { + for byte in s.bytes() { + if byte == b'\n' { + let current = CURRENT_OFFSET.load(Ordering::Relaxed); + let bytes_per_line = SCREEN_WIDTH * 2; + let offset = current % bytes_per_line; + CURRENT_OFFSET.fetch_add(bytes_per_line - offset, Ordering::Relaxed) as isize; + continue; + } + let index = CURRENT_OFFSET.fetch_add(2, Ordering::Relaxed) as isize; + + unsafe { + VGA_BUFFER.offset(index).write_volatile(byte); + VGA_BUFFER.offset(index + 1).write_volatile(0x1f); + } + } + + Ok(()) + } +} From d8931970365a2ac26088320678cc8ce6ae60150d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 15:59:43 +0200 Subject: [PATCH 164/226] Query vesa modes and filter by resolution --- bios/stage-2/src/main.rs | 14 ++- bios/stage-2/src/vesa.rs | 186 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 bios/stage-2/src/vesa.rs diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 302ff8d3..62cdaa61 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -18,6 +18,7 @@ mod disk; mod fat; mod protected_mode; mod screen; +mod vesa; /// We use this partition type to store the second bootloader stage; const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; @@ -100,7 +101,18 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); // TODO: Retrieve memory map - // TODO: VESA config + + let mut vesa_info = vesa::VesaInfo::query(disk_buffer).unwrap(); + let (mode, mode_info) = vesa_info.get_best_mode(1000, 1000).unwrap().unwrap(); + writeln!( + screen::Writer, + "VESA MODE: {}x{}", + mode_info.width, + mode_info.height + ) + .unwrap(); + + loop {} let addresses = Addresses { stage_4: Region { diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs new file mode 100644 index 00000000..e6e05107 --- /dev/null +++ b/bios/stage-2/src/vesa.rs @@ -0,0 +1,186 @@ +// info taken from https://wiki.osdev.org/VESA_Video_Modes + +use crate::{disk::AlignedBuffer, AlignedArrayBuffer}; +use core::{arch::asm, fmt::Write as _}; + +#[repr(packed)] +struct VbeInfoBlock { + signature: [u8; 4], // should be "VESA" + version: u16, // should be 0x0300 for VBE 3.0 + oem_string_ptr: u32, + capabilities: u32, + video_mode_ptr: u32, + total_memory: u16, // number of 64KB blocks + oem: [u8; 512 - 0x14], +} + +pub struct VesaInfo<'a> { + /// We must store a reference to the full block instead of only copying the + /// required information out because the video mode pointer might point inside the + /// `oem` field. + /// + /// See https://www.ctyme.com/intr/rb-0273.htm for details. + info_block: &'a VbeInfoBlock, + rest_of_buffer: &'a mut [u8], +} + +impl<'a> VesaInfo<'a> { + pub fn query(buffer: &'a mut AlignedArrayBuffer) -> Result { + assert_eq!(core::mem::size_of::(), 512); + + let (slice, rest_of_buffer) = buffer + .slice_mut() + .split_at_mut(core::mem::size_of::()); + slice.fill(0); + let block_ptr = slice.as_mut_ptr(); + let mut ret: u16 = 0; + unsafe { + asm!("mov es, bx", "int 0x10", inout("ax") 0x4f00u16 => ret, in("bx")0, in("di") block_ptr) + }; + match ret { + 0x4f => { + let info_block: &VbeInfoBlock = unsafe { &*block_ptr.cast() }; + Ok(VesaInfo { + info_block, + rest_of_buffer, + }) + } + other => Err(other), + } + } + + pub fn get_best_mode( + &mut self, + max_width: u16, + max_height: u16, + ) -> Result, u16> { + let mut best: Option<(u16, VesaModeInfo)> = None; + for i in 0.. { + let mode = match self.get_mode(i) { + Some(mode) => mode, + None => break, + }; + let mode_info = VesaModeInfo::query(mode, self.rest_of_buffer).unwrap(); + + if mode_info.attributes & 0x90 != 0x90 { + // not a graphics mode with linear frame buffer support + continue; + } + + let supported_modes = [ + 4u8, // packed pixel graphics + 6, // direct color (24-bit color) + ]; + if !supported_modes.contains(&mode_info.memory_model) { + // unsupported mode + continue; + } + + if mode_info.width > max_width || mode_info.height > max_height { + continue; + } + + if best + .as_ref() + .map(|(_, best)| mode_info.width >= best.width || mode_info.height >= best.height) + .unwrap_or(true) + { + best = Some((mode, mode_info)); + } + } + Ok(best) + } + + fn get_mode(&self, index: usize) -> Option { + let video_mode_ptr = self.info_block.video_mode_ptr; + let base_ptr = video_mode_ptr as *const u16; + let ptr = unsafe { base_ptr.add(index) }; + let mode = unsafe { *ptr }; + if mode == 0xffff { + None + } else { + Some(mode) + } + } +} + +#[derive(Debug)] +pub struct VesaModeInfo { + pub width: u16, + pub height: u16, + pub memory_model: u8, + pub attributes: u16, +} + +impl VesaModeInfo { + fn query(mode: u16, buffer: &mut [u8]) -> Result { + #[repr(C, align(256))] + struct VbeModeInfo { + attributes: u16, + window_a: u8, + window_b: u8, + granularity: u16, + window_size: u16, + segment_a: u16, + segment_b: u16, + window_function_ptr: u32, + pitch: u16, + width: u16, + height: u16, + w_char: u8, + y_char: u8, + planes: u8, + bpp: u8, // bytes per pixel + banks: u8, + memory_model: u8, + bank_size: u8, + image_pages: u8, + reserved_0: u8, + red_mask: u8, + red_position: u8, + green_mask: u8, + green_position: u8, + blue_mask: u8, + blue_position: u8, + reserved_mask: u8, + reserved_position: u8, + direct_color_attributes: u8, + framebuffer: u32, + off_screen_memory_offset: u32, + off_screen_memory_size: u16, + reserved: [u8; 206], + } + + assert_eq!(core::mem::size_of::(), 256); + + let slice = &mut buffer[..core::mem::size_of::()]; + slice.fill(0); + let block_ptr = slice.as_mut_ptr(); + + let mut ret: u16 = 0; + let mut target_addr = block_ptr as u32; + let segment = (target_addr >> 4); + target_addr -= segment << 4; + unsafe { + asm!( + "mov es, bx", "int 0x10", + inout("ax") 0x4f01u16 => ret, + in("cx") mode, + in("bx") segment as u16, + in("di") target_addr as u16 + ) + }; + match ret { + 0x4f => { + let block: &VbeModeInfo = unsafe { &*block_ptr.cast() }; + Ok(VesaModeInfo { + width: block.width, + height: block.height, + memory_model: block.memory_model, + attributes: block.attributes, + }) + } + other => Err(other), + } + } +} From d7f8990afa52d5c2f5446760934ab7ae24ed8d2f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 16:29:24 +0200 Subject: [PATCH 165/226] Fill in VESA framebuffer info in boot information --- api/src/info.rs | 5 ++++ bios/common/src/lib.rs | 25 ++++++++++++++-- bios/stage-2/src/main.rs | 24 ++++++++++----- bios/stage-2/src/protected_mode.rs | 8 ++--- bios/stage-2/src/vesa.rs | 31 +++++++++++++++++--- bios/stage-3/src/main.rs | 16 +++++----- bios/stage-4/src/main.rs | 47 +++++++++++++++++++----------- 7 files changed, 113 insertions(+), 43 deletions(-) diff --git a/api/src/info.rs b/api/src/info.rs index 46facd5b..2924e750 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -235,6 +235,11 @@ pub enum PixelFormat { /// Length might be larger than 1, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] /// for this. U8, + Unknown { + red_position: u8, + green_position: u8, + blue_position: u8, + }, } /// Information about the thread local storage (TLS) template. diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index 345fb391..9769016b 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -1,11 +1,21 @@ #![no_std] #[cfg_attr(feature = "debug", derive(Debug))] -pub struct Addresses { +pub struct BiosInfo { pub stage_4: Region, pub kernel: Region, pub memory_map: Region, - pub framebuffer: Region, + pub framebuffer: FramebufferInfo, +} + +#[cfg_attr(feature = "debug", derive(Debug))] +pub struct FramebufferInfo { + pub region: Region, + pub width: u16, + pub height: u16, + pub bytes_per_pixel: u8, + pub stride: u16, + pub pixel_format: PixelFormat, } #[cfg_attr(feature = "debug", derive(Debug))] @@ -13,3 +23,14 @@ pub struct Region { pub start: u64, pub len: u64, } + +#[cfg_attr(feature = "debug", derive(Debug))] +pub enum PixelFormat { + Rgb, + Bgr, + Unknown { + red_position: u8, + green_position: u8, + blue_position: u8, + }, +} diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 62cdaa61..47e97125 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -7,7 +7,7 @@ use crate::{ copy_to_protected_mode, enter_protected_mode_and_jump_to_stage_3, enter_unreal_mode, }, }; -use bootloader_x86_64_bios_common::{Addresses, Region}; +use bootloader_x86_64_bios_common::{BiosInfo, FramebufferInfo, Region}; use byteorder::{ByteOrder, LittleEndian}; use core::{fmt::Write as _, slice}; use disk::AlignedArrayBuffer; @@ -100,8 +100,6 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); - // TODO: Retrieve memory map - let mut vesa_info = vesa::VesaInfo::query(disk_buffer).unwrap(); let (mode, mode_info) = vesa_info.get_best_mode(1000, 1000).unwrap().unwrap(); writeln!( @@ -112,9 +110,10 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { ) .unwrap(); - loop {} + // TODO enable vesa mode + // TODO: Retrieve memory map - let addresses = Addresses { + let info = BiosInfo { stage_4: Region { start: stage_4_dst as u64, len: stage_4_len, @@ -125,11 +124,20 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { }, // TODO memory_map: Region { start: 0, len: 0 }, - // TODO - framebuffer: Region { start: 0, len: 0 }, + framebuffer: FramebufferInfo { + region: Region { + start: mode_info.framebuffer_start.into(), + len: u64::from(mode_info.height) * u64::from(mode_info.bytes_per_scanline), + }, + width: mode_info.width, + height: mode_info.height, + bytes_per_pixel: mode_info.bytes_per_pixel, + stride: mode_info.bytes_per_scanline / u16::from(mode_info.bytes_per_pixel), + pixel_format: mode_info.pixel_format, + }, }; - enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, &addresses); + enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, &info); loop {} } diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 868650cf..da566432 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -1,4 +1,4 @@ -use bootloader_x86_64_bios_common::Addresses; +use bootloader_x86_64_bios_common::BiosInfo; use core::{ arch::{asm, global_asm}, fmt::Write as _, @@ -104,7 +104,7 @@ pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { res } -pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, addresses: &Addresses) { +pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, info: &BiosInfo) { unsafe { asm!("cli") }; set_protected_mode_bit(); unsafe { @@ -112,10 +112,10 @@ pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, addresse // align the stack "and esp, 0xffffff00", // push arguments - "push {addr}", + "push {info}", // push entry point address "push {entry_point}", - addr = in(reg) addresses as *const _ as u32, + info = in(reg) info as *const _ as u32, entry_point = in(reg) entry_point as u32, out("ebx") _ ); diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index e6e05107..0a68c0a9 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -1,5 +1,7 @@ // info taken from https://wiki.osdev.org/VESA_Video_Modes +use bootloader_x86_64_bios_common::PixelFormat; + use crate::{disk::AlignedBuffer, AlignedArrayBuffer}; use core::{arch::asm, fmt::Write as _}; @@ -108,8 +110,13 @@ impl<'a> VesaInfo<'a> { pub struct VesaModeInfo { pub width: u16, pub height: u16, - pub memory_model: u8, - pub attributes: u16, + pub framebuffer_start: u32, + pub bytes_per_scanline: u16, + pub bytes_per_pixel: u8, + pub pixel_format: PixelFormat, + + memory_model: u8, + attributes: u16, } impl VesaModeInfo { @@ -124,13 +131,13 @@ impl VesaModeInfo { segment_a: u16, segment_b: u16, window_function_ptr: u32, - pitch: u16, + bytes_per_scanline: u16, width: u16, height: u16, w_char: u8, y_char: u8, planes: u8, - bpp: u8, // bytes per pixel + bits_per_pixel: u8, banks: u8, memory_model: u8, bank_size: u8, @@ -176,6 +183,22 @@ impl VesaModeInfo { Ok(VesaModeInfo { width: block.width, height: block.height, + framebuffer_start: block.framebuffer, + bytes_per_scanline: block.bytes_per_scanline, + bytes_per_pixel: block.bits_per_pixel / 8, + pixel_format: match ( + block.red_position, + block.green_position, + block.blue_position, + ) { + (0, 8, 16) => PixelFormat::Rgb, + (16, 8, 0) => PixelFormat::Bgr, + (red_position, green_position, blue_position) => PixelFormat::Unknown { + red_position, + green_position, + blue_position, + }, + }, memory_model: block.memory_model, attributes: block.attributes, }) diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 6b067e7d..0848f4a6 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -3,7 +3,7 @@ #![deny(unsafe_op_in_unsafe_fn)] use crate::vga_buffer::Writer; -use bootloader_x86_64_bios_common::Addresses; +use bootloader_x86_64_bios_common::BiosInfo; use core::{arch::asm, fmt::Write as _}; mod gdt; @@ -12,22 +12,22 @@ mod vga_buffer; #[no_mangle] #[link_section = ".start"] -pub extern "C" fn _start(addresses: &Addresses) { +pub extern "C" fn _start(info: &BiosInfo) { // Writer.clear_screen(); - writeln!(Writer, "Third Stage ({addresses:#x?})").unwrap(); + writeln!(Writer, "Third Stage ({info:#x?})").unwrap(); // set up identity mapping, enable paging, and switch CPU into long // mode (32-bit compatibility mode) paging::init(); gdt::LONG_MODE_GDT.load(); - enter_long_mode_and_jump_to_stage_4(addresses); + enter_long_mode_and_jump_to_stage_4(info); loop {} } #[no_mangle] -pub fn enter_long_mode_and_jump_to_stage_4(addresses: &Addresses) { +pub fn enter_long_mode_and_jump_to_stage_4(info: &BiosInfo) { let _ = writeln!(Writer, "Paging init done, jumping to stage 4"); unsafe { asm!( @@ -35,12 +35,12 @@ pub fn enter_long_mode_and_jump_to_stage_4(addresses: &Addresses) { "and esp, 0xffffff00", // push arguments (extended to 64 bit) "push 0", - "push {addr}", + "push {info}", // push entry point address (extended to 64 bit) "push 0", "push {entry_point}", - addr = in(reg) addresses as *const _ as u32, - entry_point = in(reg) addresses.stage_4.start as u32, + info = in(reg) info as *const _ as u32, + entry_point = in(reg) info.stage_4.start as u32, out("ebx") _ ); asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 7134ca2f..238bf6d8 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -4,7 +4,7 @@ use crate::memory_descriptor::E820MemoryRegion; use crate::vga_buffer::Writer; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; -use bootloader_x86_64_bios_common::Addresses; +use bootloader_x86_64_bios_common::BiosInfo; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, logger::LOGGER, Kernel, PageTables, SystemInfo, @@ -28,21 +28,18 @@ mod vga_buffer; #[no_mangle] #[link_section = ".start"] -pub extern "C" fn _start(addresses: &Addresses) -> ! { +pub extern "C" fn _start(info: &BiosInfo) -> ! { Writer.clear_screen(); writeln!(Writer, "4th Stage").unwrap(); - writeln!(Writer, "{addresses:#x?}").unwrap(); + writeln!(Writer, "{info:x?}").unwrap(); let e820_memory_map = { - assert!( - addresses.memory_map.start != 0, - "memory map address must be set" - ); - let ptr = usize_from(addresses.memory_map.start) as *const E820MemoryRegion; + assert!(info.memory_map.start != 0, "memory map address must be set"); + let ptr = usize_from(info.memory_map.start) as *const E820MemoryRegion; unsafe { slice::from_raw_parts( ptr, - usize_from(addresses.memory_map.len / size_of::() as u64), + usize_from(info.memory_map.len / size_of::() as u64), ) } }; @@ -53,13 +50,10 @@ pub extern "C" fn _start(addresses: &Addresses) -> ! { .expect("no physical memory regions found"); let kernel_start = { - assert!( - addresses.kernel.start != 0, - "kernel start address must be set" - ); - PhysAddr::new(addresses.kernel.start) + assert!(info.kernel.start != 0, "kernel start address must be set"); + PhysAddr::new(info.kernel.start) }; - let kernel_size = addresses.kernel.len; + let kernel_size = info.kernel.len; let mut frame_allocator = { let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); let next_free = kernel_end + 1; @@ -93,8 +87,27 @@ pub extern "C" fn _start(addresses: &Addresses) -> ! { } } - let framebuffer_addr = PhysAddr::new(addresses.framebuffer.start); - let framebuffer_info = todo!(); + let framebuffer_addr = PhysAddr::new(info.framebuffer.region.start); + let framebuffer_info = FrameBufferInfo { + byte_len: info.framebuffer.region.len.try_into().unwrap(), + horizontal_resolution: info.framebuffer.width.into(), + vertical_resolution: info.framebuffer.height.into(), + pixel_format: match info.framebuffer.pixel_format { + bootloader_x86_64_bios_common::PixelFormat::Rgb => PixelFormat::Rgb, + bootloader_x86_64_bios_common::PixelFormat::Bgr => PixelFormat::Bgr, + bootloader_x86_64_bios_common::PixelFormat::Unknown { + red_position, + green_position, + blue_position, + } => PixelFormat::Unknown { + red_position, + green_position, + blue_position, + }, + }, + bytes_per_pixel: info.framebuffer.bytes_per_pixel.into(), + stride: info.framebuffer.stride.into(), + }; log::info!("BIOS boot"); From 80b6beeddcc8986da9b9c97f719c36a8c42bac61 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 16:29:35 +0200 Subject: [PATCH 166/226] Remove old assembly file --- bios/stage-2/src/boot unreal.s | 110 --------------------------------- 1 file changed, 110 deletions(-) delete mode 100644 bios/stage-2/src/boot unreal.s diff --git a/bios/stage-2/src/boot unreal.s b/bios/stage-2/src/boot unreal.s deleted file mode 100644 index 9aa5405b..00000000 --- a/bios/stage-2/src/boot unreal.s +++ /dev/null @@ -1,110 +0,0 @@ -.section .boot, "awx" -.global _start -.code16 - -# This stage initializes the stack, enables the A20 line - -_start: - # zero segment registers - xor ax, ax - mov ds, ax - mov es, ax - mov ss, ax - mov fs, ax - mov gs, ax - - # clear the direction flag (e.g. go forward in memory when using - # instructions like lodsb) - cld - - # initialize stack - mov sp, 0x7c00 - -enable_a20: - # enable A20-Line via IO-Port 92, might not work on all motherboards - in al, 0x92 - test al, 2 - jnz enable_a20_after - or al, 2 - and al, 0xFE - out 0x92, al -enable_a20_after: - -enter_protected_mode: - # clear interrupts - cli - push ds - push es - - lgdt [gdt32info] - - mov eax, cr0 - or al, 1 # set protected mode bit - mov cr0, eax - - jmp protected_mode # tell 386/486 to not crash - -protected_mode: - mov bx, 0x10 - mov ds, bx # set data segment - mov es, bx # set extra segment - - and al, 0xfe # clear protected mode bit - mov cr0, eax - -unreal_mode: - pop es # get back old extra segment - pop ds # get back old data segment - sti - - # back to real mode, but internal data segment register is still loaded - # with gdt segment -> we can access the full 4GiB of memory - - mov bx, 0x0f02 # attrib/char of smiley - mov eax, 0xb8f00 # note 32 bit offset - mov word ptr ds:[eax], bx - -check_int13h_extensions: - push 'y' # error code - mov ah, 0x41 - mov bx, 0x55aa - # dl contains drive number - int 0x13 - jc fail - pop ax # pop error code again - -rust: - # push arguments - push dx # disk number - call first_stage - -spin: - hlt - jmp spin - -gdt32info: - .word gdt32_end - gdt32 - 1 # last byte in table - .word gdt32 # start of table - -gdt32: - # entry 0 is always unused - .quad 0 -codedesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x9a - .byte 0xcf - .byte 0 -datadesc: - .byte 0xff - .byte 0xff - .byte 0 - .byte 0 - .byte 0 - .byte 0x92 - .byte 0xcf - .byte 0 -gdt32_end: From 557c03427f5d143ee814fb908b05c584ec37b87a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 17:45:16 +0200 Subject: [PATCH 167/226] Enable VESA framebuffer and update screen writer in stages 3 and 4 --- Cargo.lock | 2 + api/src/info.rs | 4 +- bios/common/src/lib.rs | 6 + bios/common/src/racy_cell.rs | 16 ++ bios/stage-2/src/main.rs | 22 +-- bios/stage-2/src/vesa.rs | 19 ++- bios/stage-3/Cargo.toml | 1 + bios/stage-3/src/main.rs | 5 +- bios/stage-3/src/paging.rs | 34 ++--- bios/stage-3/src/screen.rs | 139 ++++++++++++++++++ bios/stage-3/src/vga_buffer.rs | 44 ------ bios/stage-4/Cargo.toml | 1 + bios/stage-4/src/main.rs | 14 +- bios/stage-4/src/screen.rs | 139 ++++++++++++++++++ bios/stage-4/src/vga_buffer.rs | 44 ------ common/src/logger.rs | 4 +- .../src/bin/check_boot_info.rs | 4 +- .../higher_half/src/bin/check_boot_info.rs | 4 +- .../map_phys_mem/src/bin/check_boot_info.rs | 4 +- .../pie/src/bin/check_boot_info.rs | 4 +- uefi/src/main.rs | 4 +- 21 files changed, 363 insertions(+), 151 deletions(-) create mode 100644 bios/common/src/racy_cell.rs create mode 100644 bios/stage-3/src/screen.rs delete mode 100644 bios/stage-3/src/vga_buffer.rs create mode 100644 bios/stage-4/src/screen.rs delete mode 100644 bios/stage-4/src/vga_buffer.rs diff --git a/Cargo.lock b/Cargo.lock index 8916f00c..a91831bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -91,6 +91,7 @@ name = "bootloader-x86_64-bios-stage-3" version = "0.1.0" dependencies = [ "bootloader-x86_64-bios-common", + "noto-sans-mono-bitmap", ] [[package]] @@ -101,6 +102,7 @@ dependencies = [ "bootloader-x86_64-common", "bootloader_api", "log", + "noto-sans-mono-bitmap", "rsdp", "usize_conversions", "x86_64", diff --git a/api/src/info.rs b/api/src/info.rs index 2924e750..e9359d50 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -200,9 +200,9 @@ pub struct FrameBufferInfo { /// The total size in bytes. pub byte_len: usize, /// The width in pixels. - pub horizontal_resolution: usize, + pub width: usize, /// The height in pixels. - pub vertical_resolution: usize, + pub height: usize, /// The color format of each pixel. pub pixel_format: PixelFormat, /// The number of bytes per pixel. diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index 9769016b..70e020ce 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -1,6 +1,9 @@ #![no_std] +pub mod racy_cell; + #[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy)] pub struct BiosInfo { pub stage_4: Region, pub kernel: Region, @@ -9,6 +12,7 @@ pub struct BiosInfo { } #[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy)] pub struct FramebufferInfo { pub region: Region, pub width: u16, @@ -19,12 +23,14 @@ pub struct FramebufferInfo { } #[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy)] pub struct Region { pub start: u64, pub len: u64, } #[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy)] pub enum PixelFormat { Rgb, Bgr, diff --git a/bios/common/src/racy_cell.rs b/bios/common/src/racy_cell.rs new file mode 100644 index 00000000..087aae27 --- /dev/null +++ b/bios/common/src/racy_cell.rs @@ -0,0 +1,16 @@ +use core::cell::UnsafeCell; + +pub struct RacyCell(UnsafeCell); + +impl RacyCell { + pub const fn new(v: T) -> Self { + Self(UnsafeCell::new(v)) + } + + pub unsafe fn get_mut(&self) -> &mut T { + unsafe { &mut *self.0.get() } + } +} + +unsafe impl Send for RacyCell where T: Send {} +unsafe impl Sync for RacyCell {} diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 47e97125..5d3b7b16 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -101,16 +101,16 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); let mut vesa_info = vesa::VesaInfo::query(disk_buffer).unwrap(); - let (mode, mode_info) = vesa_info.get_best_mode(1000, 1000).unwrap().unwrap(); + let vesa_mode = vesa_info.get_best_mode(1000, 1000).unwrap().unwrap(); writeln!( screen::Writer, "VESA MODE: {}x{}", - mode_info.width, - mode_info.height + vesa_mode.width, + vesa_mode.height ) .unwrap(); + vesa_mode.enable().unwrap(); - // TODO enable vesa mode // TODO: Retrieve memory map let info = BiosInfo { @@ -126,14 +126,14 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { memory_map: Region { start: 0, len: 0 }, framebuffer: FramebufferInfo { region: Region { - start: mode_info.framebuffer_start.into(), - len: u64::from(mode_info.height) * u64::from(mode_info.bytes_per_scanline), + start: vesa_mode.framebuffer_start.into(), + len: u64::from(vesa_mode.height) * u64::from(vesa_mode.bytes_per_scanline), }, - width: mode_info.width, - height: mode_info.height, - bytes_per_pixel: mode_info.bytes_per_pixel, - stride: mode_info.bytes_per_scanline / u16::from(mode_info.bytes_per_pixel), - pixel_format: mode_info.pixel_format, + width: vesa_mode.width, + height: vesa_mode.height, + bytes_per_pixel: vesa_mode.bytes_per_pixel, + stride: vesa_mode.bytes_per_scanline / u16::from(vesa_mode.bytes_per_pixel), + pixel_format: vesa_mode.pixel_format, }, }; diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index 0a68c0a9..e64e359d 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -55,8 +55,8 @@ impl<'a> VesaInfo<'a> { &mut self, max_width: u16, max_height: u16, - ) -> Result, u16> { - let mut best: Option<(u16, VesaModeInfo)> = None; + ) -> Result, u16> { + let mut best: Option = None; for i in 0.. { let mode = match self.get_mode(i) { Some(mode) => mode, @@ -84,10 +84,10 @@ impl<'a> VesaInfo<'a> { if best .as_ref() - .map(|(_, best)| mode_info.width >= best.width || mode_info.height >= best.height) + .map(|best| mode_info.width >= best.width || mode_info.height >= best.height) .unwrap_or(true) { - best = Some((mode, mode_info)); + best = Some(mode_info); } } Ok(best) @@ -108,6 +108,7 @@ impl<'a> VesaInfo<'a> { #[derive(Debug)] pub struct VesaModeInfo { + mode: u16, pub width: u16, pub height: u16, pub framebuffer_start: u32, @@ -181,6 +182,7 @@ impl VesaModeInfo { 0x4f => { let block: &VbeModeInfo = unsafe { &*block_ptr.cast() }; Ok(VesaModeInfo { + mode, width: block.width, height: block.height, framebuffer_start: block.framebuffer, @@ -206,4 +208,13 @@ impl VesaModeInfo { other => Err(other), } } + + pub fn enable(&self) -> Result<(), u16> { + let mut ret: u16 = 0; + unsafe { asm!("int 0x10", inout("ax") 0x4f02u16 => ret, in("bx") self.mode) }; + match ret { + 0x4f => Ok(()), + other => Err(other), + } + } } diff --git a/bios/stage-3/Cargo.toml b/bios/stage-3/Cargo.toml index 42e233ce..bd606566 100644 --- a/bios/stage-3/Cargo.toml +++ b/bios/stage-3/Cargo.toml @@ -8,3 +8,4 @@ edition = "2021" [dependencies] bootloader-x86_64-bios-common = { version = "0.1.0", path = "../common" } +noto-sans-mono-bitmap = "0.1.5" diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 0848f4a6..85c68a23 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -2,17 +2,18 @@ #![no_main] #![deny(unsafe_op_in_unsafe_fn)] -use crate::vga_buffer::Writer; +use crate::screen::Writer; use bootloader_x86_64_bios_common::BiosInfo; use core::{arch::asm, fmt::Write as _}; mod gdt; mod paging; -mod vga_buffer; +mod screen; #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(info: &BiosInfo) { + screen::init(info.framebuffer); // Writer.clear_screen(); writeln!(Writer, "Third Stage ({info:#x?})").unwrap(); diff --git a/bios/stage-3/src/paging.rs b/bios/stage-3/src/paging.rs index a00530db..d6a1f976 100644 --- a/bios/stage-3/src/paging.rs +++ b/bios/stage-3/src/paging.rs @@ -1,23 +1,9 @@ -use core::{arch::asm, cell::UnsafeCell}; +use bootloader_x86_64_bios_common::racy_cell::RacyCell; +use core::arch::asm; static LEVEL_4: RacyCell = RacyCell::new(PageTable::empty()); static LEVEL_3: RacyCell = RacyCell::new(PageTable::empty()); -static LEVEL_2: RacyCell = RacyCell::new(PageTable::empty()); - -pub struct RacyCell(UnsafeCell); - -impl RacyCell { - const fn new(v: T) -> Self { - Self(UnsafeCell::new(v)) - } - - pub unsafe fn get_mut(&self) -> &mut T { - unsafe { &mut *self.0.get() } - } -} - -unsafe impl Send for RacyCell where T: Send {} -unsafe impl Sync for RacyCell {} +static LEVEL_2: RacyCell<[PageTable; 10]> = RacyCell::new([PageTable::empty(); 10]); pub fn init() { create_mappings(); @@ -28,12 +14,17 @@ pub fn init() { fn create_mappings() { let l4 = unsafe { LEVEL_4.get_mut() }; let l3 = unsafe { LEVEL_3.get_mut() }; - let l2 = unsafe { LEVEL_2.get_mut() }; + let l2s = unsafe { LEVEL_2.get_mut() }; let common_flags = 0b11; l4.entries[0] = (l3 as *mut PageTable as u64) | common_flags; - l3.entries[0] = (l2 as *mut PageTable as u64) | common_flags; - for i in 0..512 { - l2.entries[i] = (u64::try_from(i).unwrap() * (2 * 1024 * 1024)) | common_flags | (1 << 7); + for i in 0..l2s.len() { + let l2 = &mut l2s[i]; + l3.entries[i] = (l2 as *mut PageTable as u64) | common_flags; + let offset = u64::try_from(i).unwrap() * 1024 * 1024 * 1024; + for j in 0..512 { + l2.entries[j] = + (offset + u64::try_from(j).unwrap() * (2 * 1024 * 1024)) | common_flags | (1 << 7); + } } } @@ -54,6 +45,7 @@ fn enable_paging() { unsafe { asm!("mov eax, cr0", "or eax, 1 << 31", "mov cr0, eax", out("eax")_) }; } +#[derive(Clone, Copy)] #[repr(align(4096))] struct PageTable { pub entries: [u64; 512], diff --git a/bios/stage-3/src/screen.rs b/bios/stage-3/src/screen.rs new file mode 100644 index 00000000..c2ce8b95 --- /dev/null +++ b/bios/stage-3/src/screen.rs @@ -0,0 +1,139 @@ +use bootloader_x86_64_bios_common::{racy_cell::RacyCell, FramebufferInfo, PixelFormat}; +use core::{ + fmt::{self, Write}, + ptr, +}; +use noto_sans_mono_bitmap::{get_bitmap, get_bitmap_width, BitmapChar, BitmapHeight, FontWeight}; + +static WRITER: RacyCell> = RacyCell::new(None); +pub struct Writer; + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + let writer = unsafe { WRITER.get_mut() }.as_mut().unwrap(); + writer.write_str(s) + } +} + +pub fn init(info: FramebufferInfo) { + let framebuffer = unsafe { + core::slice::from_raw_parts_mut( + info.region.start as *mut u8, + info.region.len.try_into().unwrap(), + ) + }; + let writer = ScreenWriter::new(framebuffer, info); + *unsafe { WRITER.get_mut() } = Some(writer); +} + +/// Additional vertical space between lines +const LINE_SPACING: usize = 0; +/// Additional vertical space between separate log messages +const LOG_SPACING: usize = 2; + +struct ScreenWriter { + framebuffer: &'static mut [u8], + info: FramebufferInfo, + x_pos: usize, + y_pos: usize, +} + +impl ScreenWriter { + pub fn new(framebuffer: &'static mut [u8], info: FramebufferInfo) -> Self { + let mut logger = Self { + framebuffer, + info, + x_pos: 0, + y_pos: 0, + }; + logger.clear(); + logger + } + + fn newline(&mut self) { + self.y_pos += 14 + LINE_SPACING; + self.carriage_return() + } + + fn add_vspace(&mut self, space: usize) { + self.y_pos += space; + } + + fn carriage_return(&mut self) { + self.x_pos = 0; + } + + /// Erases all text on the screen. + pub fn clear(&mut self) { + self.x_pos = 0; + self.y_pos = 0; + self.framebuffer.fill(0); + } + + fn width(&self) -> usize { + self.info.width.into() + } + + fn height(&self) -> usize { + self.info.height.into() + } + + fn write_char(&mut self, c: char) { + match c { + '\n' => self.newline(), + '\r' => self.carriage_return(), + c => { + if self.x_pos >= self.width() { + self.newline(); + } + const BITMAP_LETTER_WIDTH: usize = + get_bitmap_width(FontWeight::Regular, BitmapHeight::Size14); + if self.y_pos >= (self.height() - BITMAP_LETTER_WIDTH) { + self.clear(); + } + let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); + self.write_rendered_char(bitmap_char); + } + } + } + + fn write_rendered_char(&mut self, rendered_char: BitmapChar) { + for (y, row) in rendered_char.bitmap().iter().enumerate() { + for (x, byte) in row.iter().enumerate() { + self.write_pixel(self.x_pos + x, self.y_pos + y, *byte); + } + } + self.x_pos += rendered_char.width(); + } + + fn write_pixel(&mut self, x: usize, y: usize, intensity: u8) { + let pixel_offset = y * usize::from(self.info.stride) + x; + let color = match self.info.pixel_format { + PixelFormat::Rgb => [intensity, intensity, intensity / 2, 0], + PixelFormat::Bgr => [intensity / 2, intensity, intensity, 0], + other => { + // set a supported (but invalid) pixel format before panicking to avoid a double + // panic; it might not be readable though + self.info.pixel_format = PixelFormat::Rgb; + panic!("pixel format {:?} not supported in logger", other) + } + }; + let bytes_per_pixel = self.info.bytes_per_pixel; + let byte_offset = pixel_offset * usize::from(bytes_per_pixel); + self.framebuffer[byte_offset..(byte_offset + usize::from(bytes_per_pixel))] + .copy_from_slice(&color[..usize::from(bytes_per_pixel)]); + let _ = unsafe { ptr::read_volatile(&self.framebuffer[byte_offset]) }; + } +} + +unsafe impl Send for ScreenWriter {} +unsafe impl Sync for ScreenWriter {} + +impl fmt::Write for ScreenWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + self.write_char(c); + } + Ok(()) + } +} diff --git a/bios/stage-3/src/vga_buffer.rs b/bios/stage-3/src/vga_buffer.rs deleted file mode 100644 index b16cf3b2..00000000 --- a/bios/stage-3/src/vga_buffer.rs +++ /dev/null @@ -1,44 +0,0 @@ -use core::fmt::{Result, Write}; -use core::sync::atomic::{AtomicUsize, Ordering}; - -const VGA_BUFFER: *mut u8 = 0xb8000 as *mut _; -const SCREEN_WIDTH: usize = 80; -const SCREEN_SIZE: usize = SCREEN_WIDTH * 25; - -pub static CURRENT_OFFSET: AtomicUsize = AtomicUsize::new(0); - -pub struct Writer; - -impl Writer { - pub fn clear_screen(&mut self) { - for i in 0..(SCREEN_SIZE * 2) { - unsafe { - VGA_BUFFER.offset(i as isize).write_volatile(0); - } - } - - CURRENT_OFFSET.store(0, Ordering::Relaxed); - } -} - -impl Write for Writer { - fn write_str(&mut self, s: &str) -> Result { - for byte in s.bytes() { - if byte == b'\n' { - let current = CURRENT_OFFSET.load(Ordering::Relaxed); - let bytes_per_line = SCREEN_WIDTH * 2; - let offset = current % bytes_per_line; - CURRENT_OFFSET.fetch_add(bytes_per_line - offset, Ordering::Relaxed) as isize; - continue; - } - let index = CURRENT_OFFSET.fetch_add(2, Ordering::Relaxed) as isize; - - unsafe { - VGA_BUFFER.offset(index).write_volatile(byte); - VGA_BUFFER.offset(index + 1).write_volatile(0x2f); - } - } - - Ok(()) - } -} diff --git a/bios/stage-4/Cargo.toml b/bios/stage-4/Cargo.toml index ad3aac08..6cd50661 100644 --- a/bios/stage-4/Cargo.toml +++ b/bios/stage-4/Cargo.toml @@ -13,3 +13,4 @@ log = "0.4.14" x86_64 = "0.14.8" rsdp = "2.0.0" usize_conversions = "0.2.0" +noto-sans-mono-bitmap = "0.1.5" diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 238bf6d8..422d0aa2 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -2,7 +2,7 @@ #![no_main] use crate::memory_descriptor::E820MemoryRegion; -use crate::vga_buffer::Writer; +use crate::screen::Writer; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; use bootloader_x86_64_bios_common::BiosInfo; use bootloader_x86_64_common::{ @@ -24,12 +24,12 @@ use x86_64::structures::paging::{ use x86_64::{PhysAddr, VirtAddr}; mod memory_descriptor; -mod vga_buffer; +mod screen; #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(info: &BiosInfo) -> ! { - Writer.clear_screen(); + screen::init(info.framebuffer); writeln!(Writer, "4th Stage").unwrap(); writeln!(Writer, "{info:x?}").unwrap(); @@ -90,8 +90,8 @@ pub extern "C" fn _start(info: &BiosInfo) -> ! { let framebuffer_addr = PhysAddr::new(info.framebuffer.region.start); let framebuffer_info = FrameBufferInfo { byte_len: info.framebuffer.region.len.try_into().unwrap(), - horizontal_resolution: info.framebuffer.width.into(), - vertical_resolution: info.framebuffer.height.into(), + width: info.framebuffer.width.into(), + height: info.framebuffer.height.into(), pixel_format: match info.framebuffer.pixel_format { bootloader_x86_64_bios_common::PixelFormat::Rgb => PixelFormat::Rgb, bootloader_x86_64_bios_common::PixelFormat::Bgr => PixelFormat::Bgr, @@ -142,8 +142,8 @@ fn init_logger( let info = FrameBufferInfo { byte_len: framebuffer_size, - horizontal_resolution, - vertical_resolution, + width: horizontal_resolution, + height: vertical_resolution, bytes_per_pixel, stride, pixel_format, diff --git a/bios/stage-4/src/screen.rs b/bios/stage-4/src/screen.rs new file mode 100644 index 00000000..af5afe1e --- /dev/null +++ b/bios/stage-4/src/screen.rs @@ -0,0 +1,139 @@ +use bootloader_x86_64_bios_common::{racy_cell::RacyCell, FramebufferInfo, PixelFormat}; +use core::{ + fmt::{self, Write}, + ptr, +}; +use noto_sans_mono_bitmap::{get_bitmap, get_bitmap_width, BitmapChar, BitmapHeight, FontWeight}; + +static WRITER: RacyCell> = RacyCell::new(None); +pub struct Writer; + +impl fmt::Write for Writer { + fn write_str(&mut self, s: &str) -> fmt::Result { + let writer = unsafe { WRITER.get_mut() }.as_mut().unwrap(); + writer.write_str(s) + } +} + +pub fn init(info: FramebufferInfo) { + let framebuffer = unsafe { + core::slice::from_raw_parts_mut( + info.region.start as *mut u8, + info.region.len.try_into().unwrap(), + ) + }; + let writer = ScreenWriter::new(framebuffer, info); + *unsafe { WRITER.get_mut() } = Some(writer); +} + +/// Additional vertical space between lines +const LINE_SPACING: usize = 0; +/// Additional vertical space between separate log messages +const LOG_SPACING: usize = 2; + +struct ScreenWriter { + framebuffer: &'static mut [u8], + info: FramebufferInfo, + x_pos: usize, + y_pos: usize, +} + +impl ScreenWriter { + pub fn new(framebuffer: &'static mut [u8], info: FramebufferInfo) -> Self { + let mut logger = Self { + framebuffer, + info, + x_pos: 0, + y_pos: 0, + }; + logger.clear(); + logger + } + + fn newline(&mut self) { + self.y_pos += 14 + LINE_SPACING; + self.carriage_return() + } + + fn add_vspace(&mut self, space: usize) { + self.y_pos += space; + } + + fn carriage_return(&mut self) { + self.x_pos = 0; + } + + /// Erases all text on the screen. + pub fn clear(&mut self) { + self.x_pos = 0; + self.y_pos = 0; + self.framebuffer.fill(0); + } + + fn width(&self) -> usize { + self.info.width.into() + } + + fn height(&self) -> usize { + self.info.height.into() + } + + fn write_char(&mut self, c: char) { + match c { + '\n' => self.newline(), + '\r' => self.carriage_return(), + c => { + if self.x_pos >= self.width() { + self.newline(); + } + const BITMAP_LETTER_WIDTH: usize = + get_bitmap_width(FontWeight::Regular, BitmapHeight::Size14); + if self.y_pos >= (self.height() - BITMAP_LETTER_WIDTH) { + self.clear(); + } + let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); + self.write_rendered_char(bitmap_char); + } + } + } + + fn write_rendered_char(&mut self, rendered_char: BitmapChar) { + for (y, row) in rendered_char.bitmap().iter().enumerate() { + for (x, byte) in row.iter().enumerate() { + self.write_pixel(self.x_pos + x, self.y_pos + y, *byte); + } + } + self.x_pos += rendered_char.width(); + } + + fn write_pixel(&mut self, x: usize, y: usize, intensity: u8) { + let pixel_offset = y * usize::from(self.info.stride) + x; + let color = match self.info.pixel_format { + PixelFormat::Rgb => [intensity / 2, intensity / 2, intensity, 0], + PixelFormat::Bgr => [intensity, intensity / 2, intensity / 2, 0], + other => { + // set a supported (but invalid) pixel format before panicking to avoid a double + // panic; it might not be readable though + self.info.pixel_format = PixelFormat::Rgb; + panic!("pixel format {:?} not supported in logger", other) + } + }; + let bytes_per_pixel = self.info.bytes_per_pixel; + let byte_offset = pixel_offset * usize::from(bytes_per_pixel); + self.framebuffer[byte_offset..(byte_offset + usize::from(bytes_per_pixel))] + .copy_from_slice(&color[..usize::from(bytes_per_pixel)]); + let _ = unsafe { ptr::read_volatile(&self.framebuffer[byte_offset]) }; + } +} + +unsafe impl Send for ScreenWriter {} +unsafe impl Sync for ScreenWriter {} + +impl fmt::Write for ScreenWriter { + fn write_str(&mut self, s: &str) -> fmt::Result { + for c in s.chars() { + self.write_char(c); + } + Ok(()) + } +} diff --git a/bios/stage-4/src/vga_buffer.rs b/bios/stage-4/src/vga_buffer.rs deleted file mode 100644 index ac7c8cc8..00000000 --- a/bios/stage-4/src/vga_buffer.rs +++ /dev/null @@ -1,44 +0,0 @@ -use core::fmt::{Result, Write}; -use core::sync::atomic::{AtomicUsize, Ordering}; - -const VGA_BUFFER: *mut u8 = 0xb8000 as *mut _; -const SCREEN_WIDTH: usize = 80; -const SCREEN_SIZE: usize = SCREEN_WIDTH * 25; - -pub static CURRENT_OFFSET: AtomicUsize = AtomicUsize::new(0); - -pub struct Writer; - -impl Writer { - pub fn clear_screen(&mut self) { - for i in 0..(SCREEN_SIZE * 2) { - unsafe { - VGA_BUFFER.offset(i as isize).write_volatile(0); - } - } - - CURRENT_OFFSET.store(0, Ordering::Relaxed); - } -} - -impl Write for Writer { - fn write_str(&mut self, s: &str) -> Result { - for byte in s.bytes() { - if byte == b'\n' { - let current = CURRENT_OFFSET.load(Ordering::Relaxed); - let bytes_per_line = SCREEN_WIDTH * 2; - let offset = current % bytes_per_line; - CURRENT_OFFSET.fetch_add(bytes_per_line - offset, Ordering::Relaxed) as isize; - continue; - } - let index = CURRENT_OFFSET.fetch_add(2, Ordering::Relaxed) as isize; - - unsafe { - VGA_BUFFER.offset(index).write_volatile(byte); - VGA_BUFFER.offset(index + 1).write_volatile(0x1f); - } - } - - Ok(()) - } -} diff --git a/common/src/logger.rs b/common/src/logger.rs index b0638e1b..6f26f655 100644 --- a/common/src/logger.rs +++ b/common/src/logger.rs @@ -88,11 +88,11 @@ impl Logger { } fn width(&self) -> usize { - self.info.horizontal_resolution + self.info.width } fn height(&self) -> usize { - self.info.vertical_resolution + self.info.height } fn write_char(&mut self, c: char) { diff --git a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs index b0e3eefb..ad76a8ba 100644 --- a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs +++ b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs @@ -23,9 +23,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), - framebuffer.info().stride - * framebuffer.info().vertical_resolution - * framebuffer.info().bytes_per_pixel + framebuffer.info().stride * framebuffer.info().height * framebuffer.info().bytes_per_pixel ); // check defaults for optional features diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs index e9bf9c48..3ed1f4ef 100644 --- a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -23,9 +23,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), - framebuffer.info().stride - * framebuffer.info().vertical_resolution - * framebuffer.info().bytes_per_pixel + framebuffer.info().stride * framebuffer.info().height * framebuffer.info().bytes_per_pixel ); // check defaults for optional features diff --git a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs index e66b0487..0cbf3bae 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs @@ -23,9 +23,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), - framebuffer.info().stride - * framebuffer.info().vertical_resolution - * framebuffer.info().bytes_per_pixel + framebuffer.info().stride * framebuffer.info().height * framebuffer.info().bytes_per_pixel ); // check defaults for optional features diff --git a/tests/test_kernels/pie/src/bin/check_boot_info.rs b/tests/test_kernels/pie/src/bin/check_boot_info.rs index deb126e8..e110e115 100644 --- a/tests/test_kernels/pie/src/bin/check_boot_info.rs +++ b/tests/test_kernels/pie/src/bin/check_boot_info.rs @@ -23,9 +23,7 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { assert_eq!(framebuffer.info().pixel_format, PixelFormat::Bgr); assert_eq!( framebuffer.buffer().len(), - framebuffer.info().stride - * framebuffer.info().vertical_resolution - * framebuffer.info().bytes_per_pixel + framebuffer.info().stride * framebuffer.info().height * framebuffer.info().bytes_per_pixel ); // check defaults for optional features diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 954b3e3d..e1dac00f 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -403,8 +403,8 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F let slice = unsafe { slice::from_raw_parts_mut(framebuffer.as_mut_ptr(), framebuffer.size()) }; let info = FrameBufferInfo { byte_len: framebuffer.size(), - horizontal_resolution: mode_info.resolution().0, - vertical_resolution: mode_info.resolution().1, + width: mode_info.resolution().0, + height: mode_info.resolution().1, pixel_format: match mode_info.pixel_format() { PixelFormat::Rgb => bootloader_api::info::PixelFormat::Rgb, PixelFormat::Bgr => bootloader_api::info::PixelFormat::Bgr, From 48cd6dcd109778032ce586735f5be1f1dac67117 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 19:36:37 +0200 Subject: [PATCH 168/226] Load E820 memory map and put everything together --- bios/common/src/lib.rs | 17 ++++++- bios/stage-2/src/main.rs | 20 +++++--- bios/stage-2/src/memory_map.rs | 69 +++++++++++++++++++++++++++ bios/stage-2/src/protected_mode.rs | 2 +- bios/stage-3/src/main.rs | 6 +-- bios/stage-4/src/main.rs | 41 +++++++++------- bios/stage-4/src/memory_descriptor.rs | 16 +++---- tests/runner/src/lib.rs | 8 +--- 8 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 bios/stage-2/src/memory_map.rs diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index 70e020ce..9c36dbce 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -3,16 +3,18 @@ pub mod racy_cell; #[cfg_attr(feature = "debug", derive(Debug))] -#[derive(Clone, Copy)] +#[repr(C)] pub struct BiosInfo { pub stage_4: Region, pub kernel: Region, - pub memory_map: Region, pub framebuffer: FramebufferInfo, + pub memory_map_addr: u32, + pub memory_map_len: u16, } #[cfg_attr(feature = "debug", derive(Debug))] #[derive(Clone, Copy)] +#[repr(C)] pub struct FramebufferInfo { pub region: Region, pub width: u16, @@ -24,6 +26,7 @@ pub struct FramebufferInfo { #[cfg_attr(feature = "debug", derive(Debug))] #[derive(Clone, Copy)] +#[repr(C)] pub struct Region { pub start: u64, pub len: u64, @@ -31,6 +34,7 @@ pub struct Region { #[cfg_attr(feature = "debug", derive(Debug))] #[derive(Clone, Copy)] +#[repr(C)] pub enum PixelFormat { Rgb, Bgr, @@ -40,3 +44,12 @@ pub enum PixelFormat { blue_position: u8, }, } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(C)] +pub struct E820MemoryRegion { + pub start_addr: u64, + pub len: u64, + pub region_type: u32, + pub acpi_extended_attributes: u32, +} diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 5d3b7b16..1a299174 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -16,6 +16,7 @@ use mbr_nostd::{PartitionTableEntry, PartitionType}; mod dap; mod disk; mod fat; +mod memory_map; mod protected_mode; mod screen; mod vesa; @@ -41,7 +42,11 @@ static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { #[no_mangle] #[link_section = ".start"] -pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { +pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) -> ! { + start(disk_number, partition_table_start) +} + +fn start(disk_number: u16, partition_table_start: *const u8) -> ! { screen::Writer.write_str(" -> SECOND STAGE\n").unwrap(); enter_unreal_mode(); @@ -100,6 +105,9 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); + let memory_map = memory_map::query_memory_map().unwrap(); + writeln!(screen::Writer, "{memory_map:x?}").unwrap(); + let mut vesa_info = vesa::VesaInfo::query(disk_buffer).unwrap(); let vesa_mode = vesa_info.get_best_mode(1000, 1000).unwrap().unwrap(); writeln!( @@ -111,9 +119,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { .unwrap(); vesa_mode.enable().unwrap(); - // TODO: Retrieve memory map - - let info = BiosInfo { + let mut info = BiosInfo { stage_4: Region { start: stage_4_dst as u64, len: stage_4_len, @@ -122,8 +128,8 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { start: KERNEL_DST as u64, len: kernel_len, }, - // TODO - memory_map: Region { start: 0, len: 0 }, + memory_map_addr: memory_map.as_mut_ptr() as u32, + memory_map_len: memory_map.len().try_into().unwrap(), framebuffer: FramebufferInfo { region: Region { start: vesa_mode.framebuffer_start.into(), @@ -137,7 +143,7 @@ pub extern "C" fn _start(disk_number: u16, partition_table_start: *const u8) { }, }; - enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, &info); + enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, &mut info); loop {} } diff --git a/bios/stage-2/src/memory_map.rs b/bios/stage-2/src/memory_map.rs new file mode 100644 index 00000000..9c358ea0 --- /dev/null +++ b/bios/stage-2/src/memory_map.rs @@ -0,0 +1,69 @@ +// From http://wiki.osdev.org/Detecting_Memory_(x86)#Getting_an_E820_Memory_Map + +use crate::split_array_ref; +use bootloader_x86_64_bios_common::{racy_cell::RacyCell, E820MemoryRegion}; +use core::arch::asm; + +static MEMORY_MAP: RacyCell<[E820MemoryRegion; 100]> = RacyCell::new( + [E820MemoryRegion { + start_addr: 0, + len: 0, + region_type: 0, + acpi_extended_attributes: 0, + }; 100], +); + +/// use the INT 0x15, eax= 0xE820 BIOS function to get a memory map +pub fn query_memory_map() -> Result<&'static mut [E820MemoryRegion], ()> { + const SMAP: u32 = 0x534D4150; + + let memory_map = unsafe { MEMORY_MAP.get_mut() }; + + let mut i = 0; + + let mut offset = 0; + let buf = [0u8; 24]; + loop { + let mut ret = 0; + let mut buf_written_len = 0; + unsafe { + asm!( + "int 0x15", + inout ("eax") 0xe820 => ret, + in("edx") SMAP, + inout("ebx") offset, + inout("ecx") buf.len() => buf_written_len, + in("di") &buf + ) + }; + if ret != SMAP { + return Err(()); + } + + if buf_written_len != 0 { + let buf = &buf[..buf_written_len]; + + let (&base_raw, rest) = split_array_ref(&buf); + let (&len_raw, rest) = split_array_ref(rest); + let (&kind_raw, rest) = split_array_ref(rest); + let acpi_extended_raw: [u8; 4] = rest.try_into().unwrap_or_default(); + + let len = u64::from_ne_bytes(len_raw); + if len != 0 { + memory_map[i] = E820MemoryRegion { + start_addr: u64::from_ne_bytes(base_raw), + len, + region_type: u32::from_ne_bytes(kind_raw), + acpi_extended_attributes: u32::from_ne_bytes(acpi_extended_raw), + }; + i += 1; + } + } + + if offset == 0 { + break; + } + } + + Ok(&mut memory_map[..i]) +} diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index da566432..3ed09583 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -104,7 +104,7 @@ pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { res } -pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, info: &BiosInfo) { +pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, info: &mut BiosInfo) { unsafe { asm!("cli") }; set_protected_mode_bit(); unsafe { diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 85c68a23..f7f95be3 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -12,10 +12,10 @@ mod screen; #[no_mangle] #[link_section = ".start"] -pub extern "C" fn _start(info: &BiosInfo) { +pub extern "C" fn _start(info: &mut BiosInfo) { screen::init(info.framebuffer); // Writer.clear_screen(); - writeln!(Writer, "Third Stage ({info:#x?})").unwrap(); + writeln!(Writer, "Third Stage ({info:x?})").unwrap(); // set up identity mapping, enable paging, and switch CPU into long // mode (32-bit compatibility mode) @@ -28,7 +28,7 @@ pub extern "C" fn _start(info: &BiosInfo) { } #[no_mangle] -pub fn enter_long_mode_and_jump_to_stage_4(info: &BiosInfo) { +pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { let _ = writeln!(Writer, "Paging init done, jumping to stage 4"); unsafe { asm!( diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 422d0aa2..d42a3c94 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -1,10 +1,10 @@ #![no_std] #![no_main] -use crate::memory_descriptor::E820MemoryRegion; +use crate::memory_descriptor::MemoryRegion; use crate::screen::Writer; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; -use bootloader_x86_64_bios_common::BiosInfo; +use bootloader_x86_64_bios_common::{BiosInfo, E820MemoryRegion}; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, logger::LOGGER, Kernel, PageTables, SystemInfo, @@ -28,24 +28,26 @@ mod screen; #[no_mangle] #[link_section = ".start"] -pub extern "C" fn _start(info: &BiosInfo) -> ! { +pub extern "C" fn _start(info: &mut BiosInfo) -> ! { screen::init(info.framebuffer); writeln!(Writer, "4th Stage").unwrap(); writeln!(Writer, "{info:x?}").unwrap(); - let e820_memory_map = { - assert!(info.memory_map.start != 0, "memory map address must be set"); - let ptr = usize_from(info.memory_map.start) as *const E820MemoryRegion; - unsafe { - slice::from_raw_parts( - ptr, - usize_from(info.memory_map.len / size_of::() as u64), - ) - } + let memory_map: &mut [E820MemoryRegion] = unsafe { + core::slice::from_raw_parts_mut( + info.memory_map_addr as *mut _, + info.memory_map_len.try_into().unwrap(), + ) }; - let max_phys_addr = e820_memory_map + + memory_map.sort_unstable_by_key(|e| e.start_addr); + + let max_phys_addr = memory_map .iter() - .map(|r| r.start_addr + r.len) + .map(|r| { + writeln!(Writer, "start: {:#x}, len: {:#x}", r.start_addr, r.len).unwrap(); + r.start_addr + r.len + }) .max() .expect("no physical memory regions found"); @@ -57,10 +59,13 @@ pub extern "C" fn _start(info: &BiosInfo) -> ! { let mut frame_allocator = { let kernel_end = PhysFrame::containing_address(kernel_start + kernel_size - 1u64); let next_free = kernel_end + 1; - LegacyFrameAllocator::new_starting_at(next_free, e820_memory_map.iter().copied()) + LegacyFrameAllocator::new_starting_at( + next_free, + memory_map.iter().copied().map(MemoryRegion), + ) }; - // We identity-map all memory, so the offset between physical and virtual addresses is 0 + // We identity-mapped all memory, so the offset between physical and virtual addresses is 0 let phys_offset = VirtAddr::new(0); let mut bootloader_page_table = { @@ -68,10 +73,10 @@ pub extern "C" fn _start(info: &BiosInfo) -> ! { let table: *mut PageTable = (phys_offset + frame.start_address().as_u64()).as_mut_ptr(); unsafe { OffsetPageTable::new(&mut *table, phys_offset) } }; - // identity-map remaining physical memory (first gigabyte is already identity-mapped) + // identity-map remaining physical memory (first 10 gigabytes are already identity-mapped) { let start_frame: PhysFrame = - PhysFrame::containing_address(PhysAddr::new(4096 * 512 * 512)); + PhysFrame::containing_address(PhysAddr::new(4096 * 512 * 512 * 10)); let end_frame = PhysFrame::containing_address(PhysAddr::new(max_phys_addr - 1)); for frame in PhysFrame::range_inclusive(start_frame, end_frame) { unsafe { diff --git a/bios/stage-4/src/memory_descriptor.rs b/bios/stage-4/src/memory_descriptor.rs index 624f0110..0f02e573 100644 --- a/bios/stage-4/src/memory_descriptor.rs +++ b/bios/stage-4/src/memory_descriptor.rs @@ -1,18 +1,19 @@ use bootloader_api::info::MemoryRegionKind; +use bootloader_x86_64_bios_common::E820MemoryRegion; use bootloader_x86_64_common::legacy_memory_region::LegacyMemoryRegion; use x86_64::PhysAddr; -impl LegacyMemoryRegion for E820MemoryRegion { +impl LegacyMemoryRegion for MemoryRegion { fn start(&self) -> PhysAddr { - PhysAddr::new(self.start_addr) + PhysAddr::new(self.0.start_addr) } fn len(&self) -> u64 { - self.len + self.0.len } fn kind(&self) -> MemoryRegionKind { - match self.region_type { + match self.0.region_type { 1 => MemoryRegionKind::Usable, other => MemoryRegionKind::UnknownBios(other), } @@ -29,9 +30,4 @@ impl LegacyMemoryRegion for E820MemoryRegion { #[doc(hidden)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] -pub struct E820MemoryRegion { - pub start_addr: u64, - pub len: u64, - pub region_type: u32, - pub acpi_extended_attributes: u32, -} +pub struct MemoryRegion(pub E820MemoryRegion); diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index aa36abf6..15b115c9 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -5,13 +5,9 @@ const QEMU_ARGS: &[&str] = &[ "isa-debug-exit,iobase=0xf4,iosize=0x04", "-serial", "stdio", - // "-display", - // "none", + "-display", + "none", "--no-reboot", - // "-d", - // "int", - "-s", - // "-S", ]; pub fn run_test_kernel(kernel_binary_path: &str) { From 7a1e1a759375e33108344ba0a92e08b09cff129c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 19:38:17 +0200 Subject: [PATCH 169/226] Also run UEFI tests again --- tests/runner/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index 15b115c9..fa6e4ba7 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -30,9 +30,9 @@ pub fn run_test_kernel(kernel_binary_path: &str) { let out_tftp_path = kernel_path.with_extension(".tftp"); bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap(); - // run_test_kernel_on_uefi(&out_gpt_path); + run_test_kernel_on_uefi(&out_gpt_path); run_test_kernel_on_bios(&out_mbr_path); - // run_test_kernel_on_uefi_pxe(&out_tftp_path); + run_test_kernel_on_uefi_pxe(&out_tftp_path); } pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) { From a5fe892ea286631bcbd3c0a974ddda1d211c8403 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 19:44:16 +0200 Subject: [PATCH 170/226] Remove vscode config --- .vscode/settings.json | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index d7489c73..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.checkOnSave.extraArgs": [ - "-Zbuild-std=core,alloc", - ], - "rust-analyzer.cargo.target": "x86_64-unknown-uefi", - "editor.formatOnSave": true, - "rust-analyzer.checkOnSave.enable": true, - "rust-analyzer.checkOnSave.overrideCommand": [ - "cargo", - "c", - "-p", - "bootloader-x86_64-uefi", - "--target", - "x86_64-unknown-uefi", - "-Zbuild-std=core", - "--message-format=json" - ] -} From d983247566dc5dc5b3756cceeb49f0844f30092d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 20:41:07 +0200 Subject: [PATCH 171/226] Switch stage-4 to logger instead of using custom screen writer --- Cargo.lock | 1 - bios/stage-4/Cargo.toml | 1 - bios/stage-4/src/main.rs | 112 ++++++++++++------------------ bios/stage-4/src/screen.rs | 139 ------------------------------------- 4 files changed, 46 insertions(+), 207 deletions(-) delete mode 100644 bios/stage-4/src/screen.rs diff --git a/Cargo.lock b/Cargo.lock index a91831bc..4cfc1f39 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -102,7 +102,6 @@ dependencies = [ "bootloader-x86_64-common", "bootloader_api", "log", - "noto-sans-mono-bitmap", "rsdp", "usize_conversions", "x86_64", diff --git a/bios/stage-4/Cargo.toml b/bios/stage-4/Cargo.toml index 6cd50661..ad3aac08 100644 --- a/bios/stage-4/Cargo.toml +++ b/bios/stage-4/Cargo.toml @@ -13,4 +13,3 @@ log = "0.4.14" x86_64 = "0.14.8" rsdp = "2.0.0" usize_conversions = "0.2.0" -noto-sans-mono-bitmap = "0.1.5" diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index d42a3c94..555f9d5a 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -2,20 +2,13 @@ #![no_main] use crate::memory_descriptor::MemoryRegion; -use crate::screen::Writer; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; -use bootloader_x86_64_bios_common::{BiosInfo, E820MemoryRegion}; +use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion}; use bootloader_x86_64_common::{ - legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, logger::LOGGER, Kernel, - PageTables, SystemInfo, -}; -use core::{ - arch::{asm, global_asm}, - fmt::Write, - mem::size_of, - panic::PanicInfo, - slice, + legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, Kernel, PageTables, + SystemInfo, }; +use core::slice; use usize_conversions::usize_from; use x86_64::structures::paging::{FrameAllocator, OffsetPageTable}; use x86_64::structures::paging::{ @@ -24,14 +17,13 @@ use x86_64::structures::paging::{ use x86_64::{PhysAddr, VirtAddr}; mod memory_descriptor; -mod screen; #[no_mangle] #[link_section = ".start"] pub extern "C" fn _start(info: &mut BiosInfo) -> ! { - screen::init(info.framebuffer); - writeln!(Writer, "4th Stage").unwrap(); - writeln!(Writer, "{info:x?}").unwrap(); + let framebuffer_info = init_logger(info.framebuffer); + log::info!("4th Stage"); + log::info!("{info:x?}"); let memory_map: &mut [E820MemoryRegion] = unsafe { core::slice::from_raw_parts_mut( @@ -45,7 +37,7 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { let max_phys_addr = memory_map .iter() .map(|r| { - writeln!(Writer, "start: {:#x}, len: {:#x}", r.start_addr, r.len).unwrap(); + log::info!("start: {:#x}, len: {:#x}", r.start_addr, r.len); r.start_addr + r.len }) .max() @@ -92,28 +84,6 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { } } - let framebuffer_addr = PhysAddr::new(info.framebuffer.region.start); - let framebuffer_info = FrameBufferInfo { - byte_len: info.framebuffer.region.len.try_into().unwrap(), - width: info.framebuffer.width.into(), - height: info.framebuffer.height.into(), - pixel_format: match info.framebuffer.pixel_format { - bootloader_x86_64_bios_common::PixelFormat::Rgb => PixelFormat::Rgb, - bootloader_x86_64_bios_common::PixelFormat::Bgr => PixelFormat::Bgr, - bootloader_x86_64_bios_common::PixelFormat::Unknown { - red_position, - green_position, - blue_position, - } => PixelFormat::Unknown { - red_position, - green_position, - blue_position, - }, - }, - bytes_per_pixel: info.framebuffer.bytes_per_pixel.into(), - stride: info.framebuffer.stride.into(), - }; - log::info!("BIOS boot"); let page_tables = create_page_tables(&mut frame_allocator); @@ -125,7 +95,7 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { let kernel = Kernel::parse(kernel_slice); let system_info = SystemInfo { - framebuffer_addr, + framebuffer_addr: PhysAddr::new(info.framebuffer.region.start), framebuffer_info, rsdp_addr: detect_rsdp(), }; @@ -133,30 +103,38 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info); } -fn init_logger( - framebuffer_start: PhysAddr, - framebuffer_size: usize, - horizontal_resolution: usize, - vertical_resolution: usize, - bytes_per_pixel: usize, - stride: usize, - pixel_format: PixelFormat, -) -> FrameBufferInfo { - let ptr = framebuffer_start.as_u64() as *mut u8; - let slice = unsafe { slice::from_raw_parts_mut(ptr, framebuffer_size) }; - - let info = FrameBufferInfo { - byte_len: framebuffer_size, - width: horizontal_resolution, - height: vertical_resolution, - bytes_per_pixel, - stride, - pixel_format, +fn init_logger(info: BiosFramebufferInfo) -> FrameBufferInfo { + let framebuffer_info = FrameBufferInfo { + byte_len: info.region.len.try_into().unwrap(), + width: info.width.into(), + height: info.height.into(), + pixel_format: match info.pixel_format { + bootloader_x86_64_bios_common::PixelFormat::Rgb => PixelFormat::Rgb, + bootloader_x86_64_bios_common::PixelFormat::Bgr => PixelFormat::Bgr, + bootloader_x86_64_bios_common::PixelFormat::Unknown { + red_position, + green_position, + blue_position, + } => PixelFormat::Unknown { + red_position, + green_position, + blue_position, + }, + }, + bytes_per_pixel: info.bytes_per_pixel.into(), + stride: info.stride.into(), + }; + + let framebuffer = unsafe { + core::slice::from_raw_parts_mut( + info.region.start as *mut u8, + info.region.len.try_into().unwrap(), + ) }; - bootloader_x86_64_common::init_logger(slice, info); + bootloader_x86_64_common::init_logger(framebuffer, framebuffer_info); - info + framebuffer_info } /// Creates page table abstraction types for both the bootloader and kernel page tables. @@ -230,13 +208,15 @@ fn detect_rsdp() -> Option { } #[panic_handler] -fn panic(info: &PanicInfo) -> ! { - // TODO remove - let _ = writeln!(Writer, "{info}"); - - unsafe { LOGGER.get().map(|l| l.force_unlock()) }; +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { + unsafe { + bootloader_x86_64_common::logger::LOGGER + .get() + .map(|l| l.force_unlock()) + }; log::error!("{}", info); loop { - unsafe { asm!("cli; hlt") }; + unsafe { core::arch::asm!("cli; hlt") }; } } diff --git a/bios/stage-4/src/screen.rs b/bios/stage-4/src/screen.rs deleted file mode 100644 index af5afe1e..00000000 --- a/bios/stage-4/src/screen.rs +++ /dev/null @@ -1,139 +0,0 @@ -use bootloader_x86_64_bios_common::{racy_cell::RacyCell, FramebufferInfo, PixelFormat}; -use core::{ - fmt::{self, Write}, - ptr, -}; -use noto_sans_mono_bitmap::{get_bitmap, get_bitmap_width, BitmapChar, BitmapHeight, FontWeight}; - -static WRITER: RacyCell> = RacyCell::new(None); -pub struct Writer; - -impl fmt::Write for Writer { - fn write_str(&mut self, s: &str) -> fmt::Result { - let writer = unsafe { WRITER.get_mut() }.as_mut().unwrap(); - writer.write_str(s) - } -} - -pub fn init(info: FramebufferInfo) { - let framebuffer = unsafe { - core::slice::from_raw_parts_mut( - info.region.start as *mut u8, - info.region.len.try_into().unwrap(), - ) - }; - let writer = ScreenWriter::new(framebuffer, info); - *unsafe { WRITER.get_mut() } = Some(writer); -} - -/// Additional vertical space between lines -const LINE_SPACING: usize = 0; -/// Additional vertical space between separate log messages -const LOG_SPACING: usize = 2; - -struct ScreenWriter { - framebuffer: &'static mut [u8], - info: FramebufferInfo, - x_pos: usize, - y_pos: usize, -} - -impl ScreenWriter { - pub fn new(framebuffer: &'static mut [u8], info: FramebufferInfo) -> Self { - let mut logger = Self { - framebuffer, - info, - x_pos: 0, - y_pos: 0, - }; - logger.clear(); - logger - } - - fn newline(&mut self) { - self.y_pos += 14 + LINE_SPACING; - self.carriage_return() - } - - fn add_vspace(&mut self, space: usize) { - self.y_pos += space; - } - - fn carriage_return(&mut self) { - self.x_pos = 0; - } - - /// Erases all text on the screen. - pub fn clear(&mut self) { - self.x_pos = 0; - self.y_pos = 0; - self.framebuffer.fill(0); - } - - fn width(&self) -> usize { - self.info.width.into() - } - - fn height(&self) -> usize { - self.info.height.into() - } - - fn write_char(&mut self, c: char) { - match c { - '\n' => self.newline(), - '\r' => self.carriage_return(), - c => { - if self.x_pos >= self.width() { - self.newline(); - } - const BITMAP_LETTER_WIDTH: usize = - get_bitmap_width(FontWeight::Regular, BitmapHeight::Size14); - if self.y_pos >= (self.height() - BITMAP_LETTER_WIDTH) { - self.clear(); - } - let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); - self.write_rendered_char(bitmap_char); - } - } - } - - fn write_rendered_char(&mut self, rendered_char: BitmapChar) { - for (y, row) in rendered_char.bitmap().iter().enumerate() { - for (x, byte) in row.iter().enumerate() { - self.write_pixel(self.x_pos + x, self.y_pos + y, *byte); - } - } - self.x_pos += rendered_char.width(); - } - - fn write_pixel(&mut self, x: usize, y: usize, intensity: u8) { - let pixel_offset = y * usize::from(self.info.stride) + x; - let color = match self.info.pixel_format { - PixelFormat::Rgb => [intensity / 2, intensity / 2, intensity, 0], - PixelFormat::Bgr => [intensity, intensity / 2, intensity / 2, 0], - other => { - // set a supported (but invalid) pixel format before panicking to avoid a double - // panic; it might not be readable though - self.info.pixel_format = PixelFormat::Rgb; - panic!("pixel format {:?} not supported in logger", other) - } - }; - let bytes_per_pixel = self.info.bytes_per_pixel; - let byte_offset = pixel_offset * usize::from(bytes_per_pixel); - self.framebuffer[byte_offset..(byte_offset + usize::from(bytes_per_pixel))] - .copy_from_slice(&color[..usize::from(bytes_per_pixel)]); - let _ = unsafe { ptr::read_volatile(&self.framebuffer[byte_offset]) }; - } -} - -unsafe impl Send for ScreenWriter {} -unsafe impl Sync for ScreenWriter {} - -impl fmt::Write for ScreenWriter { - fn write_str(&mut self, s: &str) -> fmt::Result { - for c in s.chars() { - self.write_char(c); - } - Ok(()) - } -} From b007a6b30c0c37aeb89dab3982dee9c3ec750d1d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 20:43:02 +0200 Subject: [PATCH 172/226] Don't use LLVM-reserved registers --- bios/boot_sector/src/fail.rs | 2 +- bios/stage-2/src/memory_map.rs | 14 +++++++++----- bios/stage-2/src/protected_mode.rs | 9 ++++----- bios/stage-2/src/screen.rs | 2 +- bios/stage-2/src/vesa.rs | 25 +++++++++++++++++-------- bios/stage-3/src/main.rs | 5 ++--- 6 files changed, 34 insertions(+), 23 deletions(-) diff --git a/bios/boot_sector/src/fail.rs b/bios/boot_sector/src/fail.rs index 17fc1ee0..193236be 100644 --- a/bios/boot_sector/src/fail.rs +++ b/bios/boot_sector/src/fail.rs @@ -32,7 +32,7 @@ impl UnwrapOrFail for Result { pub extern "C" fn print_char(c: u8) { let ax = u16::from(c) | 0x0e00; unsafe { - asm!("int 0x10", in("ax") ax, in("bx") 0); + asm!("push bx", "mov bx, 0", "int 0x10", "pop bx", in("ax") ax); } } diff --git a/bios/stage-2/src/memory_map.rs b/bios/stage-2/src/memory_map.rs index 9c358ea0..aa8e9d54 100644 --- a/bios/stage-2/src/memory_map.rs +++ b/bios/stage-2/src/memory_map.rs @@ -24,14 +24,18 @@ pub fn query_memory_map() -> Result<&'static mut [E820MemoryRegion], ()> { let mut offset = 0; let buf = [0u8; 24]; loop { - let mut ret = 0; - let mut buf_written_len = 0; + let ret: u32; + let buf_written_len; unsafe { asm!( + "push ebx", + "mov ebx, edx", + "mov edx, 0x534D4150", "int 0x15", - inout ("eax") 0xe820 => ret, - in("edx") SMAP, - inout("ebx") offset, + "mov edx, ebx", + "pop ebx", + inout("eax") 0xe820 => ret, + inout("edx") offset, inout("ecx") buf.len() => buf_written_len, in("di") &buf ) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 3ed09583..756c5b0d 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -112,12 +112,11 @@ pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, info: &m // align the stack "and esp, 0xffffff00", // push arguments - "push {info}", + "push {info:e}", // push entry point address - "push {entry_point}", + "push {entry_point:e}", info = in(reg) info as *const _ as u32, entry_point = in(reg) entry_point as u32, - out("ebx") _ ); asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); asm!( @@ -144,7 +143,7 @@ pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, info: &m fn set_protected_mode_bit() -> u32 { let mut cr0: u32; unsafe { - asm!("mov {}, cr0", out(reg) cr0, options(nomem, nostack, preserves_flags)); + asm!("mov {:e}, cr0", out(reg) cr0, options(nomem, nostack, preserves_flags)); } let cr0_protected = cr0 | 1; write_cr0(cr0_protected); @@ -152,5 +151,5 @@ fn set_protected_mode_bit() -> u32 { } fn write_cr0(val: u32) { - unsafe { asm!("mov cr0, {}", in(reg) val, options(nostack, preserves_flags)) }; + unsafe { asm!("mov cr0, {:e}", in(reg) val, options(nostack, preserves_flags)) }; } diff --git a/bios/stage-2/src/screen.rs b/bios/stage-2/src/screen.rs index 165d6424..7c12c580 100644 --- a/bios/stage-2/src/screen.rs +++ b/bios/stage-2/src/screen.rs @@ -3,7 +3,7 @@ use core::{arch::asm, fmt::Write as _}; pub fn print_char(c: u8) { let ax = u16::from(c) | 0x0e00; unsafe { - asm!("int 0x10", in("ax") ax, in("bx") 0); + asm!("push bx", "mov bx, 0", "int 0x10", "pop bx", in("ax") ax); } } diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index e64e359d..b5535d2c 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -35,9 +35,9 @@ impl<'a> VesaInfo<'a> { .split_at_mut(core::mem::size_of::()); slice.fill(0); let block_ptr = slice.as_mut_ptr(); - let mut ret: u16 = 0; + let ret; unsafe { - asm!("mov es, bx", "int 0x10", inout("ax") 0x4f00u16 => ret, in("bx")0, in("di") block_ptr) + asm!("mov es, {:x}", "int 0x10", in(reg)0, inout("ax") 0x4f00u16 => ret, in("di") block_ptr) }; match ret { 0x4f => { @@ -165,16 +165,16 @@ impl VesaModeInfo { slice.fill(0); let block_ptr = slice.as_mut_ptr(); - let mut ret: u16 = 0; + let mut ret: u16; let mut target_addr = block_ptr as u32; - let segment = (target_addr >> 4); + let segment = target_addr >> 4; target_addr -= segment << 4; unsafe { asm!( - "mov es, bx", "int 0x10", + "mov es, {:x}", "int 0x10", + in(reg) segment as u16, inout("ax") 0x4f01u16 => ret, in("cx") mode, - in("bx") segment as u16, in("di") target_addr as u16 ) }; @@ -210,8 +210,17 @@ impl VesaModeInfo { } pub fn enable(&self) -> Result<(), u16> { - let mut ret: u16 = 0; - unsafe { asm!("int 0x10", inout("ax") 0x4f02u16 => ret, in("bx") self.mode) }; + let mut ret: u16; + unsafe { + asm!( + "push bx", + "mov bx, {:x}", + "int 0x10", + "pop bx", + in(reg) self.mode, + inout("ax") 0x4f02u16 => ret, + ) + }; match ret { 0x4f => Ok(()), other => Err(other), diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index f7f95be3..a000a2cb 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -36,13 +36,12 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { "and esp, 0xffffff00", // push arguments (extended to 64 bit) "push 0", - "push {info}", + "push {info:e}", // push entry point address (extended to 64 bit) "push 0", - "push {entry_point}", + "push {entry_point:e}", info = in(reg) info as *const _ as u32, entry_point = in(reg) info.stage_4.start as u32, - out("ebx") _ ); asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); asm!( From 4f5b574e0f2eccb785cfbee76c3113ff674e38d0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 20:43:51 +0200 Subject: [PATCH 173/226] Rename struct --- bios/common/src/lib.rs | 4 ++-- bios/stage-2/src/main.rs | 4 ++-- bios/stage-3/src/screen.rs | 13 +++++-------- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index 9c36dbce..b83786f3 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -7,7 +7,7 @@ pub mod racy_cell; pub struct BiosInfo { pub stage_4: Region, pub kernel: Region, - pub framebuffer: FramebufferInfo, + pub framebuffer: BiosFramebufferInfo, pub memory_map_addr: u32, pub memory_map_len: u16, } @@ -15,7 +15,7 @@ pub struct BiosInfo { #[cfg_attr(feature = "debug", derive(Debug))] #[derive(Clone, Copy)] #[repr(C)] -pub struct FramebufferInfo { +pub struct BiosFramebufferInfo { pub region: Region, pub width: u16, pub height: u16, diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 1a299174..619ba266 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -7,7 +7,7 @@ use crate::{ copy_to_protected_mode, enter_protected_mode_and_jump_to_stage_3, enter_unreal_mode, }, }; -use bootloader_x86_64_bios_common::{BiosInfo, FramebufferInfo, Region}; +use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, Region}; use byteorder::{ByteOrder, LittleEndian}; use core::{fmt::Write as _, slice}; use disk::AlignedArrayBuffer; @@ -130,7 +130,7 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { }, memory_map_addr: memory_map.as_mut_ptr() as u32, memory_map_len: memory_map.len().try_into().unwrap(), - framebuffer: FramebufferInfo { + framebuffer: BiosFramebufferInfo { region: Region { start: vesa_mode.framebuffer_start.into(), len: u64::from(vesa_mode.height) * u64::from(vesa_mode.bytes_per_scanline), diff --git a/bios/stage-3/src/screen.rs b/bios/stage-3/src/screen.rs index c2ce8b95..d6d7acef 100644 --- a/bios/stage-3/src/screen.rs +++ b/bios/stage-3/src/screen.rs @@ -1,8 +1,5 @@ -use bootloader_x86_64_bios_common::{racy_cell::RacyCell, FramebufferInfo, PixelFormat}; -use core::{ - fmt::{self, Write}, - ptr, -}; +use bootloader_x86_64_bios_common::{racy_cell::RacyCell, BiosFramebufferInfo, PixelFormat}; +use core::{fmt, ptr}; use noto_sans_mono_bitmap::{get_bitmap, get_bitmap_width, BitmapChar, BitmapHeight, FontWeight}; static WRITER: RacyCell> = RacyCell::new(None); @@ -15,7 +12,7 @@ impl fmt::Write for Writer { } } -pub fn init(info: FramebufferInfo) { +pub fn init(info: BiosFramebufferInfo) { let framebuffer = unsafe { core::slice::from_raw_parts_mut( info.region.start as *mut u8, @@ -33,13 +30,13 @@ const LOG_SPACING: usize = 2; struct ScreenWriter { framebuffer: &'static mut [u8], - info: FramebufferInfo, + info: BiosFramebufferInfo, x_pos: usize, y_pos: usize, } impl ScreenWriter { - pub fn new(framebuffer: &'static mut [u8], info: FramebufferInfo) -> Self { + pub fn new(framebuffer: &'static mut [u8], info: BiosFramebufferInfo) -> Self { let mut logger = Self { framebuffer, info, From b937096db4bf65afa34977857ea5b657a4b62be0 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 20:44:10 +0200 Subject: [PATCH 174/226] Fix warnings --- bios/boot_sector/src/fail.rs | 1 + bios/boot_sector/src/mbr.rs | 14 -------------- bios/stage-2/src/fat.rs | 1 - bios/stage-2/src/screen.rs | 1 + bios/stage-2/src/vesa.rs | 3 ++- bios/stage-3/src/main.rs | 1 + bios/stage-3/src/screen.rs | 6 ------ 7 files changed, 5 insertions(+), 22 deletions(-) diff --git a/bios/boot_sector/src/fail.rs b/bios/boot_sector/src/fail.rs index 193236be..07766f40 100644 --- a/bios/boot_sector/src/fail.rs +++ b/bios/boot_sector/src/fail.rs @@ -54,6 +54,7 @@ fn hlt() { } #[panic_handler] +#[cfg(not(test))] pub fn panic(_info: &core::panic::PanicInfo) -> ! { fail(b'P'); } diff --git a/bios/boot_sector/src/mbr.rs b/bios/boot_sector/src/mbr.rs index dfe6daa7..93fc08cb 100644 --- a/bios/boot_sector/src/mbr.rs +++ b/bios/boot_sector/src/mbr.rs @@ -1,19 +1,5 @@ use super::fail::{fail, UnwrapOrFail}; -/// We use this partition type to store the second bootloader stage; -const BOOTLOADER_SECOND_STAGE_PARTITION_TYPE: u8 = 0x20; - -/// Returns the first bootable partition in the partition table. -pub(crate) fn boot_partition(partitions_raw: &[u8]) -> Option { - for index in 0..4 { - let entry = get_partition(partitions_raw, index); - if entry.partition_type == BOOTLOADER_SECOND_STAGE_PARTITION_TYPE { - return Some(entry); - } - } - None -} - pub(crate) fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTableEntry { const PARTITIONS_AREA_SIZE: usize = 16 * 4; const ENTRY_SIZE: usize = 16; diff --git a/bios/stage-2/src/fat.rs b/bios/stage-2/src/fat.rs index 82453b58..58d80e7e 100644 --- a/bios/stage-2/src/fat.rs +++ b/bios/stage-2/src/fat.rs @@ -189,7 +189,6 @@ impl FileSystem { }, None => { panic!("next none"); - return None; } }, }; diff --git a/bios/stage-2/src/screen.rs b/bios/stage-2/src/screen.rs index 7c12c580..905ff537 100644 --- a/bios/stage-2/src/screen.rs +++ b/bios/stage-2/src/screen.rs @@ -36,6 +36,7 @@ impl core::fmt::Write for Writer { } #[panic_handler] +#[cfg(not(test))] pub fn panic(info: &core::panic::PanicInfo) -> ! { let _ = writeln!(Writer, "\nPANIC: {}", info); diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index b5535d2c..86602b53 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -3,9 +3,10 @@ use bootloader_x86_64_bios_common::PixelFormat; use crate::{disk::AlignedBuffer, AlignedArrayBuffer}; -use core::{arch::asm, fmt::Write as _}; +use core::arch::asm; #[repr(packed)] +#[allow(dead_code)] struct VbeInfoBlock { signature: [u8; 4], // should be "VESA" version: u16, // should be 0x0300 for VBE 3.0 diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index a000a2cb..04124d2b 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -67,6 +67,7 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { } #[panic_handler] +#[cfg(not(test))] pub fn panic(info: &core::panic::PanicInfo) -> ! { let _ = writeln!(Writer, "PANIC: {info}"); loop {} diff --git a/bios/stage-3/src/screen.rs b/bios/stage-3/src/screen.rs index d6d7acef..b39ca5ee 100644 --- a/bios/stage-3/src/screen.rs +++ b/bios/stage-3/src/screen.rs @@ -25,8 +25,6 @@ pub fn init(info: BiosFramebufferInfo) { /// Additional vertical space between lines const LINE_SPACING: usize = 0; -/// Additional vertical space between separate log messages -const LOG_SPACING: usize = 2; struct ScreenWriter { framebuffer: &'static mut [u8], @@ -52,10 +50,6 @@ impl ScreenWriter { self.carriage_return() } - fn add_vspace(&mut self, space: usize) { - self.y_pos += space; - } - fn carriage_return(&mut self) { self.x_pos = 0; } From 7be9e68c4b9f0157d72b3e53a8a844ca216406b3 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 20:47:07 +0200 Subject: [PATCH 175/226] Add TODO for VESA resolution config --- bios/stage-2/src/main.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 619ba266..726c33a0 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -108,8 +108,15 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { let memory_map = memory_map::query_memory_map().unwrap(); writeln!(screen::Writer, "{memory_map:x?}").unwrap(); + // TODO: load these from the kernel's config instead of hardcoding + let max_width = 1000; + let max_height = 1000; + let mut vesa_info = vesa::VesaInfo::query(disk_buffer).unwrap(); - let vesa_mode = vesa_info.get_best_mode(1000, 1000).unwrap().unwrap(); + let vesa_mode = vesa_info + .get_best_mode(max_width, max_height) + .unwrap() + .unwrap(); writeln!( screen::Writer, "VESA MODE: {}x{}", From a4a1fb52a80beb8f54cebcda2617c99732b5b602 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 20:50:17 +0200 Subject: [PATCH 176/226] Use new resolver --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index f23ed5a4..455adf87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ license = "MIT/Apache-2.0" description = "An experimental x86_64 bootloader that works on both BIOS and UEFI systems." repository = "https://github.com/rust-osdev/bootloader" edition = "2021" +resolver = "2" [workspace] members = [ From 6c01f45ca293861c9d3dab6f14b982cb2ca7bf7e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 28 Aug 2022 20:50:32 +0200 Subject: [PATCH 177/226] Fix some more warnings --- bios/stage-2/src/dap.rs | 1 + bios/stage-2/src/disk.rs | 7 ------- bios/stage-2/src/fat.rs | 35 +++-------------------------------- bios/stage-2/src/main.rs | 10 +--------- bios/stage-2/src/screen.rs | 14 +++++--------- 5 files changed, 10 insertions(+), 57 deletions(-) diff --git a/bios/stage-2/src/dap.rs b/bios/stage-2/src/dap.rs index 43d08f0e..b2457d8c 100644 --- a/bios/stage-2/src/dap.rs +++ b/bios/stage-2/src/dap.rs @@ -1,6 +1,7 @@ use core::arch::asm; #[derive(Debug, Clone, Copy)] +#[allow(dead_code)] #[repr(packed)] pub struct DiskAddressPacket { /// Size of the DAP structure diff --git a/bios/stage-2/src/disk.rs b/bios/stage-2/src/disk.rs index 4dbb3ba1..a64e5b0b 100644 --- a/bios/stage-2/src/disk.rs +++ b/bios/stage-2/src/disk.rs @@ -65,12 +65,6 @@ impl Seek for DiskAccess { self.current_offset = offset; self.current_offset } - SeekFrom::Current(offset) => { - self.current_offset = (i64::try_from(self.current_offset).unwrap() + offset) - .try_into() - .unwrap(); - self.current_offset - } } } } @@ -83,7 +77,6 @@ pub trait Read { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SeekFrom { Start(u64), - Current(i64), } pub trait Seek { diff --git a/bios/stage-2/src/fat.rs b/bios/stage-2/src/fat.rs index 58d80e7e..2dd7e4ff 100644 --- a/bios/stage-2/src/fat.rs +++ b/bios/stage-2/src/fat.rs @@ -1,7 +1,7 @@ // based on https://crates.io/crates/mini_fat by https://github.com/gridbugs use crate::disk::{AlignedBuffer, Read, Seek, SeekFrom}; -use core::{char::DecodeUtf16Error, fmt::Write as _}; +use core::char::DecodeUtf16Error; const DIRECTORY_ENTRY_BYTES: usize = 32; const UNUSED_ENTRY_PREFIX: u8 = 0xE5; @@ -312,6 +312,7 @@ impl FatType { } } +#[allow(dead_code)] #[derive(Clone)] pub struct DirectoryEntry<'a> { short_name: &'a str, @@ -325,37 +326,6 @@ pub struct DirectoryEntry<'a> { } impl<'a> DirectoryEntry<'a> { - pub fn name(&self) -> impl Iterator> + 'a { - let mut long_name = { - let iter = self - .long_name_1 - .chunks(2) - .chain(self.long_name_2.chunks(2)) - .chain(self.long_name_3.chunks(2)) - .map(|c| u16::from_le_bytes(c.try_into().unwrap())) - .take_while(|&c| c != 0); - char::decode_utf16(iter).peekable() - }; - let short_name = { - let iter = self.short_name.chars(); - let extension_iter = { - let raw = ".".chars().chain(self.short_name_extension.chars()); - raw.take(if self.short_name_extension.is_empty() { - 0 - } else { - self.short_name_extension.len() + 1 - }) - }; - iter.chain(extension_iter).map(Ok) - }; - - if long_name.peek().is_some() { - long_name.chain(short_name.take(0)) - } else { - long_name.chain(short_name.take(usize::MAX)) - } - } - pub fn is_directory(&self) -> bool { self.attributes & directory_attributes::DIRECTORY != 0 } @@ -370,6 +340,7 @@ struct RawDirectoryEntryNormal<'a> { file_size: u32, } +#[allow(dead_code)] #[derive(Debug)] struct RawDirectoryEntryLongName<'a> { order: u8, diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 726c33a0..d0823310 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -2,7 +2,7 @@ #![no_main] use crate::{ - disk::{AlignedBuffer, Read, Seek, SeekFrom}, + disk::{Read, Seek, SeekFrom}, protected_mode::{ copy_to_protected_mode, enter_protected_mode_and_jump_to_stage_3, enter_unreal_mode, }, @@ -28,14 +28,6 @@ const STAGE_3_DST: *mut u8 = 0x0010_0000 as *mut u8; // 1MiB (typically 14MiB ac const STAGE_4_DST: *mut u8 = 0x0020_0000 as *mut u8; // 2MiB (typically still 13MiB accessible here) const KERNEL_DST: *mut u8 = 0x0100_0000 as *mut u8; // 16MiB -extern "C" { - static _second_stage_end: u8; -} - -fn second_stage_end() -> *const u8 { - unsafe { &_second_stage_end } -} - static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { buffer: [0; 0x4000], }; diff --git a/bios/stage-2/src/screen.rs b/bios/stage-2/src/screen.rs index 905ff537..42b4c76c 100644 --- a/bios/stage-2/src/screen.rs +++ b/bios/stage-2/src/screen.rs @@ -1,4 +1,4 @@ -use core::{arch::asm, fmt::Write as _}; +use core::{arch::asm, fmt::Write}; pub fn print_char(c: u8) { let ax = u16::from(c) | 0x0e00; @@ -20,15 +20,9 @@ pub fn print_str(s: &str) { } } -fn hlt() { - unsafe { - asm!("hlt"); - } -} - pub struct Writer; -impl core::fmt::Write for Writer { +impl Write for Writer { fn write_str(&mut self, s: &str) -> core::fmt::Result { print_str(s); Ok(()) @@ -41,6 +35,8 @@ pub fn panic(info: &core::panic::PanicInfo) -> ! { let _ = writeln!(Writer, "\nPANIC: {}", info); loop { - hlt(); + unsafe { + asm!("hlt"); + }; } } From d633408ea980266b53889041cdb4f9c51afb7247 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 11:56:50 +0200 Subject: [PATCH 178/226] Fix: `repr(packed)` does not imply `repr(C)` --- bios/boot_sector/src/dap.rs | 2 +- bios/stage-2/src/dap.rs | 2 +- bios/stage-2/src/vesa.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bios/boot_sector/src/dap.rs b/bios/boot_sector/src/dap.rs index 4db49971..6fc5b752 100644 --- a/bios/boot_sector/src/dap.rs +++ b/bios/boot_sector/src/dap.rs @@ -1,6 +1,6 @@ use core::arch::asm; -#[repr(packed)] +#[repr(C, packed)] #[allow(dead_code)] // the structure format is defined by the hardware pub struct DiskAddressPacket { /// Size of the DAP structure diff --git a/bios/stage-2/src/dap.rs b/bios/stage-2/src/dap.rs index b2457d8c..00bd1268 100644 --- a/bios/stage-2/src/dap.rs +++ b/bios/stage-2/src/dap.rs @@ -2,7 +2,7 @@ use core::arch::asm; #[derive(Debug, Clone, Copy)] #[allow(dead_code)] -#[repr(packed)] +#[repr(C, packed)] pub struct DiskAddressPacket { /// Size of the DAP structure packet_size: u8, diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index 86602b53..73099660 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -5,7 +5,7 @@ use bootloader_x86_64_bios_common::PixelFormat; use crate::{disk::AlignedBuffer, AlignedArrayBuffer}; use core::arch::asm; -#[repr(packed)] +#[repr(C, packed)] #[allow(dead_code)] struct VbeInfoBlock { signature: [u8; 4], // should be "VESA" From d8943dd0d7298d2386345f4d4fc678ef8d8442a9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 11:58:45 +0200 Subject: [PATCH 179/226] Explain why we need to retain the contents of the `si` register --- bios/boot_sector/src/dap.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bios/boot_sector/src/dap.rs b/bios/boot_sector/src/dap.rs index 6fc5b752..da72b777 100644 --- a/bios/boot_sector/src/dap.rs +++ b/bios/boot_sector/src/dap.rs @@ -39,12 +39,12 @@ impl DiskAddressPacket { unsafe { asm!( "push 0x7a", // error code `z`, passed to `fail` on error - "mov {1:x}, si", + "mov {1:x}, si", // backup the `si` register, whose contents are required by LLVM "mov si, {0:x}", "int 0x13", "jc fail", "pop si", // remove error code again - "mov si, {1:x}", + "mov si, {1:x}", // restore the `si` register to its prior state in(reg) self_addr, out(reg) _, in("ax") 0x4200u16, From 718b4711da61aceab96252363289adc48afd66fe Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 11:59:58 +0200 Subject: [PATCH 180/226] Remove commented out code Co-authored-by: Tom Dohrmann --- bios/boot_sector/src/main.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 46df6694..8e8c1525 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -30,9 +30,7 @@ pub extern "C" fn first_stage(disk_number: u16) { // read partition table and look for second stage partition print_char(b'1'); let partition_table = &unsafe { slice::from_raw_parts(partition_table_raw(), 16 * 4) }; - let second_stage_partition = - // mbr::boot_partition(partition_table).unwrap_or_fail(NO_SECOND_STAGE_PARTITION); - mbr::get_partition(partition_table, 0); + let second_stage_partition = mbr::get_partition(partition_table, 0); // load second stage partition into memory print_char(b'2'); From 50e4563d89746e067098fc9034b1bbb4b3570b4a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:01:04 +0200 Subject: [PATCH 181/226] Remove unnecessary reference Co-authored-by: Tom Dohrmann --- bios/boot_sector/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs index 8e8c1525..aaacc224 100644 --- a/bios/boot_sector/src/main.rs +++ b/bios/boot_sector/src/main.rs @@ -29,7 +29,7 @@ fn second_stage_start() -> *const () { pub extern "C" fn first_stage(disk_number: u16) { // read partition table and look for second stage partition print_char(b'1'); - let partition_table = &unsafe { slice::from_raw_parts(partition_table_raw(), 16 * 4) }; + let partition_table = unsafe { slice::from_raw_parts(partition_table_raw(), 16 * 4) }; let second_stage_partition = mbr::get_partition(partition_table, 0); // load second stage partition into memory From fdc630f0ea24632fbbcf906c6304d1464624ca7a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:07:13 +0200 Subject: [PATCH 182/226] Make `query_memory_map` unsafe --- bios/stage-2/src/main.rs | 2 +- bios/stage-2/src/memory_map.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index d0823310..cc044b9a 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -97,7 +97,7 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); - let memory_map = memory_map::query_memory_map().unwrap(); + let memory_map = unsafe { memory_map::query_memory_map() }.unwrap(); writeln!(screen::Writer, "{memory_map:x?}").unwrap(); // TODO: load these from the kernel's config instead of hardcoding diff --git a/bios/stage-2/src/memory_map.rs b/bios/stage-2/src/memory_map.rs index aa8e9d54..a980d9b6 100644 --- a/bios/stage-2/src/memory_map.rs +++ b/bios/stage-2/src/memory_map.rs @@ -14,7 +14,7 @@ static MEMORY_MAP: RacyCell<[E820MemoryRegion; 100]> = RacyCell::new( ); /// use the INT 0x15, eax= 0xE820 BIOS function to get a memory map -pub fn query_memory_map() -> Result<&'static mut [E820MemoryRegion], ()> { +pub unsafe fn query_memory_map() -> Result<&'static mut [E820MemoryRegion], ()> { const SMAP: u32 = 0x534D4150; let memory_map = unsafe { MEMORY_MAP.get_mut() }; From 2eed431e9010221cdcd9c53c8ac868d438a8c896 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:08:37 +0200 Subject: [PATCH 183/226] Remove smiley write after entering unreal mode This was used for debugging to see if we can access protected mode memory. --- bios/stage-2/src/protected_mode.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 756c5b0d..8ceea99e 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -83,8 +83,6 @@ pub fn enter_unreal_mode() { unsafe { asm!("mov ds, {0:x}", in(reg) ds, options(nostack, preserves_flags)); asm!("sti"); - - asm!("mov bx, 0x0f01", "mov eax, 0xb8000", "mov [eax], bx"); } } From 2215d008c50306b066af83fa62b96a4f528e5d2b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:12:20 +0200 Subject: [PATCH 184/226] Remove nop instructions and explain why we need inline asm to access protected mode --- bios/stage-2/src/protected_mode.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 8ceea99e..a41c8910 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -90,7 +90,9 @@ pub fn enter_unreal_mode() { pub unsafe fn copy_to_protected_mode(target: *mut u8, bytes: &[u8]) { for (offset, byte) in bytes.iter().enumerate() { let dst = target.wrapping_add(offset); - unsafe { asm!("nop", "nop", "mov [{}], {}", in(reg) dst, in(reg_byte) *byte) }; + // we need to do the write in inline assembly because the compiler + // seems to truncate the address + unsafe { asm!("mov [{}], {}", in(reg) dst, in(reg_byte) *byte) }; assert_eq!(read_from_protected_mode(dst), *byte); } } @@ -98,7 +100,9 @@ pub unsafe fn copy_to_protected_mode(target: *mut u8, bytes: &[u8]) { #[no_mangle] pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { let res; - unsafe { asm!("nop", "nop", "mov {}, [{}]", out(reg_byte) res, in(reg) ptr) }; + // we need to do the read in inline assembly because the compiler + // seems to truncate the address + unsafe { asm!("mov {}, [{}]", out(reg_byte) res, in(reg) ptr) }; res } From 39b5f164c457b3331e7c356367c97c85405b88e8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:18:16 +0200 Subject: [PATCH 185/226] Let compiler select registers when enabling protected mode --- bios/stage-2/src/protected_mode.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index a41c8910..a2542a0c 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -125,19 +125,20 @@ pub fn enter_protected_mode_and_jump_to_stage_3(entry_point: *const u8, info: &m ".code32", // reload segment registers - "mov bx, 0x10", - "mov ds, bx", - "mov es, bx", - "mov ss, bx", + "mov {0}, 0x10", + "mov ds, {0}", + "mov es, {0}", + "mov ss, {0}", // jump to third stage - "pop eax", - "call eax", + "pop {1}", + "call {1}", // enter endless loop in case third stage returns "2:", "jmp 2b", - out("eax") _ + out(reg) _, + out(reg) _, ); } } From b00bf342c9dbc241482853dc16abba41c3e49aca Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:20:16 +0200 Subject: [PATCH 186/226] Let compiler select register when loading GDT --- bios/stage-2/src/protected_mode.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index a2542a0c..6c7497de 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -74,7 +74,7 @@ pub fn enter_unreal_mode() { // load GDT unsafe { - asm!("mov bx, 0x10", "mov ds, bx"); + asm!("mov {0}, 0x10", "mov ds, {0}", out(reg) _); } // unset protected mode bit again From bee20ac88adec97d7b8c8cc68366fdf3db0bfb9b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:27:41 +0200 Subject: [PATCH 187/226] Apply suggestions from code review Co-authored-by: Tom Dohrmann --- bios/stage-2/src/screen.rs | 2 +- bios/stage-3/src/main.rs | 3 ++- bios/stage-3/src/paging.rs | 9 ++++----- bios/stage-4/src/main.rs | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/bios/stage-2/src/screen.rs b/bios/stage-2/src/screen.rs index 42b4c76c..45cf0f52 100644 --- a/bios/stage-2/src/screen.rs +++ b/bios/stage-2/src/screen.rs @@ -32,7 +32,7 @@ impl Write for Writer { #[panic_handler] #[cfg(not(test))] pub fn panic(info: &core::panic::PanicInfo) -> ! { - let _ = writeln!(Writer, "\nPANIC: {}", info); + let _ = writeln!(Writer, "\nPANIC: {info}"); loop { unsafe { diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 04124d2b..b0a24755 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -61,7 +61,8 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { // enter endless loop in case 4th stage returns "2:", "jmp 2b", - out("eax") _ + out("eax") _, + options(noreturn), ); } } diff --git a/bios/stage-3/src/paging.rs b/bios/stage-3/src/paging.rs index d6a1f976..91a4d59c 100644 --- a/bios/stage-3/src/paging.rs +++ b/bios/stage-3/src/paging.rs @@ -15,14 +15,13 @@ fn create_mappings() { let l4 = unsafe { LEVEL_4.get_mut() }; let l3 = unsafe { LEVEL_3.get_mut() }; let l2s = unsafe { LEVEL_2.get_mut() }; - let common_flags = 0b11; + let common_flags = 0b11; // PRESENT | WRITEABLE l4.entries[0] = (l3 as *mut PageTable as u64) | common_flags; - for i in 0..l2s.len() { - let l2 = &mut l2s[i]; + for (i, l2) in l2s.iter_mut().enumerate() { l3.entries[i] = (l2 as *mut PageTable as u64) | common_flags; let offset = u64::try_from(i).unwrap() * 1024 * 1024 * 1024; - for j in 0..512 { - l2.entries[j] = + for (j, entry) in l2.entries.iter_mut().enumerate() { + *entry = (offset + u64::try_from(j).unwrap() * (2 * 1024 * 1024)) | common_flags | (1 << 7); } } diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 555f9d5a..ac70704e 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -153,7 +153,7 @@ fn create_page_tables(frame_allocator: &mut impl FrameAllocator) -> Pa let (kernel_page_table, kernel_level_4_frame) = { // get an unused frame for new level 4 page table let frame: PhysFrame = frame_allocator.allocate_frame().expect("no unused frames"); - log::info!("New page table at: {:#?}", &frame); + log::info!("New page table at: {frame:#?}"); // get the corresponding virtual address let addr = phys_offset + frame.start_address().as_u64(); // initialize a new page table @@ -215,7 +215,7 @@ fn panic(info: &core::panic::PanicInfo) -> ! { .get() .map(|l| l.force_unlock()) }; - log::error!("{}", info); + log::error!("{info}"); loop { unsafe { core::arch::asm!("cli; hlt") }; } From c1d01d62b8defe7419cddde514025ffa1dd4495d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:34:26 +0200 Subject: [PATCH 188/226] Remove `noreturn` option again because it disallows clobbers --- bios/stage-3/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index b0a24755..61ce8b74 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -62,7 +62,6 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { "2:", "jmp 2b", out("eax") _, - options(noreturn), ); } } From 48ced390ff914f66c9997110981fececb804ab31 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:35:20 +0200 Subject: [PATCH 189/226] The second stage-4 argument is no longer needed --- bios/stage-3/src/main.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 61ce8b74..ac00cf52 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -37,11 +37,7 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { // push arguments (extended to 64 bit) "push 0", "push {info:e}", - // push entry point address (extended to 64 bit) - "push 0", - "push {entry_point:e}", info = in(reg) info as *const _ as u32, - entry_point = in(reg) info.stage_4.start as u32, ); asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); asm!( @@ -54,14 +50,13 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { "mov ss, bx", // jump to 4th stage - "pop rax", "pop rdi", "call rax", // enter endless loop in case 4th stage returns "2:", "jmp 2b", - out("eax") _, + out("rdi") _, ); } } From 25502672fe997fee7dc233e964074b4543e4e57b Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:35:39 +0200 Subject: [PATCH 190/226] Let the compiler select the register for reloading `ds` etc --- bios/stage-3/src/main.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index ac00cf52..9d5602ea 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -44,10 +44,10 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { ".code64", // reload segment registers - "mov bx, 0x10", - "mov ds, bx", - "mov es, bx", - "mov ss, bx", + "mov {0}, 0x10", + "mov ds, {0}", + "mov es, {0}", + "mov ss, {0}", // jump to 4th stage "pop rdi", @@ -56,6 +56,7 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { // enter endless loop in case 4th stage returns "2:", "jmp 2b", + out(reg) _, out("rdi") _, ); } From 26a6aee6ef5311b7dd4882eb2ae007f1c2a7a62c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:36:14 +0200 Subject: [PATCH 191/226] Use `ptr::write` for writing page table --- bios/stage-4/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index ac70704e..53df4fae 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -157,8 +157,8 @@ fn create_page_tables(frame_allocator: &mut impl FrameAllocator) -> Pa // get the corresponding virtual address let addr = phys_offset + frame.start_address().as_u64(); // initialize a new page table - let ptr = addr.as_mut_ptr(); - unsafe { *ptr = PageTable::new() }; + let ptr: *mut PageTable = addr.as_mut_ptr(); + unsafe { ptr.write(PageTable::new()) }; let level_4_table = unsafe { &mut *ptr }; ( unsafe { OffsetPageTable::new(level_4_table, phys_offset) }, From e62a933eeb71350855a422c442ad8fdb5486b24f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:44:16 +0200 Subject: [PATCH 192/226] Make `read_exact` function unsafe --- bios/stage-2/src/disk.rs | 4 ++-- bios/stage-2/src/fat.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bios/stage-2/src/disk.rs b/bios/stage-2/src/disk.rs index a64e5b0b..263d16a7 100644 --- a/bios/stage-2/src/disk.rs +++ b/bios/stage-2/src/disk.rs @@ -8,7 +8,7 @@ pub struct DiskAccess { } impl Read for DiskAccess { - fn read_exact(&mut self, len: usize) -> &[u8] { + unsafe fn read_exact(&mut self, len: usize) -> &[u8] { let current_sector_offset = usize::try_from(self.current_offset % 512).unwrap(); static mut TMP_BUF: AlignedArrayBuffer<1024> = AlignedArrayBuffer { @@ -70,7 +70,7 @@ impl Seek for DiskAccess { } pub trait Read { - fn read_exact(&mut self, len: usize) -> &[u8]; + unsafe fn read_exact(&mut self, len: usize) -> &[u8]; fn read_exact_into(&mut self, len: usize, buf: &mut dyn AlignedBuffer); } diff --git a/bios/stage-2/src/fat.rs b/bios/stage-2/src/fat.rs index 2dd7e4ff..cfe9e24e 100644 --- a/bios/stage-2/src/fat.rs +++ b/bios/stage-2/src/fat.rs @@ -34,7 +34,7 @@ struct Bpb { impl Bpb { fn parse(disk: &mut D) -> Self { disk.seek(SeekFrom::Start(0)); - let raw = disk.read_exact(512); + let raw = unsafe { disk.read_exact(512) }; let bytes_per_sector = u16::from_le_bytes(raw[11..13].try_into().unwrap()); let sectors_per_cluster = raw[13]; @@ -483,21 +483,21 @@ where FatType::Fat32 => { let base = n as u64 * 4; disk.seek(SeekFrom::Start(fat_start + base)); - let buf = disk.read_exact(4); + let buf = unsafe { disk.read_exact(4) }; let buf: [u8; 4] = buf.try_into().unwrap(); u32::from_le_bytes(buf) & 0x0FFFFFFF } FatType::Fat16 => { let base = n as u64 * 2; disk.seek(SeekFrom::Start(fat_start + base)); - let buf = disk.read_exact(2); + let buf = unsafe { disk.read_exact(2) }; let buf: [u8; 2] = buf.try_into().unwrap(); u16::from_le_bytes(buf) as u32 } FatType::Fat12 => { let base = n as u64 + (n as u64 / 2); disk.seek(SeekFrom::Start(fat_start + base)); - let buf = disk.read_exact(2); + let buf = unsafe { disk.read_exact(2) }; let buf: [u8; 2] = buf.try_into().unwrap(); let entry16 = u16::from_le_bytes(buf); if n & 1 == 0 { From b0e805eaff81fafed45f8542bb7fe2f9634c022e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 12:52:26 +0200 Subject: [PATCH 193/226] Revert "The second stage-4 argument is no longer needed" It is of course needed for the call instruction. This reverts commit 48ced390ff914f66c9997110981fececb804ab31. --- bios/stage-3/src/main.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bios/stage-3/src/main.rs b/bios/stage-3/src/main.rs index 9d5602ea..c3d692d2 100644 --- a/bios/stage-3/src/main.rs +++ b/bios/stage-3/src/main.rs @@ -37,7 +37,11 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { // push arguments (extended to 64 bit) "push 0", "push {info:e}", + // push entry point address (extended to 64 bit) + "push 0", + "push {entry_point:e}", info = in(reg) info as *const _ as u32, + entry_point = in(reg) info.stage_4.start as u32, ); asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); asm!( @@ -50,6 +54,7 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { "mov ss, {0}", // jump to 4th stage + "pop rax", "pop rdi", "call rax", @@ -57,6 +62,7 @@ pub fn enter_long_mode_and_jump_to_stage_4(info: &mut BiosInfo) { "2:", "jmp 2b", out(reg) _, + out("rax") _, out("rdi") _, ); } From 915c25b1e4aff336e10a3049a92630a13aa1137a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 13:07:21 +0200 Subject: [PATCH 194/226] Apply more code review suggestions Co-authored-by: Tom Dohrmann --- bios/stage-2/src/protected_mode.rs | 8 ++++++-- bios/stage-3/src/paging.rs | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 6c7497de..405c5d1f 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -92,7 +92,9 @@ pub unsafe fn copy_to_protected_mode(target: *mut u8, bytes: &[u8]) { let dst = target.wrapping_add(offset); // we need to do the write in inline assembly because the compiler // seems to truncate the address - unsafe { asm!("mov [{}], {}", in(reg) dst, in(reg_byte) *byte) }; + unsafe { + asm!("mov [{}], {}", in(reg) dst, in(reg_byte) *byte, options(nostack, preserves_flags)) + }; assert_eq!(read_from_protected_mode(dst), *byte); } } @@ -102,7 +104,9 @@ pub unsafe fn read_from_protected_mode(ptr: *mut u8) -> u8 { let res; // we need to do the read in inline assembly because the compiler // seems to truncate the address - unsafe { asm!("mov {}, [{}]", out(reg_byte) res, in(reg) ptr) }; + unsafe { + asm!("mov {}, [{}]", out(reg_byte) res, in(reg) ptr, options(pure, readonly, nostack, preserves_flags)) + }; res } diff --git a/bios/stage-3/src/paging.rs b/bios/stage-3/src/paging.rs index 91a4d59c..20b5fd47 100644 --- a/bios/stage-3/src/paging.rs +++ b/bios/stage-3/src/paging.rs @@ -21,6 +21,7 @@ fn create_mappings() { l3.entries[i] = (l2 as *mut PageTable as u64) | common_flags; let offset = u64::try_from(i).unwrap() * 1024 * 1024 * 1024; for (j, entry) in l2.entries.iter_mut().enumerate() { + // map huge pages *entry = (offset + u64::try_from(j).unwrap() * (2 * 1024 * 1024)) | common_flags | (1 << 7); } From 960671c3669c7d1be95f1dfb40afdf9bea8172f8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 10 Sep 2022 13:55:27 +0200 Subject: [PATCH 195/226] Don't panic if kernel file name is shorter than 11 chars --- src/fat.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/fat.rs b/src/fat.rs index c0d9bcc7..c592b7ad 100644 --- a/src/fat.rs +++ b/src/fat.rs @@ -36,7 +36,7 @@ pub fn create_fat_filesystem( let converted = name.to_string_lossy(); let name = converted.as_bytes(); let mut new_label = [0u8; 11]; - let name = &name[..new_label.len()]; + let name = &name[..usize::min(new_label.len(), name.len())]; let slice = &mut new_label[..name.len()]; slice.copy_from_slice(name); label = new_label; From 28bd6cabbbbaf18953751296893b9c1aab5c4511 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 12:39:38 +0200 Subject: [PATCH 196/226] Remove accidentally commited `main_bak.rs` --- src/main_bak.rs | 378 ------------------------------------------------ 1 file changed, 378 deletions(-) delete mode 100644 src/main_bak.rs diff --git a/src/main_bak.rs b/src/main_bak.rs deleted file mode 100644 index 64edc13b..00000000 --- a/src/main_bak.rs +++ /dev/null @@ -1,378 +0,0 @@ -use anyhow::{anyhow, bail, Context}; -use argh::FromArgs; -use bootloader::disk_image::create_disk_image; -use std::{ - convert::TryFrom, - fs::{self, File}, - io::{self, Seek}, - path::{Path, PathBuf}, - process::Command, - str::FromStr, -}; - -type ExitCode = i32; - -#[derive(FromArgs)] -/// Build the bootloader -struct BuildArguments { - /// path to the kernel ELF binary - #[argh(option)] - kernel_binary: PathBuf, - - /// which firmware interface to build - #[argh(option, default = "Firmware::All")] - firmware: Firmware, - - /// whether to run the resulting binary in QEMU - #[argh(switch)] - run: bool, - - /// suppress stdout output - #[argh(switch)] - quiet: bool, - - /// build the bootloader with the given cargo features - #[argh(option)] - features: Vec, - - /// use the given path as target directory - #[argh(option)] - target_dir: Option, - - /// place the output binaries at the given path - #[argh(option)] - out_dir: Option, -} - -#[derive(Debug, Eq, PartialEq, Copy, Clone)] -enum Firmware { - Bios, - Uefi, - All, -} - -impl FromStr for Firmware { - type Err = FirmwareParseError; - - fn from_str(s: &str) -> Result { - match s.to_ascii_lowercase().as_str() { - "bios" => Ok(Firmware::Bios), - "uefi" => Ok(Firmware::Uefi), - "all" => Ok(Firmware::All), - _other => Err(FirmwareParseError), - } - } -} - -impl Firmware { - fn uefi(&self) -> bool { - match self { - Firmware::Bios => false, - Firmware::Uefi | Firmware::All => true, - } - } - - fn bios(&self) -> bool { - match self { - Firmware::Bios | Firmware::All => true, - Firmware::Uefi => false, - } - } -} - -/// Firmware must be one of `uefi`, `bios`, or `all`. -#[derive(Debug, displaydoc::Display, Eq, PartialEq, Copy, Clone)] -struct FirmwareParseError; - -fn main() -> anyhow::Result<()> { - let args: BuildArguments = argh::from_env(); - - if args.firmware.uefi() { - let build_or_run = if args.run { "run" } else { "build" }; - let mut cmd = Command::new(env!("CARGO")); - cmd.arg(build_or_run).arg("--bin").arg("uefi"); - cmd.arg("--release"); - cmd.arg("--target").arg("x86_64-unknown-uefi"); - cmd.arg("--features") - .arg(args.features.join(" ") + " uefi_bin"); - cmd.arg("-Zbuild-std=core"); - cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); - if let Some(target_dir) = &args.target_dir { - cmd.arg("--target-dir").arg(target_dir); - } - if args.quiet { - cmd.arg("--quiet"); - } - assert!(cmd.status()?.success()); - - // Retrieve binary paths - cmd.arg("--message-format").arg("json"); - let output = cmd - .output() - .context("failed to execute kernel build with json output")?; - if !output.status.success() { - return Err(anyhow!("{}", String::from_utf8_lossy(&output.stderr))); - } - let mut executables = Vec::new(); - for line in String::from_utf8(output.stdout) - .context("build JSON output is not valid UTF-8")? - .lines() - { - let mut artifact = json::parse(line).context("build JSON output is not valid JSON")?; - if let Some(executable) = artifact["executable"].take_string() { - executables.push(PathBuf::from(executable)); - } - } - - assert_eq!(executables.len(), 1); - let executable_path = executables.pop().unwrap(); - - let executable_name = executable_path - .file_stem() - .and_then(|stem| stem.to_str()) - .ok_or_else(|| { - anyhow!( - "executable path `{}` has invalid file stem", - executable_path.display() - ) - })?; - let kernel_name = args - .kernel_binary - .file_name() - .and_then(|name| name.to_str()) - .ok_or_else(|| { - anyhow!( - "kernel binary path `{}` has invalid file name", - args.kernel_binary.display() - ) - })?; - - if let Some(out_dir) = &args.out_dir { - let efi_file = out_dir.join(format!("boot-{}-{}.efi", executable_name, kernel_name)); - create_uefi_disk_image(&executable_path, &efi_file, &args.kernel_binary) - .context("failed to create UEFI disk image")?; - } - } - - if args.firmware.bios() { - let mut cmd = Command::new(env!("CARGO")); - cmd.arg("build").arg("-p").arg("bootloader_first_stage"); - cmd.arg("--profile").arg("first-stage"); - cmd.arg("--target").arg("bios/first_stage/x86-16bit.json"); - cmd.arg("-Zbuild-std=core"); - cmd.arg("-Zbuild-std-features=compiler-builtins-mem"); - if let Some(target_dir) = &args.target_dir { - cmd.arg("--target-dir").arg(target_dir); - } - if args.quiet { - cmd.arg("--quiet"); - } - assert!(cmd.status()?.success()); - - // Retrieve binary paths - cmd.arg("--message-format").arg("json"); - let output = cmd - .output() - .context("failed to execute kernel build with json output")?; - if !output.status.success() { - return Err(anyhow!("{}", String::from_utf8_lossy(&output.stderr))); - } - let mut executables = Vec::new(); - for line in String::from_utf8(output.stdout) - .context("build JSON output is not valid UTF-8")? - .lines() - { - let mut artifact = json::parse(line).context("build JSON output is not valid JSON")?; - if let Some(executable) = artifact["executable"].take_string() { - executables.push(PathBuf::from(executable)); - } - } - - assert_eq!(executables.len(), 1); - let executable_path = executables.pop().unwrap(); - let executable_name = executable_path.file_name().unwrap().to_str().unwrap(); - let kernel_name = args.kernel_binary.file_name().unwrap().to_str().unwrap(); - let mut output_bin_path = executable_path - .parent() - .unwrap() - .join(format!("boot-{}-{}.img", executable_name, kernel_name)); - - create_disk_image(&executable_path, &output_bin_path, &args.kernel_binary) - .context("Failed to create bootable disk image")?; - - if let Some(out_dir) = &args.out_dir { - let file = out_dir.join(output_bin_path.file_name().unwrap()); - fs::copy(output_bin_path, &file)?; - output_bin_path = file; - } - - if !args.quiet { - println!( - "Created bootable disk image at {}", - output_bin_path.display() - ); - } - - if args.run { - bios_run(&output_bin_path)?; - } - } - - Ok(()) -} - -fn create_uefi_disk_image( - executable_path: &Path, - efi_file: &Path, - kernel_binary: &Path, -) -> anyhow::Result<()> { - fs::copy(&executable_path, &efi_file).context("failed to copy efi file to out dir")?; - - let efi_size = fs::metadata(&efi_file) - .context("failed to read metadata of efi file")? - .len(); - - let kernel_size = fs::metadata(&kernel_binary) - .context("failed to read metadata of kernel binary")? - .len(); - - // create fat partition - let fat_file_path = { - const MB: u64 = 1024 * 1024; - - let fat_path = efi_file.with_extension("fat"); - let fat_file = fs::OpenOptions::new() - .read(true) - .write(true) - .create(true) - .truncate(true) - .open(&fat_path) - .context("Failed to create UEFI FAT file")?; - let fat_size = efi_size + kernel_size; - let fat_size_padded_and_rounded = ((fat_size + 1024 * 64 - 1) / MB + 1) * MB; - fat_file - .set_len(fat_size_padded_and_rounded) - .context("failed to set UEFI FAT file length")?; - - // create new FAT partition - let format_options = fatfs::FormatVolumeOptions::new().volume_label(*b"FOOO "); - fatfs::format_volume(&fat_file, format_options) - .context("Failed to format UEFI FAT file")?; - - // copy EFI file to FAT filesystem - let partition = fatfs::FileSystem::new(&fat_file, fatfs::FsOptions::new()) - .context("Failed to open FAT file system of UEFI FAT file")?; - let root_dir = partition.root_dir(); - root_dir.create_dir("efi")?; - root_dir.create_dir("efi/boot")?; - let mut bootx64 = root_dir.create_file("efi/boot/bootx64.efi")?; - bootx64.truncate()?; - io::copy(&mut fs::File::open(&executable_path)?, &mut bootx64)?; - - // copy kernel to FAT filesystem - let mut kernel_file = root_dir.create_file("kernel-x86_64")?; - kernel_file.truncate()?; - io::copy(&mut fs::File::open(&kernel_binary)?, &mut kernel_file)?; - - fat_path - }; - - // create gpt disk - { - let image_path = efi_file.with_extension("img"); - let mut image = fs::OpenOptions::new() - .create(true) - .truncate(true) - .read(true) - .write(true) - .open(&image_path) - .context("failed to create UEFI disk image")?; - - let partition_size: u64 = fs::metadata(&fat_file_path) - .context("failed to read metadata of UEFI FAT partition")? - .len(); - let image_size = partition_size + 1024 * 64; - image - .set_len(image_size) - .context("failed to set length of UEFI disk image")?; - - // Create a protective MBR at LBA0 - let mbr = gpt::mbr::ProtectiveMBR::with_lb_size( - u32::try_from((image_size / 512) - 1).unwrap_or(0xFF_FF_FF_FF), - ); - mbr.overwrite_lba0(&mut image) - .context("failed to write protective MBR")?; - - // create new GPT in image file - let block_size = gpt::disk::LogicalBlockSize::Lb512; - let block_size_bytes: u64 = block_size.into(); - let mut disk = gpt::GptConfig::new() - .writable(true) - .initialized(false) - .logical_block_size(block_size) - .create_from_device(Box::new(&mut image), None) - .context("failed to open UEFI disk image")?; - disk.update_partitions(Default::default()) - .context("failed to initialize GPT partition table")?; - - // add add EFI system partition - let partition_id = disk - .add_partition("boot", partition_size, gpt::partition_types::EFI, 0, None) - .context("failed to add boot partition")?; - - let partition = disk - .partitions() - .get(&partition_id) - .ok_or_else(|| anyhow!("Partition doesn't exist after adding it"))?; - let created_partition_size: u64 = - (partition.last_lba - partition.first_lba + 1u64) * block_size_bytes; - if created_partition_size != partition_size { - bail!( - "Created partition has invalid size (size is {:?}, expected {})", - created_partition_size, - partition_size - ); - } - let start_offset = partition - .bytes_start(block_size) - .context("failed to retrieve partition start offset")?; - - // Write the partition table - disk.write() - .context("failed to write GPT partition table to UEFI image file")?; - - image - .seek(io::SeekFrom::Start(start_offset)) - .context("failed to seek to boot partiiton start")?; - let bytes_written = io::copy( - &mut File::open(&fat_file_path).context("failed to open fat image")?, - &mut image, - ) - .context("failed to write boot partition content")?; - if bytes_written != partition_size { - bail!( - "Invalid number of partition bytes written (expected {}, got {})", - partition_size, - bytes_written - ); - } - } - - Ok(()) -} - -fn bios_run(bin_path: &Path) -> anyhow::Result> { - let mut qemu = Command::new("qemu-system-x86_64"); - qemu.arg("-drive") - .arg(format!("format=raw,file={}", bin_path.display())); - qemu.arg("-s"); - qemu.arg("--no-reboot"); - println!("{:?}", qemu); - let exit_status = qemu.status()?; - let ret = if exit_status.success() { - None - } else { - exit_status.code() - }; - Ok(ret) -} From 47a4c85d96e285f8b208142e455a04eb0a61bcf6 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 12:54:52 +0200 Subject: [PATCH 197/226] Fix last line check for logger --- common/src/logger.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/common/src/logger.rs b/common/src/logger.rs index 6f26f655..73b397d8 100644 --- a/common/src/logger.rs +++ b/common/src/logger.rs @@ -103,12 +103,10 @@ impl Logger { if self.x_pos >= self.width() { self.newline(); } - const BITMAP_LETTER_WIDTH: usize = - get_bitmap_width(FontWeight::Regular, BitmapHeight::Size14); - if self.y_pos >= (self.height() - BITMAP_LETTER_WIDTH) { + let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); + if self.y_pos >= (self.height() - bitmap_char.height()) { self.clear(); } - let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); self.write_rendered_char(bitmap_char); } } From 130d5f0ac8b3cd340ea25ce3306068db48112f70 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 13:28:44 +0200 Subject: [PATCH 198/226] Fix: also consider bitmap width when decided whether a newline is needed --- common/src/logger.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/logger.rs b/common/src/logger.rs index 73b397d8..bb73df3e 100644 --- a/common/src/logger.rs +++ b/common/src/logger.rs @@ -100,11 +100,11 @@ impl Logger { '\n' => self.newline(), '\r' => self.carriage_return(), c => { - if self.x_pos >= self.width() { + let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); + if self.x_pos + bitmap_char.width() > self.width() { self.newline(); } - let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); - if self.y_pos >= (self.height() - bitmap_char.height()) { + if self.y_pos + bitmap_char.height() > self.height() { self.clear(); } self.write_rendered_char(bitmap_char); From 08e4b5829bf5882d9d396e641e32b65de72704b2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 14:55:42 +0200 Subject: [PATCH 199/226] Allocate kernel as normal UEFI loader data Custom memory kinds are apparently not supported on some architectures. --- api/src/info.rs | 2 -- common/src/legacy_memory_region.rs | 53 +++++++++++++++++++++++++++++- common/src/lib.rs | 23 +++++++++++-- uefi/src/main.rs | 5 ++- uefi/src/memory_descriptor.rs | 2 -- 5 files changed, 75 insertions(+), 10 deletions(-) diff --git a/api/src/info.rs b/api/src/info.rs index e9359d50..46d904da 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -145,8 +145,6 @@ pub enum MemoryRegionKind { /// /// This memory should _not_ be used by the kernel. Bootloader, - /// Memory mapping of the kernel. - Kernel, /// An unknown memory region reported by the UEFI firmware. /// /// Contains the UEFI memory type tag. diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index dd6f223a..c7651242 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -101,6 +101,8 @@ where pub fn construct_memory_map( self, regions: &mut [MaybeUninit], + kernel_slice_start: u64, + kernel_slice_len: u64, ) -> &mut [MemoryRegion] { let mut next_index = 0; @@ -145,7 +147,52 @@ where end: end.as_u64(), kind, }; - Self::add_region(region, regions, &mut next_index).unwrap(); + + // check if region overlaps with kernel + let kernel_slice_end = kernel_slice_start + kernel_slice_len; + if region.kind == MemoryRegionKind::Usable + && kernel_slice_start < region.end + && kernel_slice_end >= region.start + { + // region overlaps with kernel -> we might need to split it + + // ensure that the kernel allocation does not span multiple regions + assert!( + kernel_slice_start >= region.start, + "region overlaps with kernel, but kernel begins before region \ + (kernel_slice_start: {kernel_slice_start:#x}, region_start: {:#x})", + region.start + ); + assert!( + kernel_slice_end <= region.end, + "region overlaps with kernel, but region ends before kernel \ + (kernel_slice_end: {kernel_slice_end:#x}, region_end: {:#x})", + region.end, + ); + + // split the region into three parts + let before_kernel = MemoryRegion { + end: kernel_slice_start, + ..region + }; + let kernel = MemoryRegion { + start: kernel_slice_start, + end: kernel_slice_end, + kind: MemoryRegionKind::Bootloader, + }; + let after_kernel = MemoryRegion { + start: kernel_slice_end, + ..region + }; + + // add the three regions (empty regions are ignored in `add_region`) + Self::add_region(before_kernel, regions, &mut next_index).unwrap(); + Self::add_region(kernel, regions, &mut next_index).unwrap(); + Self::add_region(after_kernel, regions, &mut next_index).unwrap(); + } else { + // add the region normally + Self::add_region(region, regions, &mut next_index).unwrap(); + } } let initialized = &mut regions[..next_index]; @@ -161,6 +208,10 @@ where regions: &mut [MaybeUninit], next_index: &mut usize, ) -> Result<(), ()> { + if region.start == region.end { + // skip zero sized regions + return Ok(()); + } unsafe { regions .get_mut(*next_index) diff --git a/common/src/lib.rs b/common/src/lib.rs index ab151352..8f5aa1d9 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -56,6 +56,8 @@ pub struct SystemInfo { pub struct Kernel<'a> { pub elf: ElfFile<'a>, pub config: BootloaderConfig, + pub start_address: *const u8, + pub len: usize, } impl<'a> Kernel<'a> { @@ -72,6 +74,8 @@ impl<'a> Kernel<'a> { Kernel { elf: kernel_elf, config, + start_address: kernel_slice.as_ptr(), + len: kernel_slice.len(), } } } @@ -151,6 +155,9 @@ where enable_write_protect_bit(); let config = kernel.config; + let kernel_slice_start = kernel.start_address as u64; + let kernel_slice_len = u64::try_from(kernel.len).unwrap(); + let (entry_point, tls_template) = load_kernel::load_kernel( kernel, kernel_page_table, @@ -313,6 +320,9 @@ where physical_memory_offset, recursive_index, tls_template, + + kernel_slice_start, + kernel_slice_len, } } @@ -333,6 +343,11 @@ pub struct Mappings { pub recursive_index: Option, /// The thread local storage template of the kernel executable, if it contains one. pub tls_template: Option, + + /// Start address of the kernel slice allocation in memory. + pub kernel_slice_start: u64, + /// Size of the kernel slice allocation in memory. + pub kernel_slice_len: u64, } /// Allocates and initializes the boot info struct and the memory map. @@ -357,7 +372,7 @@ where // allocate and map space for the boot info let (boot_info, memory_regions) = { let boot_info_layout = Layout::new::(); - let regions = frame_allocator.len() + 1; // one region might be split into used/unused + let regions = frame_allocator.len() + 4; // up to 4 regions might be split into used/unused let memory_regions_layout = Layout::array::(regions).unwrap(); let (combined, memory_regions_offset) = boot_info_layout.extend(memory_regions_layout).unwrap(); @@ -412,7 +427,11 @@ where log::info!("Create Memory Map"); // build memory map - let memory_regions = frame_allocator.construct_memory_map(memory_regions); + let memory_regions = frame_allocator.construct_memory_map( + memory_regions, + mappings.kernel_slice_start, + mappings.kernel_slice_len, + ); log::info!("Create bootinfo"); diff --git a/uefi/src/main.rs b/uefi/src/main.rs index e1dac00f..de821c01 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -7,7 +7,6 @@ use crate::memory_descriptor::UefiMemoryDescriptor; use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; use bootloader_x86_64_common::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; use core::{arch::asm, cell::UnsafeCell, fmt::Write, mem, panic::PanicInfo, ptr, slice}; -use memory_descriptor::KERNEL_MEMORY_TYPE; use uefi::{ prelude::{entry, Boot, Handle, Status, SystemTable}, proto::{ @@ -203,7 +202,7 @@ fn load_kernel_file_from_disk(image: Handle, st: &SystemTable) -> Option<& .boot_services() .allocate_pages( AllocateType::AnyPages, - KERNEL_MEMORY_TYPE, + MemoryType::LOADER_DATA, ((kernel_size - 1) / 4096) + 1, ) .unwrap() as *mut u8; @@ -283,7 +282,7 @@ fn load_kernel_file_from_tftp_boot_server( .boot_services() .allocate_pages( AllocateType::AnyPages, - KERNEL_MEMORY_TYPE, + MemoryType::LOADER_DATA, ((kernel_size - 1) / 4096) + 1, ) .expect("Failed to allocate memory for the kernel file") as *mut u8; diff --git a/uefi/src/memory_descriptor.rs b/uefi/src/memory_descriptor.rs index 5a7b850a..dee9f272 100644 --- a/uefi/src/memory_descriptor.rs +++ b/uefi/src/memory_descriptor.rs @@ -7,7 +7,6 @@ use x86_64::PhysAddr; pub struct UefiMemoryDescriptor(pub MemoryDescriptor); const PAGE_SIZE: u64 = 4096; -pub const KERNEL_MEMORY_TYPE: MemoryType = MemoryType::custom(0x80000000); impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { fn start(&self) -> PhysAddr { @@ -21,7 +20,6 @@ impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { fn kind(&self) -> MemoryRegionKind { match self.0.ty { MemoryType::CONVENTIONAL => MemoryRegionKind::Usable, - other if other == KERNEL_MEMORY_TYPE => MemoryRegionKind::Kernel, other => MemoryRegionKind::UnknownUefi(other.0), } } From c274be72b3e9dfb0220adcee3390ff3c40162171 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 14:56:55 +0200 Subject: [PATCH 200/226] Make `add_region` panic on error --- common/src/legacy_memory_region.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index c7651242..dfd3546a 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -123,8 +123,7 @@ where end: next_free.as_u64(), kind: MemoryRegionKind::Bootloader, }; - Self::add_region(used_region, regions, &mut next_index) - .expect("Failed to add memory region"); + Self::add_region(used_region, regions, &mut next_index); // add unused part normally start = next_free; @@ -186,12 +185,12 @@ where }; // add the three regions (empty regions are ignored in `add_region`) - Self::add_region(before_kernel, regions, &mut next_index).unwrap(); - Self::add_region(kernel, regions, &mut next_index).unwrap(); - Self::add_region(after_kernel, regions, &mut next_index).unwrap(); + Self::add_region(before_kernel, regions, &mut next_index); + Self::add_region(kernel, regions, &mut next_index); + Self::add_region(after_kernel, regions, &mut next_index); } else { // add the region normally - Self::add_region(region, regions, &mut next_index).unwrap(); + Self::add_region(region, regions, &mut next_index); } } @@ -207,20 +206,19 @@ where region: MemoryRegion, regions: &mut [MaybeUninit], next_index: &mut usize, - ) -> Result<(), ()> { + ) { if region.start == region.end { // skip zero sized regions - return Ok(()); + return; } unsafe { regions .get_mut(*next_index) - .ok_or(())? + .expect("cannot add region: no more free entries in memory map") .as_mut_ptr() .write(region) }; *next_index += 1; - Ok(()) } } From 667e57f552e214f9c19848306e03b00d91a8114f Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 14:58:58 +0200 Subject: [PATCH 201/226] Preserve `RUNTIME_SERVICES_*` memory regions As required by the UEFI standart. --- uefi/src/memory_descriptor.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/uefi/src/memory_descriptor.rs b/uefi/src/memory_descriptor.rs index dee9f272..ac3b2ba3 100644 --- a/uefi/src/memory_descriptor.rs +++ b/uefi/src/memory_descriptor.rs @@ -30,13 +30,16 @@ impl<'a> LegacyMemoryRegion for UefiMemoryDescriptor { MemoryType::LOADER_CODE | MemoryType::LOADER_DATA | MemoryType::BOOT_SERVICES_CODE - | MemoryType::BOOT_SERVICES_DATA - | MemoryType::RUNTIME_SERVICES_CODE - | MemoryType::RUNTIME_SERVICES_DATA => { + | MemoryType::BOOT_SERVICES_DATA => { // we don't need this data anymore after the bootloader // passes control to the kernel true } + MemoryType::RUNTIME_SERVICES_CODE | MemoryType::RUNTIME_SERVICES_DATA => { + // the UEFI standard specifies that these should be presevered + // by the bootloader and operating system + false + } _ => false, } } From 454f70740df13107d4748d63b1d646f176f6fa62 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 16:12:04 +0200 Subject: [PATCH 202/226] Update usage instructions and architecture description in README --- README.md | 98 ++++++++++++++++++++++++++----------------------------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 56540e77..5f14a59f 100644 --- a/README.md +++ b/README.md @@ -12,60 +12,56 @@ You need a nightly [Rust](https://www.rust-lang.org) compiler with the `llvm-too ## Usage -See our [documentation](https://docs.rs/bootloader). Note that the `bootimage` crate is no longer used since version 0.10.0. +To make your kernel compatible with `bootloader`: + +- Add a dependency on the `bootloader_api` crate in your kernel's `Cargo.toml`. +- Your kernel binary should be `#![no_std]` and `#![no_main]`. +- Define an entry point function with the signature `fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> !`. The function name can be arbitrary. + - The `boot_info` argument provides information about available memory, the framebuffer, and more. See the API docs for `bootloader_api` crate for details. +- Use the `entry_point` macro to register the entry point function: `bootloader_api::entry_point!(kernel_main);` + - The macro checks the signature of your entry point function and generates a `_start` entry point symbol for it. (If you use a linker script, make sure that you don't change the entry point name to something else.) + - To use non-standard configuration, you can pass a second argument of type `&'static bootloader_api::BootloaderConfig` to the `entry_point` macro. For example, you can require a specific stack size for your kernel: + ```rust + const CONFIG: bootloader_api::BootloaderConfig = { + let mut config = bootloader_api::BootloaderConfig::new_default(); + config.kernel_stack_size = 100 * 1024; // 100 KiB + config + }; + bootloader_api::entry_point!(kernel_main, config = &CONFIG); + ``` +- Compile your kernel as normal to an ELF executable. The executable will contain a special section with metadata and the serialized config, which will enable the `bootloader` crate to load it. + +To combine your kernel with a bootloader and create a bootable disk image, follow these steps: + +- Create a new runner crate, e.g. through `cargo new runner --bin`. +- Add the `bootloader` crate as a `dependency` in the `runner/Cargo.toml`. +- In the `main.rs`, invoke the build commands for your kernel. + - Alternatively, you can set up an [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) on your kernel, provided that you use a `rustup`-supported target for your kernel: + ```toml + [dependencies] + my-kernel = { path = "..", artifact = "bin", target = "x86_64-unknown-none" } + ``` +- After building your kernel, obtain the path to the kernel executable. + - When using an artifact dependency, you can retrieve this path using `env!("CARGO_BIN_FILE_MY_KERNEL_my-kernel")` +- Use the `bootloader::create_boot_partition` function to create a bootable FAT partition at some chosen path. +- Use one or multiple `bootloader::create_*_disk_image` functions to transform the bootable FAT partition into a disk image. + - Use the `bootloader::create_uefi_disk_image` function to create an UEFI-compatible GPT-formatted disk image. + - Use the `bootloader::create_bios_disk_image` function to create a BIOS-compatible MBR-formatted disk image. ## Architecture -This project consists of three separate entities: - -- A library with the entry point and boot info definitions that kernels can include as a normal cargo dependency. -- BIOS and UEFI binaries that contain the actual bootloader implementation. -- A `builder` binary to simplify the build process of the BIOS and UEFI binaries. - -These three entities are currently all combined in a single crate using cargo feature flags. The reason for this is that the kernel and bootloader must use the exact same version of the `BootInfo` struct to prevent undefined behavior (we did not stabilize the boot info format yet, so it might change between versions). - -### Build and Boot - -The build and boot process works the following way: - -- The `builder` binary is a small command line tool that takes the path to the kernel manifest and binary as arguments. Optionally, it allows to override the cargo target and output dirs. It also accepts a `--quiet` switch and allows to only build the BIOS or UEFI binary instead of both. -- After parsing the arguments, the `builder` binary invokes the actual build command for the BIOS/UEFI binaries, which includes the correct `--target` and `--features` arguments (and `-Zbuild-std`). The kernel manifest and binary paths are passed as `KERNEL_MANIFEST` and `KERNEL` environment variables. -- The next step in the build process is the `build.rs` build script. It only does something when building the BIOS/UEFI binaries (indicated by the `binary` feature), otherwise it is a no-op. - - The script first runs some sanity checks, e.g. the kernel manifest and binary should be specified in env variables and should exist, the correct target triple should be used, and the `llvm-tools` rustup component should be installed. - - Then it copies the kernel executable and strips the debug symbols from it to make it smaller. This does not affect the original kernel binary. The stripped binary is then converted to a byte array and provided to the BIOS/UEFI binaries, either as a Rust `static` or through a linker argument. - - Next, the bootloader configuration is parsed, which can be specified in a `package.metadata.bootloader` table in the kernel manifest file. This requires some custom string parsing since TOML does not support unsigned 64-bit integers. Parse errors are turned into `compile_error!` calls to give nicer error messages. - - After parsing the configuration, it is written as a Rust struct definition into a new `bootloader_config.rs` file in the cargo `OUT_DIR`. This file is then included by the UEFI/BIOS binaries. -- After the build script, the compilation continues with either the `bin/uefi.rs` or the `bin/bios.rs`: - - The `bin/uefi.rs` specifies an UEFI entry point function called `efi_main`. It uses the [`uefi`](https://docs.rs/uefi/0.8.0/uefi/) crate to set up a pixel-based framebuffer using the UEFI GOP protocol. Then it exits the UEFI boot services and stores the physical memory map. The final step is to create some page table abstractions and call into `load_and_switch_to_kernel` function that is shared with the BIOS boot code. - - The `bin/bios.rs` function does not provide a direct entry point. Instead it includes several assembly files (`asm/stage-*.rs`) that implement the CPU initialization (from real mode to long mode), the framebuffer setup (via VESA), and the memory map creation (via a BIOS call). The assembly stages are explained in more detail below. After the assembly stages, the execution jumps to the `bootloader_main` function in `bios.rs`. There we set up some additional identity mapping, translate the memory map and framebuffer into Rust structs, detect the RSDP table, and create some page table abstractions. Then we call into the `load_and_switch_to_kernel` function like the `bin/uefi.rs`. -- The common `load_and_switch_to_kernel` function is defined in `src/binary/mod.rs`. This is also the file that includes the `bootloader_config.rs` generated by the build script. The `load_and_switch_to_kernel` functions performs the following steps: - - Parse the kernel binary and map it in a new page table. This includes setting up the correct permissions for each page, initializing `.bss` sections, and allocating a stack with guard page. The relevant functions for these steps are `set_up_mappings` and `load_kernel`. - - Create the `BootInfo` struct, which abstracts over the differences between BIOS and UEFI booting. This step is implemented in the `create_boot_info` function. - - Do a context switch and jump to the kernel entry point function. This involves identity-mapping the context switch function itself in both the kernel and bootloader page tables to prevent a page fault after switching page tables. This switch step is implemented in the `switch_to_kernel` and `context_switch` functions. -- As a last step after a successful build, the `builder` binary turns the compiled bootloader executable (includes the kernel) into a bootable disk image. For UEFI, this means that a FAT partition and a GPT disk image are created. For BIOS, the `llvm-objcopy` tool is used to convert the `bootloader` executable to a flat binary, as it already contains a basic MBR. - -### BIOS Assembly Stages - -When you press the power button the computer loads the BIOS from some flash memory stored on the motherboard. The BIOS initializes and self tests the hardware then loads the first 512 bytes into memory from the media device (i.e. the cdrom or floppy disk). If the last two bytes equal 0xAA55 then the BIOS will jump to location 0x7C00 effectively transferring control to the bootloader. - -At this point the CPU is running in 16 bit mode, meaning only the 16 bit registers are available. Also since the BIOS only loads the first 512 bytes this means our bootloader code has to stay below that limit, otherwise we’ll hit uninitialised memory! Using [Bios interrupt calls](https://en.wikipedia.org/wiki/BIOS_interrupt_call) the bootloader prints debug information to the screen. - -For more information on how to write a bootloader click [here](http://3zanders.co.uk/2017/10/13/writing-a-bootloader/). The assembler files get imported through the [global_asm feature](https://doc.rust-lang.org/unstable-book/library-features/global-asm.html). The assembler syntax definition used is the one llvm uses: [GNU Assembly](http://microelectronics.esa.int/erc32/doc/as.pdf). - -The purposes of the individual assembly stages in this project are the following: - -- stage_1.s: This stage initializes the stack, enables the A20 line, loads the rest of the bootloader from disk, and jumps to stage_2. -- stage_2.s: This stage sets the target operating mode, loads the kernel from disk,creates an e820 memory map, enters protected mode, and jumps to the third stage. -- stage_3.s: This stage performs some checks on the CPU (cpuid, long mode), sets up an initial page table mapping (identity map the bootloader, map the P4 recursively, map the kernel blob to 4MB), enables paging, switches to long mode, and jumps to stage_4. - -## Future Plans - -- [ ] Create a `multiboot2` compatible disk image in addition to the BIOS and UEFI disk images. This would make it possible to use it on top of the GRUB bootloader. -- [ ] Rewrite most of the BIOS assembly stages in Rust. This has already started. -- [ ] Instead of linking the kernel bytes directly with the bootloader, use a filesystem (e.g. FAT) and load the kernel as a separate file. -- [ ] Stabilize the boot info format and make it possible to check the version at runtime. -- [ ] Instead of searching the bootloader source in the cargo cache on building, use the upcoming ["artifact dependencies"](https://github.com/rust-lang/cargo/issues/9096) feature of cargo to download the builder binary separately. Requires doing a boot info version check on build time. -- [ ] Transform this "Future Plans" list into issues and a roadmap. +This project is split into three separate entities: + +- A [`bootloader_api`](./api) library with the entry point, configuration, and boot info definitions. + - Kernels should include this library as a normal cargo dependency. + - The provided `entry_point` macro will encode the configuration settings into a separate ELF section of the compiled kernel executable. +- [BIOS](./bios) and [UEFI](./uefi) binaries that contain the actual bootloader implementation. + - The implementations share a higher-level [common library](./common). + - Both implementations load the kernel at runtime from a FAT partition. This FAT partition is created + - The configuration is read from a special section of the kernel's ELF file, which is created by the `entry_point` macro of teh `bootloader_api` library. +- A `bootloader` library to create bootable disk images that run a given kernel. This library is the top-level crate in this project. + - The library builds the BIOS and UEFI implementations in the [`build.rs`](./build.rs). + - It provides functions to create FAT-formatted bootable disk images, based on the compiled BIOS and UEFI bootloaders. ## License From 7088b5344442aadbc138d247944ca34ffdccf4cc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 16:14:17 +0200 Subject: [PATCH 203/226] Fix: Avoid mutable aliasing in `Framebuffer::buffer` --- api/src/info.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/src/info.rs b/api/src/info.rs index 46d904da..1a494291 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -178,10 +178,14 @@ impl FrameBuffer { /// Returns the raw bytes of the framebuffer as mutable slice. pub fn buffer_mut(&mut self) -> &mut [u8] { - unsafe { self.create_buffer() } + unsafe { self.create_buffer_mut() } + } + + unsafe fn create_buffer<'a>(&self) -> &'a [u8] { + unsafe { slice::from_raw_parts(self.buffer_start as *const u8, self.buffer_byte_len) } } - unsafe fn create_buffer<'a>(&self) -> &'a mut [u8] { + unsafe fn create_buffer_mut<'a>(&self) -> &'a mut [u8] { unsafe { slice::from_raw_parts_mut(self.buffer_start as *mut u8, self.buffer_byte_len) } } From 230da17aa1cc2ef76221d8ae458535d18c7abeab Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 17:48:59 +0200 Subject: [PATCH 204/226] Fix typo Co-authored-by: Tom Dohrmann --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f14a59f..6050792f 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ This project is split into three separate entities: - [BIOS](./bios) and [UEFI](./uefi) binaries that contain the actual bootloader implementation. - The implementations share a higher-level [common library](./common). - Both implementations load the kernel at runtime from a FAT partition. This FAT partition is created - - The configuration is read from a special section of the kernel's ELF file, which is created by the `entry_point` macro of teh `bootloader_api` library. + - The configuration is read from a special section of the kernel's ELF file, which is created by the `entry_point` macro of the `bootloader_api` library. - A `bootloader` library to create bootable disk images that run a given kernel. This library is the top-level crate in this project. - The library builds the BIOS and UEFI implementations in the [`build.rs`](./build.rs). - It provides functions to create FAT-formatted bootable disk images, based on the compiled BIOS and UEFI bootloaders. From 74ead928d3371e7c8f136d287dda23586a4b534d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 17:49:27 +0200 Subject: [PATCH 205/226] Rename `VeryUnsafeCell` to `RacyCell` --- uefi/src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/uefi/src/main.rs b/uefi/src/main.rs index de821c01..5c8bced4 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -34,19 +34,19 @@ use x86_64::{ mod memory_descriptor; -static SYSTEM_TABLE: VeryUnsafeCell>> = VeryUnsafeCell::new(None); +static SYSTEM_TABLE: RacyCell>> = RacyCell::new(None); -struct VeryUnsafeCell(UnsafeCell); +struct RacyCell(UnsafeCell); -impl VeryUnsafeCell { +impl RacyCell { const fn new(v: T) -> Self { Self(UnsafeCell::new(v)) } } -unsafe impl Sync for VeryUnsafeCell {} +unsafe impl Sync for RacyCell {} -impl core::ops::Deref for VeryUnsafeCell { +impl core::ops::Deref for RacyCell { type Target = UnsafeCell; fn deref(&self) -> &Self::Target { From 9cf9c4c3d638849dee4744b54a3faaf98b34da8c Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 17:51:00 +0200 Subject: [PATCH 206/226] Fix width/height bound checks in stage-3 logger --- bios/stage-3/src/screen.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/bios/stage-3/src/screen.rs b/bios/stage-3/src/screen.rs index b39ca5ee..b6e6f016 100644 --- a/bios/stage-3/src/screen.rs +++ b/bios/stage-3/src/screen.rs @@ -74,15 +74,13 @@ impl ScreenWriter { '\n' => self.newline(), '\r' => self.carriage_return(), c => { - if self.x_pos >= self.width() { + let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); + if self.x_pos + bitmap_char.width() > self.width() { self.newline(); } - const BITMAP_LETTER_WIDTH: usize = - get_bitmap_width(FontWeight::Regular, BitmapHeight::Size14); - if self.y_pos >= (self.height() - BITMAP_LETTER_WIDTH) { + if self.y_pos + bitmap_char.height() > self.height() { self.clear(); } - let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); self.write_rendered_char(bitmap_char); } } From f95e9a97190c118f94f914b0c2483b137b8be39d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 11 Sep 2022 17:55:58 +0200 Subject: [PATCH 207/226] Hide items generate by `entry_point` macro from static/fn namespace --- api/src/lib.rs | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 1f38b862..33890b57 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -112,23 +112,25 @@ macro_rules! entry_point { $crate::entry_point!($path, config = &$crate::BootloaderConfig::new_default()); }; ($path:path, config = $config:expr) => { - #[link_section = ".bootloader-config"] - pub static __BOOTLOADER_CONFIG: [u8; $crate::BootloaderConfig::SERIALIZED_LEN] = { - // validate the type - let config: &$crate::BootloaderConfig = $config; - config.serialize() - }; + const _: () = { + #[link_section = ".bootloader-config"] + pub static __BOOTLOADER_CONFIG: [u8; $crate::BootloaderConfig::SERIALIZED_LEN] = { + // validate the type + let config: &$crate::BootloaderConfig = $config; + config.serialize() + }; - #[export_name = "_start"] - pub extern "C" fn __impl_start(boot_info: &'static mut $crate::BootInfo) -> ! { - // validate the signature of the program entry point - let f: fn(&'static mut $crate::BootInfo) -> ! = $path; + #[export_name = "_start"] + pub extern "C" fn __impl_start(boot_info: &'static mut $crate::BootInfo) -> ! { + // validate the signature of the program entry point + let f: fn(&'static mut $crate::BootInfo) -> ! = $path; - // ensure that the config is used so that the linker keeps it - $crate::__force_use(&__BOOTLOADER_CONFIG); + // ensure that the config is used so that the linker keeps it + $crate::__force_use(&__BOOTLOADER_CONFIG); - f(boot_info) - } + f(boot_info) + } + }; }; } From 9e567ccc47a069dea312df65a8706f82800109c4 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 25 Sep 2022 12:32:16 +0200 Subject: [PATCH 208/226] Test booting a kernel compiled with `lto` --- Cargo.lock | 9 +++++++ Cargo.toml | 5 ++++ tests/lto.rs | 25 ++++++++++++++++++ tests/test_kernels/lto/Cargo.toml | 13 ++++++++++ tests/test_kernels/lto/src/bin/basic_boot.rs | 21 +++++++++++++++ tests/test_kernels/lto/src/lib.rs | 27 ++++++++++++++++++++ 6 files changed, 100 insertions(+) create mode 100644 tests/lto.rs create mode 100644 tests/test_kernels/lto/Cargo.toml create mode 100644 tests/test_kernels/lto/src/bin/basic_boot.rs create mode 100644 tests/test_kernels/lto/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 4cfc1f39..6ae90810 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -502,6 +502,15 @@ dependencies = [ "x86_64", ] +[[package]] +name = "test_kernel_lto" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64", +] + [[package]] name = "test_kernel_map_phys_mem" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 455adf87..45669840 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ members = [ "tests/test_kernels/map_phys_mem", "tests/test_kernels/higher_half", "tests/test_kernels/pie", + "tests/test_kernels/lto", ] exclude = ["examples/basic", "examples/test_framework"] @@ -74,6 +75,10 @@ inherits = "release" debug = true overflow-checks = true +[profile.lto] +inherits = "release" +lto = true + [profile.test.package.test_kernel_higher_half] rustflags = [ "-C", diff --git a/tests/lto.rs b/tests/lto.rs new file mode 100644 index 00000000..f7f4a47e --- /dev/null +++ b/tests/lto.rs @@ -0,0 +1,25 @@ +use std::{path::Path, process::Command}; + +use bootloader_test_runner::run_test_kernel; + +#[test] +fn basic_boot() { + // build test kernel manually to force-enable link-time optimization + let mut cmd = Command::new(std::env::var("CARGO").unwrap_or("cargo".into())); + cmd.arg("build"); + cmd.arg("-p").arg("test_kernel_lto"); + cmd.arg("--target").arg("x86_64-unknown-none"); + cmd.arg("--profile").arg("lto"); + let status = cmd.status().unwrap(); + assert!(status.success()); + + let root = env!("CARGO_MANIFEST_DIR"); + let kernel_path = Path::new(root) + .join("target") + .join("x86_64-unknown-none") + .join("lto") + .join("basic_boot"); + assert!(kernel_path.exists()); + + run_test_kernel(kernel_path.as_path().to_str().unwrap()); +} diff --git a/tests/test_kernels/lto/Cargo.toml b/tests/test_kernels/lto/Cargo.toml new file mode 100644 index 00000000..030625c1 --- /dev/null +++ b/tests/test_kernels/lto/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "test_kernel_lto" +version = "0.1.0" +authors = ["Philipp Oppermann "] +edition = "2018" + +[dependencies] +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/lto/src/bin/basic_boot.rs b/tests/test_kernels/lto/src/bin/basic_boot.rs new file mode 100644 index 00000000..be961aef --- /dev/null +++ b/tests/test_kernels/lto/src/bin/basic_boot.rs @@ -0,0 +1,21 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{entry_point, BootInfo}; +use core::panic::PanicInfo; +use test_kernel_lto::{exit_qemu, QemuExitCode}; + +entry_point!(kernel_main); + +fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + use core::fmt::Write; + + let _ = writeln!(test_kernel_lto::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/lto/src/lib.rs b/tests/test_kernels/lto/src/lib.rs new file mode 100644 index 00000000..4e46fdb6 --- /dev/null +++ b/tests/test_kernels/lto/src/lib.rs @@ -0,0 +1,27 @@ +#![no_std] + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum QemuExitCode { + Success = 0x10, + Failed = 0x11, +} + +pub fn exit_qemu(exit_code: QemuExitCode) -> ! { + use x86_64::instructions::{nop, port::Port}; + + unsafe { + let mut port = Port::new(0xf4); + port.write(exit_code as u32); + } + + loop { + nop(); + } +} + +pub fn serial() -> uart_16550::SerialPort { + let mut port = unsafe { uart_16550::SerialPort::new(0x3F8) }; + port.init(); + port +} From 076bea979fe148a23eb2eb35469726f4f87dbab5 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 25 Sep 2022 12:35:37 +0200 Subject: [PATCH 209/226] Fix `force_use` function by using original slice ptr instead of stack reference --- api/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 33890b57..2f6d5f36 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -136,6 +136,6 @@ macro_rules! entry_point { #[doc(hidden)] pub fn __force_use(slice: &[u8]) { - let force_use = &slice as *const _ as usize; + let force_use = slice.as_ptr() as usize; unsafe { core::arch::asm!("add {0}, 0", in(reg) force_use, options(nomem, nostack)) }; } From 2ac7140941738ecaf3dfd11d046116cffce90e6d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 17:25:37 +0200 Subject: [PATCH 210/226] Improve error message when bootloader config section is not found --- common/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index b39bd165..0a8a6990 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -66,7 +66,7 @@ impl<'a> Kernel<'a> { let config = { let section = kernel_elf .find_section_by_name(".bootloader-config") - .unwrap(); + .expect("bootloader config section not found; kernel must be compiled against bootloader_api"); let raw = section.raw_data(&kernel_elf); BootloaderConfig::deserialize(raw) .expect("kernel was compiled with incompatible bootloader_api version") From 305b7f260af1901a4e0bfd9909b54e9581b14880 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 17:36:09 +0200 Subject: [PATCH 211/226] Adjust max dimensions for VESA framebuffer --- bios/stage-2/src/main.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index cc044b9a..7c32683f 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -101,14 +101,14 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { writeln!(screen::Writer, "{memory_map:x?}").unwrap(); // TODO: load these from the kernel's config instead of hardcoding - let max_width = 1000; - let max_height = 1000; + let max_width = 1280; + let max_height = 720; let mut vesa_info = vesa::VesaInfo::query(disk_buffer).unwrap(); let vesa_mode = vesa_info .get_best_mode(max_width, max_height) .unwrap() - .unwrap(); + .expect("no suitable VESA mode found"); writeln!( screen::Writer, "VESA MODE: {}x{}", From 5f51766d37123a4ced9be98ee23dfa45b3643ac2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 17:40:49 +0200 Subject: [PATCH 212/226] Change version back to v0.11-alpha --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0568f1bc..84c100d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,7 +55,7 @@ dependencies = [ [[package]] name = "bootloader" -version = "0.10.13" +version = "0.11.0-alpha" dependencies = [ "anyhow", "bootloader_test_runner", diff --git a/Cargo.toml b/Cargo.toml index 06685087..f7e201fc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ cargo-features = ["profile-rustflags"] [package] name = "bootloader" -version = "0.10.13" +version = "0.11.0-alpha" authors = ["Philipp Oppermann "] license = "MIT/Apache-2.0" description = "An experimental x86_64 bootloader that works on both BIOS and UEFI systems." From e997c74a20edeb8acde77fa5aed2a3bf9411ec4e Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 18:36:57 +0200 Subject: [PATCH 213/226] Refactor image creation API --- Cargo.lock | 51 +++++++++ Cargo.toml | 1 + src/lib.rs | 226 ++++++++++++++++++++-------------------- tests/runner/src/lib.rs | 29 +++--- 4 files changed, 177 insertions(+), 130 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 84c100d7..de28cba5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -63,6 +63,7 @@ dependencies = [ "gpt", "llvm-tools", "mbrman", + "tempfile", "test_kernel_default_settings", "test_kernel_higher_half", "test_kernel_map_phys_mem", @@ -206,6 +207,15 @@ dependencies = [ "build_const", ] +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "fatfs" version = "0.3.5" @@ -247,6 +257,15 @@ dependencies = [ "uuid", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "libc" version = "0.2.122" @@ -408,6 +427,24 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "rsdp" version = "2.0.0" @@ -484,6 +521,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "test_kernel_default_settings" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f7e201fc..55db05f7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ anyhow = "1.0.32" fatfs = "0.3.4" gpt = "3.0.0" mbrman = "0.4.2" +tempfile = "3.3.0" [dev-dependencies] bootloader_test_runner = { path = "tests/runner" } diff --git a/src/lib.rs b/src/lib.rs index be3cd01a..47260d10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,74 +1,15 @@ /*! An experimental x86_64 bootloader that works on both BIOS and UEFI systems. - -To use this crate, specify it as a dependency in the `Cargo.toml` of your operating system -kernel. Then you can use the [`entry_point`] macro to mark your entry point function. This -gives you access to the [`BootInfo`] struct, which is passed by the bootloader. - -## Disk Image Creation - -Including the `bootloader` crate as a dependency makes the kernel binary suitable for booting, -but does not create any bootable disk images. To create them, two additional steps are needed: - -1. **Locate the source code of the `bootloader` dependency** on your local system. By using the - dependency source code directly, we ensure that the kernel and bootloader use the same version - of the [`BootInfo`] struct. - - When creating a builder binary written in Rust, the - [`bootloader_locator`](https://docs.rs/bootloader-locator/0.0.4/bootloader_locator/) crate can - be used to automate this step. - - Otherwise, the - [`cargo metadata`](https://doc.rust-lang.org/cargo/commands/cargo-metadata.html) subcommand - can be used to locate the dependency. The command outputs a JSON object with various metadata - for the current package. To find the `bootloader` source path in it, first look for the - "bootloader" dependency under `resolve.nodes.deps` to find out its ID (in the `pkg` field). - Then use that ID to find the bootloader in `packages`. Its `manifest_path` field contains the - local path to the `Cargo.toml` of the bootloader. -2. **Run the following command** in the source code directory of the `bootloader` dependency to create - the bootable disk images: - - ```notrust - cargo builder --kernel-manifest path/to/kernel/Cargo.toml --kernel-binary path/to/kernel_bin - ``` - - The `--kernel-manifest` argument should point to the `Cargo.toml` of your kernel. It is used - for applying configuration settings. The `--kernel-binary` argument should point to the kernel - executable that should be used for the bootable disk images. - - In addition to the `--kernel-manifest` and `--kernel-binary` arguments, it is recommended to also - set the `--target-dir` and `--out-dir` arguments. The former specifies the directory that should - used for cargo build artifacts and the latter specfies the directory where the resulting disk - images should be placed. It is recommended to set `--target-dir` to the `target` folder of your - kernel and `--out-dir` to the the parent folder of `--kernel-binary`. - -This will result in the following files, which are placed in the specified `--out-dir`: - -- A disk image suitable for BIOS booting, named `boot-bios-.img`, where `` is the - name of your kernel executable. This image can be started in QEMU or booted on a real machine - after burning it to an USB stick.. -- A disk image suitable for UEFI booting, named `boot-uefi-.img`. Like the BIOS disk image, - this can be started in QEMU (requires OVMF) and burned to an USB stick to run it on a real - machine. -- Intermediate UEFI files - - A FAT partition image named `boot-uefi-.fat`, which can be directly started in QEMU - or written as an EFI system partition to a GPT-formatted disk. - - An EFI file named `boot-uefi-.efi`. This executable is the combination of the - bootloader and kernel executables. It can be started in QEMU or used to construct a bootable - disk image: Create an EFI system partition formatted with the FAT filesystem and place the - EFI file under `efi\boot\bootx64.efi` on that filesystem. - -**You can find some examples that implement the above steps [in our GitHub repo](https://github.com/rust-osdev/bootloader/tree/main/examples).** - -## Configuration - -The bootloader can be configured through a `[package.metadata.bootloader]` table in the -`Cargo.toml` of the kernel (the one passed as `--kernel-manifest`). See the [`Config`] struct -for all possible configuration options. */ #![warn(missing_docs)] use anyhow::Context; -use std::{collections::BTreeMap, path::Path}; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, +}; +use tempfile::NamedTempFile; mod fat; mod gpt; @@ -79,61 +20,116 @@ const KERNEL_FILE_NAME: &str = "kernel-x86_64"; const BIOS_STAGE_3: &str = "boot-stage-3"; const BIOS_STAGE_4: &str = "boot-stage-4"; -/// Creates a bootable FAT partition at the given path. -pub fn create_boot_partition(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { - let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); - let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); - - let mut files = BTreeMap::new(); - files.insert("efi/boot/bootx64.efi", bootloader_path); - files.insert(KERNEL_FILE_NAME, kernel_binary); - files.insert(BIOS_STAGE_3, stage_3_path); - files.insert(BIOS_STAGE_4, stage_4_path); - - fat::create_fat_filesystem(files, &out_path).context("failed to create UEFI FAT filesystem")?; - - Ok(()) +/// Create disk images for booting on legacy BIOS systems. +pub struct BiosBoot { + kernel: PathBuf, } -pub fn create_uefi_disk_image( - boot_partition_path: &Path, - out_gpt_path: &Path, -) -> anyhow::Result<()> { - gpt::create_gpt_disk(boot_partition_path, out_gpt_path) - .context("failed to create UEFI GPT disk image")?; - - Ok(()) +impl BiosBoot { + /// Start creating a disk image for the given bootloader ELF executable. + pub fn new(kernel_path: &Path) -> Self { + Self { + kernel: kernel_path.to_owned(), + } + } + + /// Create a bootable UEFI disk image at the given path. + pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { + let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); + let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); + + let fat_partition = self + .create_fat_partition() + .context("failed to create FAT partition")?; + + mbr::create_mbr_disk( + bootsector_path, + stage_2_path, + fat_partition.path(), + out_path, + ) + .context("failed to create BIOS MBR disk image")?; + + fat_partition + .close() + .context("failed to delete FAT partition after disk image creation")?; + + Ok(()) + } + + /// Creates an BIOS-bootable FAT partition with the kernel. + fn create_fat_partition(&self) -> anyhow::Result { + let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); + let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); + + let mut files = BTreeMap::new(); + files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + files.insert(BIOS_STAGE_3, stage_3_path); + files.insert(BIOS_STAGE_4, stage_4_path); + + let out_file = NamedTempFile::new().context("failed to create temp file")?; + fat::create_fat_filesystem(files, out_file.path()) + .context("failed to create BIOS FAT filesystem")?; + + Ok(out_file) + } } -pub fn create_bios_disk_image( - boot_partition_path: &Path, - out_mbr_path: &Path, -) -> anyhow::Result<()> { - let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); - let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); - - mbr::create_mbr_disk( - bootsector_path, - stage_2_path, - boot_partition_path, - out_mbr_path, - ) - .context("failed to create BIOS MBR disk image")?; - - Ok(()) +/// Create disk images for booting on UEFI systems. +pub struct UefiBoot { + kernel: PathBuf, } -/// Prepare a folder for use with booting over UEFI_PXE. -/// -/// This places the bootloader executable under the path "bootloader". The -/// DHCP server should set the filename option to that path, otherwise the -/// bootloader won't be found. -pub fn create_uefi_pxe_tftp_folder(kernel_binary: &Path, out_path: &Path) -> anyhow::Result<()> { - let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); - - pxe::create_uefi_tftp_folder(bootloader_path, kernel_binary, out_path) - .context("failed to create UEFI PXE tftp folder")?; - - Ok(()) +impl UefiBoot { + /// Start creating a disk image for the given bootloader ELF executable. + pub fn new(kernel_path: &Path) -> Self { + Self { + kernel: kernel_path.to_owned(), + } + } + + /// Create a bootable BIOS disk image at the given path. + pub fn create_disk_image(&self, out_path: &Path) -> anyhow::Result<()> { + let fat_partition = self + .create_fat_partition() + .context("failed to create FAT partition")?; + + gpt::create_gpt_disk(fat_partition.path(), out_path) + .context("failed to create UEFI GPT disk image")?; + + fat_partition + .close() + .context("failed to delete FAT partition after disk image creation")?; + + Ok(()) + } + + /// Prepare a folder for use with booting over UEFI_PXE. + /// + /// This places the bootloader executable under the path "bootloader". The + /// DHCP server should set the filename option to that path, otherwise the + /// bootloader won't be found. + pub fn create_pxe_tftp_folder(&self, out_path: &Path) -> anyhow::Result<()> { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + + pxe::create_uefi_tftp_folder(bootloader_path, self.kernel.as_path(), out_path) + .context("failed to create UEFI PXE tftp folder")?; + + Ok(()) + } + + /// Creates an UEFI-bootable FAT partition with the kernel. + fn create_fat_partition(&self) -> anyhow::Result { + let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + + let mut files = BTreeMap::new(); + files.insert("efi/boot/bootx64.efi", bootloader_path); + files.insert(KERNEL_FILE_NAME, self.kernel.as_path()); + + let out_file = NamedTempFile::new().context("failed to create temp file")?; + fat::create_fat_filesystem(files, &out_file.path()) + .context("failed to create UEFI FAT filesystem")?; + + Ok(out_file) + } } diff --git a/tests/runner/src/lib.rs b/tests/runner/src/lib.rs index fa6e4ba7..7f50cd42 100644 --- a/tests/runner/src/lib.rs +++ b/tests/runner/src/lib.rs @@ -13,26 +13,25 @@ const QEMU_ARGS: &[&str] = &[ pub fn run_test_kernel(kernel_binary_path: &str) { let kernel_path = Path::new(kernel_binary_path); - // create FAT filesystem with kernel executable and UEFI bootloader - let out_fat_path = kernel_path.with_extension("fat"); - bootloader::create_boot_partition(kernel_path, &out_fat_path).unwrap(); - - // wrap the created filesystem in an GPT disk image for UEFI booting - let out_gpt_path = kernel_path.with_extension("gpt"); - bootloader::create_uefi_disk_image(&out_fat_path, &out_gpt_path).unwrap(); + // create an MBR disk image for legacy BIOS booting + let mbr_path = kernel_path.with_extension("mbr"); + bootloader::BiosBoot::new(kernel_path) + .create_disk_image(&mbr_path) + .unwrap(); - // wrap the created filesystem in an MBR disk image for legacy BIOS booting - let out_mbr_path = kernel_path.with_extension("mbr"); - bootloader::create_bios_disk_image(&out_fat_path, &out_mbr_path).unwrap(); + // create a GPT disk image for UEFI booting + let gpt_path = kernel_path.with_extension("gpt"); + let uefi_builder = bootloader::UefiBoot::new(kernel_path); + uefi_builder.create_disk_image(&gpt_path).unwrap(); // create a TFTP folder with the kernel executable and UEFI bootloader for // UEFI PXE booting - let out_tftp_path = kernel_path.with_extension(".tftp"); - bootloader::create_uefi_pxe_tftp_folder(kernel_path, &out_tftp_path).unwrap(); + let tftp_path = kernel_path.with_extension(".tftp"); + uefi_builder.create_pxe_tftp_folder(&tftp_path).unwrap(); - run_test_kernel_on_uefi(&out_gpt_path); - run_test_kernel_on_bios(&out_mbr_path); - run_test_kernel_on_uefi_pxe(&out_tftp_path); + run_test_kernel_on_uefi(&gpt_path); + run_test_kernel_on_bios(&mbr_path); + run_test_kernel_on_uefi_pxe(&tftp_path); } pub fn run_test_kernel_on_uefi(out_gpt_path: &Path) { From aebe0d3a1373c58eaa178b921fbc566687010dd7 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 19:54:48 +0200 Subject: [PATCH 214/226] Write location first in panic handler In case the formatted output fails for some reason. --- bios/stage-2/src/screen.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bios/stage-2/src/screen.rs b/bios/stage-2/src/screen.rs index 45cf0f52..e566c3c7 100644 --- a/bios/stage-2/src/screen.rs +++ b/bios/stage-2/src/screen.rs @@ -32,7 +32,11 @@ impl Write for Writer { #[panic_handler] #[cfg(not(test))] pub fn panic(info: &core::panic::PanicInfo) -> ! { - let _ = writeln!(Writer, "\nPANIC: {info}"); + let _ = write!(Writer, "\nPANIC: "); + if let Some(location) = info.location() { + let _ = writeln!(Writer, "{location} "); + } + let _ = writeln!(Writer, " {info}"); loop { unsafe { From 6eb0abbb7e5b4b6088fb49452c7dc976183a946a Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 19:55:10 +0200 Subject: [PATCH 215/226] Fix: Don't overwrite `es` register --- bios/stage-2/src/vesa.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index 73099660..a2d5e9ed 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -38,7 +38,7 @@ impl<'a> VesaInfo<'a> { let block_ptr = slice.as_mut_ptr(); let ret; unsafe { - asm!("mov es, {:x}", "int 0x10", in(reg)0, inout("ax") 0x4f00u16 => ret, in("di") block_ptr) + asm!("push es", "mov es, {:x}", "int 0x10", "pop es", in(reg)0, inout("ax") 0x4f00u16 => ret, in("di") block_ptr) }; match ret { 0x4f => { @@ -172,7 +172,7 @@ impl VesaModeInfo { target_addr -= segment << 4; unsafe { asm!( - "mov es, {:x}", "int 0x10", + "push es", "mov es, {:x}", "int 0x10", "pop es", in(reg) segment as u16, inout("ax") 0x4f01u16 => ret, in("cx") mode, From a6d69e9b4bd9daf4dc006d25b9d11d6c06edaab2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 20:28:38 +0200 Subject: [PATCH 216/226] Fix: Add support for segments in video mode ptr The video mode pointer field in is `[segment, offset]` form. So we should not treat it as a pointer directly. --- bios/stage-2/src/vesa.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index a2d5e9ed..6456c147 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -95,7 +95,12 @@ impl<'a> VesaInfo<'a> { } fn get_mode(&self, index: usize) -> Option { - let video_mode_ptr = self.info_block.video_mode_ptr; + let (segment, offset) = { + let raw = self.info_block.video_mode_ptr; + ((raw >> 16) as u16, raw as u16) + }; + let video_mode_ptr = ((segment as u32) << 4) + offset as u32; + let base_ptr = video_mode_ptr as *const u16; let ptr = unsafe { base_ptr.add(index) }; let mode = unsafe { *ptr }; From 80731d7daab7e7ff49ac10f8d4838fbbe942b839 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 20:37:44 +0200 Subject: [PATCH 217/226] Take pixel format into account when determining best VESA mode Prefer RGB or BGR modes over unknown ones. --- bios/common/src/lib.rs | 9 +++++++++ bios/stage-2/src/vesa.rs | 15 ++++++++++----- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/bios/common/src/lib.rs b/bios/common/src/lib.rs index b83786f3..160890f5 100644 --- a/bios/common/src/lib.rs +++ b/bios/common/src/lib.rs @@ -45,6 +45,15 @@ pub enum PixelFormat { }, } +impl PixelFormat { + pub fn is_unknown(&self) -> bool { + match self { + PixelFormat::Rgb | PixelFormat::Bgr => false, + PixelFormat::Unknown { .. } => true, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(C)] pub struct E820MemoryRegion { diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs index 6456c147..9bf54b55 100644 --- a/bios/stage-2/src/vesa.rs +++ b/bios/stage-2/src/vesa.rs @@ -83,11 +83,16 @@ impl<'a> VesaInfo<'a> { continue; } - if best - .as_ref() - .map(|best| mode_info.width >= best.width || mode_info.height >= best.height) - .unwrap_or(true) - { + let replace = match &best { + None => true, + Some(best) => { + best.pixel_format.is_unknown() + || best.width < mode_info.width + || (best.width == mode_info.width && best.height < mode_info.height) + } + }; + + if replace { best = Some(mode_info); } } From c78a2f540acce48f805a998070f8cd2368945e75 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 2 Oct 2022 20:39:16 +0200 Subject: [PATCH 218/226] Add a log message when loading kernel Loading the kernel can take some time, so users should know what's taking so long. --- bios/stage-2/src/main.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs index 7c32683f..0553f2ed 100644 --- a/bios/stage-2/src/main.rs +++ b/bios/stage-2/src/main.rs @@ -94,6 +94,8 @@ fn start(disk_number: u16, partition_table_start: *const u8) -> ! { }; let stage_4_len = load_file("boot-stage-4", stage_4_dst, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "stage 4 loaded at {stage_4_dst:#p}").unwrap(); + + writeln!(screen::Writer, "loading kernel...").unwrap(); let kernel_len = load_file("kernel-x86_64", KERNEL_DST, &mut fs, &mut disk, disk_buffer); writeln!(screen::Writer, "kernel loaded at {KERNEL_DST:#p}").unwrap(); From f0e8be2d41ce3209387a17d8c033c1ca0fb8b350 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Tue, 4 Oct 2022 14:19:23 +0200 Subject: [PATCH 219/226] remove unnecessary duplicate field --- api/src/info.rs | 13 ++++--------- common/src/lib.rs | 8 +------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/api/src/info.rs b/api/src/info.rs index 1a494291..b3db8d4f 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -158,17 +158,12 @@ pub enum MemoryRegionKind { #[repr(C)] pub struct FrameBuffer { pub(crate) buffer_start: u64, - pub(crate) buffer_byte_len: usize, pub(crate) info: FrameBufferInfo, } impl FrameBuffer { - pub fn new(buffer_start: u64, buffer_byte_len: usize, info: FrameBufferInfo) -> Self { - Self { - buffer_start, - buffer_byte_len, - info, - } + pub fn new(buffer_start: u64, info: FrameBufferInfo) -> Self { + Self { buffer_start, info } } /// Returns the raw bytes of the framebuffer as slice. @@ -182,11 +177,11 @@ impl FrameBuffer { } unsafe fn create_buffer<'a>(&self) -> &'a [u8] { - unsafe { slice::from_raw_parts(self.buffer_start as *const u8, self.buffer_byte_len) } + unsafe { slice::from_raw_parts(self.buffer_start as *const u8, self.info.byte_len) } } unsafe fn create_buffer_mut<'a>(&self) -> &'a mut [u8] { - unsafe { slice::from_raw_parts_mut(self.buffer_start as *mut u8, self.buffer_byte_len) } + unsafe { slice::from_raw_parts_mut(self.buffer_start as *mut u8, self.info.byte_len) } } /// Returns layout and pixel format information of the framebuffer. diff --git a/common/src/lib.rs b/common/src/lib.rs index 0a8a6990..af44ef7a 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -440,13 +440,7 @@ where let mut info = BootInfo::new(memory_regions.into()); info.framebuffer = mappings .framebuffer - .map(|addr| { - FrameBuffer::new( - addr.as_u64(), - system_info.framebuffer_info.byte_len, - system_info.framebuffer_info, - ) - }) + .map(|addr| FrameBuffer::new(addr.as_u64(), system_info.framebuffer_info)) .into(); info.physical_memory_offset = mappings.physical_memory_offset.map(VirtAddr::as_u64).into(); info.recursive_index = mappings.recursive_index.map(Into::into).into(); From c9444f5347646d078c78c21632dfb7be9ef2eb05 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Tue, 4 Oct 2022 14:42:38 +0200 Subject: [PATCH 220/226] make framebuffer optional in `common` --- bios/stage-4/src/main.rs | 7 +++++-- common/src/level_4_entries.rs | 8 +++++--- common/src/lib.rs | 35 +++++++++++++++++++++-------------- uefi/src/main.rs | 10 +++++++--- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 1bedc2d4..86a97328 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -4,6 +4,7 @@ use crate::memory_descriptor::MemoryRegion; use bootloader_api::info::{FrameBufferInfo, PixelFormat}; use bootloader_x86_64_bios_common::{BiosFramebufferInfo, BiosInfo, E820MemoryRegion}; +use bootloader_x86_64_common::RawFrameBufferInfo; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, load_and_switch_to_kernel, Kernel, PageTables, SystemInfo, @@ -112,8 +113,10 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { let kernel = Kernel::parse(kernel_slice); let system_info = SystemInfo { - framebuffer_addr: PhysAddr::new(info.framebuffer.region.start), - framebuffer_info, + framebuffer: Some(RawFrameBufferInfo { + addr: PhysAddr::new(info.framebuffer.region.start), + info: framebuffer_info, + }), rsdp_addr: detect_rsdp(), }; diff --git a/common/src/level_4_entries.rs b/common/src/level_4_entries.rs index ce040d93..2e5e1c4c 100644 --- a/common/src/level_4_entries.rs +++ b/common/src/level_4_entries.rs @@ -1,4 +1,4 @@ -use crate::{entropy, BootInfo}; +use crate::{entropy, BootInfo, RawFrameBufferInfo}; use bootloader_api::{config, info::MemoryRegion, BootloaderConfig}; use core::{alloc::Layout, iter::Step}; use rand::{ @@ -31,7 +31,7 @@ impl UsedLevel4Entries { pub fn new( max_phys_addr: PhysAddr, regions_len: usize, - framebuffer_size: usize, + framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, ) -> Self { let mut used = UsedLevel4Entries { @@ -70,7 +70,9 @@ impl UsedLevel4Entries { } if let config::Mapping::FixedAddress(framebuffer_address) = config.mappings.framebuffer { - used.mark_range_as_used(framebuffer_address, framebuffer_size); + if let Some(framebuffer) = framebuffer { + used.mark_range_as_used(framebuffer_address, framebuffer.info.byte_len); + } } // Mark everything before the dynamic range as unusable. diff --git a/common/src/lib.rs b/common/src/lib.rs index af44ef7a..8b49945f 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -45,14 +45,21 @@ pub fn init_logger(framebuffer: &'static mut [u8], info: FrameBufferInfo) { /// Required system information that should be queried from the BIOS or UEFI firmware. #[derive(Debug, Copy, Clone)] pub struct SystemInfo { - /// Start address of the pixel-based framebuffer. - pub framebuffer_addr: PhysAddr, - /// Information about the framebuffer, including layout and pixel format. - pub framebuffer_info: FrameBufferInfo, + /// Information about the (still unmapped) framebuffer. + pub framebuffer: Option, /// Address of the _Root System Description Pointer_ structure of the ACPI standard. pub rsdp_addr: Option, } +/// The physical address of the framebuffer and information about the framebuffer. +#[derive(Debug, Copy, Clone)] +pub struct RawFrameBufferInfo { + /// Start address of the pixel-based framebuffer. + pub addr: PhysAddr, + /// Information about the framebuffer, including layout and pixel format. + pub info: FrameBufferInfo, +} + pub struct Kernel<'a> { pub elf: ElfFile<'a>, pub config: BootloaderConfig, @@ -100,8 +107,7 @@ where kernel, &mut frame_allocator, &mut page_tables, - system_info.framebuffer_addr, - system_info.framebuffer_info.byte_len, + system_info.framebuffer.as_ref(), &config, ); let boot_info = create_boot_info( @@ -132,8 +138,7 @@ pub fn set_up_mappings( kernel: Kernel, frame_allocator: &mut LegacyFrameAllocator, page_tables: &mut PageTables, - framebuffer_addr: PhysAddr, - framebuffer_size: usize, + framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, ) -> Mappings where @@ -145,7 +150,7 @@ where let mut used_entries = UsedLevel4Entries::new( frame_allocator.max_phys_addr(), frame_allocator.len(), - framebuffer_size, + framebuffer, config, ); @@ -220,15 +225,15 @@ where } // map framebuffer - let framebuffer_virt_addr = { + let framebuffer_virt_addr = if let Some(framebuffer) = framebuffer { log::info!("Map framebuffer"); - let framebuffer_start_frame: PhysFrame = PhysFrame::containing_address(framebuffer_addr); + let framebuffer_start_frame: PhysFrame = PhysFrame::containing_address(framebuffer.addr); let framebuffer_end_frame = - PhysFrame::containing_address(framebuffer_addr + framebuffer_size - 1u64); + PhysFrame::containing_address(framebuffer.addr + framebuffer.info.byte_len - 1u64); let start_page = Page::from_start_address(mapping_addr( config.mappings.framebuffer, - u64::from_usize(framebuffer_size), + u64::from_usize(framebuffer.info.byte_len), Size4KiB::SIZE, &mut used_entries, )) @@ -248,6 +253,8 @@ where } let framebuffer_virt_addr = start_page.start_address(); Some(framebuffer_virt_addr) + } else { + None }; let physical_memory_offset = if let Some(mapping) = config.mappings.physical_memory { @@ -440,7 +447,7 @@ where let mut info = BootInfo::new(memory_regions.into()); info.framebuffer = mappings .framebuffer - .map(|addr| FrameBuffer::new(addr.as_u64(), system_info.framebuffer_info)) + .map(|addr| FrameBuffer::new(addr.as_u64(), system_info.framebuffer.expect("there shouldn't be a mapping for the framebuffer if there is not framebuffer").info)) .into(); info.physical_memory_offset = mappings.physical_memory_offset.map(VirtAddr::as_u64).into(); info.recursive_index = mappings.recursive_index.map(Into::into).into(); diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 5c8bced4..8026cc33 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -5,7 +5,9 @@ use crate::memory_descriptor::UefiMemoryDescriptor; use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; -use bootloader_x86_64_common::{legacy_memory_region::LegacyFrameAllocator, Kernel, SystemInfo}; +use bootloader_x86_64_common::{ + legacy_memory_region::LegacyFrameAllocator, Kernel, RawFrameBufferInfo, SystemInfo, +}; use core::{arch::asm, cell::UnsafeCell, fmt::Write, mem, panic::PanicInfo, ptr, slice}; use uefi::{ prelude::{entry, Boot, Handle, Status, SystemTable}, @@ -105,8 +107,10 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let page_tables = create_page_tables(&mut frame_allocator); let system_info = SystemInfo { - framebuffer_addr, - framebuffer_info, + framebuffer: Some(RawFrameBufferInfo { + addr: framebuffer_addr, + info: framebuffer_info, + }), rsdp_addr: { use uefi::table::cfg; let mut config_entries = system_table.config_table().iter(); From d6e0d2bfd0a68392c100473fd67ad17fec8ceaa1 Mon Sep 17 00:00:00 2001 From: Tom Dohrmann Date: Tue, 4 Oct 2022 14:46:44 +0200 Subject: [PATCH 221/226] don't fail when there's no uefi graphics output --- uefi/src/main.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 8026cc33..f72996bd 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -76,7 +76,7 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let kernel = load_kernel(image, &mut st); - let (framebuffer_addr, framebuffer_info) = init_logger(&st, kernel.config); + let framebuffer = init_logger(&st, kernel.config); // we no longer need the system table for printing panics unsafe { @@ -85,7 +85,9 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { log::info!("UEFI bootloader started"); log::info!("Reading kernel and configuration from disk was successful"); - log::info!("Using framebuffer at {:#x}", framebuffer_addr); + if let Some(framebuffer) = framebuffer { + log::info!("Using framebuffer at {:#x}", framebuffer.addr); + } let mmap_storage = { let max_mmap_size = @@ -107,10 +109,7 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let page_tables = create_page_tables(&mut frame_allocator); let system_info = SystemInfo { - framebuffer: Some(RawFrameBufferInfo { - addr: framebuffer_addr, - info: framebuffer_info, - }), + framebuffer, rsdp_addr: { use uefi::table::cfg; let mut config_entries = system_table.config_table().iter(); @@ -366,11 +365,11 @@ fn create_page_tables( } } -fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, FrameBufferInfo) { +fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option { let gop = st .boot_services() .locate_protocol::() - .expect("failed to locate gop"); + .ok()?; let gop = unsafe { &mut *gop.get() }; let mode = { @@ -423,7 +422,10 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> (PhysAddr, F bootloader_x86_64_common::init_logger(slice, info); - (PhysAddr::new(framebuffer.as_mut_ptr() as u64), info) + Some(RawFrameBufferInfo { + addr: PhysAddr::new(framebuffer.as_mut_ptr() as u64), + info, + }) } #[panic_handler] From 7ea5ecf6962d741a5c98b0474ef5eab5886f25a2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 14:20:38 +0100 Subject: [PATCH 222/226] Add basic error strings for deserialization errors --- api/src/config.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/api/src/config.rs b/api/src/config.rs index 4f69174c..5ca235ab 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -149,16 +149,16 @@ impl BootloaderConfig { /// ELF file. /// /// TODO: return error enum - pub fn deserialize(serialized: &[u8]) -> Result { + pub fn deserialize(serialized: &[u8]) -> Result { if serialized.len() != Self::SERIALIZED_LEN { - return Err(()); + return Err("invalid len"); } let s = serialized; let (uuid, s) = split_array_ref(s); if uuid != &Self::UUID { - return Err(()); + return Err("invalid UUID"); } let (version, s) = { @@ -169,7 +169,7 @@ impl BootloaderConfig { let pre = match pre { [0] => false, [1] => true, - _ => return Err(()), + _ => return Err("invalid pre version"), }; let version = ApiVersion { @@ -206,27 +206,27 @@ impl BootloaderConfig { physical_memory: match physical_memory_some { [0] if physical_memory == [0; 9] => Option::None, [1] => Option::Some(Mapping::deserialize(&physical_memory)?), - _ => return Err(()), + _ => return Err("invalid phys memory value"), }, page_table_recursive: match page_table_recursive_some { [0] if page_table_recursive == [0; 9] => Option::None, [1] => Option::Some(Mapping::deserialize(&page_table_recursive)?), - _ => return Err(()), + _ => return Err("invalid page table recursive value"), }, aslr: match alsr { 1 => true, 0 => false, - _ => return Err(()), + _ => return Err("invalid aslr value"), }, dynamic_range_start: match dynamic_range_start_some { [0] if dynamic_range_start == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(dynamic_range_start)), - _ => return Err(()), + _ => return Err("invalid dynamic range start value"), }, dynamic_range_end: match dynamic_range_end_some { [0] if dynamic_range_end == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(dynamic_range_end)), - _ => return Err(()), + _ => return Err("invalid dynamic range end value"), }, }; (mappings, s) @@ -242,19 +242,19 @@ impl BootloaderConfig { minimum_framebuffer_height: match min_framebuffer_height_some { [0] if min_framebuffer_height == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(min_framebuffer_height)), - _ => return Err(()), + _ => return Err("minimum_framebuffer_height invalid"), }, minimum_framebuffer_width: match min_framebuffer_width_some { [0] if min_framebuffer_width == [0; 8] => Option::None, [1] => Option::Some(u64::from_le_bytes(min_framebuffer_width)), - _ => return Err(()), + _ => return Err("minimum_framebuffer_width invalid"), }, }; (frame_buffer, s) }; if !s.is_empty() { - return Err(()); + return Err("unexpected rest"); } Ok(Self { @@ -509,17 +509,17 @@ impl Mapping { } } - fn deserialize(serialized: &[u8; 9]) -> Result { + fn deserialize(serialized: &[u8; 9]) -> Result { let (&variant, s) = split_array_ref(serialized); let (&addr, s) = split_array_ref(s); if !s.is_empty() { - return Err(()); + return Err("invalid mapping format"); } match variant { [0] if addr == [0; 8] => Ok(Mapping::Dynamic), [1] => Ok(Mapping::FixedAddress(u64::from_le_bytes(addr))), - _ => Err(()), + _ => Err("invalid mapping value"), } } } From 46c7725f37d18dadeb4880cac8a2c64dc55be4ab Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 16:07:37 +0100 Subject: [PATCH 223/226] Create migration guides --- doc/migration/v0.10.md | 121 ++++++++++++++++++++++++++++++++++++++ doc/migration/v0.9.md | 129 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 doc/migration/v0.10.md create mode 100644 doc/migration/v0.9.md diff --git a/doc/migration/v0.10.md b/doc/migration/v0.10.md new file mode 100644 index 00000000..a11b7795 --- /dev/null +++ b/doc/migration/v0.10.md @@ -0,0 +1,121 @@ +# Migration from bootloader `v0.10` + +This guide summarizes the steps for migrating from `bootloader v0.10.X` to `bootloader v0.11`. + +## Kernel + +- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate and adjust the import path in your `main.rs`: + ```diff + # in Cargo.toml + + -bootloader = { version = "0.10.13" } + +bootloader_api = "0.11" + ``` + ```diff + // in main.rs + + -use bootloader::{entry_point, BootInfo}; + +use bootloader_api::{entry_point, BootInfo}; + ``` +- If you used optional features, such as `map-physical-memory`, you can enable them again through the `entry_point` macro: + ```rust + use bootloader_api::config::{BootloaderConfig, Mapping}; + + pub static BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::Dynamic); + config + }; + + // add a `config` argument to the `entry_point` macro call + entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + ``` + See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. + +To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. + +## Booting + +The `bootloader v0.11` release simplifies the disk image creation. The [`bootloader`](https://docs.rs/bootloader/0.11) crate now provides simple functions to create bootable disk images from a kernel. The basic idea is to build your kernel first and then invoke a builder function that calls the disk image creation functions of the `bootloader` crate. + +A good way to implement this is to move your kernel into a `kernel` subdirectory. Then you can create +a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. + +The files could look like this: + +```toml +# .cargo/config.toml + +[unstable] +# enable the unstable artifact-dependencies feature, see +# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies +bindeps = true +``` + +```toml +# Cargo.toml + +[package] +name = "os" # or any other name +version = "0.1.0" + +[build-dependencies] +bootloader = "0.11" +test-kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" } + +[dependencies] +# used for UEFI booting in QEMU +ovmf_prebuilt = "0.1.0-alpha.1" + +[workspace] +members = ["kernel"] +``` + +```rust +// build.rs + +fn main() { + // set by cargo, build scripts should use this directory for output files + let out_dir = env::var_os("OUT_DIR").unwrap(); + // set by cargo's artifact dependency feature, see + // https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies + let kernel = env!("CARGO_BIN_FILE_KERNEL"); + + // create an UEFI disk image (optional) + let uefi_path = out_dir.join("uefi.img"); + bootloader::UefiBoot::new(&kernel).create_disk_image(uefi_path).unwrap(); + + // create a BIOS disk image (optional) + let out_bios_path = out_dir.join("bios.img"); + bootloader::BiosBoot::new(&kernel).create_disk_image(uefi_path).unwrap(); + + // pass the disk image paths as env variables to the `main.rs` + println!("cargo:rustc-env=UEFI_PATH={}", uefi_path.display()); + println!("cargo:rustc-env=BIOS_PATH={}", bios_path.display()); +} +``` + +```rust +// src/main.rs + +fn main() { + // read env variables that were set in build script + let uefi_path = env!("UEFI_PATH"); + let bios_path = env!("BIOS_PATH"); + + // choose whether to start the UEFI or BIOS image + let uefi = true; + + let mut cmd = std::process::Command::new("qemu-system-x86_64"); + if uefi { + cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); + cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}")); + } else { + cmd.arg("-drive").arg(format!("format=raw,file={bios_path}")); + } + let mut child = cmd.spawn()?; + child.wait()?; +} +``` + +Now you should be able to use `cargo build` to create a bootable disk image and `cargo run` to run in QEMU. Your kernel is automatically recompiled when it changes. For more advanced usage, you can add command-line arguments to your `main.rs` to e.g. pass additional arguments to QEMU or to copy the disk images to some path to make it easier to find them (e.g. for copying them to an thumb drive). diff --git a/doc/migration/v0.9.md b/doc/migration/v0.9.md new file mode 100644 index 00000000..636bed36 --- /dev/null +++ b/doc/migration/v0.9.md @@ -0,0 +1,129 @@ +# Migration from bootloader `v0.9` + +This guide summarizes the steps for migrating from `bootloader v0.9.X` to `bootloader v0.11`. Note that some bigger changes are required to support UEFI booting (to support the framebuffer and APIC). + +## Kernel + +- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate: + ```diff + -bootloader = { version = "0.9.23", features = [...]} + +bootloader_api = "0.11" + ``` +- In your `main.rs`, adjust the import path and change the signature of the entry point function: + ```diff + -use bootloader::{entry_point, BootInfo}; + +use bootloader_api::{entry_point, BootInfo}; + + entry_point!(kernel_main); + + -fn kernel_main(boot_info: &'static BootInfo) -> ! { + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + ``` +- If you used optional features, such as `map_physical_memory`, you can enable them again through the `entry_point` macro: + ```rust + use bootloader_api::config::{BootloaderConfig, Mapping}; + + pub static BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::Dynamic); + config + }; + + // add a `config` argument to the `entry_point` macro call + entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + ``` + See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. +- The `v0.11` version of the bootloader sets up a pixel-based framebuffer instead of using the VGA text mode. This means that you have to rewrite your screen output code. See the [`common/logger.rs`](../../common/src/logger.rs) module for an example implementation based on the [`noto-sans-mono-bitmap`](https://docs.rs/noto-sans-mono-bitmap/latest/noto_sans_mono_bitmap/index.html) and [`log`](https://docs.rs/log/latest) crates. +- If you want to use UEFI booting, you cannot use the [legacy PIC](https://wiki.osdev.org/8259_PIC) for interrupt handling. Instead, you have to set up the [`APIC`](https://wiki.osdev.org/APIC). Unfortunately, we don't have a guide for this yet, but the basic steps are: + - The `boot_info.rsdp_addr` field will tell you the physical address of the [`RSDP`](https://wiki.osdev.org/RSDP) structure, which is part of the [`ACPI`](https://en.wikipedia.org/wiki/ACPI) standard. Note that `ACPI` (_Advanced Configuration and Power Interface_) and `APIC` (_Advanced Programmable Interrupt Controller_) are completely different things that just have confusingly similar acronyms. + - Use the [`acpi`](https://docs.rs/acpi/4.1.1/acpi/index.html) crate and its [`AcpiTables::from_rsdp`](https://docs.rs/acpi/4.1.1/acpi/struct.AcpiTables.html#method.from_rsdp) function to load the ACPI tables. Then use the [`platform_info`](https://docs.rs/acpi/4.1.1/acpi/struct.AcpiTables.html#method.platform_info) method and read the [`interrupt_model`](https://docs.rs/acpi/4.1.1/acpi/platform/struct.PlatformInfo.html#structfield.interrupt_model) field of the returned `PlatformInfo`. This field gives you all the necessary information about the system's interrupt controller. + - Parse and set up the local and IO APIC. We're working on a [crate](https://github.com/rust-osdev/apic) for that, but it's still in a very early state. We would love contributions! Alternatively, there are some other crates on crates.io that might work too. +- If you reload the GDT at some point, ensure that all segment registers are written, including `ss` and `ds`. The `v0.9` version of the bootloader used to initialize some of them to 0, but this is no longer the case. If you don't do this, [a general protection fault might happen on `iretq`](https://github.com/rust-osdev/bootloader/issues/196). + +To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. + +## Booting + +The `bootloader v0.11` release does not use the `bootimage` tool anymore. Instead, the [`bootloader`](https://docs.rs/bootloader/0.11) crate provides functions to create bootable disk images from a kernel. The basic idea is to build your kernel first and then invoke a builder function that calls the disk image creation functions of the `bootloader` crate. + +A good way to implement this is to move your kernel into a `kernel` subdirectory. Then you can create +a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. + +The files could look like this: + +```toml +# .cargo/config.toml + +[unstable] +# enable the unstable artifact-dependencies feature, see +# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies +bindeps = true +``` + +```toml +# Cargo.toml + +[package] +name = "os" # or any other name +version = "0.1.0" + +[build-dependencies] +bootloader = "0.11" +test-kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" } + +[dependencies] +# used for UEFI booting in QEMU +ovmf_prebuilt = "0.1.0-alpha.1" + +[workspace] +members = ["kernel"] +``` + +```rust +// build.rs + +fn main() { + // set by cargo, build scripts should use this directory for output files + let out_dir = env::var_os("OUT_DIR").unwrap(); + // set by cargo's artifact dependency feature, see + // https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies + let kernel = env!("CARGO_BIN_FILE_KERNEL"); + + // create an UEFI disk image (optional) + let uefi_path = out_dir.join("uefi.img"); + bootloader::UefiBoot::new(&kernel).create_disk_image(uefi_path).unwrap(); + + // create a BIOS disk image (optional) + let out_bios_path = out_dir.join("bios.img"); + bootloader::BiosBoot::new(&kernel).create_disk_image(uefi_path).unwrap(); + + // pass the disk image paths as env variables to the `main.rs` + println!("cargo:rustc-env=UEFI_PATH={}", uefi_path.display()); + println!("cargo:rustc-env=BIOS_PATH={}", bios_path.display()); +} +``` + +```rust +// src/main.rs + +fn main() { + // read env variables that were set in build script + let uefi_path = env!("UEFI_PATH"); + let bios_path = env!("BIOS_PATH"); + + // choose whether to start the UEFI or BIOS image + let uefi = true; + + let mut cmd = std::process::Command::new("qemu-system-x86_64"); + if uefi { + cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); + cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}")); + } else { + cmd.arg("-drive").arg(format!("format=raw,file={bios_path}")); + } + let mut child = cmd.spawn()?; + child.wait()?; +} +``` + +Now you should be able to use `cargo build` to create a bootable disk image and `cargo run` to run in QEMU. Your kernel is automatically recompiled when it changes. For more advanced usage, you can add command-line arguments to your `main.rs` to e.g. pass additional arguments to QEMU or to copy the disk images to some path to make it easier to find them (e.g. for copying them to an thumb drive). From 2ceec8a7062be71240eb1dd18888dd25225638b8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 16:34:35 +0100 Subject: [PATCH 224/226] Create migration guides and a disk image creation template --- README.md | 49 +++++++---- {doc => docs}/chainloading.md | 0 .../v0.10.md => docs/create-disk-image.md | 42 +--------- docs/migration/v0.10.md | 41 ++++++++++ {doc => docs}/migration/v0.9.md | 82 +------------------ 5 files changed, 79 insertions(+), 135 deletions(-) rename {doc => docs}/chainloading.md (100%) rename doc/migration/v0.10.md => docs/create-disk-image.md (60%) create mode 100644 docs/migration/v0.10.md rename {doc => docs}/migration/v0.9.md (59%) diff --git a/README.md b/README.md index 6050792f..8099df4c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ You need a nightly [Rust](https://www.rust-lang.org) compiler with the `llvm-too ## Usage +To use this crate, you need to adjust your kernel to be bootable first. Then you can create a bootable disk image from your compiled kernel. These steps are explained in detail below. + +If you're already using an older version of the `bootloader` crate, follow our [migration guides](doc/migration). + +### Kernel + To make your kernel compatible with `bootloader`: - Add a dependency on the `bootloader_api` crate in your kernel's `Cargo.toml`. @@ -29,24 +35,37 @@ To make your kernel compatible with `bootloader`: }; bootloader_api::entry_point!(kernel_main, config = &CONFIG); ``` -- Compile your kernel as normal to an ELF executable. The executable will contain a special section with metadata and the serialized config, which will enable the `bootloader` crate to load it. +- Compile your kernel to an ELF executable by running **`cargo build --target x86_64-unknown-none`**. You might need to run `rustup target add x86_64-unknown-none` before to download precompiled versions of the `core` and `alloc` crates. +- Thanks to the `entry_point` macro, the compiled executable contains a special section with metadata and the serialized config, which will enable the `bootloader` crate to load it. + +### Booting To combine your kernel with a bootloader and create a bootable disk image, follow these steps: -- Create a new runner crate, e.g. through `cargo new runner --bin`. -- Add the `bootloader` crate as a `dependency` in the `runner/Cargo.toml`. -- In the `main.rs`, invoke the build commands for your kernel. - - Alternatively, you can set up an [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) on your kernel, provided that you use a `rustup`-supported target for your kernel: - ```toml - [dependencies] - my-kernel = { path = "..", artifact = "bin", target = "x86_64-unknown-none" } - ``` -- After building your kernel, obtain the path to the kernel executable. - - When using an artifact dependency, you can retrieve this path using `env!("CARGO_BIN_FILE_MY_KERNEL_my-kernel")` -- Use the `bootloader::create_boot_partition` function to create a bootable FAT partition at some chosen path. -- Use one or multiple `bootloader::create_*_disk_image` functions to transform the bootable FAT partition into a disk image. - - Use the `bootloader::create_uefi_disk_image` function to create an UEFI-compatible GPT-formatted disk image. - - Use the `bootloader::create_bios_disk_image` function to create a BIOS-compatible MBR-formatted disk image. +- Move your full kernel code into a `kernel` subdirectory. +- Create a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). +- Add a `build-dependencies` on the `bootloader` crate. +- Create a [`build.rs`](https://doc.rust-lang.org/cargo/reference/build-scripts.html) build script. +- Set up an [artifact dependency](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) to add your `kernel` crate as a `build-dependency`: + ```toml + # in Cargo.toml + [build-dependencies] + kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" } + ``` + ```toml + # .cargo/config.toml + + [unstable] + # enable the unstable artifact-dependencies feature, see + # https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies + bindeps = true + ``` + Alternatively, you can use [`std::process::Command`](https://doc.rust-lang.org/stable/std/process/struct.Command.html) to invoke the build command of your kernel in the `build.rs` script. +- Obtain the path to the kernel executable. When using an artifact dependency, you can retrieve this path using `env!("CARGO_BIN_FILE_MY_KERNEL_my-kernel")` +- Use `bootloader::UefiBoot` and/or `bootloader::BiosBoot` to create a bootable disk image with your kernel. +- Do something with the bootable disk images in your `main.rs` function. For example, run them with QEMU. + +See our [disk image creation template](doc/create-disk-image.md) for a more detailed example. ## Architecture diff --git a/doc/chainloading.md b/docs/chainloading.md similarity index 100% rename from doc/chainloading.md rename to docs/chainloading.md diff --git a/doc/migration/v0.10.md b/docs/create-disk-image.md similarity index 60% rename from doc/migration/v0.10.md rename to docs/create-disk-image.md index a11b7795..67302b4a 100644 --- a/doc/migration/v0.10.md +++ b/docs/create-disk-image.md @@ -1,42 +1,6 @@ -# Migration from bootloader `v0.10` +# Template: Create a Disk Image -This guide summarizes the steps for migrating from `bootloader v0.10.X` to `bootloader v0.11`. - -## Kernel - -- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate and adjust the import path in your `main.rs`: - ```diff - # in Cargo.toml - - -bootloader = { version = "0.10.13" } - +bootloader_api = "0.11" - ``` - ```diff - // in main.rs - - -use bootloader::{entry_point, BootInfo}; - +use bootloader_api::{entry_point, BootInfo}; - ``` -- If you used optional features, such as `map-physical-memory`, you can enable them again through the `entry_point` macro: - ```rust - use bootloader_api::config::{BootloaderConfig, Mapping}; - - pub static BOOTLOADER_CONFIG: BootloaderConfig = { - let mut config = BootloaderConfig::new_default(); - config.mappings.physical_memory = Some(Mapping::Dynamic); - config - }; - - // add a `config` argument to the `entry_point` macro call - entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); - ``` - See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. - -To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. - -## Booting - -The `bootloader v0.11` release simplifies the disk image creation. The [`bootloader`](https://docs.rs/bootloader/0.11) crate now provides simple functions to create bootable disk images from a kernel. The basic idea is to build your kernel first and then invoke a builder function that calls the disk image creation functions of the `bootloader` crate. +The [`bootloader`](https://docs.rs/bootloader/0.11) crate provides simple functions to create bootable disk images from a kernel. The basic idea is to build your kernel first and then invoke a builder function that calls the disk image creation functions of the `bootloader` crate. A good way to implement this is to move your kernel into a `kernel` subdirectory. Then you can create a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. @@ -79,7 +43,7 @@ fn main() { let out_dir = env::var_os("OUT_DIR").unwrap(); // set by cargo's artifact dependency feature, see // https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies - let kernel = env!("CARGO_BIN_FILE_KERNEL"); + let kernel = env!("CARGO_BIN_FILE_KERNEL_kernel"); // create an UEFI disk image (optional) let uefi_path = out_dir.join("uefi.img"); diff --git a/docs/migration/v0.10.md b/docs/migration/v0.10.md new file mode 100644 index 00000000..7536f231 --- /dev/null +++ b/docs/migration/v0.10.md @@ -0,0 +1,41 @@ +# Migration from bootloader `v0.10` + +This guide summarizes the steps for migrating from `bootloader v0.10.X` to `bootloader v0.11`. + +## Kernel + +- Replace the `bootloader` dependency of your kernel with a dependency on the `bootloader_api` crate and adjust the import path in your `main.rs`: + ```diff + # in Cargo.toml + + -bootloader = { version = "0.10.13" } + +bootloader_api = "0.11" + ``` + ```diff + // in main.rs + + -use bootloader::{entry_point, BootInfo}; + +use bootloader_api::{entry_point, BootInfo}; + ``` +- If you used optional features, such as `map-physical-memory`, you can enable them again through the `entry_point` macro: + ```rust + use bootloader_api::config::{BootloaderConfig, Mapping}; + + pub static BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::Dynamic); + config + }; + + // add a `config` argument to the `entry_point` macro call + entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + ``` + See the [`BootloaderConfig`](https://docs.rs/bootloader_api/0.11/bootloader_api/config/struct.BootloaderConfig.html) struct for all configuration options. + +To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since the `x86_64-unknown-none` target is a Tier-2 target, there is no need for `bootimage`, `cargo-xbuild`, or `xargo` anymore. Instead, you can run `rustup target add x86_64-unknown-none` to download precompiled versions of the `core` and `alloc` crates. There is no need for custom JSON-based target files anymore. + +## Booting + +The `bootloader v0.11` release simplifies the disk image creation. The [`bootloader`](https://docs.rs/bootloader/0.11) crate now provides simple functions to create bootable disk images from a kernel. The basic idea is to build your kernel first and then invoke a builder function that calls the disk image creation functions of the `bootloader` crate. + +See our [disk image creation template](../create-disk-image.md) for a detailed explanation of the new build process. diff --git a/doc/migration/v0.9.md b/docs/migration/v0.9.md similarity index 59% rename from doc/migration/v0.9.md rename to docs/migration/v0.9.md index 636bed36..856b2154 100644 --- a/doc/migration/v0.9.md +++ b/docs/migration/v0.9.md @@ -46,84 +46,4 @@ To build your kernel, run **`cargo build --target x86_64-unknown-none`**. Since The `bootloader v0.11` release does not use the `bootimage` tool anymore. Instead, the [`bootloader`](https://docs.rs/bootloader/0.11) crate provides functions to create bootable disk images from a kernel. The basic idea is to build your kernel first and then invoke a builder function that calls the disk image creation functions of the `bootloader` crate. -A good way to implement this is to move your kernel into a `kernel` subdirectory. Then you can create -a new `os` crate at the top level that defines a [workspace](https://doc.rust-lang.org/cargo/reference/workspaces.html). The root package has build-dependencies on the `kernel` [artifact](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies) and on the bootloader crate. This allows you to create the bootable disk image in a [cargo build script](https://doc.rust-lang.org/cargo/reference/build-scripts.html) and launch the created image in QEMU in the `main` function. - -The files could look like this: - -```toml -# .cargo/config.toml - -[unstable] -# enable the unstable artifact-dependencies feature, see -# https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies -bindeps = true -``` - -```toml -# Cargo.toml - -[package] -name = "os" # or any other name -version = "0.1.0" - -[build-dependencies] -bootloader = "0.11" -test-kernel = { path = "kernel", artifact = "bin", target = "x86_64-unknown-none" } - -[dependencies] -# used for UEFI booting in QEMU -ovmf_prebuilt = "0.1.0-alpha.1" - -[workspace] -members = ["kernel"] -``` - -```rust -// build.rs - -fn main() { - // set by cargo, build scripts should use this directory for output files - let out_dir = env::var_os("OUT_DIR").unwrap(); - // set by cargo's artifact dependency feature, see - // https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#artifact-dependencies - let kernel = env!("CARGO_BIN_FILE_KERNEL"); - - // create an UEFI disk image (optional) - let uefi_path = out_dir.join("uefi.img"); - bootloader::UefiBoot::new(&kernel).create_disk_image(uefi_path).unwrap(); - - // create a BIOS disk image (optional) - let out_bios_path = out_dir.join("bios.img"); - bootloader::BiosBoot::new(&kernel).create_disk_image(uefi_path).unwrap(); - - // pass the disk image paths as env variables to the `main.rs` - println!("cargo:rustc-env=UEFI_PATH={}", uefi_path.display()); - println!("cargo:rustc-env=BIOS_PATH={}", bios_path.display()); -} -``` - -```rust -// src/main.rs - -fn main() { - // read env variables that were set in build script - let uefi_path = env!("UEFI_PATH"); - let bios_path = env!("BIOS_PATH"); - - // choose whether to start the UEFI or BIOS image - let uefi = true; - - let mut cmd = std::process::Command::new("qemu-system-x86_64"); - if uefi { - cmd.arg("-bios").arg(ovmf_prebuilt::ovmf_pure_efi()); - cmd.arg("-drive").arg(format!("format=raw,file={uefi_path}")); - } else { - cmd.arg("-drive").arg(format!("format=raw,file={bios_path}")); - } - let mut child = cmd.spawn()?; - child.wait()?; -} -``` - -Now you should be able to use `cargo build` to create a bootable disk image and `cargo run` to run in QEMU. Your kernel is automatically recompiled when it changes. For more advanced usage, you can add command-line arguments to your `main.rs` to e.g. pass additional arguments to QEMU or to copy the disk images to some path to make it easier to find them (e.g. for copying them to an thumb drive). +See our [disk image creation template](../create-disk-image.md) for a detailed explanation of the new build process. From bb9e7af871268ed57e1a80c663a4472a67e682e2 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 16:55:11 +0100 Subject: [PATCH 225/226] Remove unused imports --- bios/stage-2/src/protected_mode.rs | 6 +----- bios/stage-3/src/screen.rs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/bios/stage-2/src/protected_mode.rs b/bios/stage-2/src/protected_mode.rs index 405c5d1f..e1fe5780 100644 --- a/bios/stage-2/src/protected_mode.rs +++ b/bios/stage-2/src/protected_mode.rs @@ -1,9 +1,5 @@ use bootloader_x86_64_bios_common::BiosInfo; -use core::{ - arch::{asm, global_asm}, - fmt::Write as _, - mem::size_of, -}; +use core::{arch::asm, mem::size_of}; static GDT: GdtProtectedMode = GdtProtectedMode::new(); diff --git a/bios/stage-3/src/screen.rs b/bios/stage-3/src/screen.rs index b6e6f016..6e81bf1d 100644 --- a/bios/stage-3/src/screen.rs +++ b/bios/stage-3/src/screen.rs @@ -1,6 +1,6 @@ use bootloader_x86_64_bios_common::{racy_cell::RacyCell, BiosFramebufferInfo, PixelFormat}; use core::{fmt, ptr}; -use noto_sans_mono_bitmap::{get_bitmap, get_bitmap_width, BitmapChar, BitmapHeight, FontWeight}; +use noto_sans_mono_bitmap::{get_bitmap, BitmapChar, BitmapHeight, FontWeight}; static WRITER: RacyCell> = RacyCell::new(None); pub struct Writer; From 2ca6ebd4d9a8e8b52fa48eaae8dab476b3bee8c9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sun, 13 Nov 2022 20:42:24 +0100 Subject: [PATCH 226/226] Resolve all remaining warnings and fix build with `--all-targets` Makes the `panic` implementations conditional to fix errors when building with `cargo check --all-targets --all`. This also fixes the remaining rust-analyzer errors. --- Cargo.toml | 1 - api/src/config.rs | 5 +++++ api/src/info.rs | 16 +++++++++++++++- bios/stage-2/src/screen.rs | 2 +- bios/stage-4/src/main.rs | 2 +- common/src/lib.rs | 13 ++++++++++++- tests/runner/Cargo.toml | 2 +- tests/test_kernels/default_settings/Cargo.toml | 7 +++++-- .../default_settings/src/bin/basic_boot.rs | 4 ++-- .../default_settings/src/bin/check_boot_info.rs | 4 ++-- .../default_settings/src/bin/should_panic.rs | 6 +++--- tests/test_kernels/higher_half/Cargo.toml | 2 +- .../higher_half/src/bin/basic_boot.rs | 4 ++-- .../higher_half/src/bin/check_boot_info.rs | 4 ++-- .../higher_half/src/bin/should_panic.rs | 8 +++++--- .../higher_half/src/bin/verify_higher_half.rs | 4 ++-- tests/test_kernels/lto/src/bin/basic_boot.rs | 4 ++-- tests/test_kernels/map_phys_mem/Cargo.toml | 2 +- .../map_phys_mem/src/bin/access_phys_mem.rs | 7 ++++--- .../map_phys_mem/src/bin/check_boot_info.rs | 7 ++++--- tests/test_kernels/pie/src/bin/basic_boot.rs | 4 ++-- .../test_kernels/pie/src/bin/check_boot_info.rs | 4 ++-- .../test_kernels/pie/src/bin/global_variable.rs | 8 +++----- tests/test_kernels/pie/src/bin/should_panic.rs | 7 ++++--- uefi/src/main.rs | 7 +++++-- 25 files changed, 86 insertions(+), 48 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 55db05f7..0f3e49c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ license = "MIT/Apache-2.0" description = "An experimental x86_64 bootloader that works on both BIOS and UEFI systems." repository = "https://github.com/rust-osdev/bootloader" edition = "2021" -resolver = "2" [workspace] members = [ diff --git a/api/src/config.rs b/api/src/config.rs index 5ca235ab..eadd74d2 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -282,6 +282,7 @@ impl Default for BootloaderConfig { } } +/// A semver-compatible version. #[derive(Debug, PartialEq, Eq, Clone, Copy)] #[repr(C)] pub struct ApiVersion { @@ -318,18 +319,22 @@ impl ApiVersion { } } + /// Returns the major version number. pub fn version_major(&self) -> u16 { self.version_major } + /// Returns the minor version number. pub fn version_minor(&self) -> u16 { self.version_minor } + /// Returns the patch version number. pub fn version_patch(&self) -> u16 { self.version_patch } + /// Returns whether this version is a pre-release, e.g., an alpha version. pub fn pre_release(&self) -> bool { self.pre_release } diff --git a/api/src/info.rs b/api/src/info.rs index b3db8d4f..248362ab 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -20,6 +20,7 @@ use crate::config::ApiVersion; #[repr(C)] #[non_exhaustive] pub struct BootInfo { + /// The version of the `bootloader_api` crate. Must match the `bootloader` version. pub api_version: ApiVersion, /// A map of the physical memory regions of the underlying machine. /// @@ -54,6 +55,9 @@ pub struct BootInfo { } impl BootInfo { + /// Create a new boot info structure with the given memory map. + /// + /// The other fields are initialized with default values. pub fn new(memory_regions: MemoryRegions) -> Self { Self { api_version: ApiVersion::new_default(), @@ -162,7 +166,13 @@ pub struct FrameBuffer { } impl FrameBuffer { - pub fn new(buffer_start: u64, info: FrameBufferInfo) -> Self { + /// Creates a new framebuffer instance. + /// + /// ## Safety + /// + /// The given start address and info must describe a valid, accessible, and unaliased + /// framebuffer. + pub unsafe fn new(buffer_start: u64, info: FrameBufferInfo) -> Self { Self { buffer_start, info } } @@ -232,9 +242,13 @@ pub enum PixelFormat { /// Length might be larger than 1, check [`bytes_per_pixel`][FrameBufferInfo::bytes_per_pixel] /// for this. U8, + /// Unknown pixel format. Unknown { + /// Bit offset of the red value. red_position: u8, + /// Bit offset of the green value. green_position: u8, + /// Bit offset of the blue value. blue_position: u8, }, } diff --git a/bios/stage-2/src/screen.rs b/bios/stage-2/src/screen.rs index e566c3c7..78e5a790 100644 --- a/bios/stage-2/src/screen.rs +++ b/bios/stage-2/src/screen.rs @@ -29,8 +29,8 @@ impl Write for Writer { } } +#[cfg(all(not(test), target_os = "none"))] #[panic_handler] -#[cfg(not(test))] pub fn panic(info: &core::panic::PanicInfo) -> ! { let _ = write!(Writer, "\nPANIC: "); if let Some(location) = info.location() { diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 86a97328..ce2237f2 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -227,8 +227,8 @@ fn detect_rsdp() -> Option { } } +#[cfg(all(not(test), target_os = "none"))] #[panic_handler] -#[cfg(not(test))] fn panic(info: &core::panic::PanicInfo) -> ! { unsafe { bootloader_x86_64_common::logger::LOGGER diff --git a/common/src/lib.rs b/common/src/lib.rs index 8b49945f..4a204972 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -447,7 +447,18 @@ where let mut info = BootInfo::new(memory_regions.into()); info.framebuffer = mappings .framebuffer - .map(|addr| FrameBuffer::new(addr.as_u64(), system_info.framebuffer.expect("there shouldn't be a mapping for the framebuffer if there is not framebuffer").info)) + .map(|addr| unsafe { + FrameBuffer::new( + addr.as_u64(), + system_info + .framebuffer + .expect( + "there shouldn't be a mapping for the framebuffer if there is \ + no framebuffer", + ) + .info, + ) + }) .into(); info.physical_memory_offset = mappings.physical_memory_offset.map(VirtAddr::as_u64).into(); info.recursive_index = mappings.recursive_index.map(Into::into).into(); diff --git a/tests/runner/Cargo.toml b/tests/runner/Cargo.toml index 67c0f979..51421aa0 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -2,7 +2,7 @@ name = "bootloader_test_runner" version = "0.1.0" authors = ["Philipp Oppermann "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/tests/test_kernels/default_settings/Cargo.toml b/tests/test_kernels/default_settings/Cargo.toml index 72229755..81e1be2f 100644 --- a/tests/test_kernels/default_settings/Cargo.toml +++ b/tests/test_kernels/default_settings/Cargo.toml @@ -2,9 +2,12 @@ name = "test_kernel_default_settings" version = "0.1.0" authors = ["Philipp Oppermann "] -edition = "2018" +edition = "2021" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = ["instructions", "inline_asm"] } +x86_64 = { version = "0.14.7", default-features = false, features = [ + "instructions", + "inline_asm", +] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/default_settings/src/bin/basic_boot.rs b/tests/test_kernels/default_settings/src/bin/basic_boot.rs index a31d9528..e6bd3a0b 100644 --- a/tests/test_kernels/default_settings/src/bin/basic_boot.rs +++ b/tests/test_kernels/default_settings/src/bin/basic_boot.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; use test_kernel_default_settings::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -13,7 +12,8 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { /// This function is called on panic. #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_default_settings::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs index ad76a8ba..df1721d6 100644 --- a/tests/test_kernels/default_settings/src/bin/check_boot_info.rs +++ b/tests/test_kernels/default_settings/src/bin/check_boot_info.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; -use core::panic::PanicInfo; use test_kernel_default_settings::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -41,8 +40,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_default_settings::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/default_settings/src/bin/should_panic.rs b/tests/test_kernels/default_settings/src/bin/should_panic.rs index b79c7ecd..0f78d0ba 100644 --- a/tests/test_kernels/default_settings/src/bin/should_panic.rs +++ b/tests/test_kernels/default_settings/src/bin/should_panic.rs @@ -2,8 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_default_settings::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -12,7 +10,9 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(_info: &PanicInfo) -> ! { +fn panic(_info: &core::panic::PanicInfo) -> ! { + use test_kernel_default_settings::{exit_qemu, QemuExitCode}; exit_qemu(QemuExitCode::Success); } diff --git a/tests/test_kernels/higher_half/Cargo.toml b/tests/test_kernels/higher_half/Cargo.toml index 8c4b5036..aedecae0 100644 --- a/tests/test_kernels/higher_half/Cargo.toml +++ b/tests/test_kernels/higher_half/Cargo.toml @@ -4,7 +4,7 @@ cargo-features = ["profile-rustflags"] name = "test_kernel_higher_half" version = "0.1.0" authors = ["Philipp Oppermann "] -edition = "2018" +edition = "2021" [dependencies] bootloader_api = { path = "../../../api" } diff --git a/tests/test_kernels/higher_half/src/bin/basic_boot.rs b/tests/test_kernels/higher_half/src/bin/basic_boot.rs index 5651bef4..4133963e 100644 --- a/tests/test_kernels/higher_half/src/bin/basic_boot.rs +++ b/tests/test_kernels/higher_half/src/bin/basic_boot.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); @@ -12,7 +11,8 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(_info: &PanicInfo) -> ! { +fn panic(_info: &core::panic::PanicInfo) -> ! { exit_qemu(QemuExitCode::Failed); } diff --git a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs index 3ed1f4ef..c35bb534 100644 --- a/tests/test_kernels/higher_half/src/bin/check_boot_info.rs +++ b/tests/test_kernels/higher_half/src/bin/check_boot_info.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; -use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); @@ -41,8 +40,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_higher_half::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/higher_half/src/bin/should_panic.rs b/tests/test_kernels/higher_half/src/bin/should_panic.rs index dcc8ddfa..44287806 100644 --- a/tests/test_kernels/higher_half/src/bin/should_panic.rs +++ b/tests/test_kernels/higher_half/src/bin/should_panic.rs @@ -2,8 +2,7 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_higher_half::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; +use test_kernel_higher_half::BOOTLOADER_CONFIG; entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); @@ -12,7 +11,10 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(_info: &PanicInfo) -> ! { +fn panic(_info: &core::panic::PanicInfo) -> ! { + use test_kernel_higher_half::{exit_qemu, QemuExitCode}; + exit_qemu(QemuExitCode::Success); } diff --git a/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs index 7ffd7986..930ff40d 100644 --- a/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs +++ b/tests/test_kernels/higher_half/src/bin/verify_higher_half.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; use test_kernel_higher_half::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); @@ -30,8 +29,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_higher_half::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/lto/src/bin/basic_boot.rs b/tests/test_kernels/lto/src/bin/basic_boot.rs index be961aef..987f6781 100644 --- a/tests/test_kernels/lto/src/bin/basic_boot.rs +++ b/tests/test_kernels/lto/src/bin/basic_boot.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; use test_kernel_lto::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -12,8 +11,9 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_lto::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/map_phys_mem/Cargo.toml b/tests/test_kernels/map_phys_mem/Cargo.toml index b00cc996..51233f1c 100644 --- a/tests/test_kernels/map_phys_mem/Cargo.toml +++ b/tests/test_kernels/map_phys_mem/Cargo.toml @@ -2,7 +2,7 @@ name = "test_kernel_map_phys_mem" version = "0.1.0" authors = ["Philipp Oppermann "] -edition = "2018" +edition = "2021" [target.'cfg(target_arch = "x86_64")'.dependencies] bootloader_api = { path = "../../../api" } diff --git a/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs b/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs index f51d5d25..2b906603 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/access_phys_mem.rs @@ -2,8 +2,7 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode, BOOTLOADER_CONFIG}; +use test_kernel_map_phys_mem::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); @@ -17,9 +16,11 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; + use test_kernel_map_phys_mem::serial; let _ = writeln!(serial(), "PANIC: {}", info); exit_qemu(QemuExitCode::Failed); diff --git a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs index 0cbf3bae..7fa487b7 100644 --- a/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs +++ b/tests/test_kernels/map_phys_mem/src/bin/check_boot_info.rs @@ -2,8 +2,7 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode, BOOTLOADER_CONFIG}; +use test_kernel_map_phys_mem::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); @@ -44,9 +43,11 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; + use test_kernel_map_phys_mem::serial; let _ = writeln!(serial(), "PANIC: {}", info); exit_qemu(QemuExitCode::Failed); diff --git a/tests/test_kernels/pie/src/bin/basic_boot.rs b/tests/test_kernels/pie/src/bin/basic_boot.rs index 261e5236..2201d179 100644 --- a/tests/test_kernels/pie/src/bin/basic_boot.rs +++ b/tests/test_kernels/pie/src/bin/basic_boot.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; use test_kernel_pie::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -12,8 +11,9 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_pie::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/pie/src/bin/check_boot_info.rs b/tests/test_kernels/pie/src/bin/check_boot_info.rs index e110e115..a7b3a5e3 100644 --- a/tests/test_kernels/pie/src/bin/check_boot_info.rs +++ b/tests/test_kernels/pie/src/bin/check_boot_info.rs @@ -2,7 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; -use core::panic::PanicInfo; use test_kernel_pie::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -41,8 +40,9 @@ fn kernel_main(boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_pie::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/pie/src/bin/global_variable.rs b/tests/test_kernels/pie/src/bin/global_variable.rs index dad5869e..cba6338b 100644 --- a/tests/test_kernels/pie/src/bin/global_variable.rs +++ b/tests/test_kernels/pie/src/bin/global_variable.rs @@ -2,10 +2,7 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::{ - panic::PanicInfo, - sync::atomic::{AtomicU64, Ordering}, -}; +use core::sync::atomic::{AtomicU64, Ordering}; use test_kernel_pie::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -29,8 +26,9 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(info: &PanicInfo) -> ! { +fn panic(info: &core::panic::PanicInfo) -> ! { use core::fmt::Write; let _ = writeln!(test_kernel_pie::serial(), "PANIC: {}", info); diff --git a/tests/test_kernels/pie/src/bin/should_panic.rs b/tests/test_kernels/pie/src/bin/should_panic.rs index 3c82784c..fb08bf5c 100644 --- a/tests/test_kernels/pie/src/bin/should_panic.rs +++ b/tests/test_kernels/pie/src/bin/should_panic.rs @@ -2,8 +2,6 @@ #![no_main] // disable all Rust-level entry points use bootloader_api::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_pie::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -12,7 +10,10 @@ fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { } /// This function is called on panic. +#[cfg(not(test))] #[panic_handler] -fn panic(_info: &PanicInfo) -> ! { +fn panic(_info: &core::panic::PanicInfo) -> ! { + use test_kernel_pie::{exit_qemu, QemuExitCode}; + exit_qemu(QemuExitCode::Success); } diff --git a/uefi/src/main.rs b/uefi/src/main.rs index f72996bd..f86d8334 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -8,7 +8,7 @@ use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; use bootloader_x86_64_common::{ legacy_memory_region::LegacyFrameAllocator, Kernel, RawFrameBufferInfo, SystemInfo, }; -use core::{arch::asm, cell::UnsafeCell, fmt::Write, mem, panic::PanicInfo, ptr, slice}; +use core::{cell::UnsafeCell, fmt::Write, mem, ptr, slice}; use uefi::{ prelude::{entry, Boot, Handle, Status, SystemTable}, proto::{ @@ -428,8 +428,11 @@ fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option ! { +fn panic(info: &core::panic::PanicInfo) -> ! { + use core::arch::asm; + if let Some(st) = unsafe { &mut *SYSTEM_TABLE.get() } { let _ = writeln!(st.stdout(), "{}", info); }