diff --git a/.cargo/config.toml b/.cargo/config.toml index 7e0eed7a..dfa84e85 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 --" +[unstable] +bindeps = true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1647e27..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: @@ -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) @@ -62,30 +53,22 @@ 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: "-- --test-threads 1" + args: -p bootloader_api - - name: "Example: `basic`" - working-directory: examples/basic - run: cargo kimage - - - name: "Example: `test_framework` example" - working-directory: examples/test_framework - run: cargo ktest + - name: Run integration tests + uses: actions-rs/cargo@v1 + with: + command: test fmt: name: Check Formatting 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: @@ -97,11 +80,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: diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 309b3c40..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "rust-analyzer.checkOnSave.allTargets": false, - "rust-analyzer.checkOnSave.extraArgs": [ - "-Zbuild-std=core,alloc" - ] -} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 276ac332..6db246ac 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,44 +4,30 @@ version = 3 [[package]] name = "anyhow" -version = "1.0.40" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b" +checksum = "4361135be9122e0870de935d7c439aef945b9f9ddd4199a553b5270b49c82a27" [[package]] -name = "argh" -version = "0.1.4" +name = "arrayvec" +version = "0.5.2" 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", -] +checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" [[package]] -name = "argh_shared" -version = "0.1.4" +name = "autocfg" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "781f336cc9826dbaddb9754cb5db61e64cab4f69668bd19dcc4a0394a86f4cb1" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "autocfg" -version = "1.0.1" +name = "bincode" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] [[package]] name = "bit_field" @@ -51,48 +37,119 @@ 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 = "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.10.13" +version = "0.11.0-alpha" dependencies = [ "anyhow", - "argh", - "bit_field", - "conquer-once", - "displaydoc", + "bootloader_test_runner", "fatfs", "gpt", - "json", "llvm-tools", + "mbrman", + "tempfile", + "test_kernel_default_settings", + "test_kernel_higher_half", + "test_kernel_map_phys_mem", + "test_kernel_pie", +] + +[[package]] +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", +] + +[[package]] +name = "bootloader-x86_64-bios-stage-3" +version = "0.1.0" +dependencies = [ + "bootloader-x86_64-bios-common", + "noto-sans-mono-bitmap 0.1.6", +] + +[[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", - "noto-sans-mono-bitmap", - "proc-macro2", - "quote", + "rsdp", + "usize_conversions", + "x86_64", +] + +[[package]] +name = "bootloader-x86_64-common" +version = "0.1.0-alpha.0" +dependencies = [ + "bootloader_api", + "conquer-once", + "log", + "noto-sans-mono-bitmap 0.2.0", "rand", - "rand_chacha", + "rand_hc", "raw-cpuid", - "rsdp", - "serde", "spinning_top", - "thiserror", - "toml", - "uefi", "usize_conversions", "x86_64", "xmas-elf", ] [[package]] -name = "bootloader-locator" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aaaa9db3339d32c2622f2e5d0731eb82a468d3439797c9d4fe426744fe2bd551" +name = "bootloader-x86_64-uefi" +version = "0.1.0-alpha.0" +dependencies = [ + "bootloader-x86_64-common", + "bootloader_api", + "log", + "uefi", + "x86_64", +] + +[[package]] +name = "bootloader_api" +version = "0.1.0-alpha.0" dependencies = [ - "json", + "rand", +] + +[[package]] +name = "bootloader_test_runner" +version = "0.1.0" +dependencies = [ + "bootloader", + "ovmf-prebuilt", + "strip-ansi-escapes", ] [[package]] @@ -128,18 +185,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" @@ -151,14 +208,12 @@ dependencies = [ ] [[package]] -name = "displaydoc" -version = "0.1.7" +name = "fastrand" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adc2ab4d5a16117f9029e9a6b5e4e79f4c67f6519bc134210d4d4a04ba31f41b" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" dependencies = [ - "proc-macro2", - "quote", - "syn", + "instant", ] [[package]] @@ -173,11 +228,17 @@ 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.3" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753" +checksum = "9be70c98951c83b8d2f8f60d7065fa6d5146873094452a1008da8c2f1e4205ad" dependencies = [ "cfg-if", "libc", @@ -186,9 +247,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", @@ -197,25 +258,19 @@ dependencies = [ ] [[package]] -name = "heck" -version = "0.3.2" +name = "instant" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ - "unicode-segmentation", + "cfg-if", ] -[[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" +version = "0.2.122" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +checksum = "ec647867e2bf0772e28c8bcde4f0d19a9216916e890543b5a03ed8ef27b8f259" [[package]] name = "llvm-tools" @@ -224,32 +279,51 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955be5d0ca0465caf127165acb47964f911e2bc26073e865deb8be7189302faf" [[package]] -name = "locate-cargo-manifest" -version = "0.2.2" +name = "lock_api" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db985b63431fe09e8d71f50aeceffcc31e720cb86be8dad2f38d084c5a328466" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" dependencies = [ - "json", + "autocfg", + "scopeguard", ] [[package]] -name = "lock_api" -version = "0.4.4" +name = "log" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" dependencies = [ - "scopeguard", + "cfg-if", ] [[package]] -name = "log" -version = "0.4.14" +name = "mbr-nostd" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" +checksum = "70e7b12e539fe14d6423dc8864bd5b5fc1f04fd14f5d9152a137de285e4329f7" dependencies = [ - "cfg-if", + "byteorder", +] + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f45ec3ff6dc7bea48d52dd14a5565be0d596ac4758c9a55e42539177001a983b" + [[package]] name = "noto-sans-mono-bitmap" version = "0.2.0" @@ -275,36 +349,50 @@ 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.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.27" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0d8caf72986c1a598726adc988bb5984792ef84f5ee5aa50209145ee8077038" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" dependencies = [ - "unicode-xid", + "unicode-ident", ] [[package]] name = "quote" -version = "1.0.9" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" 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.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] @@ -323,31 +411,53 @@ 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 = "raw-cpuid" -version = "10.2.0" +version = "10.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929f54e29691d4e6a9cc558479de70db7aa3d98cd6fe7ab86d7507aa2886b9d2" +checksum = "738bc47119e3eeccc7e94c4a506901aea5e7b4944ecd0829cbebf4af04ceda12" dependencies = [ "bitflags", ] [[package]] -name = "rsdp" -version = "1.1.0" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f4f3deb5b80701c91a04117876f7d44996b2da123a822054166180e970f5226" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "log", + "bitflags", ] [[package]] -name = "runner" -version = "0.1.0" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66d3add2fc55ef37511bcf81a08ee7a09eff07b23aae38b06a29024a38c604b1" dependencies = [ - "bootloader-locator", - "locate-cargo-manifest", + "log", ] [[package]] @@ -364,18 +474,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.126" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -391,22 +501,51 @@ 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.72" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote", - "unicode-xid", + "unicode-ident", +] + +[[package]] +name = "tap" +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" dependencies = [ - "bootloader", + "bootloader_api", "uart_16550", "x86_64", ] @@ -415,7 +554,16 @@ dependencies = [ name = "test_kernel_higher_half" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", + "uart_16550", + "x86_64", +] + +[[package]] +name = "test_kernel_lto" +version = "0.1.0" +dependencies = [ + "bootloader_api", "uart_16550", "x86_64", ] @@ -424,7 +572,7 @@ dependencies = [ name = "test_kernel_map_phys_mem" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", "uart_16550", "x86_64", ] @@ -433,25 +581,25 @@ dependencies = [ name = "test_kernel_pie" version = "0.1.0" dependencies = [ - "bootloader", + "bootloader_api", "uart_16550", "x86_64", ] [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -468,20 +616,11 @@ 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" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "503a6c0e6d82daa87985e662d120c0176b09587c92a68db22781b28ae95405dd" +checksum = "3616395dbb38a9c39a5865b5691c21d9f8369ba876355cfef8ce39d0d4cf3281" dependencies = [ "bitflags", "x86_64", @@ -498,9 +637,9 @@ dependencies = [ [[package]] name = "uefi" -version = "0.11.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4630a92e80ac72f2b3dedb865dac3cf9e0215ce7e222301f0a37d8e6e3c5dbf4" +checksum = "705535cf386e4b033cc7acdea55ec8710f3dde2f07457218791aac35c83be21f" dependencies = [ "bitflags", "log", @@ -510,9 +649,9 @@ dependencies = [ [[package]] name = "uefi-macros" -version = "0.3.3" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcca10ca861f34a320d178f3fdb29ffbf05087fc2c70d2a99860e3329bee1a8" +checksum = "0b9917831bc5abb78c2e6a0f4fba2be165105ed53d288718c999e0efbd433bb7" dependencies = [ "proc-macro2", "quote", @@ -520,22 +659,22 @@ dependencies = [ ] [[package]] -name = "unicode-segmentation" -version = "1.7.1" +name = "unicode-ident" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] -name = "unicode-xid" -version = "0.2.2" +name = "usize_conversions" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +checksum = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" [[package]] -name = "usize_conversions" +name = "utf8parse" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" +checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" [[package]] name = "uuid" @@ -552,6 +691,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" @@ -580,6 +740,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 042a6d34..0f3e49c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,89 +1,45 @@ +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." repository = "https://github.com/rust-osdev/bootloader" -edition = "2018" -build = "build.rs" +edition = "2021" [workspace] members = [ + "api", + "common", + "uefi", + "bios/boot_sector", + "bios/stage-*", + "bios/common", "tests/runner", "tests/test_kernels/default_settings", "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", -] - -[[bin]] -name = "builder" -required-features = ["builder"] +exclude = ["examples/basic", "examples/test_framework"] -[[bin]] -name = "bios" -required-features = ["bios_bin"] - -[[bin]] -name = "uefi" -required-features = ["uefi_bin"] [dependencies] -xmas-elf = { version = "0.8.0", optional = true } -x86_64 = { version = "0.14.9", optional = true, default-features = false, features = ["instructions", "inline_asm", "step_trait"] } -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 } -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 = "2.0.0", optional = true } -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 } - -[dependencies.noto-sans-mono-bitmap] -version = "0.2.0" -default-features = false -features = [ - "regular", - "size_16", - "unicode-basic-latin", - # required for the fallback char '�' - "unicode-specials" -] -optional = true +anyhow = "1.0.32" +fatfs = "0.3.4" +gpt = "3.0.0" +mbrman = "0.4.2" +tempfile = "3.3.0" -[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 } - -[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", "noto-sans-mono-bitmap", "quote", "proc-macro2", "raw-cpuid", "rand", - "rand_chacha" -] +[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" } [profile.dev] panic = "abort" @@ -94,6 +50,48 @@ lto = false debug = true overflow-checks = true +[profile.stage-1] +inherits = "release" +opt-level = "s" +lto = true +codegen-units = 1 +debug = false +overflow-checks = false + +[profile.stage-2] +inherits = "release" +opt-level = "s" +codegen-units = 1 +debug = false +overflow-checks = true + +[profile.stage-3] +inherits = "release" +debug = true +overflow-checks = true + +[profile.stage-4] +inherits = "release" +debug = true +overflow-checks = true + +[profile.lto] +inherits = "release" +lto = true + +[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", +] + +[build-dependencies] +llvm-tools = "0.1.1" + [package.metadata.docs.rs] default-target = "x86_64-unknown-linux-gnu" diff --git a/README.md b/README.md index 56540e77..8099df4c 100644 --- a/README.md +++ b/README.md @@ -12,60 +12,75 @@ 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 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`. +- 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 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: + +- 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 -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 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. ## License diff --git a/api/Cargo.toml b/api/Cargo.toml new file mode 100644 index 00000000..a2fe956f --- /dev/null +++ b/api/Cargo.toml @@ -0,0 +1,13 @@ +[package] +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 + +[dependencies] + +[dev-dependencies] +rand = "0.8.4" diff --git a/api/build.rs b/api/build.rs new file mode 100644 index 00000000..142c2e72 --- /dev/null +++ b/api/build.rs @@ -0,0 +1,68 @@ +use std::{env, fs, path::Path}; + +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, 1), + (79, 9), + (88, 9), + (97, 9), + (106, 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"); + + 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 new file mode 100644 index 00000000..eadd74d2 --- /dev/null +++ b/api/src/config.rs @@ -0,0 +1,570 @@ +use crate::{concat::*, version_info}; + +/// Allows configuring the bootloader behavior. +/// +/// TODO: describe use together with `entry_point` macro +/// TODO: example +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[non_exhaustive] +pub struct BootloaderConfig { + /// 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, +} + +impl BootloaderConfig { + pub(crate) const UUID: [u8; 16] = [ + 0x74, 0x3C, 0xA9, 0x61, 0x09, 0x36, 0x46, 0xA0, 0xBB, 0x55, 0x5C, 0x15, 0x89, 0x15, 0x25, + 0x3D, + ]; + #[doc(hidden)] + pub const SERIALIZED_LEN: usize = 115; + + /// 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: 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, + mappings, + kernel_stack_size, + frame_buffer, + } = self; + let ApiVersion { + version_major, + version_minor, + version_patch, + pre_release, + } = version; + let Mappings { + kernel_stack, + boot_info, + framebuffer, + physical_memory, + page_table_recursive, + aslr, + dynamic_range_start, + dynamic_range_end, + } = 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_1(buf, [(*aslr) as u8]); + let buf = concat_79_9( + buf, + match dynamic_range_start { + Option::None => [0; 9], + Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), + }, + ); + let buf = concat_88_9( + buf, + match dynamic_range_end { + Option::None => [0; 9], + Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), + }, + ); + + let buf = concat_97_9( + buf, + match minimum_framebuffer_height { + Option::None => [0; 9], + Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), + }, + ); + let buf = concat_106_9( + buf, + match minimum_framebuffer_width { + Option::None => [0; 9], + Option::Some(addr) => concat_1_8([1], addr.to_le_bytes()), + }, + ); + + 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("invalid len"); + } + + let s = serialized; + + let (uuid, s) = split_array_ref(s); + if uuid != &Self::UUID { + return Err("invalid UUID"); + } + + let (version, s) = { + 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, + _ => return Err("invalid pre 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), + pre_release: pre, + }; + (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) = { + 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 (&[alsr], s) = split_array_ref(s); + let (&dynamic_range_start_some, s) = split_array_ref(s); + let (&dynamic_range_start, s) = split_array_ref(s); + let (&dynamic_range_end_some, s) = split_array_ref(s); + let (&dynamic_range_end, s) = split_array_ref(s); + + 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("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("invalid page table recursive value"), + }, + aslr: match alsr { + 1 => true, + 0 => false, + _ => 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("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("invalid dynamic range end value"), + }, + }; + (mappings, s) + }; + + let (frame_buffer, s) = { + 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 { + [0] if min_framebuffer_height == [0; 8] => Option::None, + [1] => Option::Some(u64::from_le_bytes(min_framebuffer_height)), + _ => 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("minimum_framebuffer_width invalid"), + }, + }; + (frame_buffer, s) + }; + + if !s.is_empty() { + return Err("unexpected rest"); + } + + Ok(Self { + version, + kernel_stack_size: u64::from_le_bytes(kernel_stack_size), + mappings, + frame_buffer, + }) + } + + #[cfg(test)] + fn random() -> Self { + Self { + version: ApiVersion::random(), + mappings: Mappings::random(), + kernel_stack_size: rand::random(), + frame_buffer: FrameBuffer::random(), + } + } +} + +impl Default for BootloaderConfig { + fn default() -> Self { + Self::new_default() + } +} + +/// A semver-compatible version. +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[repr(C)] +pub struct ApiVersion { + /// Bootloader version (major). + version_major: u16, + /// Bootloader version (minor). + version_minor: u16, + /// Bootloader version (patch). + version_patch: u16, + /// 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. + pre_release: bool, +} + +impl ApiVersion { + pub(crate) const fn new_default() -> Self { + Self { + version_major: version_info::VERSION_MAJOR, + version_minor: version_info::VERSION_MINOR, + version_patch: version_info::VERSION_PATCH, + pre_release: version_info::VERSION_PRE, + } + } + + #[cfg(test)] + fn random() -> ApiVersion { + Self { + version_major: rand::random(), + version_minor: rand::random(), + version_patch: rand::random(), + pre_release: rand::random(), + } + } + + /// 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 + } +} + +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, Clone, Copy)] +#[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, + /// Whether to randomize non-statically configured addresses. + /// The kernel base address will be randomized when it's compiled as + /// a position independent executable. + /// + /// Defaults to `false`. + pub aslr: bool, + /// The lowest virtual address for dynamic addresses. + /// + /// Defaults to `0`. + pub dynamic_range_start: Option, + /// The highest virtual address for dynamic addresses. + /// + /// Defaults to `0xffff_ffff_ffff_f000`. + pub dynamic_range_end: 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(), + boot_info: Mapping::new_default(), + framebuffer: Mapping::new_default(), + physical_memory: Option::None, + page_table_recursive: Option::None, + aslr: false, + dynamic_range_start: None, + dynamic_range_end: 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 + }, + aslr: rand::random(), + dynamic_range_start: if rand::random() { + Option::Some(rand::random()) + } else { + Option::None + }, + dynamic_range_end: if rand::random() { + Option::Some(rand::random()) + } else { + Option::None + }, + } + } +} + +/// Configuration for the frame buffer used for graphical output. +#[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. + /// + /// 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, + 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 + }, + } + } +} + +/// 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 + } + + #[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()), + } + } + + 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("invalid mapping format"); + } + + match variant { + [0] if addr == [0; 8] => Ok(Mapping::Dynamic), + [1] => Ok(Mapping::FixedAddress(u64::from_le_bytes(addr))), + _ => Err("invalid mapping value"), + } + } +} + +impl Default for Mapping { + fn default() -> Self { + Self::new_default() + } +} + +/// 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::*; + + #[test] + fn mapping_serde() { + for _ in 0..10000 { + let config = Mapping::random(); + assert_eq!(Mapping::deserialize(&config.serialize()), Ok(config)); + } + } + + #[test] + fn config_serde() { + for _ in 0..10000 { + let config = BootloaderConfig::random(); + assert_eq!( + BootloaderConfig::deserialize(&config.serialize()), + Ok(config) + ); + } + } +} diff --git a/src/boot_info.rs b/api/src/info.rs similarity index 84% rename from src/boot_info.rs rename to api/src/info.rs index adb97bc6..248362ab 100644 --- a/src/boot_info.rs +++ b/api/src/info.rs @@ -1,12 +1,14 @@ 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 /// have the following signature: /// /// ``` -/// # use bootloader::BootInfo; +/// # use bootloader_api::BootInfo; /// # type _SIGNATURE = /// extern "C" fn(boot_info: &'static mut BootInfo) -> !; /// ``` @@ -18,17 +20,8 @@ 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, + /// 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. /// /// The bootloader queries this information from the BIOS/UEFI firmware and translates this @@ -61,6 +54,23 @@ pub struct BootInfo { pub tls_template: Optional, } +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(), + 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]`. /// @@ -135,17 +145,15 @@ 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, /// 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), } @@ -154,11 +162,20 @@ 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 { + /// 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 } + } + /// Returns the raw bytes of the framebuffer as slice. pub fn buffer(&self) -> &[u8] { unsafe { self.create_buffer() } @@ -166,11 +183,15 @@ 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.info.byte_len) } } - 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) } + unsafe fn create_buffer_mut<'a>(&self) -> &'a mut [u8] { + 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. @@ -186,9 +207,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. @@ -210,17 +231,26 @@ 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] /// 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, + }, } /// Information about the thread local storage (TLS) template. diff --git a/api/src/lib.rs b/api/src/lib.rs new file mode 100644 index 00000000..2f6d5f36 --- /dev/null +++ b/api/src/lib.rs @@ -0,0 +1,141 @@ +//! 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}; + +/// 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 { + 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) -> !`. +/// +/// 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) => { + $crate::entry_point!($path, config = &$crate::BootloaderConfig::new_default()); + }; + ($path:path, config = $config:expr) => { + 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; + + // 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_ptr() as usize; + unsafe { core::arch::asm!("add {0}, 0", in(reg) force_use, options(nomem, nostack)) }; +} diff --git a/bios/boot_sector/.gitignore b/bios/boot_sector/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/bios/boot_sector/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/basic/simple_boot/Cargo.toml b/bios/boot_sector/Cargo.toml similarity index 50% rename from examples/basic/simple_boot/Cargo.toml rename to bios/boot_sector/Cargo.toml index 5302cdb4..620a036f 100644 --- a/examples/basic/simple_boot/Cargo.toml +++ b/bios/boot_sector/Cargo.toml @@ -1,11 +1,9 @@ [package] -name = "simple_boot" +name = "bootloader-x86_64-bios-boot-sector" 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 [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/bios/boot_sector/README.md b/bios/boot_sector/README.md new file mode 100644 index 00000000..5c9cf4e6 --- /dev/null +++ b/bios/boot_sector/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` diff --git a/bios/boot_sector/boot-sector-link.ld b/bios/boot_sector/boot-sector-link.ld new file mode 100644 index 00000000..26995bc2 --- /dev/null +++ b/bios/boot_sector/boot-sector-link.ld @@ -0,0 +1,56 @@ +ENTRY(_start) + +SECTIONS { + . = 0x500; + _stack_start = .; + . = 0x7c00; + _stack_end = .; + + _mbr_start = .; + .boot : + { + *(.boot .boot.*) + } + .text : + { + *(.text .text.*) + } + .rodata : + { + *(.rodata .rodata.*) + } + .data : + { + *(.rodata .rodata.*) + *(.data .data.*) + *(.got .got.*) + } + _mbr_end = .; + + . = 0x7c00 + 446; + _partition_table = .; + .partition_table : + { + /* 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; + + .magic_number : + { + SHORT(0xaa55) /* magic number for bootable disk */ + } + + _second_stage_start = .; +} diff --git a/bios/boot_sector/build.rs b/bios/boot_sector/build.rs new file mode 100644 index 00000000..d657e5a4 --- /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("boot-sector-link.ld").display() + ) +} diff --git a/bios/boot_sector/src/boot.s b/bios/boot_sector/src/boot.s new file mode 100644 index 00000000..992ce693 --- /dev/null +++ b/bios/boot_sector/src/boot.s @@ -0,0 +1,50 @@ +.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: + +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 + diff --git a/bios/boot_sector/src/dap.rs b/bios/boot_sector/src/dap.rs new file mode 100644 index 00000000..da72b777 --- /dev/null +++ b/bios/boot_sector/src/dap.rs @@ -0,0 +1,55 @@ +use core::arch::asm; + +#[repr(C, packed)] +#[allow(dead_code)] // the structure format is defined by the hardware +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 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: target_offset, + segment: target_segment, + start_lba, + } + } + + pub unsafe fn perform_load(&self, disk_number: u16) { + let self_addr = self as *const Self as u16; + unsafe { + asm!( + "push 0x7a", // error code `z`, passed to `fail` on error + "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}", // restore the `si` register to its prior state + in(reg) self_addr, + out(reg) _, + in("ax") 0x4200u16, + in("dx") disk_number, + ); + } + } +} diff --git a/bios/boot_sector/src/fail.rs b/bios/boot_sector/src/fail.rs new file mode 100644 index 00000000..07766f40 --- /dev/null +++ b/bios/boot_sector/src/fail.rs @@ -0,0 +1,60 @@ +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!("push bx", "mov bx, 0", "int 0x10", "pop bx", in("ax") ax); + } +} + +#[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] +#[cfg(not(test))] +pub fn panic(_info: &core::panic::PanicInfo) -> ! { + fail(b'P'); +} diff --git a/bios/boot_sector/src/main.rs b/bios/boot_sector/src/main.rs new file mode 100644 index 00000000..aaacc224 --- /dev/null +++ b/bios/boot_sector/src/main.rs @@ -0,0 +1,77 @@ +#![no_std] +#![no_main] +#![warn(unsafe_op_in_unsafe_fn)] + +use core::{arch::global_asm, slice}; +use fail::{print_char, UnwrapOrFail}; + +global_asm!(include_str!("boot.s")); + +mod dap; +mod fail; +mod mbr; + +extern "C" { + static _partition_table: u8; + static _second_stage_start: u8; +} + +unsafe fn partition_table_raw() -> *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) { + // 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::get_partition(partition_table, 0); + + // load second stage partition into memory + print_char(b'2'); + 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 + print_char(b'3'); + let second_stage_entry_point: extern "C" fn( + disk_number: u16, + partition_table_start: *const u8, + ) = 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'); + } + + loop {} +} diff --git a/bios/boot_sector/src/mbr.rs b/bios/boot_sector/src/mbr.rs new file mode 100644 index 00000000..93fc08cb --- /dev/null +++ b/bios/boot_sector/src/mbr.rs @@ -0,0 +1,68 @@ +use super::fail::{fail, UnwrapOrFail}; + +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'); + } + + 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) +} + +/// An entry in a partition table. +/// +/// Based on https://docs.rs/mbr-nostd +#[derive(Copy, Clone, Eq, PartialEq)] +pub(crate) struct PartitionTableEntry { + /// Whether this partition is a boot partition. + pub(crate) bootable: bool, + + /// The type of partition in this entry. + pub(crate) partition_type: u8, + + /// The index of the first block of this entry. + pub(crate) logical_block_address: u32, + + /// The total number of blocks in this entry. + pub(crate) 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/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..160890f5 --- /dev/null +++ b/bios/common/src/lib.rs @@ -0,0 +1,64 @@ +#![no_std] + +pub mod racy_cell; + +#[cfg_attr(feature = "debug", derive(Debug))] +#[repr(C)] +pub struct BiosInfo { + pub stage_4: Region, + pub kernel: Region, + pub framebuffer: BiosFramebufferInfo, + pub memory_map_addr: u32, + pub memory_map_len: u16, +} + +#[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct BiosFramebufferInfo { + 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))] +#[derive(Clone, Copy)] +#[repr(C)] +pub struct Region { + pub start: u64, + pub len: u64, +} + +#[cfg_attr(feature = "debug", derive(Debug))] +#[derive(Clone, Copy)] +#[repr(C)] +pub enum PixelFormat { + Rgb, + Bgr, + Unknown { + red_position: u8, + green_position: u8, + blue_position: u8, + }, +} + +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 { + pub start_addr: u64, + pub len: u64, + pub region_type: u32, + pub acpi_extended_attributes: u32, +} 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/.gitignore b/bios/stage-2/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/bios/stage-2/.gitignore @@ -0,0 +1 @@ +/target diff --git a/bios/stage-2/Cargo.toml b/bios/stage-2/Cargo.toml new file mode 100644 index 00000000..f70bf2b4 --- /dev/null +++ b/bios/stage-2/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "bootloader-x86_64-bios-stage-2" +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] +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/build.rs b/bios/stage-2/build.rs new file mode 100644 index 00000000..674be6e9 --- /dev/null +++ b/bios/stage-2/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-2-link.ld").display() + ) +} diff --git a/bios/stage-2/src/dap.rs b/bios/stage-2/src/dap.rs new file mode 100644 index 00000000..00bd1268 --- /dev/null +++ b/bios/stage-2/src/dap.rs @@ -0,0 +1,54 @@ +use core::arch::asm; + +#[derive(Debug, Clone, Copy)] +#[allow(dead_code)] +#[repr(C, 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 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: target_addr, + segment: target_addr_segment, + 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/stage-2/src/disk.rs b/bios/stage-2/src/disk.rs new file mode 100644 index 00000000..263d16a7 --- /dev/null +++ b/bios/stage-2/src/disk.rs @@ -0,0 +1,103 @@ +use crate::dap; + +#[derive(Clone)] +pub struct DiskAccess { + pub disk_number: u16, + pub base_offset: u64, + pub current_offset: u64, +} + +impl Read for DiskAccess { + 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 { + buffer: [0; 512 * 2], + }; + let buf = unsafe { &mut TMP_BUF }; + assert!(current_sector_offset + len <= buf.buffer.len()); + + self.read_exact_into(buf.buffer.len(), buf); + + &buf.buffer[current_sector_offset..][..len] + } + + 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; + let end_lba = (end_addr - 1) / 512; + + 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(), + ); + unsafe { + dap.perform_load(self.disk_number); + } + + 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; + } +} + +impl Seek for DiskAccess { + fn seek(&mut self, pos: SeekFrom) -> u64 { + match pos { + SeekFrom::Start(offset) => { + self.current_offset = offset; + self.current_offset + } + } + } +} + +pub trait Read { + unsafe fn read_exact(&mut self, len: usize) -> &[u8]; + fn read_exact_into(&mut self, len: usize, buf: &mut dyn AlignedBuffer); +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SeekFrom { + Start(u64), +} + +pub trait Seek { + fn seek(&mut self, pos: SeekFrom) -> u64; +} + +#[repr(align(2))] +pub struct AlignedArrayBuffer { + pub buffer: [u8; LEN], +} + +pub trait AlignedBuffer { + fn slice(&self) -> &[u8]; + fn slice_mut(&mut self) -> &mut [u8]; +} + +impl AlignedBuffer for AlignedArrayBuffer { + fn slice(&self) -> &[u8] { + &self.buffer[..] + } + fn slice_mut(&mut self) -> &mut [u8] { + &mut self.buffer[..] + } +} diff --git a/bios/stage-2/src/fat.rs b/bios/stage-2/src/fat.rs new file mode 100644 index 00000000..cfe9e24e --- /dev/null +++ b/bios/stage-2/src/fat.rs @@ -0,0 +1,510 @@ +// based on https://crates.io/crates/mini_fat by https://github.com/gridbugs + +use crate::disk::{AlignedBuffer, Read, Seek, SeekFrom}; +use core::char::DecodeUtf16Error; + +const DIRECTORY_ENTRY_BYTES: usize = 32; +const UNUSED_ENTRY_PREFIX: u8 = 0xE5; +const END_OF_DIRECTORY_PREFIX: u8 = 0; + +pub struct File { + first_cluster: u32, + file_size: u32, +} + +impl File { + pub fn file_size(&self) -> u32 { + self.file_size + } +} + +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 Bpb { + fn parse(disk: &mut D) -> Self { + disk.seek(SeekFrom::Start(0)); + 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]; + 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"); + } + + 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, + } + } + + 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 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 + } + + 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 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 { + disk: D, + bpb: Bpb, +} + +impl FileSystem { + pub fn parse(mut disk: D) -> Self { + Self { + bpb: Bpb::parse(&mut disk), + disk, + } + } + + 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 { + 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"); + } + }, + }; + + if entry.is_directory() { + None + } else { + Some(File { + first_cluster: entry.first_cluster, + file_size: entry.file_size, + }) + } + } + + 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; + unimplemented!(); + } + FatType::Fat12 | FatType::Fat16 => { + let root_directory_size = self.bpb.root_directory_size(); + + self.disk + .seek(SeekFrom::Start(self.bpb.root_directory_offset())); + self.disk.read_exact_into(root_directory_size, buffer); + + 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) + } + } + } + + 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, + } + } +} + +#[derive(Debug)] +pub struct Cluster { + pub index: u32, + pub start_offset: u64, + pub len_bytes: u32, +} + +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; + 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(), + })) + } +} + +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)] +enum FatType { + Fat12, + Fat16, + Fat32, +} + +impl FatType { + fn fat_entry_defective(self) -> u32 { + match self { + Self::Fat12 => 0xFF7, + Self::Fat16 => 0xFFF7, + Self::Fat32 => 0x0FFFFFF7, + } + } +} + +#[allow(dead_code)] +#[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 is_directory(&self) -> bool { + self.attributes & directory_attributes::DIRECTORY != 0 + } +} + +#[derive(Debug)] +struct RawDirectoryEntryNormal<'a> { + short_filename_main: &'a str, + short_filename_extension: &'a str, + attributes: u8, + first_cluster: u32, + file_size: u32, +} + +#[allow(dead_code)] +#[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(); + 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])?; + 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, + })) + } + } + + 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 { + 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; +} + +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 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 = 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 = unsafe { 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 + } else { + (entry16 >> 4) as u32 + } + } + } +} diff --git a/bios/stage-2/src/main.rs b/bios/stage-2/src/main.rs new file mode 100644 index 00000000..0553f2ed --- /dev/null +++ b/bios/stage-2/src/main.rs @@ -0,0 +1,217 @@ +#![no_std] +#![no_main] + +use crate::{ + disk::{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::{BiosFramebufferInfo, BiosInfo, Region}; +use byteorder::{ByteOrder, LittleEndian}; +use core::{fmt::Write as _, slice}; +use disk::AlignedArrayBuffer; +use mbr_nostd::{PartitionTableEntry, PartitionType}; + +mod dap; +mod disk; +mod fat; +mod memory_map; +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; + +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 + +static mut DISK_BUFFER: AlignedArrayBuffer<0x4000> = AlignedArrayBuffer { + buffer: [0; 0x4000], +}; + +#[no_mangle] +#[link_section = ".start"] +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(); + + // 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(_) + )); + + // 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 fs = fat::FileSystem::parse(disk.clone()); + + 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 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()); + assert!(STAGE_4_DST > stage_3_end); + STAGE_4_DST + }; + 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(); + + 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 + 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() + .expect("no suitable VESA mode found"); + writeln!( + screen::Writer, + "VESA MODE: {}x{}", + vesa_mode.width, + vesa_mode.height + ) + .unwrap(); + vesa_mode.enable().unwrap(); + + let mut info = BiosInfo { + stage_4: Region { + start: stage_4_dst as u64, + len: stage_4_len, + }, + kernel: Region { + start: KERNEL_DST as u64, + len: kernel_len, + }, + memory_map_addr: memory_map.as_mut_ptr() as u32, + memory_map_len: memory_map.len().try_into().unwrap(), + framebuffer: BiosFramebufferInfo { + region: Region { + start: vesa_mode.framebuffer_start.into(), + len: u64::from(vesa_mode.height) * u64::from(vesa_mode.bytes_per_scanline), + }, + 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, + }, + }; + + enter_protected_mode_and_jump_to_stage_3(STAGE_3_DST, &mut info); + + loop {} +} + +fn load_file( + file_name: &str, + dst: *mut u8, + fs: &mut fat::FileSystem, + disk: &mut disk::DiskAccess, + disk_buffer: &mut AlignedArrayBuffer<16384>, +) -> u64 { + let disk_buffer_size = disk_buffer.buffer.len(); + let file = fs + .find_file_in_root_dir(file_name, disk_buffer) + .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); + + let mut offset = 0; + loop { + let range_start = cluster_start + offset; + if range_start >= cluster_end { + break; + } + let range_end = u64::min( + range_start + u64::try_from(disk_buffer_size).unwrap(), + cluster_end, + ); + let len = range_end - range_start; + + 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(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 +} + +/// 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/stage-2/src/memory_map.rs b/bios/stage-2/src/memory_map.rs new file mode 100644 index 00000000..a980d9b6 --- /dev/null +++ b/bios/stage-2/src/memory_map.rs @@ -0,0 +1,73 @@ +// 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 unsafe 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 ret: u32; + let buf_written_len; + unsafe { + asm!( + "push ebx", + "mov ebx, edx", + "mov edx, 0x534D4150", + "int 0x15", + "mov edx, ebx", + "pop ebx", + inout("eax") 0xe820 => ret, + inout("edx") 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 new file mode 100644 index 00000000..e1fe5780 --- /dev/null +++ b/bios/stage-2/src/protected_mode.rs @@ -0,0 +1,158 @@ +use bootloader_x86_64_bios_common::BiosInfo; +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: self, + 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 cr0 = set_protected_mode_bit(); + + // load GDT + unsafe { + asm!("mov {0}, 0x10", "mov ds, {0}", out(reg) _); + } + + // unset protected mode bit again + write_cr0(cr0); + + unsafe { + asm!("mov ds, {0:x}", in(reg) ds, options(nostack, preserves_flags)); + asm!("sti"); + } +} + +#[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); + // 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, options(nostack, preserves_flags)) + }; + assert_eq!(read_from_protected_mode(dst), *byte); + } +} + +#[no_mangle] +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, options(pure, readonly, nostack, preserves_flags)) + }; + res +} + +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 { + asm!( + // align the stack + "and esp, 0xffffff00", + // push arguments + "push {info:e}", + // push entry point address + "push {entry_point:e}", + info = in(reg) info as *const _ as u32, + entry_point = in(reg) entry_point as u32, + ); + asm!("ljmp $0x8, $2f", "2:", options(att_syntax)); + asm!( + ".code32", + + // reload segment registers + "mov {0}, 0x10", + "mov ds, {0}", + "mov es, {0}", + "mov ss, {0}", + + // jump to third stage + "pop {1}", + "call {1}", + + // enter endless loop in case third stage returns + "2:", + "jmp 2b", + out(reg) _, + out(reg) _, + ); + } +} + +fn set_protected_mode_bit() -> u32 { + let mut cr0: u32; + unsafe { + asm!("mov {:e}, 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, {:e}", in(reg) val, options(nostack, preserves_flags)) }; +} diff --git a/bios/stage-2/src/screen.rs b/bios/stage-2/src/screen.rs new file mode 100644 index 00000000..78e5a790 --- /dev/null +++ b/bios/stage-2/src/screen.rs @@ -0,0 +1,46 @@ +use core::{arch::asm, fmt::Write}; + +pub fn print_char(c: u8) { + let ax = u16::from(c) | 0x0e00; + unsafe { + asm!("push bx", "mov bx, 0", "int 0x10", "pop bx", in("ax") ax); + } +} + +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'); + } + } +} + +pub struct Writer; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + print_str(s); + Ok(()) + } +} + +#[cfg(all(not(test), target_os = "none"))] +#[panic_handler] +pub fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = write!(Writer, "\nPANIC: "); + if let Some(location) = info.location() { + let _ = writeln!(Writer, "{location} "); + } + let _ = writeln!(Writer, " {info}"); + + loop { + unsafe { + asm!("hlt"); + }; + } +} diff --git a/bios/stage-2/src/vesa.rs b/bios/stage-2/src/vesa.rs new file mode 100644 index 00000000..9bf54b55 --- /dev/null +++ b/bios/stage-2/src/vesa.rs @@ -0,0 +1,240 @@ +// 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; + +#[repr(C, packed)] +#[allow(dead_code)] +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 ret; + unsafe { + asm!("push es", "mov es, {:x}", "int 0x10", "pop es", in(reg)0, inout("ax") 0x4f00u16 => ret, 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 = 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; + } + + 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); + } + } + Ok(best) + } + + fn get_mode(&self, index: usize) -> Option { + 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 }; + if mode == 0xffff { + None + } else { + Some(mode) + } + } +} + +#[derive(Debug)] +pub struct VesaModeInfo { + mode: u16, + pub width: u16, + pub height: 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 { + 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, + bytes_per_scanline: u16, + width: u16, + height: u16, + w_char: u8, + y_char: u8, + planes: u8, + bits_per_pixel: u8, + 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; + let mut target_addr = block_ptr as u32; + let segment = target_addr >> 4; + target_addr -= segment << 4; + unsafe { + asm!( + "push es", "mov es, {:x}", "int 0x10", "pop es", + in(reg) segment as u16, + inout("ax") 0x4f01u16 => ret, + in("cx") mode, + in("di") target_addr as u16 + ) + }; + match ret { + 0x4f => { + let block: &VbeModeInfo = unsafe { &*block_ptr.cast() }; + Ok(VesaModeInfo { + mode, + 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, + }) + } + other => Err(other), + } + } + + pub fn enable(&self) -> Result<(), u16> { + 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-2/stage-2-link.ld b/bios/stage-2/stage-2-link.ld new file mode 100644 index 00000000..b80c7473 --- /dev/null +++ b/bios/stage-2/stage-2-link.ld @@ -0,0 +1,37 @@ +ENTRY(_start) + +SECTIONS { + . = 0x7c00 + 512; + + .start : { + *(.start) + } + .text : { + *(.text .text.*) + } + .bss : { + *(.bss .bss.*) + } + .rodata : { + *(.rodata .rodata.*) + } + .data : { + *(.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) + } +} diff --git a/bios/stage-3/.gitignore b/bios/stage-3/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/bios/stage-3/.gitignore @@ -0,0 +1 @@ +/target diff --git a/bios/stage-3/Cargo.toml b/bios/stage-3/Cargo.toml new file mode 100644 index 00000000..bd606566 --- /dev/null +++ b/bios/stage-3/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "bootloader-x86_64-bios-stage-3" +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] +bootloader-x86_64-bios-common = { version = "0.1.0", path = "../common" } +noto-sans-mono-bitmap = "0.1.5" diff --git a/bios/stage-3/build.rs b/bios/stage-3/build.rs new file mode 100644 index 00000000..3aefe234 --- /dev/null +++ b/bios/stage-3/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-3-link.ld").display() + ) +} 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 new file mode 100644 index 00000000..c3d692d2 --- /dev/null +++ b/bios/stage-3/src/main.rs @@ -0,0 +1,76 @@ +#![no_std] +#![no_main] +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::screen::Writer; +use bootloader_x86_64_bios_common::BiosInfo; +use core::{arch::asm, fmt::Write as _}; + +mod gdt; +mod paging; +mod screen; + +#[no_mangle] +#[link_section = ".start"] +pub extern "C" fn _start(info: &mut BiosInfo) { + screen::init(info.framebuffer); + // Writer.clear_screen(); + 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(info); + + loop {} +} + +#[no_mangle] +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!( + // align the stack + "and esp, 0xffffff00", + // 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!( + ".code64", + + // reload segment registers + "mov {0}, 0x10", + "mov ds, {0}", + "mov es, {0}", + "mov ss, {0}", + + // jump to 4th stage + "pop rax", + "pop rdi", + "call rax", + + // enter endless loop in case 4th stage returns + "2:", + "jmp 2b", + out(reg) _, + out("rax") _, + out("rdi") _, + ); + } +} + +#[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/paging.rs b/bios/stage-3/src/paging.rs new file mode 100644 index 00000000..20b5fd47 --- /dev/null +++ b/bios/stage-3/src/paging.rs @@ -0,0 +1,58 @@ +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<[PageTable; 10]> = RacyCell::new([PageTable::empty(); 10]); + +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 l2s = unsafe { LEVEL_2.get_mut() }; + let common_flags = 0b11; // PRESENT | WRITEABLE + l4.entries[0] = (l3 as *mut PageTable as u64) | common_flags; + 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, entry) in l2.entries.iter_mut().enumerate() { + // map huge pages + *entry = + (offset + u64::try_from(j).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")_) }; +} + +#[derive(Clone, Copy)] +#[repr(align(4096))] +struct PageTable { + pub entries: [u64; 512], +} + +impl PageTable { + pub const fn empty() -> Self { + Self { entries: [0; 512] } + } +} diff --git a/bios/stage-3/src/screen.rs b/bios/stage-3/src/screen.rs new file mode 100644 index 00000000..6e81bf1d --- /dev/null +++ b/bios/stage-3/src/screen.rs @@ -0,0 +1,128 @@ +use bootloader_x86_64_bios_common::{racy_cell::RacyCell, BiosFramebufferInfo, PixelFormat}; +use core::{fmt, ptr}; +use noto_sans_mono_bitmap::{get_bitmap, 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: BiosFramebufferInfo) { + 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; + +struct ScreenWriter { + framebuffer: &'static mut [u8], + info: BiosFramebufferInfo, + x_pos: usize, + y_pos: usize, +} + +impl ScreenWriter { + pub fn new(framebuffer: &'static mut [u8], info: BiosFramebufferInfo) -> 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 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 => { + let bitmap_char = get_bitmap(c, FontWeight::Regular, BitmapHeight::Size14).unwrap(); + if self.x_pos + bitmap_char.width() > self.width() { + self.newline(); + } + if self.y_pos + bitmap_char.height() > self.height() { + self.clear(); + } + 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/stage-3-link.ld b/bios/stage-3/stage-3-link.ld new file mode 100644 index 00000000..c82e6f52 --- /dev/null +++ b/bios/stage-3/stage-3-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/bios/stage-4/Cargo.toml b/bios/stage-4/Cargo.toml new file mode 100644 index 00000000..ad3aac08 --- /dev/null +++ b/bios/stage-4/Cargo.toml @@ -0,0 +1,15 @@ +[package] +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-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" +rsdp = "2.0.0" +usize_conversions = "0.2.0" 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/src/main.rs b/bios/stage-4/src/main.rs new file mode 100644 index 00000000..ce2237f2 --- /dev/null +++ b/bios/stage-4/src/main.rs @@ -0,0 +1,242 @@ +#![no_std] +#![no_main] + +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, +}; +use core::{cmp, 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}; + +const GIGABYTE: u64 = 4096 * 512 * 512; + +mod memory_descriptor; + +#[no_mangle] +#[link_section = ".start"] +pub extern "C" fn _start(info: &mut BiosInfo) -> ! { + 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( + info.memory_map_addr as *mut _, + info.memory_map_len.try_into().unwrap(), + ) + }; + + memory_map.sort_unstable_by_key(|e| e.start_addr); + + let max_phys_addr = { + let max = memory_map + .iter() + .map(|r| { + log::info!("start: {:#x}, len: {:#x}", r.start_addr, r.len); + r.start_addr + r.len + }) + .max() + .expect("no physical memory regions found"); + // Don't consider addresses > 4GiB when determining the maximum physical + // address for the bootloader, as we are in protected mode and cannot + // address more than 4 GiB of memory anyway. + cmp::min(max, 4 * GIGABYTE) + }; + + let kernel_start = { + assert!(info.kernel.start != 0, "kernel start address must be set"); + PhysAddr::new(info.kernel.start) + }; + 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; + LegacyFrameAllocator::new_starting_at( + next_free, + memory_map.iter().copied().map(MemoryRegion), + ) + }; + + // 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 = { + 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 10 gigabytes are already identity-mapped) + { + let start_frame: PhysFrame = + PhysFrame::containing_address(PhysAddr::new(GIGABYTE * 10)); + let end_frame = PhysFrame::containing_address(PhysAddr::new(max_phys_addr - 1)); + for frame in PhysFrame::range_inclusive(start_frame, end_frame) { + let flusher = unsafe { + bootloader_page_table + .identity_map( + frame, + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + &mut frame_allocator, + ) + .unwrap() + }; + // skip flushing the entry from the TLB for now, as we will + // flush the entire TLB at the end of the loop. + flusher.ignore(); + } + } + + // once all the physical memory is mapped, flush the TLB by reloading the + // CR3 register. + // + // we perform a single flush here rather than flushing each individual entry as + // it's mapped using `invlpg`, for efficiency. + x86_64::instructions::tlb::flush_all(); + + log::info!("BIOS boot"); + + 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: Some(RawFrameBufferInfo { + addr: PhysAddr::new(info.framebuffer.region.start), + info: framebuffer_info, + }), + rsdp_addr: detect_rsdp(), + }; + + load_and_switch_to_kernel(kernel, frame_allocator, page_tables, system_info); +} + +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(framebuffer, framebuffer_info); + + framebuffer_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: *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) }, + 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)) + } +} + +#[cfg(all(not(test), target_os = "none"))] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + unsafe { + bootloader_x86_64_common::logger::LOGGER + .get() + .map(|l| l.force_unlock()) + }; + log::error!("{info}"); + loop { + unsafe { core::arch::asm!("cli; hlt") }; + } +} diff --git a/src/binary/bios/memory_descriptor.rs b/bios/stage-4/src/memory_descriptor.rs similarity index 51% rename from src/binary/bios/memory_descriptor.rs rename to bios/stage-4/src/memory_descriptor.rs index fcd80c12..0f02e573 100644 --- a/src/binary/bios/memory_descriptor.rs +++ b/bios/stage-4/src/memory_descriptor.rs @@ -1,21 +1,27 @@ -use crate::{binary::legacy_memory_region::LegacyMemoryRegion, boot_info::MemoryRegionKind}; +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), } } + + fn usable_after_bootloader_exit(&self) -> bool { + matches!(self.kind(), MemoryRegionKind::Usable) + } } /// A physical memory region returned by an `e820` BIOS call. @@ -24,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/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 c1d25f52..979050ed 100644 --- a/build.rs +++ b/build.rs @@ -1,493 +1,270 @@ -#[cfg(not(feature = "binary"))] -fn main() {} +use std::{ + path::{Path, PathBuf}, + process::Command, +}; -#[cfg(feature = "binary")] -fn main() { - binary::main(); -} - -#[cfg(feature = "binary")] -mod binary { - use quote::quote; - use std::convert::TryInto; +const BOOTLOADER_X86_64_UEFI_VERSION: &str = "0.1.0-alpha.0"; - 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; +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"; - 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"); +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!( + "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() + ); + let bios_stage_2_path = build_bios_stage_2(&out_dir); + println!( + "cargo:rustc-env=BIOS_STAGE_2_PATH={}", + bios_stage_2_path.display() + ); + + let bios_stage_3_path = build_bios_stage_3(&out_dir); + println!( + "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() + ); +} - // check that the kernel file exists +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 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); + } + 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"); + cmd.env_remove("CARGO_ENCODED_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!( - kernel.exists(), - "KERNEL does not exist: {}", - kernel.display() + path.exists(), + "uefi bootloader executable does not exist after building" ); - - // 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 + path + } else { + panic!("failed to build uefi bootloader"); } +} - /// 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, - #[serde(default)] - pub aslr: 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, - pub dynamic_range_start: Option, - pub dynamic_range_end: Option, +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); + println!("cargo:rerun-if-changed={}", local_path.display()); + } else { + cmd.arg("--version") + .arg(BOOTLOADER_X86_64_BIOS_BOOT_SECTOR_VERSION); } + cmd.arg("--locked"); + 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); + 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 boot sector"); + let elf_path = 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"); + }; + convert_elf_to_bin(elf_path) +} - /// 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 aslr = self.aslr; - 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); - let dynamic_range_start = optional(self.dynamic_range_start); - let dynamic_range_end = optional(self.dynamic_range_end); - - tokens.extend(quote! { Config { - map_physical_memory: #map_physical_memory, - map_page_table_recursively: #map_page_table_recursively, - map_framebuffer: #map_framebuffer, - aslr: #aslr, - 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, - dynamic_range_start: #dynamic_range_start, - dynamic_range_end: #dynamic_range_end, - }}); - } +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-stage-2"); + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("bios") + .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_STAGE_2_VERSION); } + cmd.arg("--locked"); + 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); + 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 second stage"); + let elf_path = if status.success() { + 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" + ); + path + } else { + panic!("failed to build bios second stage"); + }; + convert_elf_to_bin(elf_path) +} - #[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); - } +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-stage-3"); + let local_path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("bios") + .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_STAGE_3_VERSION); } + cmd.arg("--locked"); + 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); + 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-3"); + let elf_path = if status.success() { + let path = out_dir.join("bin").join("bootloader-x86_64-bios-stage-3"); + assert!( + path.exists(), + "bios stage-3 executable does not exist after building" + ); + path + } else { + panic!("failed to build bios stage-3"); + }; + convert_elf_to_bin(elf_path) +} - impl<'de> serde::Deserialize<'de> for AlignedAddress { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_str(AlignedAddressVisitor) - } +fn build_bios_stage_4(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-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"); + }; - /// 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 - )) - })?; + convert_elf_to_bin(elf_path) +} - self.visit_u64(num) - } +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/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 00000000..67c3505c --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,31 @@ +[package] +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 + +[dependencies] +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" +usize_conversions = "0.2.0" +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_hc = "0.3.1" + +[dependencies.noto-sans-mono-bitmap] +version = "0.2.0" +default-features = false +features = [ + "regular", + "size_16", + "unicode-basic-latin", + # required for the fallback char '�' + "unicode-specials", +] diff --git a/src/binary/entropy.rs b/common/src/entropy.rs similarity index 95% rename from src/binary/entropy.rs rename to common/src/entropy.rs index 65ef3821..c38f22ef 100644 --- a/src/binary/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/src/binary/gdt.rs b/common/src/gdt.rs similarity index 74% rename from src/binary/gdt.rs rename to common/src/gdt.rs index af9e8781..9ac45ade 100644 --- a/src/binary/gdt.rs +++ b/common/src/gdt.rs @@ -1,5 +1,5 @@ use x86_64::{ - instructions::segmentation::{Segment, CS, DS, ES, SS}, + instructions::segmentation::{self, Segment}, structures::{ gdt::{Descriptor, GlobalDescriptorTable}, paging::PhysFrame, @@ -24,9 +24,9 @@ pub fn create_and_load(frame: PhysFrame) { gdt.load(); unsafe { - CS::set_reg(code_selector); - DS::set_reg(data_selector); - ES::set_reg(data_selector); - SS::set_reg(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); } } diff --git a/src/binary/legacy_memory_region.rs b/common/src/legacy_memory_region.rs similarity index 66% rename from src/binary/legacy_memory_region.rs rename to common/src/legacy_memory_region.rs index 831f9a94..dfd3546a 100644 --- a/src/binary/legacy_memory_region.rs +++ b/common/src/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}, @@ -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; + + /// 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. @@ -98,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; @@ -118,27 +123,20 @@ 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; MemoryRegionKind::Usable } } - // 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) { - 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), - } + _ 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, }; @@ -148,27 +146,79 @@ 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); + 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); + } } 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( region: MemoryRegion, regions: &mut [MaybeUninit], next_index: &mut usize, - ) -> Result<(), ()> { + ) { + if region.start == region.end { + // skip zero sized regions + 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(()) } } diff --git a/src/binary/level_4_entries.rs b/common/src/level_4_entries.rs similarity index 81% rename from src/binary/level_4_entries.rs rename to common/src/level_4_entries.rs index 726054cc..2e5e1c4c 100644 --- a/src/binary/level_4_entries.rs +++ b/common/src/level_4_entries.rs @@ -1,9 +1,11 @@ -use core::{alloc::Layout, convert::TryInto, iter::Step}; +use crate::{entropy, BootInfo, RawFrameBufferInfo}; +use bootloader_api::{config, info::MemoryRegion, BootloaderConfig}; +use core::{alloc::Layout, iter::Step}; 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}, @@ -11,11 +13,6 @@ use x86_64::{ }; use xmas_elf::program::ProgramHeader; -use crate::{ - binary::{entropy, MemoryRegion, CONFIG}, - BootInfo, -}; - /// Keeps track of used entries in a level 4 page table. /// /// Useful for determining a free virtual memory block, e.g. for mapping additional data. @@ -24,38 +21,46 @@ 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 { /// Initializes a new instance. /// /// Marks the statically configured virtual address ranges from the config as used. - pub fn new(max_phys_addr: PhysAddr, regions_len: usize, framebuffer_size: usize) -> Self { + pub fn new( + max_phys_addr: PhysAddr, + regions_len: usize, + framebuffer: Option<&RawFrameBufferInfo>, + config: &BootloaderConfig, + ) -> Self { let mut used = UsedLevel4Entries { entry_state: [false; 512], - rng: CONFIG.aslr.then(entropy::build_rng), + rng: config.mappings.aslr.then(entropy::build_rng), }; used.entry_state[0] = true; // TODO: Can we do this dynamically? // Mark the statically configured ranges from the config as used. - if let Some(physical_memory_offset) = CONFIG.physical_memory_offset { + if let Some(config::Mapping::FixedAddress(physical_memory_offset)) = + config.mappings.physical_memory + { used.mark_range_as_used(physical_memory_offset, max_phys_addr.as_u64().into_usize()); } - if CONFIG.map_page_table_recursively { - if let Some(recursive_index) = CONFIG.recursive_index { - used.mark_p4_index_as_used(PageTableIndex::new(recursive_index)); - } + if let Some(config::Mapping::FixedAddress(recursive_address)) = + config.mappings.page_table_recursive + { + let recursive_index = VirtAddr::new(recursive_address).p4_index(); + used.mark_p4_index_as_used(recursive_index); } - if let Some(kernel_stack_address) = CONFIG.kernel_stack_address { - used.mark_range_as_used(kernel_stack_address, CONFIG.kernel_stack_size()); + if let config::Mapping::FixedAddress(kernel_stack_address) = config.mappings.kernel_stack { + used.mark_range_as_used(kernel_stack_address, config.kernel_stack_size); } - if let Some(boot_info_address) = CONFIG.boot_info_address { + if let config::Mapping::FixedAddress(boot_info_address) = config.mappings.boot_info { let boot_info_layout = Layout::new::(); let regions = regions_len + 1; // one region might be split into used/unused let memory_regions_layout = Layout::array::(regions).unwrap(); @@ -64,14 +69,14 @@ impl UsedLevel4Entries { used.mark_range_as_used(boot_info_address, combined.size()); } - if CONFIG.map_framebuffer { - if let Some(framebuffer_address) = CONFIG.framebuffer_address { - used.mark_range_as_used(framebuffer_address, framebuffer_size); + if let config::Mapping::FixedAddress(framebuffer_address) = config.mappings.framebuffer { + if let Some(framebuffer) = framebuffer { + used.mark_range_as_used(framebuffer_address, framebuffer.info.byte_len); } } // Mark everything before the dynamic range as unusable. - if let Some(dynamic_range_start) = CONFIG.dynamic_range_start { + if let Some(dynamic_range_start) = config.mappings.dynamic_range_start { let dynamic_range_start = VirtAddr::new(dynamic_range_start); let start_page: Page = Page::containing_address(dynamic_range_start); if let Some(unusable_page) = Step::backward_checked(start_page, 1) { @@ -82,7 +87,7 @@ impl UsedLevel4Entries { } // Mark everything after the dynamic range as unusable. - if let Some(dynamic_range_end) = CONFIG.dynamic_range_end { + if let Some(dynamic_range_end) = config.mappings.dynamic_range_end { let dynamic_range_end = VirtAddr::new(dynamic_range_end); let end_page: Page = Page::containing_address(dynamic_range_end); if let Some(unusable_page) = Step::forward_checked(end_page, 1) { diff --git a/src/binary/mod.rs b/common/src/lib.rs similarity index 72% rename from src/binary/mod.rs rename to common/src/lib.rs index 22becc58..4a204972 100644 --- a/src/binary/mod.rs +++ b/common/src/lib.rs @@ -1,25 +1,24 @@ -use crate::{ - binary::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}, - boot_info::{BootInfo, FrameBuffer, FrameBufferInfo, MemoryRegion, TlsTemplate}, +#![no_std] +#![feature(step_trait)] +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::legacy_memory_region::{LegacyFrameAllocator, LegacyMemoryRegion}; +use bootloader_api::{ + config::Mapping, + info::{FrameBuffer, FrameBufferInfo, MemoryRegion, TlsTemplate}, + BootInfo, BootloaderConfig, }; use core::{alloc::Layout, arch::asm, mem::MaybeUninit, slice}; use level_4_entries::UsedLevel4Entries; -use parsed_config::CONFIG; use usize_conversions::FromUsize; use x86_64::{ structures::paging::{ - FrameAllocator, Mapper, OffsetPageTable, Page, PageSize, PageTableFlags, PageTableIndex, - PhysFrame, Size2MiB, Size4KiB, + page_table::PageTableLevel, FrameAllocator, Mapper, OffsetPageTable, Page, PageSize, + PageTableFlags, PageTableIndex, PhysFrame, Size2MiB, Size4KiB, }, PhysAddr, VirtAddr, }; - -/// 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; +use xmas_elf::ElfFile; /// Provides a function to gather entropy and build a RNG. mod entropy; @@ -33,19 +32,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. @@ -59,21 +45,55 @@ 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, + pub start_address: *const u8, + pub len: usize, +} + +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") + .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") + }; + Kernel { + elf: kernel_elf, + config, + start_address: kernel_slice.as_ptr(), + len: kernel_slice.len(), + } + } +} + /// 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, @@ -82,14 +102,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, + system_info.framebuffer.as_ref(), + &config, ); let boot_info = create_boot_info( + &config, frame_allocator, &mut page_tables, &mut mappings, @@ -113,11 +135,11 @@ 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, - framebuffer_size: usize, + framebuffer: Option<&RawFrameBufferInfo>, + config: &BootloaderConfig, ) -> Mappings where I: ExactSizeIterator + Clone, @@ -128,7 +150,8 @@ where let mut used_entries = UsedLevel4Entries::new( frame_allocator.max_phys_addr(), frame_allocator.len(), - framebuffer_size, + framebuffer, + config, ); // Enable support for the no-execute bit in page tables. @@ -136,8 +159,12 @@ 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 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_bytes, + kernel, kernel_page_table, frame_allocator, &mut used_entries, @@ -146,10 +173,15 @@ where 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, + config.kernel_stack_size, + 16, + &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(); + 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) { @@ -193,15 +225,19 @@ where } // map framebuffer - let framebuffer_virt_addr = if CONFIG.map_framebuffer { + 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); - let start_page = - Page::from_start_address(frame_buffer_location(&mut used_entries, framebuffer_size)) - .expect("the framebuffer address must be page aligned"); + 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.info.byte_len), + Size4KiB::SIZE, + &mut used_entries, + )) + .expect("the framebuffer address must be page aligned"); for (i, frame) in PhysFrame::range_inclusive(framebuffer_start_frame, framebuffer_end_frame).enumerate() { @@ -221,7 +257,7 @@ where 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 start_frame = PhysFrame::containing_address(PhysAddr::new(0)); @@ -230,10 +266,7 @@ where let size = max_phys.as_u64(); let alignment = Size2MiB::SIZE; - let offset = CONFIG - .physical_memory_offset - .map(VirtAddr::new) - .unwrap_or_else(|| used_entries.get_free_address(size, alignment)); + let offset = mapping_addr(mapping, size, alignment, &mut used_entries); for frame in PhysFrame::range_inclusive(start_frame, end_frame) { let page = Page::containing_address(offset + frame.start_address().as_u64()); @@ -252,12 +285,24 @@ 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_entries(1)); + let index = match mapping { + Mapping::Dynamic => used_entries.get_free_entries(1), + Mapping::FixedAddress(offset) => { + let offset = VirtAddr::new(offset); + 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() + ); + } + + offset.p4_index() + } + }; let entry = &mut kernel_page_table.level_4_table()[index]; if !entry.is_unused() { @@ -282,6 +327,9 @@ where physical_memory_offset, recursive_index, tls_template, + + kernel_slice_start, + kernel_slice_len, } } @@ -302,6 +350,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. @@ -311,6 +364,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, @@ -325,12 +379,17 @@ 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(); - let boot_info_addr = boot_info_location(&mut mappings.used_entries, combined); + let boot_info_addr = mapping_addr( + config.mappings.boot_info, + u64::from_usize(combined.size()), + u64::from_usize(combined.align()), + &mut mappings.used_entries, + ); assert!( boot_info_addr.is_aligned(u64::from_usize(combined.align())), "boot info addr is not properly aligned" @@ -375,29 +434,37 @@ 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"); // 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, + .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(), - 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 @@ -463,38 +530,19 @@ struct Addresses { page_table: PhysFrame, stack_top: VirtAddr, entry_point: VirtAddr, - boot_info: &'static mut crate::boot_info::BootInfo, -} - -fn boot_info_location(used_entries: &mut UsedLevel4Entries, layout: Layout) -> VirtAddr { - CONFIG - .boot_info_address - .map(VirtAddr::new) - .unwrap_or_else(|| { - used_entries.get_free_address( - u64::from_usize(layout.size()), - u64::from_usize(layout.align()), - ) - }) + boot_info: &'static mut BootInfo, } -fn frame_buffer_location( +fn mapping_addr( + mapping: Mapping, + size: u64, + alignment: u64, used_entries: &mut UsedLevel4Entries, - framebuffer_size: usize, ) -> VirtAddr { - CONFIG - .framebuffer_address - .map(VirtAddr::new) - .unwrap_or_else(|| { - used_entries.get_free_address(u64::from_usize(framebuffer_size), Size4KiB::SIZE) - }) -} - -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(CONFIG.kernel_stack_size(), 16)) + match mapping { + Mapping::FixedAddress(addr) => VirtAddr::new(addr), + Mapping::Dynamic => used_entries.get_free_address(size, alignment), + } } fn enable_nxe_bit() { diff --git a/src/binary/load_kernel.rs b/common/src/load_kernel.rs similarity index 98% rename from src/binary/load_kernel.rs rename to common/src/load_kernel.rs index 8a62f166..c0bb3eb1 100644 --- a/src/binary/load_kernel.rs +++ b/common/src/load_kernel.rs @@ -1,9 +1,7 @@ +use crate::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}; +use bootloader_api::info::TlsTemplate; use core::mem::align_of; -use crate::{ - binary::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}, - boot_info::TlsTemplate, -}; use x86_64::{ align_up, structures::paging::{ @@ -19,6 +17,8 @@ use xmas_elf::{ ElfFile, }; +use super::Kernel; + /// Used by [`Inner::make_mut`] and [`Inner::clean_copied_flag`]. const COPIED: Flags = Flags::BIT_9; @@ -40,18 +40,18 @@ where F: FrameAllocator, { fn new( - bytes: &'a [u8], + kernel: Kernel<'a>, page_table: &'a mut M, frame_allocator: &'a mut F, used_entries: &mut UsedLevel4Entries, ) -> 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; for program_header in elf_file.program_iter() { program::sanity_check(program_header, &elf_file)?; } @@ -564,12 +564,12 @@ fn check_is_in_load(elf_file: &ElfFile, virt_offset: u64) -> Result<(), &'static /// 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 + Translate), frame_allocator: &mut impl FrameAllocator, used_entries: &mut UsedLevel4Entries, ) -> Result<(VirtAddr, Option), &'static str> { - let mut loader = Loader::new(bytes, page_table, frame_allocator, used_entries)?; + let mut loader = Loader::new(kernel, page_table, frame_allocator, used_entries)?; let tls_template = loader.load_segments()?; Ok((loader.entry_point(), tls_template)) diff --git a/src/binary/logger.rs b/common/src/logger.rs similarity index 90% rename from src/binary/logger.rs rename to common/src/logger.rs index 6600827d..a008886c 100644 --- a/src/binary/logger.rs +++ b/common/src/logger.rs @@ -1,10 +1,10 @@ -use crate::binary::logger::font_constants::BACKUP_CHAR; -use crate::boot_info::{FrameBufferInfo, PixelFormat}; +use bootloader_api::info::{FrameBufferInfo, PixelFormat}; use conquer_once::spin::OnceCell; use core::{ fmt::{self, Write}, ptr, }; +use font_constants::BACKUP_CHAR; use noto_sans_mono_bitmap::{ get_raster, get_raster_width, FontWeight, RasterHeight, RasterizedChar, }; @@ -119,11 +119,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 } /// Writes a single char to the framebuffer. Takes care of special control characters, such as @@ -161,9 +161,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/doc/chainloading.md b/docs/chainloading.md similarity index 100% rename from doc/chainloading.md rename to docs/chainloading.md diff --git a/docs/create-disk-image.md b/docs/create-disk-image.md new file mode 100644 index 00000000..67302b4a --- /dev/null +++ b/docs/create-disk-image.md @@ -0,0 +1,85 @@ +# Template: Create a Disk Image + +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. + +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_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/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/docs/migration/v0.9.md b/docs/migration/v0.9.md new file mode 100644 index 00000000..856b2154 --- /dev/null +++ b/docs/migration/v0.9.md @@ -0,0 +1,49 @@ +# 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. + +See our [disk image creation template](../create-disk-image.md) for a detailed explanation of the new build process. 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 cbab0afe..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", -] - -[[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 = "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 2c3cc202..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 = { path = "../.." } # 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/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 afd8fe87..00000000 --- a/examples/basic/src/main.rs +++ /dev/null @@ -1,24 +0,0 @@ -#![no_std] -#![no_main] - -use bootloader::{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 808adb68..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 = { path = "../.." } # 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 a367e983..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::{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 diff --git a/i386-code16-boot-sector.json b/i386-code16-boot-sector.json new file mode 100644 index 00000000..f5ed774e --- /dev/null +++ b/i386-code16-boot-sector.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": "static" +} diff --git a/i386-code16-stage-2.json b/i386-code16-stage-2.json new file mode 100644 index 00000000..f5ed774e --- /dev/null +++ b/i386-code16-stage-2.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": "static" +} diff --git a/i686-stage-3.json b/i686-stage-3.json new file mode 100644 index 00000000..b444faec --- /dev/null +++ b/i686-stage-3.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": "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", + "features": "+soft-float,-sse,-mmx" +} 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/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..d04213d1 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,4 @@ +[toolchain] +channel = "nightly" +components = ["rustfmt", "clippy", "rust-src", "llvm-tools-preview"] +targets = ["x86_64-unknown-none"] diff --git a/src/asm/e820.s b/src/asm/e820.s deleted file mode 100644 index 59d9c78b..00000000 --- a/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/src/asm/stage_1.s b/src/asm/stage_1.s deleted file mode 100644 index f065564e..00000000 --- a/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/src/asm/stage_2.s b/src/asm/stage_2.s deleted file mode 100644 index 2e4645bb..00000000 --- a/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/src/asm/stage_3.s b/src/asm/stage_3.s deleted file mode 100644 index d9bcd102..00000000 --- a/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/src/asm/vesa.s b/src/asm/vesa.s deleted file mode 100644 index adeaf4fd..00000000 --- a/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/src/bin/bios.rs b/src/bin/bios.rs deleted file mode 100644 index a5fd84e4..00000000 --- a/src/bin/bios.rs +++ /dev/null @@ -1,306 +0,0 @@ -#![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 core::{ - arch::{asm, global_asm}, - cmp, - 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}; - -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")); - -// 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::binary::{ - bios::memory_descriptor::E820MemoryRegion, legacy_memory_region::LegacyFrameAllocator, - }; - const GIGABYTE: u64 = 4096 * 512 * 512; - - 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 = { - let max = e820_memory_map - .iter() - .map(|r| r.start_addr + r.len) - .max() - .expect("no physical memory regions found"); - // Don't consider addresses > 4GiB when determining the maximum physical - // address for the bootloader, as we are in protected mode and cannot - // address more than 4 GiB of memory anyway. - cmp::min(max, 4 * GIGABYTE) - }; - - 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(GIGABYTE)); - let end_frame = PhysFrame::containing_address(PhysAddr::new(max_phys_addr - 1)); - for frame in PhysFrame::range_inclusive(start_frame, end_frame) { - let flusher = unsafe { - bootloader_page_table - .identity_map( - frame, - PageTableFlags::PRESENT | PageTableFlags::WRITABLE, - &mut frame_allocator, - ) - .unwrap() - }; - // skip flushing the entry from the TLB for now, as we will - // flush the entire TLB at the end of the loop. - flusher.ignore(); - } - } - - // once all the physical memory is mapped, flush the TLB by reloading the - // CR3 register. - // - // we perform a single flush here rather than flushing each individual entry as - // it's mapped using `invlpg`, for efficiency. - x86_64::instructions::tlb::flush_all(); - - 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 = { - let ptr = kernel_start.as_u64() as *const u8; - unsafe { slice::from_raw_parts(ptr, usize_from(kernel_size)) } - }; - - let system_info = SystemInfo { - framebuffer_addr, - framebuffer_info, - rsdp_addr: detect_rsdp(), - }; - - bootloader::binary::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 = bootloader::boot_info::FrameBufferInfo { - byte_len: framebuffer_size, - horizontal_resolution, - vertical_resolution, - bytes_per_pixel, - stride, - pixel_format, - }; - - bootloader::binary::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 { - // 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, - ) - }; - - bootloader::binary::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 { - physical_start: physical_address, - virtual_start: NonNull::new(physical_address as *mut _).unwrap(), - region_length: size, - mapped_length: size, - handler: Self, - } - } - - fn unmap_physical_region(&self, _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 { - bootloader::binary::logger::LOGGER - .get() - .map(|l| l.force_unlock()) - }; - log::error!("{}", info); - loop { - unsafe { asm!("cli; hlt") }; - } -} diff --git a/src/bin/builder.rs b/src/bin/builder.rs deleted file mode 100644 index f5b6fece..00000000 --- a/src/bin/builder.rs +++ /dev/null @@ -1,376 +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 `Cargo.toml` of the kernel - #[argh(option)] - kernel_manifest: PathBuf, - - /// 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"); - } - cmd.env("KERNEL", &args.kernel_binary); - cmd.env("KERNEL_MANIFEST", &args.kernel_manifest); - 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) - .context("failed to create UEFI disk image")?; - } - } - - 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("-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"); - } - cmd.env("KERNEL", &args.kernel_binary); - cmd.env("KERNEL_MANIFEST", &args.kernel_manifest); - cmd.env("RUSTFLAGS", "-C opt-level=s"); - 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) - .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) -> 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(); - - // 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 efi_size_padded_and_rounded = ((efi_size + 1024 * 64 - 1) / MB + 1) * MB; - fat_file - .set_len(efi_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)?; - - 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) - .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) -} diff --git a/src/bin/uefi.rs b/src/bin/uefi.rs deleted file mode 100644 index a4c1ce10..00000000 --- a/src/bin/uefi.rs +++ /dev/null @@ -1,209 +0,0 @@ -#![no_std] -#![no_main] -#![feature(abi_efiapi)] -#![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); - -use bootloader::{ - binary::{legacy_memory_region::LegacyFrameAllocator, parsed_config::CONFIG, SystemInfo}, - boot_info::FrameBufferInfo, -}; -use core::{arch::asm, mem, panic::PanicInfo, slice}; -use uefi::{ - prelude::{entry, Boot, Handle, ResultExt, Status, SystemTable}, - proto::console::gop::{GraphicsOutput, PixelFormat}, - table::boot::{MemoryDescriptor, MemoryType}, - Completion, -}; -use x86_64::{ - structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, - PhysAddr, VirtAddr, -}; - -#[entry] -fn efi_main(image: Handle, st: SystemTable) -> Status { - let (framebuffer_addr, framebuffer_info) = init_logger(&st); - log::info!("Hello World from UEFI bootloader!"); - 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.0, - frame_allocator, - page_tables, - system_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 { - // UEFI identity-maps 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 - log::trace!("switching to new level 4 table"); - let bootloader_page_table = { - let old_table = { - let frame = x86_64::registers::control::Cr3::read().0; - let ptr: *const PageTable = (phys_offset + frame.start_address().as_u64()).as_ptr(); - unsafe { &*ptr } - }; - let new_frame = frame_allocator - .allocate_frame() - .expect("Failed to allocate frame for new level 4 table"); - let new_table: &mut PageTable = { - let ptr: *mut PageTable = - (phys_offset + new_frame.start_address().as_u64()).as_mut_ptr(); - // create a new, empty page table - unsafe { - ptr.write(PageTable::new()); - &mut *ptr - } - }; - - // copy the first entry (we don't need to access more than 512 GiB; also, some UEFI - // implementations seem to create an level 4 table entry 0 in all slots) - new_table[0] = old_table[0].clone(); - - // the first level 4 table entry is now identical, so we can just load the new one - unsafe { - x86_64::registers::control::Cr3::write( - new_frame, - x86_64::registers::control::Cr3Flags::empty(), - ); - OffsetPageTable::new(&mut *new_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, - ) - }; - - bootloader::binary::PageTables { - bootloader: bootloader_page_table, - kernel: kernel_page_table, - kernel_level_4_frame, - } -} - -fn init_logger(st: &SystemTable) -> (PhysAddr, FrameBufferInfo) { - let gop = st - .boot_services() - .locate_protocol::() - .expect_success("failed to locate gop"); - let gop = unsafe { &mut *gop.get() }; - - let mode = { - let modes = gop.modes().map(Completion::unwrap); - match ( - CONFIG.minimum_framebuffer_height, - CONFIG.minimum_framebuffer_width, - ) { - (Some(height), Some(width)) => modes - .filter(|m| { - let res = m.info().resolution(); - res.1 >= height && res.0 >= width - }) - .last(), - (Some(height), None) => modes.filter(|m| m.info().resolution().1 >= height).last(), - (None, Some(width)) => modes.filter(|m| m.info().resolution().0 >= width).last(), - _ => None, - } - }; - if let Some(mode) = mode { - gop.set_mode(&mode) - .expect_success("Failed to apply the desired display mode"); - } - - let mode_info = gop.current_mode_info(); - let mut framebuffer = gop.frame_buffer(); - 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, - pixel_format: match mode_info.pixel_format() { - PixelFormat::Rgb => bootloader::boot_info::PixelFormat::BGR, - PixelFormat::Bgr => bootloader::boot_info::PixelFormat::BGR, - PixelFormat::Bitmask | PixelFormat::BltOnly => { - panic!("Bitmask and BltOnly framebuffers are not supported") - } - }, - bytes_per_pixel: 4, - stride: mode_info.stride(), - }; - - log::info!("UEFI boot"); - - bootloader::binary::init_logger(slice, info); - - (PhysAddr::new(framebuffer.as_mut_ptr() as u64), info) -} - -#[panic_handler] -fn panic(info: &PanicInfo) -> ! { - unsafe { - bootloader::binary::logger::LOGGER - .get() - .map(|l| l.force_unlock()) - }; - log::error!("{}", info); - loop { - unsafe { asm!("cli; hlt") }; - } -} 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/memory_descriptor.rs b/src/binary/uefi/memory_descriptor.rs deleted file mode 100644 index 7d1c3a53..00000000 --- a/src/binary/uefi/memory_descriptor.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::{binary::legacy_memory_region::LegacyMemoryRegion, boot_info::MemoryRegionKind}; -use uefi::table::boot::{MemoryDescriptor, MemoryType}; -use x86_64::PhysAddr; - -const PAGE_SIZE: u64 = 4096; - -impl<'a> LegacyMemoryRegion for MemoryDescriptor { - fn start(&self) -> PhysAddr { - PhysAddr::new(self.phys_start) - } - - fn len(&self) -> u64 { - self.page_count * PAGE_SIZE - } - - fn kind(&self) -> MemoryRegionKind { - match self.ty { - MemoryType::CONVENTIONAL => MemoryRegionKind::Usable, - other => MemoryRegionKind::UnknownUefi(other.0), - } - } -} 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/config.rs b/src/config.rs deleted file mode 100644 index 8e0b31e8..00000000 --- a/src/config.rs +++ /dev/null @@ -1,114 +0,0 @@ -#[cfg(feature = "binary")] -const PAGE_SIZE: u64 = 4096; - -/// 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. The addresses can be restricted by setting the `dynamic-range` configuration key. -#[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, - /// Whether to randomize non-statically configured addresses. - /// The kernel base address will be randomized when it's compiled as - /// a position independent executable. - /// - /// Defaults to `false`. - pub aslr: 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, - /// The lowest virtual address for dynamic addresses. - /// - /// Defaults to `0`. - pub dynamic_range_start: Option, - /// The highest virtual address for dynamic addresses. - /// - /// Defaults to `0xffff_ffff_ffff_f000`. - pub dynamic_range_end: Option, -} - -#[cfg(feature = "binary")] -impl Config { - pub(crate) fn kernel_stack_size(&self) -> u64 { - self.kernel_stack_size.unwrap_or(20 * PAGE_SIZE) - } -} 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(()) } diff --git a/src/fat.rs b/src/fat.rs new file mode 100644 index 00000000..c592b7ad --- /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[..usize::min(new_label.len(), name.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 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/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 0155098d..47260d10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,129 +1,135 @@ /*! 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. */ -#![cfg_attr(not(feature = "builder"), no_std)] -#![feature(maybe_uninit_slice)] -#![feature(step_trait)] -#![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"). -/// -/// 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."); - -/// 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) => { - /// Kernel entry point. - #[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) +use anyhow::Context; +use std::{ + collections::BTreeMap, + path::{Path, PathBuf}, +}; +use tempfile::NamedTempFile; + +mod fat; +mod gpt; +mod mbr; +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"; + +/// Create disk images for booting on legacy BIOS systems. +pub struct BiosBoot { + kernel: PathBuf, +} + +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) + } +} + +/// Create disk images for booting on UEFI systems. +pub struct UefiBoot { + kernel: PathBuf, +} + +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/src/mbr.rs b/src/mbr.rs new file mode 100644 index 00000000..8f7dfade --- /dev/null +++ b/src/mbr.rs @@ -0,0 +1,100 @@ +use anyhow::Context; +use std::{ + fs::{self, File}, + io::{self, 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<()> { + 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 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[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? + 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")?; + + // 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(), + )) + .context("seek failed")?; + io::copy(&mut boot_partition, &mut disk) + .context("failed to copy FAT image to MBR disk image")?; + + 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/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/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/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..51421aa0 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -1,11 +1,12 @@ [package] -name = "runner" +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 [dependencies] -bootloader-locator = "0.0.4" -locate-cargo-manifest = "0.2.1" +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 new file mode 100644 index 00000000..7f50cd42 --- /dev/null +++ b/tests/runner/src/lib.rs @@ -0,0 +1,105 @@ +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); + + // 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(); + + // 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 tftp_path = kernel_path.with_extension(".tftp"); + uefi_builder.create_pxe_tftp_folder(&tftp_path).unwrap(); + + 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) { + 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.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), + } +} + +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); + + 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", + 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"), + 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/Cargo.toml b/tests/test_kernels/default_settings/Cargo.toml index 0410f326..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 = { 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" 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..e6bd3a0b 100644 --- a/tests/test_kernels/default_settings/src/bin/basic_boot.rs +++ b/tests/test_kernels/default_settings/src/bin/basic_boot.rs @@ -1,8 +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 core::panic::PanicInfo; +use bootloader_api::{entry_point, BootInfo}; 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 fab65b53..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 @@ -1,8 +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 core::panic::PanicInfo; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; use test_kernel_default_settings::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -14,33 +13,16 @@ 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.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 @@ -50,7 +32,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); @@ -59,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 48546907..0f78d0ba 100644 --- a/tests/test_kernels/default_settings/src/bin/should_panic.rs +++ b/tests/test_kernels/default_settings/src/bin/should_panic.rs @@ -1,9 +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 core::panic::PanicInfo; -use test_kernel_default_settings::{exit_qemu, QemuExitCode}; +use bootloader_api::{entry_point, BootInfo}; 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/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 99a7107d..aedecae0 100644 --- a/tests/test_kernels/higher_half/Cargo.toml +++ b/tests/test_kernels/higher_half/Cargo.toml @@ -1,13 +1,17 @@ +cargo-features = ["profile-rustflags"] + [package] name = "test_kernel_higher_half" version = "0.1.0" authors = ["Philipp Oppermann "] -edition = "2018" +edition = "2021" [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] -dynamic-range-start = "0xffff_8000_0000_0000" +# set to higher half through profile.test.rustflags key in top-level Cargo.toml 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..4133963e 100644 --- a/tests/test_kernels/higher_half/src/bin/basic_boot.rs +++ b/tests/test_kernels/higher_half/src/bin/basic_boot.rs @@ -1,18 +1,18 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_higher_half::{exit_qemu, QemuExitCode}; +use bootloader_api::{entry_point, BootInfo}; +use test_kernel_higher_half::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; -entry_point!(kernel_main); +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { exit_qemu(QemuExitCode::Success); } /// 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 474f37c4..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 @@ -1,11 +1,10 @@ #![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 core::panic::PanicInfo; -use test_kernel_higher_half::{exit_qemu, QemuExitCode}; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; +use test_kernel_higher_half::{exit_qemu, 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 @@ -14,33 +13,16 @@ 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.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 @@ -50,7 +32,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); @@ -59,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 ea3bae75..44287806 100644 --- a/tests/test_kernels/higher_half/src/bin/should_panic.rs +++ b/tests/test_kernels/higher_half/src/bin/should_panic.rs @@ -1,18 +1,20 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_higher_half::{exit_qemu, QemuExitCode}; +use bootloader_api::{entry_point, BootInfo}; +use test_kernel_higher_half::BOOTLOADER_CONFIG; -entry_point!(kernel_main); +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); fn kernel_main(_boot_info: &'static mut BootInfo) -> ! { panic!(); } /// 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 267f8262..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 @@ -1,11 +1,10 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_higher_half::{exit_qemu, QemuExitCode}; +use bootloader_api::{entry_point, BootInfo}; +use test_kernel_higher_half::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; -entry_point!(kernel_main); +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); fn kernel_main(boot_info: &'static mut BootInfo) -> ! { // verify that kernel is really running in the higher half of the address space @@ -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/higher_half/src/lib.rs b/tests/test_kernels/higher_half/src/lib.rs index 4e46fdb6..99ae71dc 100644 --- a/tests/test_kernels/higher_half/src/lib.rs +++ b/tests/test_kernels/higher_half/src/lib.rs @@ -1,5 +1,13 @@ #![no_std] +use bootloader_api::BootloaderConfig; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.dynamic_range_start = Some(0xffff_8000_0000_0000); + config +}; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[repr(u32)] pub enum QemuExitCode { 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/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..987f6781 --- /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 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. +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &core::panic::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 +} 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 e34621a2..51233f1c 100644 --- a/tests/test_kernels/map_phys_mem/Cargo.toml +++ b/tests/test_kernels/map_phys_mem/Cargo.toml @@ -2,13 +2,12 @@ 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 = { 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] -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 eec06f36..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 @@ -1,11 +1,10 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; -use core::panic::PanicInfo; -use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode}; +use bootloader_api::{entry_point, BootInfo}; +use test_kernel_map_phys_mem::{exit_qemu, 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(); @@ -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 361c4fb4..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 @@ -1,11 +1,10 @@ #![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 core::panic::PanicInfo; -use test_kernel_map_phys_mem::{exit_qemu, serial, QemuExitCode}; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; +use test_kernel_map_phys_mem::{exit_qemu, 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 @@ -14,33 +13,16 @@ 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.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 @@ -53,7 +35,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); @@ -62,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/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..2201d179 100644 --- a/tests/test_kernels/pie/src/bin/basic_boot.rs +++ b/tests/test_kernels/pie/src/bin/basic_boot.rs @@ -1,8 +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 core::panic::PanicInfo; +use bootloader_api::{entry_point, BootInfo}; 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 909f99c2..a7b3a5e3 100644 --- a/tests/test_kernels/pie/src/bin/check_boot_info.rs +++ b/tests/test_kernels/pie/src/bin/check_boot_info.rs @@ -1,8 +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 core::panic::PanicInfo; +use bootloader_api::{entry_point, info::PixelFormat, BootInfo}; use test_kernel_pie::{exit_qemu, QemuExitCode}; entry_point!(kernel_main); @@ -14,33 +13,16 @@ 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.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 @@ -50,7 +32,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); @@ -59,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 d6a6f634..cba6338b 100644 --- a/tests/test_kernels/pie/src/bin/global_variable.rs +++ b/tests/test_kernels/pie/src/bin/global_variable.rs @@ -1,11 +1,8 @@ #![no_std] // don't link the Rust standard library #![no_main] // disable all Rust-level entry points -use bootloader::{entry_point, BootInfo}; -use core::{ - panic::PanicInfo, - sync::atomic::{AtomicU64, Ordering}, -}; +use bootloader_api::{entry_point, BootInfo}; +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 a5b0c2fc..fb08bf5c 100644 --- a/tests/test_kernels/pie/src/bin/should_panic.rs +++ b/tests/test_kernels/pie/src/bin/should_panic.rs @@ -1,9 +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 core::panic::PanicInfo; -use test_kernel_pie::{exit_qemu, QemuExitCode}; +use bootloader_api::{entry_point, BootInfo}; 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/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 - } diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml new file mode 100644 index 00000000..109b9298 --- /dev/null +++ b/uefi/Cargo.toml @@ -0,0 +1,15 @@ +[package] +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 + +[dependencies] +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.16.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/uefi/src/main.rs b/uefi/src/main.rs new file mode 100644 index 00000000..f86d8334 --- /dev/null +++ b/uefi/src/main.rs @@ -0,0 +1,450 @@ +#![no_std] +#![no_main] +#![feature(abi_efiapi)] +#![deny(unsafe_op_in_unsafe_fn)] + +use crate::memory_descriptor::UefiMemoryDescriptor; +use bootloader_api::{info::FrameBufferInfo, BootloaderConfig}; +use bootloader_x86_64_common::{ + legacy_memory_region::LegacyFrameAllocator, Kernel, RawFrameBufferInfo, SystemInfo, +}; +use core::{cell::UnsafeCell, fmt::Write, mem, ptr, slice}; +use uefi::{ + prelude::{entry, Boot, Handle, Status, SystemTable}, + proto::{ + console::gop::{GraphicsOutput, PixelFormat}, + device_path::DevicePath, + loaded_image::LoadedImage, + media::{ + file::{File, FileAttribute, FileInfo, FileMode}, + fs::SimpleFileSystem, + }, + network::{ + pxe::{BaseCode, DhcpV4Packet}, + IpAddress, + }, + }, + table::boot::{ + AllocateType, MemoryDescriptor, MemoryType, OpenProtocolAttributes, OpenProtocolParams, + }, + CStr16, CStr8, +}; +use x86_64::{ + structures::paging::{FrameAllocator, OffsetPageTable, PageTable, PhysFrame, Size4KiB}, + PhysAddr, VirtAddr, +}; + +mod memory_descriptor; + +static SYSTEM_TABLE: RacyCell>> = RacyCell::new(None); + +struct RacyCell(UnsafeCell); + +impl RacyCell { + const fn new(v: T) -> Self { + Self(UnsafeCell::new(v)) + } +} + +unsafe impl Sync for RacyCell {} + +impl core::ops::Deref for RacyCell { + 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 { + // temporarily clone the y table for printing panics + unsafe { + *SYSTEM_TABLE.get() = Some(st.unsafe_clone()); + } + + st.stdout().clear().unwrap(); + writeln!( + st.stdout(), + "UEFI bootloader started; trying to load kernel" + ) + .unwrap(); + + let kernel = load_kernel(image, &mut st); + + let framebuffer = 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"); + if let Some(framebuffer) = framebuffer { + log::info!("Using framebuffer at {:#x}", framebuffer.addr); + } + + let mmap_storage = { + let max_mmap_size = + 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)?; + 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("Failed to exit boot services"); + + let mut frame_allocator = + LegacyFrameAllocator::new(memory_map.copied().map(UefiMemoryDescriptor)); + + let page_tables = create_page_tables(&mut frame_allocator); + + let system_info = SystemInfo { + framebuffer, + 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_x86_64_common::load_and_switch_to_kernel( + kernel, + frame_allocator, + page_tables, + system_info, + ); +} + +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) +} + +/// 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)) +} + +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 + .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.interface.get() }; + + let device_handle = loaded_image.device(); + + let device_path = this + .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.interface.get() }; + + let fs_handle = this + .locate_device_path::(&mut device_path) + .ok()?; + + this.open_protocol::( + OpenProtocolParams { + handle: fs_handle, + agent: image, + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + } + .unwrap(); + let file_system = unsafe { &mut *file_system_raw.interface.get() }; + + 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(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(); + 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() 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(); + + 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 this = st.boot_services(); + + // Try to locate a `BaseCode` protocol on the boot device. + + let loaded_image = this + .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.interface.get() }; + + let device_handle = loaded_image.device(); + + let device_path = this + .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.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(); + 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(); + + // 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( + AllocateType::AnyPages, + MemoryType::LOADER_DATA, + ((kernel_size - 1) / 4096) + 1, + ) + .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)) + .expect("Failed to read kernel file from the TFTP boot server"); + + Some(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_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); + + // copy the currently active level 4 page table, because it might be read-only + log::trace!("switching to new level 4 table"); + let bootloader_page_table = { + let old_table = { + let frame = x86_64::registers::control::Cr3::read().0; + let ptr: *const PageTable = (phys_offset + frame.start_address().as_u64()).as_ptr(); + unsafe { &*ptr } + }; + let new_frame = frame_allocator + .allocate_frame() + .expect("Failed to allocate frame for new level 4 table"); + let new_table: &mut PageTable = { + let ptr: *mut PageTable = + (phys_offset + new_frame.start_address().as_u64()).as_mut_ptr(); + // create a new, empty page table + unsafe { + ptr.write(PageTable::new()); + &mut *ptr + } + }; + + // copy the first entry (we don't need to access more than 512 GiB; also, some UEFI + // implementations seem to create an level 4 table entry 0 in all slots) + new_table[0] = old_table[0].clone(); + + // the first level 4 table entry is now identical, so we can just load the new one + unsafe { + x86_64::registers::control::Cr3::write( + new_frame, + x86_64::registers::control::Cr3Flags::empty(), + ); + OffsetPageTable::new(&mut *new_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, + ) + }; + + bootloader_x86_64_common::PageTables { + bootloader: bootloader_page_table, + kernel: kernel_page_table, + kernel_level_4_frame, + } +} + +fn init_logger(st: &SystemTable, config: BootloaderConfig) -> Option { + let gop = st + .boot_services() + .locate_protocol::() + .ok()?; + let gop = unsafe { &mut *gop.get() }; + + let mode = { + let modes = gop.modes(); + match ( + 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| { + let res = m.info().resolution(); + res.1 >= height && res.0 >= width + }) + .last(), + (Some(height), None) => modes.filter(|m| m.info().resolution().1 >= height).last(), + (None, Some(width)) => modes.filter(|m| m.info().resolution().0 >= width).last(), + _ => None, + } + }; + if let Some(mode) = mode { + gop.set_mode(&mode) + .expect("Failed to apply the desired display mode"); + } + + let mode_info = gop.current_mode_info(); + let mut framebuffer = gop.frame_buffer(); + let slice = unsafe { slice::from_raw_parts_mut(framebuffer.as_mut_ptr(), framebuffer.size()) }; + let info = FrameBufferInfo { + byte_len: framebuffer.size(), + 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, + PixelFormat::Bitmask | PixelFormat::BltOnly => { + panic!("Bitmask and BltOnly framebuffers are not supported") + } + }, + bytes_per_pixel: 4, + stride: mode_info.stride(), + }; + + log::info!("UEFI boot"); + + bootloader_x86_64_common::init_logger(slice, info); + + Some(RawFrameBufferInfo { + addr: PhysAddr::new(framebuffer.as_mut_ptr() as u64), + info, + }) +} + +#[cfg(all(not(test), target_os = "uefi"))] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + use core::arch::asm; + + if let Some(st) = unsafe { &mut *SYSTEM_TABLE.get() } { + let _ = writeln!(st.stdout(), "{}", info); + } + + unsafe { + bootloader_x86_64_common::logger::LOGGER + .get() + .map(|l| l.force_unlock()) + }; + log::error!("{}", info); + + loop { + unsafe { asm!("cli; hlt") }; + } +} diff --git a/uefi/src/memory_descriptor.rs b/uefi/src/memory_descriptor.rs new file mode 100644 index 00000000..ac3b2ba3 --- /dev/null +++ b/uefi/src/memory_descriptor.rs @@ -0,0 +1,46 @@ +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 UefiMemoryDescriptor { + fn start(&self) -> PhysAddr { + PhysAddr::new(self.0.phys_start) + } + + fn len(&self) -> u64 { + self.0.page_count * PAGE_SIZE + } + + fn kind(&self) -> MemoryRegionKind { + match self.0.ty { + MemoryType::CONVENTIONAL => MemoryRegionKind::Usable, + other => MemoryRegionKind::UnknownUefi(other.0), + } + } + + fn usable_after_bootloader_exit(&self) -> bool { + match self.0.ty { + MemoryType::CONVENTIONAL => true, + MemoryType::LOADER_CODE + | MemoryType::LOADER_DATA + | MemoryType::BOOT_SERVICES_CODE + | 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, + } + } +} 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" -} 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" +}