diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a397f5fd..f136dc79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,9 @@ jobs: - uses: r7kamura/rust-problem-matchers@v1.1.0 - name: "Run `cargo check`" run: cargo check --all-targets --all + - name: "Check test kernels" + run: cargo check --all + working-directory: tests/test_kernels - name: "Check docs.rs build" run: cargo check env: @@ -36,7 +39,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest, windows-latest, macos-latest] runs-on: ${{ matrix.os }} timeout-minutes: 30 @@ -68,6 +71,9 @@ jobs: - uses: r7kamura/rust-problem-matchers@v1.1.0 - name: Run api tests run: cargo test -p bootloader_api + - name: Run bootloader common tests + if: runner.arch == 'X64' + run: cargo test -p bootloader-x86_64-common - name: Run integration tests run: cargo test -- --test-threads 1 @@ -86,6 +92,8 @@ jobs: - uses: actions/checkout@v3 - uses: r7kamura/rust-problem-matchers@v1.1.0 - run: cargo fmt --all -- --check + - run: cargo fmt --all -- --check + working-directory: tests/test_kernels clippy: name: Clippy @@ -96,6 +104,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - uses: r7kamura/rust-problem-matchers@v1.1.0 - run: cargo clippy --all --all-targets + - run: cargo clippy --all + working-directory: tests/test_kernels semver-checks: name: Semver Checks diff --git a/Cargo.lock b/Cargo.lock index 5023a397..c97f6a54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "anyhow" @@ -14,70 +14,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "async-channel" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" -dependencies = [ - "concurrent-queue", - "event-listener", - "futures-core", -] - -[[package]] -name = "async-io" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" -dependencies = [ - "async-lock", - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-lite", - "log", - "parking", - "polling", - "rustix", - "slab", - "socket2", - "waker-fn", -] - -[[package]] -name = "async-lock" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" -dependencies = [ - "event-listener", -] - -[[package]] -name = "async-process" -version = "1.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9d28b1d97e08915212e2e45310d47854eafa69600756fc735fb788f75199c9" -dependencies = [ - "async-io", - "async-lock", - "autocfg", - "blocking", - "cfg-if", - "event-listener", - "futures-lite", - "rustix", - "signal-hook", - "windows-sys", -] - -[[package]] -name = "async-task" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc7ab41815b3c653ccd2978ec3255c81349336702dfdf62ee6f7069b12a3aae" - [[package]] name = "atomic-polyfill" version = "0.1.11" @@ -87,12 +23,6 @@ dependencies = [ "critical-section", ] -[[package]] -name = "atomic-waker" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" - [[package]] name = "autocfg" version = "1.1.0" @@ -138,32 +68,14 @@ dependencies = [ "wyz", ] -[[package]] -name = "blocking" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77231a1c8f801696fc0123ec6150ce92cffb8e164a02afb9c8ddee0e9b65ad65" -dependencies = [ - "async-channel", - "async-lock", - "async-task", - "atomic-waker", - "fastrand", - "futures-lite", - "log", -] - [[package]] name = "bootloader" -version = "0.11.4" +version = "0.11.11" dependencies = [ "anyhow", - "async-process", "bootloader-boot-config", "bootloader_test_runner", "fatfs", - "futures", - "futures-concurrency", "gpt", "llvm-tools", "mbrman", @@ -171,31 +83,34 @@ dependencies = [ "tempfile", "test_kernel_config_file", "test_kernel_default_settings", + "test_kernel_fixed_kernel_address", "test_kernel_higher_half", + "test_kernel_lower_memory_free", "test_kernel_map_phys_mem", "test_kernel_min_stack", "test_kernel_pie", "test_kernel_ramdisk", + "test_kernel_write_usable_memory", ] [[package]] name = "bootloader-boot-config" -version = "0.11.4" +version = "0.11.11" dependencies = [ "serde", ] [[package]] name = "bootloader-x86_64-bios-boot-sector" -version = "0.11.4" +version = "0.11.11" [[package]] name = "bootloader-x86_64-bios-common" -version = "0.11.4" +version = "0.11.11" [[package]] name = "bootloader-x86_64-bios-stage-2" -version = "0.11.4" +version = "0.11.11" dependencies = [ "bootloader-x86_64-bios-common", "byteorder", @@ -204,7 +119,7 @@ dependencies = [ [[package]] name = "bootloader-x86_64-bios-stage-3" -version = "0.11.4" +version = "0.11.11" dependencies = [ "bootloader-x86_64-bios-common", "noto-sans-mono-bitmap 0.1.6", @@ -212,7 +127,7 @@ dependencies = [ [[package]] name = "bootloader-x86_64-bios-stage-4" -version = "0.11.4" +version = "0.11.11" dependencies = [ "bootloader-boot-config", "bootloader-x86_64-bios-common", @@ -222,12 +137,12 @@ dependencies = [ "rsdp", "serde-json-core", "usize_conversions", - "x86_64", + "x86_64 0.15.2", ] [[package]] name = "bootloader-x86_64-common" -version = "0.11.4" +version = "0.11.11" dependencies = [ "bootloader-boot-config", "bootloader_api", @@ -238,15 +153,15 @@ dependencies = [ "rand_hc", "raw-cpuid", "spinning_top", - "uart_16550", + "uart_16550 0.3.2", "usize_conversions", - "x86_64", + "x86_64 0.15.2", "xmas-elf", ] [[package]] name = "bootloader-x86_64-uefi" -version = "0.11.4" +version = "0.11.11" dependencies = [ "bootloader-boot-config", "bootloader-x86_64-common", @@ -254,12 +169,12 @@ dependencies = [ "log", "serde-json-core", "uefi", - "x86_64", + "x86_64 0.15.2", ] [[package]] name = "bootloader_api" -version = "0.11.4" +version = "0.11.11" dependencies = [ "rand", ] @@ -291,15 +206,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "concurrent-queue" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" -dependencies = [ - "crossbeam-utils", -] - [[package]] name = "conquer-once" version = "0.3.2" @@ -336,15 +242,6 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6548a0ad5d2549e111e1f6a11a6c2e2d00ce6a3dafe22948d67c2b443f775e52" -[[package]] -name = "crossbeam-utils" -version = "0.8.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] - [[package]] name = "errno" version = "0.3.1" @@ -366,12 +263,6 @@ dependencies = [ "libc", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "fastrand" version = "1.9.0" @@ -398,121 +289,6 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" -[[package]] -name = "futures" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-concurrency" -version = "7.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b726119e6cd29cf120724495b2085e1ed3d17821ea17b86de54576d1aa565f5e" -dependencies = [ - "bitvec", - "futures-core", - "pin-project", -] - -[[package]] -name = "futures-core" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" - -[[package]] -name = "futures-executor" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" - -[[package]] -name = "futures-lite" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" -dependencies = [ - "fastrand", - "futures-core", - "futures-io", - "memchr", - "parking", - "pin-project-lite", - "waker-fn", -] - -[[package]] -name = "futures-macro" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.23", -] - -[[package]] -name = "futures-sink" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" - -[[package]] -name = "futures-task" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" - -[[package]] -name = "futures-util" -version = "0.3.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "getrandom" version = "0.2.10" @@ -646,12 +422,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - [[package]] name = "noto-sans-mono-bitmap" version = "0.1.6" @@ -670,60 +440,6 @@ version = "0.1.0-alpha.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa50141d081512ab30fd9e7e7692476866df5098b028536ad6680212e717fa8d" -[[package]] -name = "parking" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" - -[[package]] -name = "pin-project" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "030ad2bc4db10a8944cb0d837f158bdfec4d4a4873ab701a95046770d11f8842" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec2e072ecce94ec471b13398d5402c188e76ac03cf74dd1a975161b23a3f6d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.23", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40d25201921e5ff0c862a505c6557ea88568a4e3ace775ab55e93f2f4f9d57" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "polling" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "concurrent-queue", - "libc", - "log", - "pin-project-lite", - "windows-sys", -] - [[package]] name = "ppv-lite86" version = "0.2.17" @@ -851,9 +567,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.22" +version = "0.37.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8818fa822adcc98b18fedbb3632a6a33213c070556b5aa7c4c8cc21cff565c4c" +checksum = "84f3f8f960ed3b5a59055428714943298bf3fa2d4a1d53135084e0544829d995" dependencies = [ "bitflags 1.3.2", "errno", @@ -938,44 +654,6 @@ dependencies = [ "serde", ] -[[package]] -name = "signal-hook" -version = "0.3.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9" -dependencies = [ - "libc", - "signal-hook-registry", -] - -[[package]] -name = "signal-hook-registry" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" -dependencies = [ - "autocfg", -] - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - [[package]] name = "spin" version = "0.9.8" @@ -1056,8 +734,8 @@ name = "test_kernel_config_file" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] @@ -1065,8 +743,17 @@ name = "test_kernel_default_settings" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_fixed_kernel_address" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] @@ -1074,17 +761,17 @@ name = "test_kernel_higher_half" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] -name = "test_kernel_lto" +name = "test_kernel_lower_memory_free" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] @@ -1092,8 +779,8 @@ name = "test_kernel_map_phys_mem" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] @@ -1101,8 +788,8 @@ name = "test_kernel_min_stack" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] @@ -1110,8 +797,8 @@ name = "test_kernel_pie" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] @@ -1119,8 +806,17 @@ name = "test_kernel_ramdisk" version = "0.1.0" dependencies = [ "bootloader_api", - "uart_16550", - "x86_64", + "uart_16550 0.2.18", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_write_usable_memory" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550 0.2.18", + "x86_64 0.15.2", ] [[package]] @@ -1151,7 +847,18 @@ checksum = "b074eb9300ad949edd74c529c0e8d451625af71bb948e6b65fe69f72dc1363d9" dependencies = [ "bitflags 1.3.2", "rustversion", - "x86_64", + "x86_64 0.14.13", +] + +[[package]] +name = "uart_16550" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e492212ac378a5e00da953718dafb1340d9fbaf4f27d6f3c5cab03d931d1c049" +dependencies = [ + "bitflags 2.3.3", + "rustversion", + "x86", ] [[package]] @@ -1241,40 +948,12 @@ dependencies = [ "quote", ] -[[package]] -name = "waker-fn" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.48.0" @@ -1351,13 +1030,36 @@ dependencies = [ ] [[package]] -name = "x86_64" -version = "0.14.10" +name = "x86" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "100555a863c0092238c2e0e814c1096c1e5cf066a309c696a87e907b5f8c5d69" +checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" dependencies = [ "bit_field", "bitflags 1.3.2", + "raw-cpuid", +] + +[[package]] +name = "x86_64" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491" +dependencies = [ + "bit_field", + "bitflags 2.3.3", + "rustversion", + "volatile", +] + +[[package]] +name = "x86_64" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f042214de98141e9c8706e8192b73f56494087cc55ebec28ce10f26c5c364ae" +dependencies = [ + "bit_field", + "bitflags 2.3.3", "rustversion", "volatile", ] diff --git a/Cargo.toml b/Cargo.toml index b6ab32f2..eec8eb7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,3 @@ -cargo-features = ["profile-rustflags"] - [package] name = "bootloader" description = "An experimental x86_64 bootloader that works on both BIOS and UEFI systems." @@ -19,27 +17,20 @@ members = [ "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", - "tests/test_kernels/ramdisk", - "tests/test_kernels/min_stack", ] -exclude = ["examples/basic", "examples/test_framework"] +exclude = ["examples/basic", "examples/test_framework", "tests/test_kernels/*"] [workspace.package] # don't forget to update `workspace.dependencies` below -version = "0.11.4" -license = "MIT/Apache-2.0" +version = "0.11.11" +license = "MIT OR Apache-2.0" repository = "https://github.com/rust-osdev/bootloader" [workspace.dependencies] -bootloader_api = { version = "0.11.4", path = "api" } -bootloader-x86_64-common = { version = "0.11.4", path = "common" } -bootloader-boot-config = { version = "0.11.4", path = "common/config" } -bootloader-x86_64-bios-common = { version = "0.11.4", path = "bios/common" } +bootloader_api = { version = "0.11.11", path = "api" } +bootloader-x86_64-common = { version = "0.11.11", path = "common" } +bootloader-boot-config = { version = "0.11.11", path = "common/config" } +bootloader-x86_64-bios-common = { version = "0.11.11", path = "bios/common" } [features] default = ["bios", "uefi"] @@ -67,6 +58,9 @@ test_kernel_pie = { path = "tests/test_kernels/pie", artifact = "bin", target = test_kernel_ramdisk = { path = "tests/test_kernels/ramdisk", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_config_file = { path = "tests/test_kernels/config_file", artifact = "bin", target = "x86_64-unknown-none" } test_kernel_min_stack = { path = "tests/test_kernels/min_stack", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_lower_memory_free = { path = "tests/test_kernels/lower_memory_free", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_write_usable_memory = { path = "tests/test_kernels/write_usable_memory", artifact = "bin", target = "x86_64-unknown-none" } +test_kernel_fixed_kernel_address = { path = "tests/test_kernels/fixed_kernel_address", artifact = "bin", target = "x86_64-unknown-none" } [profile.dev] panic = "abort" @@ -106,28 +100,8 @@ 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=pic", - "-C", - "code-model=large", -] - -[profile.test.package.test_kernel_min_stack] -opt-level = 2 - [build-dependencies] llvm-tools = "0.1.1" -async-process = "1.6.0" -futures = "0.3.25" -futures-concurrency = "7.0.0" [package.metadata.docs.rs] rustc-args = ["--cfg", "docsrs_dummy_build"] diff --git a/Changelog.md b/Changelog.md index b8ecf0b8..7daa1cdf 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,79 @@ # Unreleased +# 0.11.11 – 2025-07-31 + +This release is compatible with Rust nightlies starting with `nightly-2025-07-24`. + +* [Update uart_16550 to 0.3.2](https://github.com/rust-osdev/bootloader/pull/495) +* [add `kernel_base` mapping to the BootloaderConfig](https://github.com/rust-osdev/bootloader/pull/494) +* [Use Result::ok](https://github.com/rust-osdev/bootloader/pull/496) +* [implement Send+Sync for MemoryRegions](https://github.com/rust-osdev/bootloader/pull/502) +* [Document physical memory mapping size](https://github.com/rust-osdev/bootloader/pull/506) +* [Fixes the type of target-c-int-width in target jsons. #](https://github.com/rust-osdev/bootloader/pull/509) + +**Full Changelog**: https://github.com/rust-osdev/bootloader/compare/v0.11.10...v0.11.11 + +# 0.11.10 – 2025-02-10 + +* [Remove "UEFI boot" log message](https://github.com/rust-osdev/bootloader/pull/476) +* [use threads instead of futures in build.rs](https://github.com/rust-osdev/bootloader/pull/484) +* [Move test kernels to a separate workspace](https://github.com/rust-osdev/bootloader/pull/486) +* [fix condition for running bootloader common tests](https://github.com/rust-osdev/bootloader/pull/487) +* [Update `x86_64` to `0.15.2`](https://github.com/rust-osdev/bootloader/pull/490) +* [change rustc-abi in custom targets to x86-softfloat](https://github.com/rust-osdev/bootloader/pull/491) + +**Full Changelog**: https://github.com/rust-osdev/bootloader/compare/v0.11.9...v0.11.10 + +# 0.11.9 – 2024-11-30 + +This release is compatible with Rust nightlies starting with `nightly-2024-11-23`. + +* [copy more PML4 entries](https://github.com/rust-osdev/bootloader/pull/466) +* [Convert LF to CRLF when writing to serial port](https://github.com/rust-osdev/bootloader/pull/474) +* [Update x86_64 & fix build on latest nightly](https://github.com/rust-osdev/bootloader/pull/478) + +**Full Changelog**: https://github.com/rust-osdev/bootloader/compare/v0.11.8...v0.11.9 + +# 0.11.8 – 2024-11-02 + +* [avoid 32-bit relocation to __BOOTLOADER_CONFIG](https://github.com/rust-osdev/bootloader/pull/428) +* [Fix doc comment and error message only referencing the BIOS but used for UEFI](https://github.com/rust-osdev/bootloader/pull/439) +* [Ensure all page table frames are mapped as writable](https://github.com/rust-osdev/bootloader/pull/444) +* [Guard the lower 1MB of memory](https://github.com/rust-osdev/bootloader/pull/446) +* [always cover at least the first 4 GiB of physical memory](https://github.com/rust-osdev/bootloader/pull/448) +* [Fixed "jc fail" instructions not working properly and updated README.md](https://github.com/rust-osdev/bootloader/pull/453) +* [Remove 3dnow features from stage4 target](https://github.com/rust-osdev/bootloader/pull/471) +* [mention E820 in docs for UnknownBios](https://github.com/rust-osdev/bootloader/pull/461) + +**Full Changelog**: https://github.com/rust-osdev/bootloader/compare/v0.11.7...v0.11.8 + +# 0.11.7 – 2024-02-16 + +* Set `NO_EXECUTE` flag for all writable memory regions by @phil-opp in https://github.com/rust-osdev/bootloader/pull/409 +* adapt data layout to match LLVM's by @tsatke in https://github.com/rust-osdev/bootloader/pull/420 + +**Full Changelog**: https://github.com/rust-osdev/bootloader/compare/v0.11.6...v0.11.7 + +# 0.11.6 – 2024-01-28 + +* [Embed bios and uefi binaries](https://github.com/rust-osdev/bootloader/pull/395) +* [Add a `take` method to `Optional`](https://github.com/rust-osdev/bootloader/pull/411) +* [Fix data layout for stage 3 target](https://github.com/rust-osdev/bootloader/pull/413) + +**Full Changelog**: https://github.com/rust-osdev/bootloader/compare/v0.11.5...v0.11.6 + +# 0.11.5 – 2023-12-28 + +* [RacyCell: Data race allowed on `T`](https://github.com/rust-osdev/bootloader/pull/390) +* [Update license field following SPDX 2.1 license expression standard](https://github.com/rust-osdev/bootloader/pull/391) +* [kernel image fields & zero out rbp](https://github.com/rust-osdev/bootloader/pull/346) +* [Update `rustix` dependency](https://github.com/rust-osdev/bootloader/pull/398) +* [Add an additional MB of space to the generated FAT partition](https://github.com/rust-osdev/bootloader/pull/397) +* [Fix: Enable test runner again](https://github.com/rust-osdev/bootloader/pull/407) +* [Fix: Mark `ramdisk` as used in memory map](https://github.com/rust-osdev/bootloader/pull/408) + +**Full Changelog**: https://github.com/rust-osdev/bootloader/compare/v0.11.4...v0.11.5 + # 0.11.4 – 2023-07-05 - [Fix bug stemming from treating an exclusive range as an inclusive ranges](https://github.com/rust-osdev/bootloader/pull/362) diff --git a/README.md b/README.md index 202a1984..e8f4ef01 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Docs](https://docs.rs/bootloader/badge.svg)](https://docs.rs/bootloader) [![Build Status](https://github.com/rust-osdev/bootloader/actions/workflows/build.yml/badge.svg)](https://github.com/rust-osdev/bootloader/actions/workflows/build.yml) -[![Join the chat at https://gitter.im/rust-osdev/bootloader](https://badges.gitter.im/rust-osdev/bootloader.svg)](https://gitter.im/rust-osdev/bootloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Join the chat at https://rust-osdev.zulipchat.com](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://rust-osdev.zulipchat.com) An experimental x86_64 bootloader that works on both BIOS and UEFI systems. Written in Rust and some inline assembly, buildable on all platforms without additional build-time dependencies (just some `rustup` components). diff --git a/api/build.rs b/api/build.rs index e0b1437d..75754523 100644 --- a/api/build.rs +++ b/api/build.rs @@ -15,14 +15,15 @@ fn main() { (31, 9), (40, 9), (49, 9), - (58, 10), - (68, 10), - (78, 1), - (79, 9), + (58, 9), + (67, 10), + (77, 10), + (87, 1), (88, 9), (97, 9), (106, 9), (115, 9), + (124, 9), ]; let mut code = String::new(); diff --git a/api/src/config.rs b/api/src/config.rs index 9def2a5f..c0f574bd 100644 --- a/api/src/config.rs +++ b/api/src/config.rs @@ -43,7 +43,7 @@ impl BootloaderConfig { 0x3D, ]; #[doc(hidden)] - pub const SERIALIZED_LEN: usize = 124; + pub const SERIALIZED_LEN: usize = 133; /// Creates a new default configuration with the following values: /// @@ -77,6 +77,7 @@ impl BootloaderConfig { } = version; let Mappings { kernel_stack, + kernel_base, boot_info, framebuffer, physical_memory, @@ -97,35 +98,38 @@ impl BootloaderConfig { 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_40_9(buf, kernel_base.serialize()); + + let buf = concat_49_9(buf, boot_info.serialize()); + let buf = concat_58_9(buf, framebuffer.serialize()); - let buf = concat_58_10( + let buf = concat_67_10( buf, match physical_memory { Option::None => [0; 10], Option::Some(m) => concat_1_9([1], m.serialize()), }, ); - let buf = concat_68_10( + let buf = concat_77_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( + let buf = concat_87_1(buf, [(*aslr) as u8]); + let buf = concat_88_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( + let buf = concat_97_9( buf, match dynamic_range_end { Option::None => [0; 9], @@ -133,9 +137,9 @@ impl BootloaderConfig { }, ); - let buf = concat_97_9(buf, ramdisk_memory.serialize()); + let buf = concat_106_9(buf, ramdisk_memory.serialize()); - let buf = concat_106_9( + let buf = concat_115_9( buf, match minimum_framebuffer_height { Option::None => [0; 9], @@ -143,7 +147,7 @@ impl BootloaderConfig { }, ); - concat_115_9( + concat_124_9( buf, match minimum_framebuffer_width { Option::None => [0; 9], @@ -196,6 +200,7 @@ impl BootloaderConfig { let (mappings, s) = { let (&kernel_stack, s) = split_array_ref(s); + let (&kernel_base, 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); @@ -211,6 +216,7 @@ impl BootloaderConfig { let mappings = Mappings { kernel_stack: Mapping::deserialize(&kernel_stack)?, + kernel_base: Mapping::deserialize(&kernel_base)?, boot_info: Mapping::deserialize(&boot_info)?, framebuffer: Mapping::deserialize(&framebuffer)?, physical_memory: match physical_memory_some { @@ -371,14 +377,25 @@ pub struct Mappings { /// `FixedAddress(0xf_0000_0000)` will result in a guard page at address /// `0xf_0000_0000` and the kernel stack starting at address `0xf_0000_1000`. pub kernel_stack: Mapping, + /// Configures the base address of the kernel. + /// + /// If a fixed address is set, it must be paged aligned and the kernel must be + /// a position-independent exectuable. + pub kernel_base: 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 + /// The bootloader supports mapping 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. /// + /// This mapping will go from physical address `0x0` to whichever is larger: + /// - The end of the last region in the BIOS/UEFI memory map + /// - The address `0x1_0000_0000` (such that at least 4 GiB of physical memory are always mapped). + /// This is to ensure that useful MMIO regions (local APIC, I/O APIC, PCI bars) are + /// accessible to the kernel even if less physical memory than that is on the system. + /// /// 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`]), @@ -413,6 +430,7 @@ impl Mappings { pub const fn new_default() -> Self { Self { kernel_stack: Mapping::new_default(), + kernel_base: Mapping::new_default(), boot_info: Mapping::new_default(), framebuffer: Mapping::new_default(), physical_memory: Option::None, @@ -430,6 +448,7 @@ impl Mappings { let recursive = rand::random(); Self { kernel_stack: Mapping::random(), + kernel_base: Mapping::random(), boot_info: Mapping::random(), framebuffer: Mapping::random(), physical_memory: if phys { diff --git a/api/src/info.rs b/api/src/info.rs index cdb48722..d934c532 100644 --- a/api/src/info.rs +++ b/api/src/info.rs @@ -56,6 +56,12 @@ pub struct BootInfo { pub ramdisk_addr: Optional, /// Ramdisk image size, set to 0 if addr is None pub ramdisk_len: u64, + /// Physical address of the kernel ELF in memory. + pub kernel_addr: u64, + /// Size of the kernel ELF in memory. + pub kernel_len: u64, + /// Virtual address of the loaded kernel image. + pub kernel_image_offset: u64, #[doc(hidden)] pub _test_sentinel: u64, @@ -76,6 +82,9 @@ impl BootInfo { tls_template: Optional::None, ramdisk_addr: Optional::None, ramdisk_len: 0, + kernel_addr: 0, + kernel_len: 0, + kernel_image_offset: 0, _test_sentinel: 0, } } @@ -123,6 +132,9 @@ impl From for &'static mut [MemoryRegion] { } } +unsafe impl Send for MemoryRegions {} +unsafe impl Sync for MemoryRegions {} + /// Represent a physical memory region. #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[repr(C)] @@ -164,6 +176,8 @@ pub enum MemoryRegionKind { /// Contains the UEFI memory type tag. UnknownUefi(u32), /// An unknown memory region reported by the BIOS firmware. + /// + /// Contains the E820 memory type. UnknownBios(u32), } @@ -332,6 +346,11 @@ impl Optional { Self::None => None, } } + + /// Takes the value out of the `Optional`, leaving a `None` in its place. + pub fn take(&mut self) -> Option { + core::mem::replace(self, Optional::None).into_option() + } } impl From> for Optional { diff --git a/api/src/lib.rs b/api/src/lib.rs index 4ada525f..586ae1d0 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -106,6 +106,7 @@ mod version_info { /// `#[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. +#[cfg(target_arch = "x86_64")] #[macro_export] macro_rules! entry_point { ($path:path) => { @@ -120,13 +121,17 @@ macro_rules! entry_point { config.serialize() }; + // Workaround for https://github.com/rust-osdev/bootloader/issues/427 + static __BOOTLOADER_CONFIG_REF: &[u8; $crate::BootloaderConfig::SERIALIZED_LEN] = + &__BOOTLOADER_CONFIG; + #[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); + $crate::__force_use(&__BOOTLOADER_CONFIG_REF); f(boot_info) } @@ -135,7 +140,8 @@ macro_rules! entry_point { } #[doc(hidden)] -pub fn __force_use(slice: &[u8]) { - let force_use = slice.as_ptr() as usize; +#[cfg(target_arch = "x86_64")] +pub fn __force_use(slice: &&[u8; BootloaderConfig::SERIALIZED_LEN]) { + let force_use = slice as *const _ as usize; unsafe { core::arch::asm!("add {0}, 0", in(reg) force_use, options(nomem, nostack)) }; } diff --git a/bios/boot_sector/README.md b/bios/boot_sector/README.md index 5c9cf4e6..a783d80e 100644 --- a/bios/boot_sector/README.md +++ b/bios/boot_sector/README.md @@ -4,13 +4,13 @@ This executable needs to fit into the 512-byte boot sector, so we need to use al ## 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 +1. `cargo build --profile=stage-1 -Zbuild-std=core --target ../../i386-code16-boot-sector.json -Zbuild-std-features=compiler-builtins-mem` +2. `objcopy -I elf32-i386 -O binary ../../target/i386-code16-boot-sector/stage-1/bootloader-x86_64-bios-boot-sector ../../target/disk_image.img` To run in QEMU: -- `qemu-system-x86_64 -drive format=raw,file=target/disk_image.bin` +- `qemu-system-x86_64 -drive format=raw,file=../../target/disk_image.img` 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` +- `objdump -xsdS -M i8086,intel ../../target/i386-code16-boot-sector/stage-1/bootloader-x86_64-bios-boot-sector` diff --git a/bios/boot_sector/src/boot.s b/bios/boot_sector/src/boot.s index 992ce693..1544cac2 100644 --- a/bios/boot_sector/src/boot.s +++ b/bios/boot_sector/src/boot.s @@ -36,13 +36,18 @@ check_int13h_extensions: mov bx, 0x55aa # dl contains drive number int 0x13 - jc fail + jnc .int13_pass + call fail +.int13_pass: pop ax # pop error code again rust: # push arguments push dx # disk number call first_stage + # Fail code if first stage returns + push 'x' + call fail spin: hlt diff --git a/bios/boot_sector/src/dap.rs b/bios/boot_sector/src/dap.rs index da72b777..11e8a853 100644 --- a/bios/boot_sector/src/dap.rs +++ b/bios/boot_sector/src/dap.rs @@ -38,11 +38,13 @@ impl DiskAddressPacket { let self_addr = self as *const Self as u16; unsafe { asm!( - "push 0x7a", // error code `z`, passed to `fail` on error + "push 'z'", // 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", + "jnc 2f", // carry is set on fail + "call fail", + "2:", "pop si", // remove error code again "mov si, {1:x}", // restore the `si` register to its prior state in(reg) self_addr, diff --git a/bios/boot_sector/src/mbr.rs b/bios/boot_sector/src/mbr.rs index 1f7eed52..05382d28 100644 --- a/bios/boot_sector/src/mbr.rs +++ b/bios/boot_sector/src/mbr.rs @@ -16,14 +16,14 @@ pub(crate) fn get_partition(partitions_raw: &[u8], index: usize) -> PartitionTab .get(8..) .and_then(|s| s.get(..4)) .and_then(|s| s.try_into().ok()) - .unwrap_or_fail(b'e'), + .unwrap_or_fail(b'f'), ); 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'), + .unwrap_or_fail(b'g'), ); PartitionTableEntry::new(bootable, partition_type, lba, len) } diff --git a/bios/common/src/racy_cell.rs b/bios/common/src/racy_cell.rs index d2797f7b..439d1b18 100644 --- a/bios/common/src/racy_cell.rs +++ b/bios/common/src/racy_cell.rs @@ -18,4 +18,4 @@ impl RacyCell { } unsafe impl Send for RacyCell where T: Send {} -unsafe impl Sync for RacyCell {} +unsafe impl Sync for RacyCell {} diff --git a/bios/stage-2/src/dap.rs b/bios/stage-2/src/dap.rs index 00bd1268..f94b7fde 100644 --- a/bios/stage-2/src/dap.rs +++ b/bios/stage-2/src/dap.rs @@ -38,11 +38,13 @@ impl DiskAddressPacket { pub unsafe fn perform_load(&self, disk_number: u16) { let self_addr = self as *const Self as u16; asm!( - "push 0x7a", // error code `z`, passed to `fail` on error + "push 'z'", // error code `z`, passed to `fail` on error "mov {1:x}, si", "mov si, {0:x}", "int 0x13", - "jc fail", + "jnc 2f", // carry is set on fail + "call fail", + "2:", "pop si", // remove error code again "mov si, {1:x}", in(reg) self_addr, diff --git a/bios/stage-4/Cargo.toml b/bios/stage-4/Cargo.toml index 769ca657..2afb8654 100644 --- a/bios/stage-4/Cargo.toml +++ b/bios/stage-4/Cargo.toml @@ -14,7 +14,7 @@ bootloader-x86_64-common = { workspace = true } bootloader-x86_64-bios-common = { workspace = true } bootloader-boot-config = { workspace = true } log = "0.4.14" -x86_64 = "0.14.8" +x86_64 = "0.15.2" rsdp = "2.0.0" usize_conversions = "0.2.0" serde-json-core = "0.5.0" diff --git a/bios/stage-4/src/main.rs b/bios/stage-4/src/main.rs index 260dd622..cf159a61 100644 --- a/bios/stage-4/src/main.rs +++ b/bios/stage-4/src/main.rs @@ -78,7 +78,9 @@ pub extern "C" fn _start(info: &mut BiosInfo) -> ! { bootloader_page_table .identity_map( frame, - PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + PageTableFlags::PRESENT + | PageTableFlags::WRITABLE + | PageTableFlags::NO_EXECUTE, &mut frame_allocator, ) .unwrap() @@ -260,6 +262,10 @@ fn detect_rsdp() -> Option { #[derive(Clone)] struct IdentityMapped; impl AcpiHandler for IdentityMapped { + // TODO FIXME: This inline(never) annotation is required. Without it, + // LLVM replaces the `search_for_on_bios` call below with a `ud2` + // instruction. See https://github.com/rust-osdev/bootloader/issues/425 + #[inline(never)] unsafe fn map_physical_region( &self, physical_address: usize, diff --git a/build.rs b/build.rs index d40373d4..c83c6145 100644 --- a/build.rs +++ b/build.rs @@ -1,43 +1,40 @@ -use async_process::Command; -use futures::executor::block_on; -use futures_concurrency::future::Join; use std::path::{Path, PathBuf}; +use std::process::Command; const BOOTLOADER_VERSION: &str = env!("CARGO_PKG_VERSION"); fn main() { #[cfg(not(feature = "uefi"))] - async fn uefi_main() {} + fn uefi_main() {} #[cfg(not(feature = "bios"))] - async fn bios_main() {} + fn bios_main() {} - block_on((uefi_main(), bios_main()).join()); + // Spawn two threads to build the uefi and bios code concurrently. + let uefi_main_handle = std::thread::spawn(uefi_main); + let bios_main_handle = std::thread::spawn(bios_main); + + // Wait for the threads to finish. + uefi_main_handle.join().unwrap(); + bios_main_handle.join().unwrap(); } #[cfg(feature = "bios")] -async fn bios_main() { - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); +fn bios_main() { // Run the bios build commands concurrently. // (Cargo already uses multiple threads for building dependencies, but these // BIOS crates don't have enough dependencies to utilize all cores on modern // CPUs. So by running the build commands in parallel, we increase the number // of utilized cores.) - #[cfg(not(docsrs_dummy_build))] - let (bios_boot_sector_path, bios_stage_2_path, bios_stage_3_path, bios_stage_4_path) = ( - build_bios_boot_sector(&out_dir), - build_bios_stage_2(&out_dir), - build_bios_stage_3(&out_dir), - build_bios_stage_4(&out_dir), - ) - .join() - .await; - // dummy implementations because docsrs builds have no network access - #[cfg(docsrs_dummy_build)] - let (bios_boot_sector_path, bios_stage_2_path, bios_stage_3_path, bios_stage_4_path) = ( - PathBuf::new(), - PathBuf::new(), - PathBuf::new(), - PathBuf::new(), - ); + let bios_boot_sector_path_handle = std::thread::spawn(build_bios_boot_sector); + let bios_stage_2_path_handle = std::thread::spawn(build_bios_stage_2); + let bios_stage_3_path_handle = std::thread::spawn(build_bios_stage_3); + let bios_stage_4_path_handle = std::thread::spawn(build_bios_stage_4); + + // Wait for the commands to finish. + let bios_boot_sector_path = bios_boot_sector_path_handle.join().unwrap(); + let bios_stage_2_path = bios_stage_2_path_handle.join().unwrap(); + let bios_stage_3_path = bios_stage_3_path_handle.join().unwrap(); + let bios_stage_4_path = bios_stage_4_path_handle.join().unwrap(); + println!( "cargo:rustc-env=BIOS_BOOT_SECTOR_PATH={}", bios_boot_sector_path.display() @@ -57,15 +54,8 @@ async fn bios_main() { } #[cfg(feature = "uefi")] -async fn uefi_main() { - let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); - - #[cfg(not(docsrs_dummy_build))] - let uefi_path = build_uefi_bootloader(&out_dir).await; - // dummy implementation because docsrs builds have no network access - #[cfg(docsrs_dummy_build)] - let uefi_path = PathBuf::new(); - +fn uefi_main() { + let uefi_path = build_uefi_bootloader(); println!( "cargo:rustc-env=UEFI_BOOTLOADER_PATH={}", uefi_path.display() @@ -74,7 +64,8 @@ async fn uefi_main() { #[cfg(not(docsrs_dummy_build))] #[cfg(feature = "uefi")] -async fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { +fn build_uefi_bootloader() -> PathBuf { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-uefi"); @@ -90,12 +81,11 @@ async fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { 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.arg("--root").arg(&out_dir); cmd.env_remove("RUSTFLAGS"); cmd.env_remove("CARGO_ENCODED_RUSTFLAGS"); let status = cmd .status() - .await .expect("failed to run cargo install for uefi bootloader"); if status.success() { let path = out_dir.join("bin").join("bootloader-x86_64-uefi.efi"); @@ -109,9 +99,31 @@ async fn build_uefi_bootloader(out_dir: &Path) -> PathBuf { } } +// dummy implementation because docsrs builds have no network access. +// This will put an empty file in out_dir and return its path. +#[cfg(docsrs_dummy_build)] +#[cfg(feature = "uefi")] +fn build_uefi_bootloader() -> PathBuf { + use std::fs::File; + + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let path = out_dir.join("bootloader-dummy-bootloader-uefi"); + + if File::create(&path).is_err() { + panic!("Failed to create dummy uefi bootloader"); + } + assert!( + path.exists(), + "uefi bootloader dummy file does not exist after file creation" + ); + + path +} + #[cfg(not(docsrs_dummy_build))] #[cfg(feature = "bios")] -async fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { +fn build_bios_boot_sector() -> PathBuf { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-bios-boot-sector"); @@ -130,13 +142,12 @@ async fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { 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.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() - .await .expect("failed to run cargo install for bios bootsector"); let elf_path = if status.success() { let path = out_dir @@ -150,12 +161,34 @@ async fn build_bios_boot_sector(out_dir: &Path) -> PathBuf { } else { panic!("failed to build bios boot sector"); }; - convert_elf_to_bin(elf_path).await + convert_elf_to_bin(elf_path) +} + +// dummy implementation because docsrs builds have no network access. +// This will put an empty file in out_dir and return its path. +#[cfg(docsrs_dummy_build)] +#[cfg(feature = "bios")] +fn build_bios_boot_sector() -> PathBuf { + use std::fs::File; + + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let path = out_dir.join("bootloader-dummy-bios-boot-sector"); + + if File::create(&path).is_err() { + panic!("Failed to create dummy bios boot sector"); + } + assert!( + path.exists(), + "bios boot sector dummy file does not exist after file creation" + ); + + path } #[cfg(not(docsrs_dummy_build))] #[cfg(feature = "bios")] -async fn build_bios_stage_2(out_dir: &Path) -> PathBuf { +fn build_bios_stage_2() -> PathBuf { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-bios-stage-2"); @@ -178,13 +211,12 @@ async fn build_bios_stage_2(out_dir: &Path) -> PathBuf { 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.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() - .await .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"); @@ -196,12 +228,34 @@ async fn build_bios_stage_2(out_dir: &Path) -> PathBuf { } else { panic!("failed to build bios second stage"); }; - convert_elf_to_bin(elf_path).await + convert_elf_to_bin(elf_path) +} + +// dummy implementation because docsrs builds have no network access. +// This will put an empty file in out_dir and return its path. +#[cfg(docsrs_dummy_build)] +#[cfg(feature = "bios")] +fn build_bios_stage_2() -> PathBuf { + use std::fs::File; + + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let path = out_dir.join("bootloader-dummy-bios-stage-2"); + + if File::create(&path).is_err() { + panic!("Failed to create dummy bios second stage"); + } + assert!( + path.exists(), + "bios second stage dummy file does not exist after file creation" + ); + + path } #[cfg(not(docsrs_dummy_build))] #[cfg(feature = "bios")] -async fn build_bios_stage_3(out_dir: &Path) -> PathBuf { +fn build_bios_stage_3() -> PathBuf { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-bios-stage-3"); @@ -220,13 +274,12 @@ async fn build_bios_stage_3(out_dir: &Path) -> PathBuf { 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.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() - .await .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"); @@ -238,12 +291,34 @@ async fn build_bios_stage_3(out_dir: &Path) -> PathBuf { } else { panic!("failed to build bios stage-3"); }; - convert_elf_to_bin(elf_path).await + convert_elf_to_bin(elf_path) +} + +// dummy implementation because docsrs builds have no network access. +// This will put an empty file in out_dir and return its path. +#[cfg(docsrs_dummy_build)] +#[cfg(feature = "bios")] +fn build_bios_stage_3() -> PathBuf { + use std::fs::File; + + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let path = out_dir.join("bootloader-dummy-bios-stage-3"); + + if File::create(&path).is_err() { + panic!("Failed to create dummy bios stage-3"); + } + assert!( + path.exists(), + "bios stage-3 dummy file does not exist after file creation" + ); + + path } #[cfg(not(docsrs_dummy_build))] #[cfg(feature = "bios")] -async fn build_bios_stage_4(out_dir: &Path) -> PathBuf { +fn build_bios_stage_4() -> PathBuf { + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let cargo = std::env::var("CARGO").unwrap_or_else(|_| "cargo".into()); let mut cmd = Command::new(cargo); cmd.arg("install").arg("bootloader-x86_64-bios-stage-4"); @@ -262,13 +337,12 @@ async fn build_bios_stage_4(out_dir: &Path) -> PathBuf { 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.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() - .await .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"); @@ -281,12 +355,33 @@ async fn build_bios_stage_4(out_dir: &Path) -> PathBuf { panic!("failed to build bios stage-4"); }; - convert_elf_to_bin(elf_path).await + convert_elf_to_bin(elf_path) +} + +// dummy implementation because docsrs builds have no network access. +// This will put an empty file in out_dir and return its path. +#[cfg(docsrs_dummy_build)] +#[cfg(feature = "bios")] +fn build_bios_stage_4() -> PathBuf { + use std::fs::File; + + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); + let path = out_dir.join("bootloader-dummy-bios-stage-4"); + + if File::create(&path).is_err() { + panic!("Failed to create dummy bios stage-4"); + } + assert!( + path.exists(), + "bios stage-4 dummy file does not exist after file creation" + ); + + path } #[cfg(not(docsrs_dummy_build))] #[cfg(feature = "bios")] -async fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { +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"); @@ -303,7 +398,6 @@ async fn convert_elf_to_bin(elf_path: PathBuf) -> PathBuf { cmd.arg(&flat_binary_path); let output = cmd .output() - .await .expect("failed to execute llvm-objcopy command"); if !output.status.success() { panic!( diff --git a/common/Cargo.toml b/common/Cargo.toml index afc8ed7d..7f9531c6 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -14,12 +14,12 @@ bootloader-boot-config = { workspace = true } conquer-once = { version = "0.3.2", default-features = false } spinning_top = "0.2.4" usize_conversions = "0.2.0" -x86_64 = { version = "0.14.8" } +x86_64 = { version = "0.15.2" } xmas-elf = "0.8.0" raw-cpuid = "10.2.0" rand = { version = "0.8.4", default-features = false } rand_hc = "0.3.1" -uart_16550 = "0.2.18" +uart_16550 = "0.3.2" log = "0.4.17" [dependencies.noto-sans-mono-bitmap] diff --git a/common/src/gdt.rs b/common/src/gdt.rs index 9ac45ade..5ba16c5b 100644 --- a/common/src/gdt.rs +++ b/common/src/gdt.rs @@ -15,8 +15,8 @@ pub fn create_and_load(frame: PhysFrame) { let ptr: *mut GlobalDescriptorTable = virt_addr.as_mut_ptr(); let mut gdt = GlobalDescriptorTable::new(); - let code_selector = gdt.add_entry(Descriptor::kernel_code_segment()); - let data_selector = gdt.add_entry(Descriptor::kernel_data_segment()); + let code_selector = gdt.append(Descriptor::kernel_code_segment()); + let data_selector = gdt.append(Descriptor::kernel_data_segment()); let gdt = unsafe { ptr.write(gdt); &*ptr diff --git a/common/src/legacy_memory_region.rs b/common/src/legacy_memory_region.rs index bf804e9c..0c613392 100644 --- a/common/src/legacy_memory_region.rs +++ b/common/src/legacy_memory_region.rs @@ -1,10 +1,31 @@ use bootloader_api::info::{MemoryRegion, MemoryRegionKind}; -use core::mem::MaybeUninit; +use core::{cmp, mem::MaybeUninit}; use x86_64::{ + align_down, align_up, structures::paging::{FrameAllocator, PhysFrame, Size4KiB}, PhysAddr, }; +/// A slice of memory that is used by the bootloader and needs to be reserved +/// in the kernel +#[derive(Clone, Copy, Debug)] +pub struct UsedMemorySlice { + /// the physical start of the slice + pub start: u64, + /// The physical end address (exclusive) of the region. + pub end: u64, +} + +impl UsedMemorySlice { + /// Creates a new slice + pub fn new_from_len(start: u64, len: u64) -> Self { + Self { + start, + end: start + len, + } + } +} + /// Abstraction trait for a memory region returned by the UEFI or BIOS firmware. pub trait LegacyMemoryRegion: Copy + core::fmt::Debug { /// Returns the physical start address of the region. @@ -28,8 +49,12 @@ pub struct LegacyFrameAllocator { memory_map: I, current_descriptor: Option, next_frame: PhysFrame, + min_frame: PhysFrame, } +/// Start address of the first frame that is not part of the lower 1MB of frames +const LOWER_MEMORY_END_PAGE: u64 = 0x10_0000; + impl LegacyFrameAllocator where I: ExactSizeIterator + Clone, @@ -40,20 +65,28 @@ where /// Skips the frame at physical address zero to avoid potential problems. For example /// identity-mapping the frame at address zero is not valid in Rust, because Rust's `core` /// library assumes that references can never point to virtual address `0`. + /// Also skips the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI). pub fn new(memory_map: I) -> Self { // skip frame 0 because the rust core library does not see 0 as a valid address - let start_frame = PhysFrame::containing_address(PhysAddr::new(0x1000)); + // Also skip at least the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI). + let start_frame = PhysFrame::containing_address(PhysAddr::new(LOWER_MEMORY_END_PAGE)); Self::new_starting_at(start_frame, memory_map) } /// Creates a new frame allocator based on the given legacy memory regions. Skips any frames - /// before the given `frame`. + /// before the given `frame` or `0x10000`(1MB) whichever is higher, there are use cases that require + /// lower conventional memory access (Such as SMP SIPI). pub fn new_starting_at(frame: PhysFrame, memory_map: I) -> Self { + // skip frame 0 because the rust core library does not see 0 as a valid address + // Also skip at least the lower 1MB of frames, there are use cases that require lower conventional memory access (Such as SMP SIPI). + let lower_mem_end = PhysFrame::containing_address(PhysAddr::new(LOWER_MEMORY_END_PAGE)); + let frame = core::cmp::max(frame, lower_mem_end); Self { original: memory_map.clone(), memory_map, current_descriptor: None, next_frame: frame, + min_frame: frame, } } @@ -71,6 +104,7 @@ where if self.next_frame <= end_frame { let ret = self.next_frame; self.next_frame += 1; + Some(ret) } else { None @@ -94,11 +128,25 @@ where /// /// Useful for creating a mapping for all physical memory. pub fn max_phys_addr(&self) -> PhysAddr { - self.original + let max = self + .original .clone() .map(|r| r.start() + r.len()) .max() - .unwrap() + .unwrap(); + + // Always cover at least the first 4 GiB of physical memory. That area + // contains useful MMIO regions (local APIC, I/O APIC, PCI bars) that + // we want to make accessible to the kernel even if no DRAM exists >4GiB. + cmp::max(max, PhysAddr::new(0x1_0000_0000)) + } + + /// Calculate the maximum number of regions produced by [Self::construct_memory_map] + pub fn memory_map_max_region_count(&self) -> usize { + // every used region can split an original region into 3 new regions, + // this means we need to reserve 2 extra spaces for each region. + // There are 3 used regions: kernel, ramdisk and the bootloader heap + self.len() + 6 } /// Converts this type to a boot info memory map. @@ -110,35 +158,31 @@ where pub fn construct_memory_map( self, regions: &mut [MaybeUninit], - kernel_slice_start: u64, + kernel_slice_start: PhysAddr, kernel_slice_len: u64, + ramdisk_slice_start: Option, + ramdisk_slice_len: u64, ) -> &mut [MemoryRegion] { - let mut next_index = 0; + let used_slices = [ + UsedMemorySlice { + start: self.min_frame.start_address().as_u64(), + end: self.next_frame.start_address().as_u64(), + }, + UsedMemorySlice::new_from_len(kernel_slice_start.as_u64(), kernel_slice_len), + ] + .into_iter() + .chain( + ramdisk_slice_start + .map(|start| UsedMemorySlice::new_from_len(start.as_u64(), ramdisk_slice_len)), + ) + .map(|slice| UsedMemorySlice { + start: align_down(slice.start, 0x1000), + end: align_up(slice.end, 0x1000), + }); + let mut next_index = 0; for descriptor in self.original { - let mut start = descriptor.start(); - let end = start + descriptor.len(); - let next_free = self.next_frame.start_address(); let kind = match descriptor.kind() { - MemoryRegionKind::Usable => { - if end <= next_free { - MemoryRegionKind::Bootloader - } else if descriptor.start() >= next_free { - MemoryRegionKind::Usable - } else { - // part of the region is used -> add it separately - let used_region = MemoryRegion { - start: descriptor.start().as_u64(), - end: next_free.as_u64(), - kind: MemoryRegionKind::Bootloader, - }; - Self::add_region(used_region, regions, &mut next_index); - - // add unused part normally - start = next_free; - MemoryRegionKind::Usable - } - } _ if descriptor.usable_after_bootloader_exit() => { // Region was not usable before, but it will be as soon as // the bootloader passes control to the kernel. We don't @@ -150,55 +194,15 @@ where other => other, }; + let end = descriptor.start() + descriptor.len(); let region = MemoryRegion { - start: start.as_u64(), + start: descriptor.start().as_u64(), end: end.as_u64(), kind, }; - - // 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); + if region.kind == MemoryRegionKind::Usable { + Self::split_and_add_region(region, regions, &mut next_index, used_slices.clone()); } else { - // add the region normally Self::add_region(region, regions, &mut next_index); } } @@ -211,6 +215,64 @@ where } } + fn split_and_add_region<'a, U>( + mut region: MemoryRegion, + regions: &mut [MaybeUninit], + next_index: &mut usize, + used_slices: U, + ) where + U: Iterator + Clone, + { + assert!(region.kind == MemoryRegionKind::Usable); + // Each loop iteration takes a chunk of `region` and adds it to + // `regions`. Do this until `region` is empty. + while region.start != region.end { + // Check if there is overlap between `region` and `used_slices`. + if let Some((overlap_start, overlap_end)) = used_slices + .clone() + .map(|slice| { + // Calculate the start and end points of the overlap + // between `slice` and `region`. If `slice` and `region` + // don't overlap, the range will be ill-formed + // (overlap_start > overlap_end). + let overlap_start = cmp::max(region.start, slice.start); + let overlap_end = cmp::min(region.end, slice.end); + (overlap_start, overlap_end) + }) + .filter(|(overlap_start, overlap_end)| { + // Only consider non-empty overlap. + overlap_start < overlap_end + }) + .min_by_key(|&(overlap_start, _)| { + // Find the earliest overlap. + overlap_start + }) + { + // There's no overlapping used slice before `overlap_start`, so + // we know that memory between `region.start` and + // `overlap_start` is usable. + let usable = MemoryRegion { + start: region.start, + end: overlap_start, + kind: MemoryRegionKind::Usable, + }; + let bootloader = MemoryRegion { + start: overlap_start, + end: overlap_end, + kind: MemoryRegionKind::Bootloader, + }; + Self::add_region(usable, regions, next_index); + Self::add_region(bootloader, regions, next_index); + // Continue after the overlapped region. + region.start = overlap_end; + } else { + // There's no overlap. We can add the whole region. + Self::add_region(region, regions, next_index); + break; + } + } + } + fn add_region( region: MemoryRegion, regions: &mut [MaybeUninit], @@ -260,3 +322,275 @@ where None } } + +#[cfg(test)] +mod tests { + use super::*; + + #[derive(Copy, Clone, Debug)] + struct TestMemoryRegion { + start: PhysAddr, + len: u64, + kind: MemoryRegionKind, + } + + impl LegacyMemoryRegion for TestMemoryRegion { + fn start(&self) -> PhysAddr { + self.start + } + + fn len(&self) -> u64 { + assert!(self.len % 4096 == 0); + self.len + } + + fn kind(&self) -> MemoryRegionKind { + self.kind + } + + fn usable_after_bootloader_exit(&self) -> bool { + match self.kind { + MemoryRegionKind::Usable => true, + _ => false, + } + } + } + + // we need some kind of max phys memory, 4GB seems reasonable + const MAX_PHYS_ADDR: u64 = 0x4000_0000; + + fn create_single_test_region() -> Vec { + vec![TestMemoryRegion { + start: PhysAddr::new(0), + len: MAX_PHYS_ADDR, + kind: MemoryRegionKind::Usable, + }] + } + + #[test] + fn test_all_regions_frame_alligned() { + let regions = create_single_test_region(); + let mut allocator = LegacyFrameAllocator::new(regions.into_iter()); + // allocate at least 1 frame + allocator.allocate_frame(); + + let mut regions = [MaybeUninit::uninit(); 10]; + let kernel_slice_start = PhysAddr::new(0x50000); + let kernel_slice_len = 0x0500; + let ramdisk_slice_start = None; + let ramdisk_slice_len = 0; + + let kernel_regions = allocator.construct_memory_map( + &mut regions, + kernel_slice_start, + kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, + ); + + for region in kernel_regions.iter() { + assert!(region.start % 0x1000 == 0); + assert!(region.end % 0x1000 == 0); + } + } + + #[test] + fn test_kernel_and_ram_in_same_region() { + let regions = create_single_test_region(); + let mut allocator = LegacyFrameAllocator::new(regions.into_iter()); + // allocate at least 1 frame + allocator.allocate_frame(); + + let mut regions = [MaybeUninit::uninit(); 10]; + let kernel_slice_start = PhysAddr::new(0x50000); + let kernel_slice_len = 0x1000; + let ramdisk_slice_start = Some(PhysAddr::new(0x60000)); + let ramdisk_slice_len = 0x2000; + + let kernel_regions = allocator.construct_memory_map( + &mut regions, + kernel_slice_start, + kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, + ); + let mut kernel_regions = kernel_regions.iter(); + // usable memory before the kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x0000, + end: 0x50000, + kind: MemoryRegionKind::Usable + }) + ); + // kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x50000, + end: 0x51000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabel memory between kernel and ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x51000, + end: 0x60000, + kind: MemoryRegionKind::Usable + }) + ); + // ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x60000, + end: 0x62000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabele memory after ramdisk, up until bootloader allocated memory + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x62000, + end: 0x10_0000, + kind: MemoryRegionKind::Usable + }) + ); + // bootloader allocated memory + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_0000, + end: 0x10_1000, + kind: MemoryRegionKind::Bootloader + }) + ); + // rest is free + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_1000, + end: MAX_PHYS_ADDR, + kind: MemoryRegionKind::Usable + }) + ); + assert_eq!(kernel_regions.next(), None); + } + + #[test] + fn test_multiple_regions() { + let regions = vec![ + TestMemoryRegion { + start: PhysAddr::new(0), + len: 0x10_0000, + kind: MemoryRegionKind::Usable, + }, + TestMemoryRegion { + start: PhysAddr::new(0x10_0000), + len: 0x5000, + kind: MemoryRegionKind::UnknownBios(0), + }, + TestMemoryRegion { + start: PhysAddr::new(0x10_5000), + len: MAX_PHYS_ADDR - 0x10_5000, + kind: MemoryRegionKind::Usable, + }, + ]; + let mut allocator = LegacyFrameAllocator::new(regions.into_iter()); + // allocate at least 1 frame + allocator.allocate_frame(); + + let mut regions = [MaybeUninit::uninit(); 10]; + let kernel_slice_start = PhysAddr::new(0x50000); + let kernel_slice_len = 0x1000; + let ramdisk_slice_start = Some(PhysAddr::new(0x60000)); + let ramdisk_slice_len = 0x2000; + + let kernel_regions = allocator.construct_memory_map( + &mut regions, + kernel_slice_start, + kernel_slice_len, + ramdisk_slice_start, + ramdisk_slice_len, + ); + let mut kernel_regions = kernel_regions.iter(); + + // usable memory before the kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x0000, + end: 0x50000, + kind: MemoryRegionKind::Usable + }) + ); + // kernel + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x50000, + end: 0x51000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabel memory between kernel and ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x51000, + end: 0x60000, + kind: MemoryRegionKind::Usable + }) + ); + // ramdisk + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x60000, + end: 0x62000, + kind: MemoryRegionKind::Bootloader + }) + ); + // usabele memory after ramdisk, up until bootloader allocated memory + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x62000, + end: 0x10_0000, + kind: MemoryRegionKind::Usable + }) + ); + // the unknown bios region + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_0000, + end: 0x10_5000, + kind: MemoryRegionKind::UnknownBios(0) + }) + ); + // bootloader allocated memory, this gets pushed back by the bios region + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_5000, + end: 0x10_6000, + kind: MemoryRegionKind::Bootloader + }) + ); + // rest is free + assert_eq!( + kernel_regions.next(), + Some(&MemoryRegion { + start: 0x10_6000, + end: MAX_PHYS_ADDR, + kind: MemoryRegionKind::Usable + }) + ); + assert_eq!(kernel_regions.next(), None); + } +} diff --git a/common/src/level_4_entries.rs b/common/src/level_4_entries.rs index 6e6ad9d3..926dd541 100644 --- a/common/src/level_4_entries.rs +++ b/common/src/level_4_entries.rs @@ -1,4 +1,8 @@ -use crate::{entropy, load_kernel::VirtualAddressOffset, BootInfo, RawFrameBufferInfo}; +use crate::{ + entropy, + load_kernel::{calc_elf_memory_requirements, ElfMemoryRequirements, VirtualAddressOffset}, + BootInfo, RawFrameBufferInfo, +}; use bootloader_api::{config, info::MemoryRegion, BootloaderConfig}; use core::{alloc::Layout, iter::Step}; use rand::{ @@ -11,7 +15,7 @@ use x86_64::{ structures::paging::{Page, PageTableIndex, Size4KiB}, PhysAddr, VirtAddr, }; -use xmas_elf::program::ProgramHeader; +use xmas_elf::{header, program::ProgramHeader, ElfFile}; /// Keeps track of used entries in a level 4 page table. /// @@ -33,20 +37,34 @@ impl UsedLevel4Entries { regions_len: usize, framebuffer: Option<&RawFrameBufferInfo>, config: &BootloaderConfig, - ) -> Self { + kernel_elf: &ElfFile<'_>, + ) -> Result { let mut used = UsedLevel4Entries { entry_state: [false; 512], rng: config.mappings.aslr.then(entropy::build_rng), }; - used.entry_state[0] = true; // TODO: Can we do this dynamically? + // The bootloader maps of the kernel's memory into its own page tables. + // We need to prevent overlaps, so mark all memory that could already + // be used by the bootload as inaccessible. + + // All memory in this range is identity mapped. + used.mark_range_as_used(0, max_phys_addr.as_u64()); + + // The bootload needs to access the frame buffer. + if let Some(frame_buffer) = framebuffer { + used.mark_range_as_used( + frame_buffer.addr.as_u64(), + frame_buffer.info.byte_len as u64, + ); + } // Mark the statically configured ranges from the config as used. 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()); + used.mark_range_as_used(physical_memory_offset, max_phys_addr.as_u64()); } if let Some(config::Mapping::FixedAddress(recursive_address)) = @@ -60,18 +78,35 @@ impl UsedLevel4Entries { used.mark_range_as_used(kernel_stack_address, config.kernel_stack_size); } + if let config::Mapping::FixedAddress(kernel_base) = config.mappings.kernel_base { + let ElfMemoryRequirements { size, align, .. } = + calc_elf_memory_requirements(kernel_elf); + + if !VirtAddr::new(kernel_base).is_aligned(align) { + return Err("kernel_code mapping alignment does not match elf file"); + } + + used.mark_range_as_used(kernel_base, size); + } + if kernel_elf.header.pt2.type_().as_type() == header::Type::Executable { + let ElfMemoryRequirements { size, min_addr, .. } = + calc_elf_memory_requirements(kernel_elf); + + used.mark_range_as_used(min_addr, size); + } + 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(); let (combined, _) = boot_info_layout.extend(memory_regions_layout).unwrap(); - used.mark_range_as_used(boot_info_address, combined.size()); + used.mark_range_as_used(boot_info_address, combined.size() as u64); } 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); + used.mark_range_as_used(framebuffer_address, framebuffer.info.byte_len as u64); } } @@ -97,18 +132,13 @@ impl UsedLevel4Entries { } } - used + Ok(used) } /// Marks all p4 entries in the range `[address..address+size)` as used. - /// - /// `size` can be a `u64` or `usize`. - fn mark_range_as_used(&mut self, address: u64, size: S) - where - VirtAddr: core::ops::Add, - { + fn mark_range_as_used(&mut self, address: u64, size: u64) { let start = VirtAddr::new(address); - let end_inclusive = (start + size) - 1usize; + let end_inclusive = (start + size) - 1; let start_page = Page::::containing_address(start); let end_page_inclusive = Page::::containing_address(end_inclusive); diff --git a/common/src/lib.rs b/common/src/lib.rs index ecd0d9cc..0a4f729d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] #![feature(step_trait)] #![deny(unsafe_op_in_unsafe_fn)] @@ -39,7 +39,7 @@ pub mod serial; const PAGE_SIZE: u64 = 4096; -/// Initialize a text-based logger using the given pixel-based framebuffer as output. +/// Initialize a text-based logger using the given pixel-based framebuffer as output. pub fn init_logger( framebuffer: &'static mut [u8], info: FrameBufferInfo, @@ -187,7 +187,9 @@ where frame_allocator.len(), framebuffer, config, - ); + &kernel.elf, + ) + .expect("Failed to mark level 4 entries as used"); // Enable support for the no-execute bit in page tables. enable_nxe_bit(); @@ -195,10 +197,10 @@ where enable_write_protect_bit(); let config = kernel.config; - let kernel_slice_start = kernel.start_address as u64; + let kernel_slice_start = PhysAddr::new(kernel.start_address as _); let kernel_slice_len = u64::try_from(kernel.len).unwrap(); - let (entry_point, tls_template) = load_kernel::load_kernel( + let (kernel_image_offset, entry_point, tls_template) = load_kernel::load_kernel( kernel, kernel_page_table, frame_allocator, @@ -225,7 +227,7 @@ where let frame = frame_allocator .allocate_frame() .expect("frame allocation failed when mapping a kernel stack"); - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE; match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { Ok(tlb) => tlb.flush(), Err(err) => panic!("failed to map page {:?}: {:?}", page, err), @@ -241,8 +243,18 @@ where context_switch_function_start_frame, context_switch_function_start_frame + 1, ) { + let page = Page::containing_address(VirtAddr::new(frame.start_address().as_u64())); match unsafe { - kernel_page_table.identity_map(frame, PageTableFlags::PRESENT, frame_allocator) + // The parent table flags need to be both readable and writable to + // support recursive page tables. + // See https://github.com/rust-osdev/bootloader/issues/443#issuecomment-2130010621 + kernel_page_table.map_to_with_table_flags( + page, + frame, + PageTableFlags::PRESENT, + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + frame_allocator, + ) } { Ok(tlb) => tlb.flush(), Err(err) => panic!("failed to identity map frame {:?}: {:?}", frame, err), @@ -254,8 +266,17 @@ where .allocate_frame() .expect("failed to allocate GDT frame"); gdt::create_and_load(gdt_frame); + let gdt_page = Page::containing_address(VirtAddr::new(gdt_frame.start_address().as_u64())); match unsafe { - kernel_page_table.identity_map(gdt_frame, PageTableFlags::PRESENT, frame_allocator) + // The parent table flags need to be both readable and writable to + // support recursive page tables. + kernel_page_table.map_to_with_table_flags( + gdt_page, + gdt_frame, + PageTableFlags::PRESENT, + PageTableFlags::PRESENT | PageTableFlags::WRITABLE, + frame_allocator, + ) } { Ok(tlb) => tlb.flush(), Err(err) => panic!("failed to identity map frame {:?}: {:?}", gdt_frame, err), @@ -266,8 +287,9 @@ where log::info!("Map framebuffer"); let framebuffer_start_frame: PhysFrame = PhysFrame::containing_address(framebuffer.addr); - let framebuffer_end_frame = - PhysFrame::containing_address(framebuffer.addr + framebuffer.info.byte_len - 1u64); + let framebuffer_end_frame = PhysFrame::containing_address( + framebuffer.addr + framebuffer.info.byte_len as u64 - 1u64, + ); let start_page = mapping_addr_page_aligned( config.mappings.framebuffer, u64::from_usize(framebuffer.info.byte_len), @@ -278,7 +300,8 @@ where PhysFrame::range_inclusive(framebuffer_start_frame, framebuffer_end_frame).enumerate() { let page = start_page + u64::from_usize(i); - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + let flags = + PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE; match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { Ok(tlb) => tlb.flush(), Err(err) => panic!( @@ -293,20 +316,20 @@ where None }; let ramdisk_slice_len = system_info.ramdisk_len; - let ramdisk_slice_start = if let Some(ramdisk_address) = system_info.ramdisk_addr { + let ramdisk_slice_phys_start = system_info.ramdisk_addr.map(PhysAddr::new); + let ramdisk_slice_start = if let Some(physical_address) = ramdisk_slice_phys_start { let start_page = mapping_addr_page_aligned( config.mappings.ramdisk_memory, system_info.ramdisk_len, &mut used_entries, "ramdisk start", ); - let physical_address = PhysAddr::new(ramdisk_address); let ramdisk_physical_start_page: PhysFrame = PhysFrame::containing_address(physical_address); let ramdisk_page_count = (system_info.ramdisk_len - 1) / Size4KiB::SIZE; let ramdisk_physical_end_page = ramdisk_physical_start_page + ramdisk_page_count; - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE; for (i, frame) in PhysFrame::range_inclusive(ramdisk_physical_start_page, ramdisk_physical_end_page) .enumerate() @@ -339,7 +362,8 @@ where for frame in PhysFrame::range_inclusive(start_frame, end_frame) { let page = Page::containing_address(offset + frame.start_address().as_u64()); - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + let flags = + PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE; match unsafe { kernel_page_table.map_to(page, frame, flags, frame_allocator) } { Ok(tlb) => tlb.ignore(), Err(err) => panic!( @@ -373,14 +397,14 @@ where } }; - let entry = &mut kernel_page_table.level_4_table()[index]; + let entry = &mut kernel_page_table.level_4_table_mut()[index]; if !entry.is_unused() { panic!( "Could not set up recursive mapping: index {} already in use", u16::from(index) ); } - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE; entry.set_frame(page_tables.kernel_level_4_frame, flags); Some(index) @@ -402,6 +426,9 @@ where kernel_slice_start, kernel_slice_len, + kernel_image_offset, + + ramdisk_slice_phys_start, ramdisk_slice_start, ramdisk_slice_len, } @@ -426,9 +453,12 @@ pub struct Mappings { pub tls_template: Option, /// Start address of the kernel slice allocation in memory. - pub kernel_slice_start: u64, + pub kernel_slice_start: PhysAddr, /// Size of the kernel slice allocation in memory. pub kernel_slice_len: u64, + /// Relocation offset of the kernel image in virtual memory. + pub kernel_image_offset: VirtAddr, + pub ramdisk_slice_phys_start: Option, pub ramdisk_slice_start: Option, pub ramdisk_slice_len: u64, } @@ -456,7 +486,7 @@ where // allocate and map space for the boot info let (boot_info, memory_regions) = { let boot_info_layout = Layout::new::(); - let regions = frame_allocator.len() + 4; // up to 4 regions might be split into used/unused + let regions = frame_allocator.memory_map_max_region_count(); let memory_regions_layout = Layout::array::(regions).unwrap(); let (combined, memory_regions_offset) = boot_info_layout.extend(memory_regions_layout).unwrap(); @@ -469,13 +499,14 @@ where ) .expect("boot info addr is not properly aligned"); - let memory_map_regions_addr = boot_info_addr + memory_regions_offset; - let memory_map_regions_end = boot_info_addr + combined.size(); + let memory_map_regions_addr = boot_info_addr + memory_regions_offset as u64; + let memory_map_regions_end = boot_info_addr + combined.size() as u64; let start_page = Page::containing_address(boot_info_addr); let end_page = Page::containing_address(memory_map_regions_end - 1u64); for page in Page::range_inclusive(start_page, end_page) { - let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE; + let flags = + PageTableFlags::PRESENT | PageTableFlags::WRITABLE | PageTableFlags::NO_EXECUTE; let frame = frame_allocator .allocate_frame() .expect("frame allocation for boot info failed"); @@ -512,6 +543,8 @@ where memory_regions, mappings.kernel_slice_start, mappings.kernel_slice_len, + mappings.ramdisk_slice_phys_start, + mappings.ramdisk_slice_len, ); log::info!("Create bootinfo"); @@ -543,6 +576,9 @@ where .map(|addr| addr.as_u64()) .into(); info.ramdisk_len = mappings.ramdisk_slice_len; + info.kernel_addr = mappings.kernel_slice_start.as_u64(); + info.kernel_len = mappings.kernel_slice_len as _; + info.kernel_image_offset = mappings.kernel_image_offset.as_u64(); info._test_sentinel = boot_config._test_sentinel; info }); @@ -587,7 +623,7 @@ pub struct PageTables { /// /// Must be the page table that the `kernel` field of this struct refers to. /// - /// This frame is loaded into the `CR3` register on the final context switch to the kernel. + /// This frame is loaded into the `CR3` register on the final context switch to the kernel. pub kernel_level_4_frame: PhysFrame, } @@ -595,7 +631,13 @@ pub struct PageTables { unsafe fn context_switch(addresses: Addresses) -> ! { unsafe { asm!( - "mov cr3, {}; mov rsp, {}; push 0; jmp {}", + r#" + xor rbp, rbp + mov cr3, {} + mov rsp, {} + push 0 + jmp {} + "#, in(reg) addresses.page_table.start_address().as_u64(), in(reg) addresses.stack_top.as_u64(), in(reg) addresses.entry_point.as_u64(), diff --git a/common/src/load_kernel.rs b/common/src/load_kernel.rs index a79a404a..b54bd246 100644 --- a/common/src/load_kernel.rs +++ b/common/src/load_kernel.rs @@ -1,5 +1,5 @@ use crate::{level_4_entries::UsedLevel4Entries, PAGE_SIZE}; -use bootloader_api::info::TlsTemplate; +use bootloader_api::{config::Mapping, info::TlsTemplate}; use core::{cmp, iter::Step, mem::size_of, ops::Add}; use x86_64::{ @@ -59,27 +59,30 @@ where let virtual_address_offset = match elf_file.header.pt2.type_().as_type() { header::Type::None => unimplemented!(), header::Type::Relocatable => unimplemented!(), - header::Type::Executable => VirtualAddressOffset::zero(), + header::Type::Executable => match kernel.config.mappings.kernel_base { + Mapping::Dynamic => VirtualAddressOffset::zero(), + _ => { + return Err(concat!( + "Invalid kernel_code mapping. ", + "Executable can only be mapped at virtual_address_offset 0." + )) + } + }, header::Type::SharedObject => { - // Find the highest virtual memory address and the biggest alignment. - let load_program_headers = elf_file - .program_iter() - .filter(|h| matches!(h.get_type(), Ok(Type::Load))); - let max_addr = load_program_headers - .clone() - .map(|h| h.virtual_addr() + h.mem_size()) - .max() - .unwrap_or(0); - let min_addr = load_program_headers - .clone() - .map(|h| h.virtual_addr()) - .min() - .unwrap_or(0); - let size = max_addr - min_addr; - let align = load_program_headers.map(|h| h.align()).max().unwrap_or(1); - - let offset = used_entries.get_free_address(size, align).as_u64(); - VirtualAddressOffset::new(i128::from(offset) - i128::from(min_addr)) + let ElfMemoryRequirements { + size, + align, + min_addr, + } = calc_elf_memory_requirements(&elf_file); + match kernel.config.mappings.kernel_base { + Mapping::Dynamic => { + let offset = used_entries.get_free_address(size, align).as_u64(); + VirtualAddressOffset::new(i128::from(offset) - i128::from(min_addr)) + } + Mapping::FixedAddress(address) => { + VirtualAddressOffset::new(i128::from(address)) + } + } } header::Type::Core => unimplemented!(), header::Type::ProcessorSpecific(_) => unimplemented!(), @@ -185,8 +188,17 @@ where let offset = frame - start_frame; let page = start_page + offset; let flusher = unsafe { + // The parent table flags need to be both readable and writable to + // support recursive page tables. + // See https://github.com/rust-osdev/bootloader/issues/443#issuecomment-2130010621 self.page_table - .map_to(page, frame, segment_flags, self.frame_allocator) + .map_to_with_table_flags( + page, + frame, + segment_flags, + Flags::PRESENT | Flags::WRITABLE, + self.frame_allocator, + ) .map_err(|_err| "map_to failed")? }; // we operate on an inactive page table, so there's no need to flush anything @@ -280,8 +292,17 @@ where // map frame let flusher = unsafe { + // The parent table flags need to be both readable and writable to + // support recursive page tables. + // See https://github.com/rust-osdev/bootloader/issues/443#issuecomment-2130010621 self.page_table - .map_to(page, frame, segment_flags, self.frame_allocator) + .map_to_with_table_flags( + page, + frame, + segment_flags, + Flags::PRESENT | Flags::WRITABLE, + self.frame_allocator, + ) .map_err(|_err| "Failed to map new frame for bss memory")? }; // we operate on an inactive page table, so we don't need to flush our changes @@ -324,8 +345,8 @@ where let end_inclusive_copy_address = cmp::min(end_inclusive_addr, page_end_inclusive); // These are the offsets into the frame we want to copy from. - let start_offset_in_frame = (start_copy_address - page_start) as usize; - let end_inclusive_offset_in_frame = (end_inclusive_copy_address - page_start) as usize; + let start_offset_in_frame = start_copy_address - page_start; + let end_inclusive_offset_in_frame = end_inclusive_copy_address - page_start; // Calculate how many bytes we want to copy from this frame. let copy_len = end_inclusive_offset_in_frame - start_offset_in_frame + 1; @@ -335,7 +356,7 @@ where // These are the offsets from the start address. These correspond // to the destination indices in `buf`. - let start_offset_in_buf = Step::steps_between(&addr, &start_copy_address).unwrap(); + let start_offset_in_buf = Step::steps_between(&addr, &start_copy_address).1.unwrap(); // Calculate the source slice. // Utilize that frames are identity mapped. @@ -344,11 +365,11 @@ where // SAFETY: We know that this memory is valid because we got it // as a result from a translation. There are not other // references to it. - &*core::ptr::slice_from_raw_parts(src_ptr, copy_len) + &*core::ptr::slice_from_raw_parts(src_ptr, copy_len as usize) }; // Calculate the destination pointer. - let dest = &mut buf[start_offset_in_buf..][..copy_len]; + let dest = &mut buf[start_offset_in_buf..][..copy_len as usize]; // Do the actual copy. dest.copy_from_slice(src); @@ -391,8 +412,8 @@ where let end_inclusive_copy_address = cmp::min(end_inclusive_addr, page_end_inclusive); // These are the offsets into the frame we want to copy from. - let start_offset_in_frame = (start_copy_address - page_start) as usize; - let end_inclusive_offset_in_frame = (end_inclusive_copy_address - page_start) as usize; + let start_offset_in_frame = start_copy_address - page_start; + let end_inclusive_offset_in_frame = end_inclusive_copy_address - page_start; // Calculate how many bytes we want to copy from this frame. let copy_len = end_inclusive_offset_in_frame - start_offset_in_frame + 1; @@ -402,7 +423,7 @@ where // These are the offsets from the start address. These correspond // to the destination indices in `buf`. - let start_offset_in_buf = Step::steps_between(&addr, &start_copy_address).unwrap(); + let start_offset_in_buf = Step::steps_between(&addr, &start_copy_address).1.unwrap(); // Calculate the source slice. // Utilize that frames are identity mapped. @@ -411,11 +432,11 @@ where // SAFETY: We know that this memory is valid because we got it // as a result from a translation. There are not other // references to it. - &mut *core::ptr::slice_from_raw_parts_mut(dest_ptr, copy_len) + &mut *core::ptr::slice_from_raw_parts_mut(dest_ptr, copy_len as usize) }; // Calculate the destination pointer. - let src = &buf[start_offset_in_buf..][..copy_len]; + let src = &buf[start_offset_in_buf..][..copy_len as usize]; // Do the actual copy. dest.copy_from_slice(src); @@ -721,11 +742,50 @@ pub fn load_kernel( page_table: &mut (impl MapperAllSizes + Translate), frame_allocator: &mut impl FrameAllocator, used_entries: &mut UsedLevel4Entries, -) -> Result<(VirtAddr, Option), &'static str> { +) -> Result<(VirtAddr, VirtAddr, Option), &'static str> { let mut loader = Loader::new(kernel, page_table, frame_allocator, used_entries)?; let tls_template = loader.load_segments()?; - Ok((loader.entry_point(), tls_template)) + Ok(( + VirtAddr::new(loader.inner.virtual_address_offset.virtual_address_offset() as u64), + loader.entry_point(), + tls_template, + )) +} + +/// Basic information about the memory segments of an elf file. +pub struct ElfMemoryRequirements { + /// total size needed for all segments + pub size: u64, + /// memory alignment for the elf file + pub align: u64, + /// the smallest virtual address used by the elf file + pub min_addr: u64, +} + +/// Calculates basic requirements needed to allocate memory for an elf file. +pub fn calc_elf_memory_requirements(elf_file: &ElfFile) -> ElfMemoryRequirements { + // Find the highest virtual memory address and the biggest alignment. + let load_program_headers = elf_file + .program_iter() + .filter(|h| matches!(h.get_type(), Ok(Type::Load))); + let max_addr = load_program_headers + .clone() + .map(|h| h.virtual_addr() + h.mem_size()) + .max() + .unwrap_or(0); + let min_addr = load_program_headers + .clone() + .map(|h| h.virtual_addr()) + .min() + .unwrap_or(0); + let size = max_addr - min_addr; + let align = load_program_headers.map(|h| h.align()).max().unwrap_or(1); + ElfMemoryRequirements { + size, + align, + min_addr, + } } /// A helper type used to offset virtual addresses for position independent diff --git a/common/src/serial.rs b/common/src/serial.rs index 1882e366..8435c8e1 100644 --- a/common/src/serial.rs +++ b/common/src/serial.rs @@ -17,7 +17,12 @@ impl SerialPort { impl fmt::Write for SerialPort { fn write_str(&mut self, s: &str) -> fmt::Result { - self.port.write_str(s).unwrap(); + for char in s.bytes() { + match char { + b'\n' => self.port.write_str("\r\n").unwrap(), + byte => self.port.send(byte), + } + } Ok(()) } } diff --git a/i386-code16-boot-sector.json b/i386-code16-boot-sector.json index f5ed774e..9cf5c514 100644 --- a/i386-code16-boot-sector.json +++ b/i386-code16-boot-sector.json @@ -1,7 +1,7 @@ { "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", + "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", "dynamic-linking": false, "executables": true, "linker-flavor": "ld.lld", @@ -10,7 +10,7 @@ "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, - "target-c-int-width": "32", + "target-c-int-width": 32, "target-pointer-width": "32", "target-endian": "little", "panic-strategy": "abort", diff --git a/i386-code16-stage-2.json b/i386-code16-stage-2.json index f5ed774e..9cf5c514 100644 --- a/i386-code16-stage-2.json +++ b/i386-code16-stage-2.json @@ -1,7 +1,7 @@ { "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", + "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", "dynamic-linking": false, "executables": true, "linker-flavor": "ld.lld", @@ -10,7 +10,7 @@ "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, - "target-c-int-width": "32", + "target-c-int-width": 32, "target-pointer-width": "32", "target-endian": "little", "panic-strategy": "abort", diff --git a/i686-stage-3.json b/i686-stage-3.json index b444faec..82fc22c6 100644 --- a/i686-stage-3.json +++ b/i686-stage-3.json @@ -1,7 +1,7 @@ { "arch": "x86", "cpu": "i386", - "data-layout": "e-m:e-i32:32-f80:128-n8:16:32-S128-p:32:32", + "data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128", "dynamic-linking": false, "executables": true, "linker-flavor": "ld.lld", @@ -10,12 +10,13 @@ "max-atomic-width": 64, "position-independent-executables": false, "disable-redzone": true, - "target-c-int-width": "32", + "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" + "features": "+soft-float,-sse,-mmx", + "rustc-abi": "x86-softfloat" } diff --git a/src/fat.rs b/src/fat.rs index aa2a450b..c81033b5 100644 --- a/src/fat.rs +++ b/src/fat.rs @@ -26,7 +26,7 @@ pub fn create_fat_filesystem( .truncate(true) .open(out_fat_path) .unwrap(); - let fat_size_padded_and_rounded = ((needed_size + 1024 * 64 - 1) / MB + 1) * MB; + let fat_size_padded_and_rounded = ((needed_size + 1024 * 64 - 1) / MB + 1) * MB + MB; fat_file.set_len(fat_size_padded_and_rounded).unwrap(); // choose a file system label diff --git a/src/file_data_source.rs b/src/file_data_source.rs index 3e1128b3..47656ff8 100644 --- a/src/file_data_source.rs +++ b/src/file_data_source.rs @@ -1,4 +1,3 @@ -use alloc::vec::Vec; use anyhow::Context; use core::fmt::{Debug, Formatter}; @@ -11,6 +10,7 @@ use std::{fs, io}; pub enum FileDataSource { File(PathBuf), Data(Vec), + Bytes(&'static [u8]), } impl Debug for FileDataSource { @@ -22,6 +22,9 @@ impl Debug for FileDataSource { FileDataSource::Data(d) => { f.write_fmt(format_args!("data source: {} raw bytes ", d.len())) } + FileDataSource::Bytes(b) => { + f.write_fmt(format_args!("data source: {} raw bytes ", b.len())) + } } } } @@ -34,6 +37,7 @@ impl FileDataSource { .with_context(|| format!("failed to read metadata of file `{}`", path.display()))? .len(), FileDataSource::Data(v) => v.len() as u64, + FileDataSource::Bytes(s) => s.len() as u64, }) } /// Copy this data source to the specified target that implements io::Write @@ -51,6 +55,10 @@ impl FileDataSource { let mut cursor = Cursor::new(contents); io::copy(&mut cursor, target)?; } + FileDataSource::Bytes(contents) => { + let mut cursor = Cursor::new(contents); + io::copy(&mut cursor, target)?; + } }; Ok(()) diff --git a/src/lib.rs b/src/lib.rs index 179f1d10..db5f32e3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,17 @@ const KERNEL_FILE_NAME: &str = "kernel-x86_64"; const RAMDISK_FILE_NAME: &str = "ramdisk"; const CONFIG_FILE_NAME: &str = "boot.json"; +#[cfg(feature = "uefi")] +const UEFI_BOOTLOADER: &[u8] = include_bytes!(env!("UEFI_BOOTLOADER_PATH")); +#[cfg(feature = "bios")] +const BIOS_BOOT_SECTOR: &[u8] = include_bytes!(env!("BIOS_BOOT_SECTOR_PATH")); +#[cfg(feature = "bios")] +const BIOS_STAGE_2: &[u8] = include_bytes!(env!("BIOS_STAGE_2_PATH")); +#[cfg(feature = "bios")] +const BIOS_STAGE_3: &[u8] = include_bytes!(env!("BIOS_STAGE_3_PATH")); +#[cfg(feature = "bios")] +const BIOS_STAGE_4: &[u8] = include_bytes!(env!("BIOS_STAGE_4_PATH")); + /// Allows creating disk images for a specified set of files. /// /// It can currently create `MBR` (BIOS), `GPT` (UEFI), and `TFTP` (UEFI) images. @@ -98,28 +109,19 @@ impl DiskImageBuilder { #[cfg(feature = "bios")] /// Create an MBR disk image for booting on BIOS systems. pub fn create_bios_image(&self, image_path: &Path) -> anyhow::Result<()> { - const BIOS_STAGE_3: &str = "boot-stage-3"; - const BIOS_STAGE_4: &str = "boot-stage-4"; - let bootsector_path = Path::new(env!("BIOS_BOOT_SECTOR_PATH")); - let stage_2_path = Path::new(env!("BIOS_STAGE_2_PATH")); - let stage_3_path = Path::new(env!("BIOS_STAGE_3_PATH")); - let stage_4_path = Path::new(env!("BIOS_STAGE_4_PATH")); + const BIOS_STAGE_3_NAME: &str = "boot-stage-3"; + const BIOS_STAGE_4_NAME: &str = "boot-stage-4"; + let stage_3 = FileDataSource::Bytes(BIOS_STAGE_3); + let stage_4 = FileDataSource::Bytes(BIOS_STAGE_4); let mut internal_files = BTreeMap::new(); - internal_files.insert( - BIOS_STAGE_3, - FileDataSource::File(stage_3_path.to_path_buf()), - ); - internal_files.insert( - BIOS_STAGE_4, - FileDataSource::File(stage_4_path.to_path_buf()), - ); - + internal_files.insert(BIOS_STAGE_3_NAME, stage_3); + internal_files.insert(BIOS_STAGE_4_NAME, stage_4); let fat_partition = self .create_fat_filesystem_image(internal_files) .context("failed to create FAT partition")?; mbr::create_mbr_disk( - bootsector_path, - stage_2_path, + BIOS_BOOT_SECTOR, + BIOS_STAGE_2, fat_partition.path(), image_path, ) @@ -135,12 +137,9 @@ impl DiskImageBuilder { /// Create a GPT disk image for booting on UEFI systems. pub fn create_uefi_image(&self, image_path: &Path) -> anyhow::Result<()> { const UEFI_BOOT_FILENAME: &str = "efi/boot/bootx64.efi"; - let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); + let mut internal_files = BTreeMap::new(); - internal_files.insert( - UEFI_BOOT_FILENAME, - FileDataSource::File(bootloader_path.to_path_buf()), - ); + internal_files.insert(UEFI_BOOT_FILENAME, FileDataSource::Bytes(UEFI_BOOTLOADER)); let fat_partition = self .create_fat_filesystem_image(internal_files) .context("failed to create FAT partition")?; @@ -159,15 +158,13 @@ impl DiskImageBuilder { use std::{fs, ops::Deref}; const UEFI_TFTP_BOOT_FILENAME: &str = "bootloader"; - let bootloader_path = Path::new(env!("UEFI_BOOTLOADER_PATH")); fs::create_dir_all(tftp_path) .with_context(|| format!("failed to create out dir at {}", tftp_path.display()))?; let to = tftp_path.join(UEFI_TFTP_BOOT_FILENAME); - fs::copy(bootloader_path, &to).with_context(|| { + fs::write(&to, UEFI_BOOTLOADER).with_context(|| { format!( - "failed to copy bootloader from {} to {}", - bootloader_path.display(), + "failed to copy bootloader from the embedded binary to {}", to.display() ) })?; @@ -219,7 +216,7 @@ impl DiskImageBuilder { let out_file = NamedTempFile::new().context("failed to create temp file")?; fat::create_fat_filesystem(local_map, out_file.path()) - .context("failed to create BIOS FAT filesystem")?; + .context("failed to create FAT filesystem")?; Ok(out_file) } diff --git a/src/mbr.rs b/src/mbr.rs index 6c7a9f0d..3328a07f 100644 --- a/src/mbr.rs +++ b/src/mbr.rs @@ -5,15 +5,17 @@ use std::{ io::{self, Seek, SeekFrom}, path::Path, }; + const SECTOR_SIZE: u32 = 512; pub fn create_mbr_disk( - bootsector_path: &Path, - second_stage_path: &Path, + bootsector_binary: &[u8], + second_stage_binary: &[u8], boot_partition_path: &Path, out_mbr_path: &Path, ) -> anyhow::Result<()> { - let mut boot_sector = File::open(bootsector_path).context("failed to open boot sector")?; + use std::io::Cursor; + let mut boot_sector = Cursor::new(bootsector_binary); let mut mbr = mbrman::MBR::read_from(&mut boot_sector, SECTOR_SIZE).context("failed to read MBR")?; @@ -23,12 +25,9 @@ pub fn create_mbr_disk( } } - let mut second_stage = - File::open(second_stage_path).context("failed to open second stage binary")?; - let second_stage_size = second_stage - .metadata() - .context("failed to read file metadata of second stage")? - .len(); + let mut second_stage = Cursor::new(second_stage_binary); + let second_stage_size = second_stage_binary.len() as u64; + let second_stage_start_sector = 1; let second_stage_sectors = ((second_stage_size - 1) / u64::from(SECTOR_SIZE) + 1) .try_into() diff --git a/src/uefi/mod.rs b/src/uefi/mod.rs index 7a27e728..34c63527 100644 --- a/src/uefi/mod.rs +++ b/src/uefi/mod.rs @@ -4,7 +4,7 @@ use bootloader_boot_config::BootConfig; use crate::DiskImageBuilder; -/// Create disk images for booting on legacy BIOS systems. +/// Create disk images for booting on UEFI systems. pub struct UefiBoot { image_builder: DiskImageBuilder, } diff --git a/tests/fixed_kernel_address.rs b/tests/fixed_kernel_address.rs new file mode 100644 index 00000000..25e15d67 --- /dev/null +++ b/tests/fixed_kernel_address.rs @@ -0,0 +1,29 @@ +use bootloader_test_runner::run_test_kernel; + +#[test] +fn basic_boot() { + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_basic_boot" + )); +} + +#[test] +fn should_panic() { + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_should_panic" + )); +} + +#[test] +fn check_boot_info() { + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_check_boot_info" + )); +} + +#[test] +fn verify_kernel_address() { + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_FIXED_KERNEL_ADDRESS_verify_kernel_address" + )); +} diff --git a/tests/lower_memory_free.rs b/tests/lower_memory_free.rs new file mode 100644 index 00000000..aedaf061 --- /dev/null +++ b/tests/lower_memory_free.rs @@ -0,0 +1,7 @@ +use bootloader_test_runner::run_test_kernel; +#[test] +fn lower_memory_free() { + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_LOWER_MEMORY_FREE_lower_memory_free" + )); +} diff --git a/tests/lto.rs b/tests/lto.rs index 00cfe60f..5c259236 100644 --- a/tests/lto.rs +++ b/tests/lto.rs @@ -6,9 +6,9 @@ use bootloader_test_runner::run_test_kernel; fn basic_boot() { // build test kernel manually to force-enable link-time optimization let mut cmd = Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".into())); + cmd.current_dir("tests/test_kernels"); 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()); diff --git a/tests/ramdisk.rs b/tests/ramdisk.rs index bdd7f9db..08c86f9b 100644 --- a/tests/ramdisk.rs +++ b/tests/ramdisk.rs @@ -18,3 +18,11 @@ fn check_ramdisk() { Some(Path::new(RAMDISK_PATH)), ); } + +#[test] +fn memory_map() { + run_test_kernel_with_ramdisk( + env!("CARGO_BIN_FILE_TEST_KERNEL_RAMDISK_memory_map"), + Some(Path::new(RAMDISK_PATH)), + ); +} diff --git a/tests/runner/Cargo.toml b/tests/runner/Cargo.toml index 127184a6..796ffb01 100644 --- a/tests/runner/Cargo.toml +++ b/tests/runner/Cargo.toml @@ -7,6 +7,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +default = ["bios", "uefi"] bios = ["bootloader/bios"] uefi = ["bootloader/uefi", "dep:ovmf-prebuilt"] diff --git a/tests/test_kernels/.cargo/config.toml b/tests/test_kernels/.cargo/config.toml index 9766b811..412c4a33 100644 --- a/tests/test_kernels/.cargo/config.toml +++ b/tests/test_kernels/.cargo/config.toml @@ -1,2 +1,3 @@ [build] target-dir = "../../target" +target = "x86_64-unknown-none" diff --git a/tests/test_kernels/Cargo.lock b/tests/test_kernels/Cargo.lock new file mode 100644 index 00000000..110fc16d --- /dev/null +++ b/tests/test_kernels/Cargo.lock @@ -0,0 +1,162 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" + +[[package]] +name = "bootloader_api" +version = "0.11.10" + +[[package]] +name = "rustversion" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" + +[[package]] +name = "test_kernel_default_settings" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_fixed_kernel_address" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_higher_half" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_lower_memory_free" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_lto" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_map_phys_mem" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_min_stack" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_pie" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_ramdisk" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "test_kernel_write_usable_memory" +version = "0.1.0" +dependencies = [ + "bootloader_api", + "uart_16550", + "x86_64 0.15.2", +] + +[[package]] +name = "uart_16550" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614ff2a87880d4bd4374722268598a970bbad05ced8bf630439417347254ab2e" +dependencies = [ + "bitflags 1.3.2", + "rustversion", + "x86_64 0.14.13", +] + +[[package]] +name = "volatile" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442887c63f2c839b346c192d047a7c87e73d0689c9157b00b53dcc27dd5ea793" + +[[package]] +name = "x86_64" +version = "0.14.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c101112411baafbb4bf8d33e4c4a80ab5b02d74d2612331c61e8192fc9710491" +dependencies = [ + "bit_field", + "bitflags 2.8.0", + "rustversion", + "volatile", +] + +[[package]] +name = "x86_64" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f042214de98141e9c8706e8192b73f56494087cc55ebec28ce10f26c5c364ae" +dependencies = [ + "bit_field", + "bitflags 2.8.0", + "rustversion", + "volatile", +] diff --git a/tests/test_kernels/Cargo.toml b/tests/test_kernels/Cargo.toml new file mode 100644 index 00000000..80daa869 --- /dev/null +++ b/tests/test_kernels/Cargo.toml @@ -0,0 +1,39 @@ +cargo-features = ["profile-rustflags"] + +[workspace] +resolver = "2" +members = [ + "default_settings", + "map_phys_mem", + "higher_half", + "pie", + "lto", + "ramdisk", + "min_stack", + "lower_memory_free", + "write_usable_memory", + "fixed_kernel_address", +] + +[profile.release] +panic = "abort" +lto = false +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=pic", + "-C", + "code-model=large", +] + +[profile.test.package.test_kernel_min_stack] +opt-level = 2 diff --git a/tests/test_kernels/config_file/Cargo.toml b/tests/test_kernels/config_file/Cargo.toml index 990b9508..86a90cf7 100644 --- a/tests/test_kernels/config_file/Cargo.toml +++ b/tests/test_kernels/config_file/Cargo.toml @@ -6,8 +6,7 @@ edition = "2021" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/default_settings/Cargo.toml b/tests/test_kernels/default_settings/Cargo.toml index 81e1be2f..8d643cdd 100644 --- a/tests/test_kernels/default_settings/Cargo.toml +++ b/tests/test_kernels/default_settings/Cargo.toml @@ -6,8 +6,7 @@ edition = "2021" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/fixed_kernel_address/Cargo.toml b/tests/test_kernels/fixed_kernel_address/Cargo.toml new file mode 100644 index 00000000..8a402dca --- /dev/null +++ b/tests/test_kernels/fixed_kernel_address/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "test_kernel_fixed_kernel_address" +version = "0.1.0" +edition = "2021" + +[target.'cfg(target_arch = "x86_64")'.dependencies] +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.15.2", default-features = false, features = [ + "instructions", +] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/fixed_kernel_address/src/bin/basic_boot.rs b/tests/test_kernels/fixed_kernel_address/src/bin/basic_boot.rs new file mode 100644 index 00000000..6d8f696c --- /dev/null +++ b/tests/test_kernels/fixed_kernel_address/src/bin/basic_boot.rs @@ -0,0 +1,18 @@ +#![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_fixed_kernel_address::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG}; + +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: &core::panic::PanicInfo) -> ! { + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/fixed_kernel_address/src/bin/check_boot_info.rs b/tests/test_kernels/fixed_kernel_address/src/bin/check_boot_info.rs new file mode 100644 index 00000000..24267c7a --- /dev/null +++ b/tests/test_kernels/fixed_kernel_address/src/bin/check_boot_info.rs @@ -0,0 +1,23 @@ +#![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_fixed_kernel_address::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG, KERNEL_ADDR}; + +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + assert_eq!(boot_info.kernel_image_offset, KERNEL_ADDR); + + 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_fixed_kernel_address::serial(), "PANIC: {info}"); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/fixed_kernel_address/src/bin/should_panic.rs b/tests/test_kernels/fixed_kernel_address/src/bin/should_panic.rs new file mode 100644 index 00000000..a005c051 --- /dev/null +++ b/tests/test_kernels/fixed_kernel_address/src/bin/should_panic.rs @@ -0,0 +1,20 @@ +#![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_fixed_kernel_address::BOOTLOADER_CONFIG; + +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: &core::panic::PanicInfo) -> ! { + use test_kernel_fixed_kernel_address::{exit_qemu, QemuExitCode}; + + exit_qemu(QemuExitCode::Success); +} diff --git a/tests/test_kernels/fixed_kernel_address/src/bin/verify_kernel_address.rs b/tests/test_kernels/fixed_kernel_address/src/bin/verify_kernel_address.rs new file mode 100644 index 00000000..13944bd1 --- /dev/null +++ b/tests/test_kernels/fixed_kernel_address/src/bin/verify_kernel_address.rs @@ -0,0 +1,29 @@ +#![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_fixed_kernel_address::{exit_qemu, QemuExitCode, BOOTLOADER_CONFIG, KERNEL_ADDR}; + +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + // verify that kernel is loaded at the specified base address. + let rip = x86_64::registers::read_rip().as_u64(); + let kernel_start = KERNEL_ADDR; + let kernel_end = kernel_start + boot_info.kernel_len; + let kernel_range = kernel_start..kernel_end; + + assert!(kernel_range.contains(&rip)); + + 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_fixed_kernel_address::serial(), "PANIC: {info}"); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/fixed_kernel_address/src/lib.rs b/tests/test_kernels/fixed_kernel_address/src/lib.rs new file mode 100644 index 00000000..1231418a --- /dev/null +++ b/tests/test_kernels/fixed_kernel_address/src/lib.rs @@ -0,0 +1,37 @@ +#![no_std] + +use bootloader_api::{config::Mapping, BootloaderConfig}; + +pub const KERNEL_ADDR: u64 = 0x1987_6543_0000; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.kernel_base = Mapping::FixedAddress(KERNEL_ADDR); + config +}; + +#[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/higher_half/Cargo.toml b/tests/test_kernels/higher_half/Cargo.toml index aedecae0..b7d7f2c7 100644 --- a/tests/test_kernels/higher_half/Cargo.toml +++ b/tests/test_kernels/higher_half/Cargo.toml @@ -8,9 +8,8 @@ edition = "2021" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/lower_memory_free/Cargo.toml b/tests/test_kernels/lower_memory_free/Cargo.toml new file mode 100644 index 00000000..be0249c7 --- /dev/null +++ b/tests/test_kernels/lower_memory_free/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "test_kernel_lower_memory_free" +version = "0.1.0" +edition = "2021" + +[dependencies] +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.15.2", default-features = false, features = [ + "instructions", +] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs b/tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs new file mode 100644 index 00000000..9620d1ca --- /dev/null +++ b/tests/test_kernels/lower_memory_free/src/bin/lower_memory_free.rs @@ -0,0 +1,53 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{ + config::Mapping, entry_point, info::MemoryRegionKind, BootInfo, BootloaderConfig, +}; +use test_kernel_lower_memory_free::{exit_qemu, QemuExitCode}; + +const LOWER_MEMORY_END_PAGE: u64 = 0x0010_0000; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_4000_0000_0000)); + config +}; + +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + use core::fmt::Write; + use test_kernel_lower_memory_free::serial; + + let mut count = 0; + for region in boot_info.memory_regions.iter() { + writeln!( + serial(), + "Region: {:016x}-{:016x} - {:?}", + region.start, + region.end, + region.kind + ) + .unwrap(); + if region.kind == MemoryRegionKind::Usable && region.start < LOWER_MEMORY_END_PAGE { + let end = core::cmp::min(region.end, LOWER_MEMORY_END_PAGE); + let pages = (end - region.start) / 4096; + count += pages; + } + } + + writeln!(serial(), "Free lower memory page count: {}", count).unwrap(); + assert!(count > 0x10); // 0x10 chosen arbitrarily, we need _some_ free conventional memory, but not all of it. Some, especially on BIOS, may be reserved for hardware. + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { + use core::fmt::Write; + + let _ = writeln!(test_kernel_lower_memory_free::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/lower_memory_free/src/lib.rs b/tests/test_kernels/lower_memory_free/src/lib.rs new file mode 100644 index 00000000..4e46fdb6 --- /dev/null +++ b/tests/test_kernels/lower_memory_free/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/lto/Cargo.toml b/tests/test_kernels/lto/Cargo.toml index 030625c1..0f8e4e3c 100644 --- a/tests/test_kernels/lto/Cargo.toml +++ b/tests/test_kernels/lto/Cargo.toml @@ -6,8 +6,7 @@ edition = "2018" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/map_phys_mem/Cargo.toml b/tests/test_kernels/map_phys_mem/Cargo.toml index 51233f1c..5dcb5b16 100644 --- a/tests/test_kernels/map_phys_mem/Cargo.toml +++ b/tests/test_kernels/map_phys_mem/Cargo.toml @@ -6,8 +6,7 @@ edition = "2021" [target.'cfg(target_arch = "x86_64")'.dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/min_stack/Cargo.toml b/tests/test_kernels/min_stack/Cargo.toml index afc7c2d6..82aae6d3 100644 --- a/tests/test_kernels/min_stack/Cargo.toml +++ b/tests/test_kernels/min_stack/Cargo.toml @@ -6,8 +6,7 @@ edition = "2021" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/pie/Cargo.toml b/tests/test_kernels/pie/Cargo.toml index c6ee298a..93f71abd 100644 --- a/tests/test_kernels/pie/Cargo.toml +++ b/tests/test_kernels/pie/Cargo.toml @@ -6,8 +6,7 @@ edition = "2018" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/ramdisk/Cargo.toml b/tests/test_kernels/ramdisk/Cargo.toml index 4258b0a5..0ca537e7 100644 --- a/tests/test_kernels/ramdisk/Cargo.toml +++ b/tests/test_kernels/ramdisk/Cargo.toml @@ -6,8 +6,7 @@ edition = "2021" [dependencies] bootloader_api = { path = "../../../api" } -x86_64 = { version = "0.14.7", default-features = false, features = [ +x86_64 = { version = "0.15.2", default-features = false, features = [ "instructions", - "inline_asm", ] } uart_16550 = "0.2.10" diff --git a/tests/test_kernels/ramdisk/src/bin/memory_map.rs b/tests/test_kernels/ramdisk/src/bin/memory_map.rs new file mode 100644 index 00000000..b939a420 --- /dev/null +++ b/tests/test_kernels/ramdisk/src/bin/memory_map.rs @@ -0,0 +1,88 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{ + config::Mapping, entry_point, info::MemoryRegionKind, BootInfo, BootloaderConfig, +}; +use core::{fmt::Write, ptr::slice_from_raw_parts}; +use test_kernel_ramdisk::{exit_qemu, serial, QemuExitCode, RAMDISK_CONTENTS}; +use x86_64::{ + structures::paging::{OffsetPageTable, PageTable, PageTableFlags, Translate}, + VirtAddr, +}; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_6000_0000_0000)); + config +}; + +entry_point!(kernel_main, config = &BOOTLOADER_CONFIG); + +fn kernel_main(boot_info: &'static mut BootInfo) -> ! { + writeln!(serial(), "Boot info: {boot_info:?}").unwrap(); + + let phys_mem_offset = VirtAddr::new(boot_info.physical_memory_offset.into_option().unwrap()); + let level_4_table = unsafe { active_level_4_table(phys_mem_offset) }; + let page_table = unsafe { OffsetPageTable::new(level_4_table, phys_mem_offset) }; + + let ramdisk_start_addr = VirtAddr::new(boot_info.ramdisk_addr.into_option().unwrap()); + assert_eq!(boot_info.ramdisk_len as usize, RAMDISK_CONTENTS.len()); + let ramdisk_end_addr = ramdisk_start_addr + boot_info.ramdisk_len; + + let mut next_addr = ramdisk_start_addr; + while next_addr < ramdisk_end_addr { + let phys_addr = match page_table.translate(next_addr) { + x86_64::structures::paging::mapper::TranslateResult::Mapped { + frame, + offset: _, + flags, + } => { + assert!(flags.contains(PageTableFlags::PRESENT)); + assert!(flags.contains(PageTableFlags::WRITABLE)); + + next_addr += frame.size(); + + frame.start_address() + } + other => panic!("invalid result: {other:?}"), + }; + let region = boot_info + .memory_regions + .iter() + .find(|r| r.start <= phys_addr.as_u64() && r.end > phys_addr.as_u64()) + .unwrap(); + assert_eq!(region.kind, MemoryRegionKind::Bootloader); + } + + let actual_ramdisk = unsafe { + &*slice_from_raw_parts( + boot_info.ramdisk_addr.into_option().unwrap() as *const u8, + boot_info.ramdisk_len as usize, + ) + }; + writeln!(serial(), "Actual contents: {actual_ramdisk:?}").unwrap(); + assert_eq!(RAMDISK_CONTENTS, actual_ramdisk); + + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &core::panic::PanicInfo) -> ! { + let _ = writeln!(test_kernel_ramdisk::serial(), "PANIC: {info}"); + exit_qemu(QemuExitCode::Failed); +} + +pub unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable { + use x86_64::registers::control::Cr3; + + let (level_4_table_frame, _) = Cr3::read(); + + let phys = level_4_table_frame.start_address(); + let virt = physical_memory_offset + phys.as_u64(); + let page_table_ptr: *mut PageTable = virt.as_mut_ptr(); + + &mut *page_table_ptr // unsafe +} diff --git a/tests/test_kernels/write_usable_memory/Cargo.toml b/tests/test_kernels/write_usable_memory/Cargo.toml new file mode 100644 index 00000000..200abc9d --- /dev/null +++ b/tests/test_kernels/write_usable_memory/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "test_kernel_write_usable_memory" +version = "0.1.0" +edition = "2021" + +[dependencies] +bootloader_api = { path = "../../../api" } +x86_64 = { version = "0.15.2", default-features = false, features = [ + "instructions", +] } +uart_16550 = "0.2.10" diff --git a/tests/test_kernels/write_usable_memory/src/bin/write_usable_memory.rs b/tests/test_kernels/write_usable_memory/src/bin/write_usable_memory.rs new file mode 100644 index 00000000..95a27d37 --- /dev/null +++ b/tests/test_kernels/write_usable_memory/src/bin/write_usable_memory.rs @@ -0,0 +1,42 @@ +#![no_std] // don't link the Rust standard library +#![no_main] // disable all Rust-level entry points + +use bootloader_api::{ + config::Mapping, entry_point, info::MemoryRegionKind, BootInfo, BootloaderConfig, +}; +use test_kernel_write_usable_memory::{exit_qemu, QemuExitCode}; + +pub const BOOTLOADER_CONFIG: BootloaderConfig = { + let mut config = BootloaderConfig::new_default(); + config.mappings.physical_memory = Some(Mapping::FixedAddress(0x0000_4000_0000_0000)); + config +}; + +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(); + + for region in boot_info.memory_regions.iter() { + if region.kind == MemoryRegionKind::Usable { + // ensure region is actually writable + let addr = phys_mem_offset + region.start; + let size = region.end - region.start; + unsafe { + core::ptr::write_bytes(addr as *mut u8, 0xff, size as usize); + } + } + } + + exit_qemu(QemuExitCode::Success); +} + +/// This function is called on panic. +#[panic_handler] +#[cfg(not(test))] +fn panic(info: &core::panic::PanicInfo) -> ! { + use core::fmt::Write; + + let _ = writeln!(test_kernel_write_usable_memory::serial(), "PANIC: {}", info); + exit_qemu(QemuExitCode::Failed); +} diff --git a/tests/test_kernels/write_usable_memory/src/lib.rs b/tests/test_kernels/write_usable_memory/src/lib.rs new file mode 100644 index 00000000..4e46fdb6 --- /dev/null +++ b/tests/test_kernels/write_usable_memory/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/write_usable_memory.rs b/tests/write_usable_memory.rs new file mode 100644 index 00000000..a2c7b11a --- /dev/null +++ b/tests/write_usable_memory.rs @@ -0,0 +1,7 @@ +use bootloader_test_runner::run_test_kernel; +#[test] +fn lower_memory_free() { + run_test_kernel(env!( + "CARGO_BIN_FILE_TEST_KERNEL_WRITE_USABLE_MEMORY_write_usable_memory" + )); +} diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 80168be5..9e96ce64 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -13,6 +13,6 @@ bootloader_api = { workspace = true } bootloader-x86_64-common = { workspace = true } bootloader-boot-config = { workspace = true } log = "0.4.14" -x86_64 = "0.14.8" +x86_64 = "0.15.2" serde-json-core = "0.5.0" uefi = "0.20.0" diff --git a/uefi/src/main.rs b/uefi/src/main.rs index 93dfb6c7..99a4821c 100644 --- a/uefi/src/main.rs +++ b/uefi/src/main.rs @@ -144,7 +144,8 @@ fn main_inner(image: Handle, mut st: SystemTable) -> Status { let mut frame_allocator = LegacyFrameAllocator::new(memory_map.entries().copied().map(UefiMemoryDescriptor)); - let page_tables = create_page_tables(&mut frame_allocator); + let max_phys_addr = frame_allocator.max_phys_addr(); + let page_tables = create_page_tables(&mut frame_allocator, max_phys_addr, framebuffer.as_ref()); let mut ramdisk_len = 0u64; let ramdisk_addr = if let Some(rd) = ramdisk { ramdisk_len = rd.len() as u64; @@ -312,10 +313,7 @@ fn load_file_from_disk( let file_handle_result = root.open(filename, FileMode::Read, FileAttribute::empty()); - let file_handle = match file_handle_result { - Err(_) => return None, - Ok(handle) => handle, - }; + let file_handle = file_handle_result.ok()?; let mut file = match file_handle.into_type().unwrap() { uefi::proto::media::file::FileType::Regular(f) => f, @@ -385,6 +383,8 @@ fn load_file_from_tftp_boot_server( /// Creates page table abstraction types for both the bootloader and kernel page tables. fn create_page_tables( frame_allocator: &mut impl FrameAllocator, + max_phys_addr: PhysAddr, + frame_buffer: Option<&RawFrameBufferInfo>, ) -> 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); @@ -410,9 +410,21 @@ fn create_page_tables( } }; - // 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(); + // copy the pml4 entries for all identity mapped memory. + let end_addr = VirtAddr::new(max_phys_addr.as_u64() - 1); + for p4 in 0..=usize::from(end_addr.p4_index()) { + new_table[p4] = old_table[p4].clone(); + } + + // copy the pml4 entry for the frame buffer (the frame buffer is not + // necessarily part of the identity mapping). + if let Some(frame_buffer) = frame_buffer { + let start_addr = VirtAddr::new(frame_buffer.addr.as_u64()); + let end_addr = start_addr + frame_buffer.info.byte_len as u64; + for p4 in usize::from(start_addr.p4_index())..=usize::from(end_addr.p4_index()) { + new_table[p4] = old_table[p4].clone(); + } + } // the first level 4 table entry is now identical, so we can just load the new one unsafe { @@ -516,8 +528,6 @@ fn init_logger( stride: mode_info.stride(), }; - log::info!("UEFI boot"); - bootloader_x86_64_common::init_logger( slice, info, diff --git a/x86_64-stage-4.json b/x86_64-stage-4.json index 976fd56d..b2e7e997 100644 --- a/x86_64-stage-4.json +++ b/x86_64-stage-4.json @@ -2,9 +2,9 @@ "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", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-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", + "features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float", "linker": "rust-lld", "linker-flavor": "ld.lld", "llvm-target": "x86_64-unknown-none-elf", @@ -18,5 +18,6 @@ "static-position-independent-executables": true, "target-pointer-width": "64", "relocation-model": "static", - "os": "none" + "os": "none", + "rustc-abi": "x86-softfloat" }