diff --git a/.github/workflows/book.yml b/.github/workflows/book.yml index ba1369f97..45268d67f 100644 --- a/.github/workflows/book.yml +++ b/.github/workflows/book.yml @@ -2,6 +2,7 @@ name: Book on: push: branches: [main] + tags: ['*'] permissions: contents: write # Adapted from: @@ -21,24 +22,32 @@ jobs: echo `pwd`/mdbook >> $GITHUB_PATH - name: Deploy GitHub Pages run: | - cd book - mdbook build - git worktree add gh-pages gh-pages + # Configure git user so that `git commit` works. git config user.name "Deploy from CI" git config user.email "" - cd gh-pages + + # Get the highest `uefi` release tag. + highest_tag="$(git tag --list | grep uefi-v | sort -V | tail -1)" + + # Create a worktree for the tag. + git worktree add wt-tag refs/tags/"${highest_tag}" + + # Create a worktree for the `gh-pages` branch. + git worktree add wt-gh-pages gh-pages + # Delete the ref to avoid keeping history. - git update-ref -d refs/heads/gh-pages - # Place the book under a "HEAD" directory so that we can later - # add other versions (e.g. "stable" or "v0.17") without breaking - # URLs. - rm -rf HEAD - mv ../book HEAD - git add HEAD - # Add an index in the root to redirect to HEAD. If we eventually - # serve multiple versions, this can be changed to a real index. - cp ../head_redirect.html index.html - git add index.html + git -C wt-gh-pages update-ref -d refs/heads/gh-pages + + # Build the book for the tag. Don't use `--dest-dir` because it will + # delete the destination directory including the worktree checkout's + # ".git". + mdbook build wt-tag/book + # Copy output to the destination directory. Note the "/." is needed at + # the end of the source path so that hidden files are included. + cp -r wt-tag/book/book/. wt-gh-pages + # Commit and push. + cd wt-gh-pages + git add . git commit -m "Deploy $GITHUB_SHA to gh-pages" git push --force diff --git a/.github/workflows/qa.yml b/.github/workflows/qa.yml index 080857889..cd37d3e64 100644 --- a/.github/workflows/qa.yml +++ b/.github/workflows/qa.yml @@ -7,4 +7,4 @@ jobs: steps: - uses: actions/checkout@v4 # Executes "typos ." - - uses: crate-ci/typos@v1.22.9 + - uses: crate-ci/typos@v1.23.6 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 24c8ff538..0f09f2701 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -13,10 +13,13 @@ on: - cron: '0 0 * * 0-6' env: RUSTFLAGS: -D warnings +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true jobs: test_aarch64: name: Integration Test (AArch64) - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 @@ -44,7 +47,7 @@ jobs: timeout-minutes: 4 test_ia32: name: Integration Test (IA-32) - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - name: Checkout sources uses: actions/checkout@v4 @@ -123,11 +126,6 @@ jobs: build_feature_permutations: name: Build (feature permutations) runs-on: ubuntu-latest - env: - # TODO: temporarily allow warnings to not be errors on nightly due to - # incorrect dead_code lint. - # https://github.com/rust-osdev/uefi-rs/issues/1205 - RUSTFLAGS: "" steps: - name: Checkout sources uses: actions/checkout@v4 @@ -136,14 +134,10 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: Build run: cargo xtask build --feature-permutations + # Nightly + unstable feature nightly_channel: - name: Build (nightly + unstable feature) + name: Nightly (build, test, doc) runs-on: ubuntu-latest - env: - # TODO: temporarily allow warnings to not be errors on nightly due to - # incorrect dead_code lint. - # https://github.com/rust-osdev/uefi-rs/issues/1205 - RUSTFLAGS: "" steps: - name: Checkout sources uses: actions/checkout@v4 @@ -163,6 +157,15 @@ jobs: # Skip testing uefi-macros on nightly because the tests that check the # compiler error output produce different output on stable vs nightly. run: cargo xtask test --unstable --skip-macro-tests + miri: + name: Unit + Doc Tests (Miri) + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Enable nightly toolchain + run: cp .github/workflows/nightly_toolchain.toml rust-toolchain.toml + - uses: Swatinem/rust-cache@v2 - name: Run unit tests and doctests under Miri run: | rustup component add miri diff --git a/Cargo.lock b/Cargo.lock index 386dc81ff..c40e334fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.7" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anyhow" @@ -97,9 +97,12 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cc" -version = "1.0.101" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" +checksum = "72db2f7947ecee9b03b510377e8bb9077afa27176fdbff55c51027e976fdcc48" +dependencies = [ + "shlex", +] [[package]] name = "cfg-if" @@ -115,9 +118,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.8" +version = "4.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84b3edb18336f4df585bc9aa31dd99c036dfa5dc5e9a2939a722a188f3a8970d" +checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" dependencies = [ "clap_builder", "clap_derive", @@ -125,9 +128,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.8" +version = "4.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1c09dd5ada6c6c78075d6fd0da3f90d8080651e2d6cc8eb2f1aaa4034ced708" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" dependencies = [ "anstyle", "clap_lex", @@ -135,27 +138,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.8" +version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.75", ] [[package]] name = "clap_lex" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" dependencies = [ "libc", ] @@ -223,7 +226,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -245,21 +248,21 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.23" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +checksum = "bf401df4a4e3872c4fe8151134cf483738e74b67fc934d6532c882b3d24a4550" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys", + "libredox", + "windows-sys 0.59.0", ] [[package]] name = "flate2" -version = "1.0.30" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "7f211bbe8e69bbd0cfdea405084f128ae8b4aaa6b0b522fc8f2b009084797920" dependencies = [ "crc32fast", "miniz_oxide", @@ -322,12 +325,6 @@ version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -346,9 +343,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.6" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" dependencies = [ "equivalent", "hashbrown", @@ -371,9 +368,20 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.157" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", + "redox_syscall", +] [[package]] name = "linux-raw-sys" @@ -450,7 +458,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae99c7fa6dd38c7cafe1ec085e804f8f555a2f8659b0dbe03f1f9963a9b51092" dependencies = [ "log", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -511,18 +519,18 @@ checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.6.0", ] [[package]] name = "regex" -version = "1.10.5" +version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", @@ -559,7 +567,7 @@ dependencies = [ "libc", "spin", "untrusted", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -572,16 +580,17 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] name = "rustls" -version = "0.22.4" +version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "log", + "once_cell", "ring", "rustls-pki-types", "rustls-webpki", @@ -591,15 +600,15 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" [[package]] name = "rustls-webpki" -version = "0.102.4" +version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", @@ -623,9 +632,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.203" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" dependencies = [ "serde_derive", ] @@ -641,31 +650,32 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.203" +version = "1.0.208" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.75", ] [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.125" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" dependencies = [ "serde", ] @@ -681,6 +691,12 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "spin" version = "0.9.8" @@ -706,9 +722,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.68" +version = "2.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" +checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" dependencies = [ "proc-macro2", "quote", @@ -734,14 +750,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.10.1" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" dependencies = [ "cfg-if", "fastrand", + "once_cell", "rustix", - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -755,29 +772,29 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.61" +version = "1.0.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.75", ] [[package]] name = "tinyvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -790,9 +807,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" -version = "0.8.14" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -802,18 +819,18 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.6" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.22.14" +version = "0.22.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" dependencies = [ "indexmap", "serde", @@ -824,9 +841,9 @@ dependencies = [ [[package]] name = "trybuild" -version = "1.0.96" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33a5f13f11071020bb12de7a16b925d2d58636175c20c11dc5f96cb64bb6c9b3" +checksum = "207aa50d36c4be8d8c6ea829478be44a372c6a77669937bb39c698e52f1491e8" dependencies = [ "glob", "serde", @@ -853,7 +870,7 @@ dependencies = [ [[package]] name = "uefi" -version = "0.30.0" +version = "0.31.0" dependencies = [ "bitflags 2.6.0", "cfg-if", @@ -868,18 +885,18 @@ dependencies = [ [[package]] name = "uefi-macros" -version = "0.14.0" +version = "0.15.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.68", + "syn 2.0.75", "trybuild", "uefi", ] [[package]] name = "uefi-raw" -version = "0.6.0" +version = "0.7.0" dependencies = [ "bitflags 2.6.0", "ptr_meta", @@ -893,6 +910,7 @@ dependencies = [ "log", "qemu-exit", "uefi", + "uefi-raw", ] [[package]] @@ -937,9 +955,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "ureq" -version = "2.9.7" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d11a831e3c0b56e438a28308e7c810799e3c118417f342d30ecec080105395cd" +checksum = "b74fc6b57825be3373f7054754755f03ac3a8f5d70015ccad699ba2029956f4a" dependencies = [ "base64", "flate2", @@ -947,7 +965,6 @@ dependencies = [ "once_cell", "rustls", "rustls-pki-types", - "rustls-webpki", "url", "webpki-roots", ] @@ -965,9 +982,9 @@ dependencies = [ [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "walkdir" @@ -996,11 +1013,11 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", ] [[package]] @@ -1012,11 +1029,20 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", @@ -1030,57 +1056,57 @@ dependencies = [ [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" -version = "0.52.5" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.6.13" +version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f" dependencies = [ "memchr", ] @@ -1113,7 +1139,7 @@ dependencies = [ "clap", "fatfs", "fs-err", - "heck 0.4.1", + "heck", "itertools", "lzma-rs", "mbrman", @@ -1124,7 +1150,7 @@ dependencies = [ "regex", "serde_json", "sha2", - "syn 2.0.68", + "syn 2.0.75", "tar", "tempfile", "ureq", diff --git a/README.md b/README.md index 768964871..f352b7632 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,10 @@ # uefi-rs +Rusty wrapper for the [Unified Extensible Firmware Interface][UEFI]. + +This crate makes it easy to develop Rust software that leverages **safe**, +**convenient**, and **performant** abstractions for [UEFI] functionality. + [![Crates.io](https://img.shields.io/crates/v/uefi)](https://crates.io/crates/uefi) [![Docs.rs](https://docs.rs/uefi/badge.svg)](https://docs.rs/uefi) ![License](https://img.shields.io/github/license/rust-osdev/uefi-rs) @@ -8,14 +13,6 @@ ## Description -[UEFI] started as the successor firmware to the BIOS in x86 space and developed -to a universal firmware specification for various platforms, such as ARM. It -provides an early boot environment with a variety of [specified][spec] -ready-to-use "high-level" functionality, such as accessing disks or the network. -EFI images, the files that can be loaded by an UEFI environment, can leverage -these abstractions to extend the functionality in form of additional drivers, -OS-specific bootloaders, or different kind of low-level applications. - Our mission is to provide **safe** and **performant** wrappers for UEFI interfaces, and allow developers to write idiomatic Rust code. @@ -33,10 +30,6 @@ You can use the abstractions for example to: - create OS-specific loaders and leverage UEFI boot service - access UEFI runtime services from an OS -All crates are compatible with all platforms that both the Rust compiler and -UEFI support, such as `i686`, `x86_64`, and `aarch64`). Please note that we -can't test all possible hardware/firmware/platform combinations. - [UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface ![UEFI App running in QEMU](https://imgur.com/SFPSVuO.png) @@ -123,11 +116,14 @@ most of the library's functionality. Check out the testing project's [`README.md`](uefi-test-runner/README.md) for prerequisites for running the tests. -## Contributing +## Discuss and Contribute -We welcome issues and pull requests! For instructions on how to set up a -development environment and how to add new protocols, check out -[CONTRIBUTING.md](CONTRIBUTING.md). +For general discussions, feel free to join us in our [Zulip] and ask +your questions there. + +Further, you can submit bugs and also ask questions in our [issue tracker]. +Contributions in the form of a PR are also highly welcome. Check our +[contributing guide](./CONTRIBUTING.md) for details. ## License @@ -136,3 +132,6 @@ This license allows you to use the crate in proprietary programs, but any modifications to the files must be open-sourced. The full text of the license is available in the [license file](LICENSE). + +[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface +[Zulip]: https://rust-osdev.zulipchat.com diff --git a/book/head_redirect.html b/book/head_redirect.html deleted file mode 100644 index 7c75cfd02..000000000 --- a/book/head_redirect.html +++ /dev/null @@ -1,8 +0,0 @@ - - - -Redirecting to latest documentation - - - -Redirecting to ./HEAD... diff --git a/book/src/tutorial/app.md b/book/src/tutorial/app.md index e8b80832a..adfc58285 100644 --- a/book/src/tutorial/app.md +++ b/book/src/tutorial/app.md @@ -24,7 +24,7 @@ to your `Cargo.toml`. The resulting `Cargo.toml` should look like that: ```toml [dependencies] log = "0.4.21" -uefi = { version = "0.30.0", features = [ "panic_handler", "logger" ] } +uefi = { version = "0.31.0", features = [ "panic_handler", "logger" ] } ``` Replace the contents of `src/main.rs` with this: diff --git a/docs/funcs_migration.md b/docs/funcs_migration.md new file mode 100644 index 000000000..8459e633d --- /dev/null +++ b/docs/funcs_migration.md @@ -0,0 +1,83 @@ +# API migration: Deprecating SystemTable/BootServices/RuntimeServices + +We are in the process of introducing a significant API change in the `uefi` +crate. We are transitioning away from modeling UEFI tables with structs, and +instead providing an API based on freestanding functions. These functions make +use of a global system table pointer that is set automatically by the `entry` +macro. + +A short example: + +```rust +// Old API: +use uefi::table::boot::{BootServices, HandleBuffer}; +fn find_loaded_image_handles(bt: &BootServices) -> Result { + bt.locate_handle_buffer(SearchType::from_proto::()) +} + +// New API: +use uefi::boot::{self, HandleBuffer}; +fn find_loaded_image_handles() -> Result { + boot::locate_handle_buffer(SearchType::from_proto::()) +} +``` + +The new functions generally have almost the same signature as the methods they +are replacing, so in most cases migration should be as simple as updating +imports and calling the freestanding function instead of a method on +`SystemTable`, `BootServices`, or `RuntimeServices`. + +You can retrieve a global `SystemTable` with `uefi::table::system_table_boot` or +`uefi::table::system_table_runtime` to help ease the transition. + +If you run into any issues with this migration, please feel free to chat with us +on [Zulip] or file an [issue]. + +## Timeline + +As of uefi-0.31, the new API has been introduced alongside the old struct-based +API. We plan to deprecate the old API in the release after that, and then fully +remove the old API in a later release. + +## Reason for the change + +See [issue #893][RFC] for the discussion that lead to this change. + +### Safety of `exit_boot_services` + +One of the main motivations for the old API was to make transitioning from boot +services to runtime services a safe operation. Calling `exit_boot_services` +would consume `SystemTable` and return a `SystemTable`, ensuring +that it was no longer possible to call boot services methods. + +That was the theory, but in practice it didn't always work. Many real-world +programs had to call `SystemTable::unsafe_clone` in order to get another handle +to the system table, and that immediately reintroduced the possibility of +unintentionally accessing boot services after calling `exit_boot_services`. + +In addition, there are a great many kinds of resources that should not be +accessed after calling `exit_boot_services`, so even if the `SystemTable` +was gone, it's very hard to _statically_ ensure that all references to +boot-services resources are dropped. + +Realistically the `exit_boot_services` operation is just too complex to model as +part of Rust's safety guarantees. So in the end, we decided it is better to make +`exit_boot_services` an `unsafe` operation. We do make use of runtime checks +when possible to help catch mistakes (for example, calling a `boot` function +after exiting boot services will panic). + +### API complexity + +Some parts of the API need to free a pool allocation on drop, or do some other +type of resource cleanup. [`DevicePathToText`] is one example. The user has to +pass in a reference to `BootServices`, and that means the object containing the +allocation needs to hang on to that reference, so it needs a lifetime +parameter. That may "infect" other parts of the API, requiring adding references +and lifetimes to calling functions and to types containing the returned +value. By using a global table pointer instead, this complexity is hidden and +the API becomes simpler. + +[`DevicePathToText`]: https://docs.rs/uefi/latest/uefi/proto/device_path/text/struct.DevicePathToText.html +[RFC]: https://github.com/rust-osdev/uefi-rs/issues/893 +[Zulip]: https://rust-osdev.zulipchat.com/#narrow/stream/426438-uefi-rs +[issue]: https://github.com/rust-osdev/uefi-rs/issues/new diff --git a/nix/sources.json b/nix/sources.json index 33ce93db6..632c1fc44 100644 --- a/nix/sources.json +++ b/nix/sources.json @@ -5,10 +5,10 @@ "homepage": null, "owner": "NixOS", "repo": "nixpkgs", - "rev": "dd457de7e08c6d06789b1f5b88fc9327f4d96309", - "sha256": "1kpamwmvs5xrmjgl3baxphmm69i0qydvgvk1n1c582ii4bdnzky0", + "rev": "12bf09802d77264e441f48e25459c10c93eada2e", + "sha256": "077p0zni9hh5gx2hmi6fy1k5xy1hbz9bdd8i4x8flvygp95bs1mj", "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/dd457de7e08c6d06789b1f5b88fc9327f4d96309.tar.gz", + "url": "https://github.com/NixOS/nixpkgs/archive/12bf09802d77264e441f48e25459c10c93eada2e.tar.gz", "url_template": "https://github.com/%3Cowner%3E/%3Crepo%3E/archive/%3Crev%3E.tar.gz" }, "rust-overlay": { @@ -17,10 +17,10 @@ "homepage": "", "owner": "oxalica", "repo": "rust-overlay", - "rev": "c9a793a5278f711a59fe77b9bf54b215667022c6", - "sha256": "0q6k94320ivsjb5jjldywy68q6jb0qbd6lsji86ig8a54l649jcf", + "rev": "c02e7d32607e4e16c80152a40ee141c4877b00cb", + "sha256": "035wx6sbm19mwrnc5cfycqpm1vykfwrcxrabafah9xwdbxsnrjzp", "type": "tarball", - "url": "https://github.com/oxalica/rust-overlay/archive/c9a793a5278f711a59fe77b9bf54b215667022c6.tar.gz", + "url": "https://github.com/oxalica/rust-overlay/archive/c02e7d32607e4e16c80152a40ee141c4877b00cb.tar.gz", "url_template": "https://github.com/%3Cowner%3E/%3Crepo%3E/archive/%3Crev%3E.tar.gz" } } diff --git a/template/Cargo.toml b/template/Cargo.toml index d42653118..2d6b3f013 100644 --- a/template/Cargo.toml +++ b/template/Cargo.toml @@ -5,4 +5,4 @@ edition = "2021" publish = false [dependencies] -uefi = { version = "0.30.0", features = ["panic_handler"] } +uefi = { version = "0.31.0", features = ["panic_handler"] } diff --git a/template/src/main.rs b/template/src/main.rs index 64ae41733..d3bb8ad5f 100644 --- a/template/src/main.rs +++ b/template/src/main.rs @@ -4,8 +4,8 @@ use uefi::prelude::*; #[entry] -fn main(_handle: Handle, mut system_table: SystemTable) -> Status { - uefi::helpers::init(&mut system_table).unwrap(); +fn main(_handle: Handle, system_table: SystemTable) -> Status { + uefi::helpers::init().unwrap(); Status::SUCCESS } diff --git a/uefi-macros/CHANGELOG.md b/uefi-macros/CHANGELOG.md index ab6c8829b..3ba8f832a 100644 --- a/uefi-macros/CHANGELOG.md +++ b/uefi-macros/CHANGELOG.md @@ -1,6 +1,14 @@ # uefi-macros - [Unreleased] +# uefi-macros - 0.15.0 (2024-08-20) + +## Changed + +- The `entry` macro now accepts a function with zero arguments in addition to + the two-argument form. + + # uefi-macros - 0.14.0 (2024-07-02) ## Changed diff --git a/uefi-macros/Cargo.toml b/uefi-macros/Cargo.toml index df2c3fc35..e1ef9ffbf 100644 --- a/uefi-macros/Cargo.toml +++ b/uefi-macros/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uefi-macros" -version = "0.14.0" +version = "0.15.0" readme = "README.md" description = "Procedural macros for the `uefi` crate." diff --git a/uefi-macros/src/lib.rs b/uefi-macros/src/lib.rs index 09a67716e..08be64f54 100644 --- a/uefi-macros/src/lib.rs +++ b/uefi-macros/src/lib.rs @@ -8,8 +8,8 @@ use proc_macro2::TokenStream as TokenStream2; use quote::{quote, quote_spanned, TokenStreamExt}; use syn::spanned::Spanned; use syn::{ - parse_macro_input, parse_quote, Error, Expr, ExprLit, ExprPath, FnArg, Ident, ItemFn, - ItemStruct, Lit, Pat, Visibility, + parse_macro_input, parse_quote, parse_quote_spanned, Error, Expr, ExprLit, ExprPath, FnArg, + Ident, ItemFn, ItemStruct, Lit, Pat, Visibility, }; macro_rules! err { @@ -121,18 +121,34 @@ fn get_function_arg_name(f: &ItemFn, arg_index: usize, errors: &mut TokenStream2 /// Custom attribute for a UEFI executable entry point. /// /// This attribute modifies a function to mark it as the entry point for -/// a UEFI executable. The function must have two parameters, [`Handle`] -/// and [`SystemTable`], and return a [`Status`]. The function can -/// optionally be `unsafe`. +/// a UEFI executable. The function: +/// * Must return [`Status`]. +/// * Must have either zero parameters or two: [`Handle`] and [`SystemTable`]. +/// * Can optionally be `unsafe`. /// /// Due to internal implementation details the parameters must both be /// named, so `arg` or `_arg` are allowed, but not `_`. /// -/// The [`BootServices::set_image_handle`] function will be called -/// automatically with the image [`Handle`] argument. +/// The global system table pointer and global image handle will be set +/// automatically. /// /// # Examples /// +/// With no arguments: +/// +/// ```no_run +/// #![no_main] +/// +/// use uefi::prelude::*; +/// +/// #[entry] +/// fn main() -> Status { +/// Status::SUCCESS +/// } +/// ``` +/// +/// With two arguments: +/// /// ```no_run /// #![no_main] /// @@ -180,6 +196,18 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { )); } + let signature_span = f.sig.span(); + + // If the user doesn't specify any arguments to the entry function, fill in + // the image handle and system table arguments automatically. + if f.sig.inputs.is_empty() { + f.sig.inputs = parse_quote_spanned!( + signature_span=> + image_handle: ::uefi::Handle, + system_table: ::uefi::table::SystemTable<::uefi::table::Boot> + ); + } + let image_handle_ident = get_function_arg_name(&f, 0, &mut errors); let system_table_ident = get_function_arg_name(&f, 1, &mut errors); @@ -188,8 +216,6 @@ pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream { return errors.into(); } - let signature_span = f.sig.span(); - f.sig.abi = Some(syn::parse2(quote_spanned! (signature_span=> extern "efiapi")).unwrap()); // allow the entry function to be unsafe (by moving the keyword around so that it actually works) diff --git a/uefi-macros/tests/ui/fail/entry_bad_arg.stderr b/uefi-macros/tests/ui/fail/entry_bad_arg.stderr index 2f8f96ad3..9d292cf66 100644 --- a/uefi-macros/tests/ui/fail/entry_bad_arg.stderr +++ b/uefi-macros/tests/ui/fail/entry_bad_arg.stderr @@ -4,5 +4,5 @@ error[E0308]: mismatched types 8 | fn main(_handle: Handle, _st: SystemTable, _x: usize) -> Status { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ incorrect number of function parameters | - = note: expected fn pointer `extern "efiapi" fn(uefi::Handle, uefi::table::SystemTable) -> uefi::Status` - found fn pointer `extern "efiapi" fn(uefi::Handle, uefi::table::SystemTable, usize) -> uefi::Status` + = note: expected fn pointer `extern "efiapi" fn(uefi::Handle, uefi::prelude::SystemTable) -> uefi::Status` + found fn pointer `extern "efiapi" fn(uefi::Handle, uefi::prelude::SystemTable, usize) -> uefi::Status` diff --git a/uefi-macros/tests/ui/fail/entry_bad_return_type.stderr b/uefi-macros/tests/ui/fail/entry_bad_return_type.stderr index 4c7d1674e..6e718c631 100644 --- a/uefi-macros/tests/ui/fail/entry_bad_return_type.stderr +++ b/uefi-macros/tests/ui/fail/entry_bad_return_type.stderr @@ -4,5 +4,5 @@ error[E0308]: mismatched types 8 | fn main(_handle: Handle, _st: SystemTable) -> bool { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `Status`, found `bool` | - = note: expected fn pointer `extern "efiapi" fn(uefi::Handle, uefi::table::SystemTable) -> Status` - found fn pointer `extern "efiapi" fn(uefi::Handle, uefi::table::SystemTable) -> bool` + = note: expected fn pointer `extern "efiapi" fn(uefi::Handle, uefi::prelude::SystemTable) -> Status` + found fn pointer `extern "efiapi" fn(uefi::Handle, uefi::prelude::SystemTable) -> bool` diff --git a/uefi-macros/tests/ui/pass/entry_no_args.rs b/uefi-macros/tests/ui/pass/entry_no_args.rs new file mode 100644 index 000000000..e2271d5a2 --- /dev/null +++ b/uefi-macros/tests/ui/pass/entry_no_args.rs @@ -0,0 +1,9 @@ +use uefi::{entry, Status}; + +#[entry] +fn efi_main() -> Status { + Status::SUCCESS +} + +// trybuild requires a `main` function. +fn main() {} diff --git a/uefi-raw/CHANGELOG.md b/uefi-raw/CHANGELOG.md index 337e917bf..489bc8ca8 100644 --- a/uefi-raw/CHANGELOG.md +++ b/uefi-raw/CHANGELOG.md @@ -1,6 +1,13 @@ # uefi-raw - [Unreleased] +# uefi-raw - 0.7.0 (2024-08-20) + +## Added +- New `MemoryType` constants: `UNACCEPTED`, `MAX`, `RESERVED_FOR_OEM`, and + `RESERVED_FOR_OS_LOADER`. + + # uefi-raw - 0.6.0 (2024-07-02) ## Added diff --git a/uefi-raw/Cargo.toml b/uefi-raw/Cargo.toml index 5614d23ac..c567f81d1 100644 --- a/uefi-raw/Cargo.toml +++ b/uefi-raw/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "uefi-raw" -version = "0.6.0" +version = "0.7.0" readme = "README.md" description = """ Raw UEFI types and bindings for protocols, boot, and runtime services. This can diff --git a/uefi-raw/src/lib.rs b/uefi-raw/src/lib.rs index 003dd9584..78d320bfc 100644 --- a/uefi-raw/src/lib.rs +++ b/uefi-raw/src/lib.rs @@ -10,10 +10,15 @@ #![no_std] #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![deny(missing_debug_implementations)] -#![deny(clippy::all)] -#![deny(clippy::ptr_as_ptr, unused)] -#![deny(clippy::must_use_candidate)] +#![deny( + clippy::all, + clippy::missing_const_for_fn, + clippy::must_use_candidate, + clippy::ptr_as_ptr, + clippy::use_self, + missing_debug_implementations, + unused +)] #[macro_use] mod enums; diff --git a/uefi-raw/src/status.rs b/uefi-raw/src/status.rs index 531e0579a..9bc74a784 100644 --- a/uefi-raw/src/status.rs +++ b/uefi-raw/src/status.rs @@ -104,14 +104,14 @@ impl Status { #[inline] #[must_use] pub fn is_success(self) -> bool { - self == Status::SUCCESS + self == Self::SUCCESS } /// Returns true if status code indicates a warning. #[inline] #[must_use] pub fn is_warning(self) -> bool { - (self != Status::SUCCESS) && (self.0 & Self::ERROR_BIT == 0) + (self != Self::SUCCESS) && (self.0 & Self::ERROR_BIT == 0) } /// Returns true if the status code indicates an error. diff --git a/uefi-raw/src/table/boot.rs b/uefi-raw/src/table/boot.rs index f0d3bbf02..d197d424a 100644 --- a/uefi-raw/src/table/boot.rs +++ b/uefi-raw/src/table/boot.rs @@ -5,6 +5,7 @@ use crate::table::Header; use crate::{Char16, Event, Guid, Handle, PhysicalAddress, Status, VirtualAddress}; use bitflags::bitflags; use core::ffi::c_void; +use core::ops::RangeInclusive; /// Table of pointers to all the boot services. #[derive(Debug)] @@ -366,8 +367,8 @@ impl MemoryDescriptor { } impl Default for MemoryDescriptor { - fn default() -> MemoryDescriptor { - MemoryDescriptor { + fn default() -> Self { + Self { ty: MemoryType::RESERVED, phys_start: 0, virt_start: 0, @@ -381,11 +382,11 @@ newtype_enum! { /// The type of a memory range. /// /// UEFI allows firmwares and operating systems to introduce new memory types -/// in the 0x70000000..0xFFFFFFFF range. Therefore, we don't know the full set +/// in the `0x7000_0000..=0xFFFF_FFFF` range. Therefore, we don't know the full set /// of memory types at compile time, and it is _not_ safe to model this C enum /// as a Rust enum. pub enum MemoryType: u32 => { - /// This enum variant is not used. + /// Not usable. RESERVED = 0, /// The code portions of a loaded UEFI application. LOADER_CODE = 1, @@ -421,15 +422,26 @@ pub enum MemoryType: u32 => { PAL_CODE = 13, /// Memory region which is usable and is also non-volatile. PERSISTENT_MEMORY = 14, + /// Memory that must be accepted by the boot target before it can be used. + UNACCEPTED = 15, + /// End of the defined memory types. Higher values are possible though, see + /// [`MemoryType::RESERVED_FOR_OEM`] and [`MemoryType::RESERVED_FOR_OS_LOADER`]. + MAX = 16, }} impl MemoryType { - /// Construct a custom `MemoryType`. Values in the range `0x80000000..=0xffffffff` are free for use if you are + /// Range reserved for OEM use. + pub const RESERVED_FOR_OEM: RangeInclusive = 0x7000_0000..=0x7fff_ffff; + + /// Range reserved for OS loaders. + pub const RESERVED_FOR_OS_LOADER: RangeInclusive = 0x8000_0000..=0xffff_ffff; + + /// Construct a custom `MemoryType`. Values in the range `0x8000_0000..=0xffff_ffff` are free for use if you are /// an OS loader. #[must_use] - pub const fn custom(value: u32) -> MemoryType { + pub const fn custom(value: u32) -> Self { assert!(value >= 0x80000000); - MemoryType(value) + Self(value) } } diff --git a/uefi-raw/src/table/revision.rs b/uefi-raw/src/table/revision.rs index b67169537..d3dc0d7c5 100644 --- a/uefi-raw/src/table/revision.rs +++ b/uefi-raw/src/table/revision.rs @@ -61,7 +61,7 @@ impl Revision { let major = major as u32; let minor = minor as u32; let value = (major << 16) | minor; - Revision(value) + Self(value) } /// Returns the major revision. diff --git a/uefi-raw/src/time.rs b/uefi-raw/src/time.rs index b657ae895..f16325283 100644 --- a/uefi-raw/src/time.rs +++ b/uefi-raw/src/time.rs @@ -111,7 +111,7 @@ impl Display for Time { /// The padding fields of `Time` are ignored for comparison. impl PartialEq for Time { - fn eq(&self, other: &Time) -> bool { + fn eq(&self, other: &Self) -> bool { self.year == other.year && self.month == other.month && self.day == other.day diff --git a/uefi-test-runner/Cargo.toml b/uefi-test-runner/Cargo.toml index be314c930..f52406776 100644 --- a/uefi-test-runner/Cargo.toml +++ b/uefi-test-runner/Cargo.toml @@ -6,6 +6,7 @@ publish = false edition = "2021" [dependencies] +uefi-raw = { path = "../uefi-raw" } uefi = { path = "../uefi", features = ["alloc", "global_allocator", "panic_handler", "logger", "qemu"] } log.workspace = true diff --git a/uefi-test-runner/examples/hello_world.rs b/uefi-test-runner/examples/hello_world.rs index a2b46d826..91081cb9e 100644 --- a/uefi-test-runner/examples/hello_world.rs +++ b/uefi-test-runner/examples/hello_world.rs @@ -11,10 +11,10 @@ use uefi::prelude::*; // ANCHOR: entry #[entry] -fn main(_image_handle: Handle, mut system_table: SystemTable) -> Status { +fn main(_image_handle: Handle, system_table: SystemTable) -> Status { // ANCHOR_END: entry // ANCHOR: services - uefi::helpers::init(&mut system_table).unwrap(); + uefi::helpers::init().unwrap(); // ANCHOR_END: services // ANCHOR: log info!("Hello world!"); diff --git a/uefi-test-runner/examples/loaded_image.rs b/uefi-test-runner/examples/loaded_image.rs index 01debc0cb..216af7a89 100644 --- a/uefi-test-runner/examples/loaded_image.rs +++ b/uefi-test-runner/examples/loaded_image.rs @@ -13,8 +13,8 @@ use uefi::{Identify, Result}; // ANCHOR: main #[entry] -fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { - uefi::helpers::init(&mut system_table).unwrap(); +fn main(image_handle: Handle, system_table: SystemTable) -> Status { + uefi::helpers::init().unwrap(); let boot_services = system_table.boot_services(); print_image_path(boot_services).unwrap(); diff --git a/uefi-test-runner/examples/shell_params.rs b/uefi-test-runner/examples/shell_params.rs index ad95a89f4..90d72bcb8 100644 --- a/uefi-test-runner/examples/shell_params.rs +++ b/uefi-test-runner/examples/shell_params.rs @@ -17,10 +17,10 @@ use alloc::vec::Vec; // ANCHOR: entry #[entry] -fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { +fn main(image_handle: Handle, system_table: SystemTable) -> Status { // ANCHOR_END: entry // ANCHOR: services - uefi::helpers::init(&mut system_table).unwrap(); + uefi::helpers::init().unwrap(); let boot_services = system_table.boot_services(); // ANCHOR_END: services diff --git a/uefi-test-runner/examples/sierpinski.rs b/uefi-test-runner/examples/sierpinski.rs index ac690eaef..f0b0912c3 100644 --- a/uefi-test-runner/examples/sierpinski.rs +++ b/uefi-test-runner/examples/sierpinski.rs @@ -144,8 +144,8 @@ fn draw_sierpinski(bt: &BootServices) -> Result { } #[entry] -fn main(_handle: Handle, mut system_table: SystemTable) -> Status { - uefi::helpers::init(&mut system_table).unwrap(); +fn main(_handle: Handle, system_table: SystemTable) -> Status { + uefi::helpers::init().unwrap(); let bt = system_table.boot_services(); draw_sierpinski(bt).unwrap(); Status::SUCCESS diff --git a/uefi-test-runner/examples/timestamp.rs b/uefi-test-runner/examples/timestamp.rs index deccc7e98..1ce578614 100644 --- a/uefi-test-runner/examples/timestamp.rs +++ b/uefi-test-runner/examples/timestamp.rs @@ -16,10 +16,10 @@ use uefi::proto::misc::Timestamp; // ANCHOR: entry #[entry] -fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { +fn main(image_handle: Handle, system_table: SystemTable) -> Status { // ANCHOR_END: entry // ANCHOR: services - uefi::helpers::init(&mut system_table).unwrap(); + uefi::helpers::init().unwrap(); let boot_services = system_table.boot_services(); // ANCHOR_END: services diff --git a/uefi-test-runner/src/bin/shell_launcher.rs b/uefi-test-runner/src/bin/shell_launcher.rs index 4accdca6f..a087293ad 100644 --- a/uefi-test-runner/src/bin/shell_launcher.rs +++ b/uefi-test-runner/src/bin/shell_launcher.rs @@ -13,21 +13,19 @@ extern crate alloc; use alloc::vec::Vec; use log::info; +use uefi::boot::{self, LoadImageSource}; use uefi::prelude::*; use uefi::proto::device_path::build::{self, DevicePathBuilder}; use uefi::proto::device_path::{DevicePath, DeviceSubType, DeviceType, LoadedImageDevicePath}; use uefi::proto::loaded_image::LoadedImage; -use uefi::table::boot::LoadImageSource; +use uefi::proto::BootPolicy; /// Get the device path of the shell app. This is the same as the /// currently-loaded image's device path, but with the file path part changed. -fn get_shell_app_device_path<'a>( - boot_services: &BootServices, - storage: &'a mut Vec, -) -> &'a DevicePath { - let loaded_image_device_path = boot_services - .open_protocol_exclusive::(boot_services.image_handle()) - .expect("failed to open LoadedImageDevicePath protocol"); +fn get_shell_app_device_path(storage: &mut Vec) -> &DevicePath { + let loaded_image_device_path = + boot::open_protocol_exclusive::(boot::image_handle()) + .expect("failed to open LoadedImageDevicePath protocol"); let mut builder = DevicePathBuilder::with_vec(storage); for node in loaded_image_device_path.node_iter() { @@ -45,28 +43,25 @@ fn get_shell_app_device_path<'a>( } #[entry] -fn efi_main(image: Handle, mut st: SystemTable) -> Status { - uefi::helpers::init(&mut st).unwrap(); - let boot_services = st.boot_services(); +fn efi_main() -> Status { + uefi::helpers::init().unwrap(); let mut storage = Vec::new(); - let shell_image_path = get_shell_app_device_path(boot_services, &mut storage); + let shell_image_path = get_shell_app_device_path(&mut storage); // Load the shell app. - let shell_image_handle = boot_services - .load_image( - image, - LoadImageSource::FromDevicePath { - device_path: shell_image_path, - from_boot_manager: false, - }, - ) - .expect("failed to load shell app"); + let shell_image_handle = boot::load_image( + boot::image_handle(), + LoadImageSource::FromDevicePath { + device_path: shell_image_path, + boot_policy: BootPolicy::ExactMatch, + }, + ) + .expect("failed to load shell app"); // Set the command line passed to the shell app so that it will run the // test-runner app. This automatically turns off the five-second delay. - let mut shell_loaded_image = boot_services - .open_protocol_exclusive::(shell_image_handle) + let mut shell_loaded_image = boot::open_protocol_exclusive::(shell_image_handle) .expect("failed to open LoadedImage protocol"); let load_options = cstr16!(r"shell.efi test_runner.efi arg1 arg2"); unsafe { @@ -77,9 +72,7 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { } info!("launching the shell app"); - boot_services - .start_image(shell_image_handle) - .expect("failed to launch the shell app"); + boot::start_image(shell_image_handle).expect("failed to launch the shell app"); Status::SUCCESS } diff --git a/uefi-test-runner/src/boot/memory.rs b/uefi-test-runner/src/boot/memory.rs index 133110b43..826a0a54f 100644 --- a/uefi-test-runner/src/boot/memory.rs +++ b/uefi-test-runner/src/boot/memory.rs @@ -1,15 +1,49 @@ -use uefi::table::boot::{AllocateType, BootServices, MemoryType}; - use alloc::vec::Vec; +use uefi::boot; +use uefi::mem::memory_map::{MemoryMap, MemoryMapMut, MemoryMapOwned, MemoryType}; +use uefi::table::boot::{AllocateType, BootServices}; pub fn test(bt: &BootServices) { info!("Testing memory functions"); + test_allocate_pages_freestanding(); + test_allocate_pool_freestanding(); + allocate_pages(bt); vec_alloc(); alloc_alignment(); memory_map(bt); + memory_map_freestanding(); +} + +fn test_allocate_pages_freestanding() { + let num_pages = 1; + let ptr = + boot::allocate_pages(AllocateType::AnyPages, MemoryType::LOADER_DATA, num_pages).unwrap(); + let addr = ptr.as_ptr() as usize; + assert_eq!(addr % 4096, 0, "Page pointer is not page-aligned"); + + // Verify the page can be written to. + { + let ptr = ptr.as_ptr(); + unsafe { ptr.write_volatile(0xff) }; + unsafe { ptr.add(4095).write_volatile(0xff) }; + } + + unsafe { boot::free_pages(ptr, num_pages) }.unwrap(); +} + +fn test_allocate_pool_freestanding() { + let ptr = boot::allocate_pool(MemoryType::LOADER_DATA, 10).unwrap(); + + // Verify the allocation can be written to. + { + let ptr = ptr.as_ptr(); + unsafe { ptr.write_volatile(0xff) }; + unsafe { ptr.add(9).write_volatile(0xff) }; + } + unsafe { boot::free_pool(ptr) }.unwrap(); } fn allocate_pages(bt: &BootServices) { @@ -60,44 +94,53 @@ fn alloc_alignment() { assert_eq!(value.as_ptr() as usize % 0x100, 0, "Wrong alignment"); } -fn memory_map(bt: &BootServices) { - info!("Testing memory map functions"); - - // Ensure that the memory map is freed after each iteration (on drop). - // Otherwise, we will have an OOM. - for _ in 0..200000 { - let mut memory_map = bt - .memory_map(MemoryType::LOADER_DATA) - .expect("Failed to retrieve UEFI memory map"); +fn check_memory_map(mut memory_map: MemoryMapOwned) { + memory_map.sort(); - memory_map.sort(); + // Collect the descriptors into a vector + let descriptors = memory_map.entries().copied().collect::>(); - // Collect the descriptors into a vector - let descriptors = memory_map.entries().copied().collect::>(); + // Ensured we have at least one entry. + // Real memory maps usually have dozens of entries. + assert!(!descriptors.is_empty(), "Memory map is empty"); - // Ensured we have at least one entry. - // Real memory maps usually have dozens of entries. - assert!(!descriptors.is_empty(), "Memory map is empty"); + let mut curr_value = descriptors[0]; - let mut curr_value = descriptors[0]; - - for value in descriptors.iter().skip(1) { - if value.phys_start <= curr_value.phys_start { - panic!("memory map sorting failed"); - } - curr_value = *value; + for value in descriptors.iter().skip(1) { + if value.phys_start <= curr_value.phys_start { + panic!("memory map sorting failed"); } + curr_value = *value; + } - // This is pretty much a basic sanity test to ensure returned memory - // isn't filled with random values. - let first_desc = descriptors[0]; + // This is pretty much a basic sanity test to ensure returned memory + // isn't filled with random values. + let first_desc = descriptors[0]; - #[cfg(target_arch = "x86_64")] - { - let phys_start = first_desc.phys_start; - assert_eq!(phys_start, 0, "Memory does not start at address 0"); - } - let page_count = first_desc.page_count; - assert!(page_count != 0, "Memory map entry has size zero"); + #[cfg(target_arch = "x86_64")] + { + let phys_start = first_desc.phys_start; + assert_eq!(phys_start, 0, "Memory does not start at address 0"); } + let page_count = first_desc.page_count; + assert!(page_count != 0, "Memory map entry has size zero"); +} + +fn memory_map(bt: &BootServices) { + info!("Testing memory map functions"); + + let memory_map = bt + .memory_map(MemoryType::LOADER_DATA) + .expect("Failed to retrieve UEFI memory map"); + + check_memory_map(memory_map); +} + +fn memory_map_freestanding() { + info!("Testing memory map functions (freestanding)"); + + let memory_map = + boot::memory_map(MemoryType::LOADER_DATA).expect("Failed to retrieve UEFI memory map"); + + check_memory_map(memory_map); } diff --git a/uefi-test-runner/src/boot/misc.rs b/uefi-test-runner/src/boot/misc.rs index 8b7422be4..7fab703f5 100644 --- a/uefi-test-runner/src/boot/misc.rs +++ b/uefi-test-runner/src/boot/misc.rs @@ -2,29 +2,69 @@ use core::ffi::c_void; use core::ptr::{self, NonNull}; use core::mem; +use uefi::mem::memory_map::MemoryType; use uefi::proto::unsafe_protocol; use uefi::table::boot::{ - BootServices, EventType, MemoryType, OpenProtocolAttributes, OpenProtocolParams, SearchType, - TimerTrigger, Tpl, + BootServices, EventType, OpenProtocolAttributes, OpenProtocolParams, SearchType, TimerTrigger, + Tpl, }; use uefi::table::{Boot, SystemTable}; -use uefi::{guid, Event, Guid, Identify}; +use uefi::{boot, guid, system, Event, Guid, Identify, Status}; pub fn test(st: &SystemTable) { let bt = st.boot_services(); + test_tpl(); info!("Testing timer..."); test_timer(bt); info!("Testing events..."); + test_check_event_freestanding(); + test_timer_freestanding(); test_event_callback(bt); test_callback_with_ctx(bt); info!("Testing watchdog..."); test_watchdog(bt); info!("Testing protocol handler services..."); test_register_protocol_notify(bt); + test_register_protocol_notify_freestanding(); + test_protocol_interface_management(); test_install_protocol_interface(bt); test_reinstall_protocol_interface(bt); test_uninstall_protocol_interface(bt); test_install_configuration_table(st); + test_install_configuration_table_freestanding(); +} + +fn test_tpl() { + info!("Testing watchdog..."); + // There's no way to query the TPL, so we can't assert that this does anything. + let _guard = unsafe { boot::raise_tpl(Tpl::NOTIFY) }; +} + +fn test_check_event_freestanding() { + extern "efiapi" fn callback(_event: Event, _ctx: Option>) { + info!("Callback triggered by check_event"); + } + + let event = + unsafe { boot::create_event(EventType::NOTIFY_WAIT, Tpl::CALLBACK, Some(callback), None) } + .unwrap(); + + let event_clone = unsafe { event.unsafe_clone() }; + let is_signaled = boot::check_event(event_clone).unwrap(); + assert!(!is_signaled); + + boot::close_event(event).unwrap(); +} + +fn test_timer_freestanding() { + let timer_event = + unsafe { boot::create_event_ex(EventType::TIMER, Tpl::CALLBACK, None, None, None) } + .unwrap(); + let mut events = unsafe { [timer_event.unsafe_clone()] }; + boot::set_timer(&timer_event, TimerTrigger::Relative(5_0 /*00 ns */)).unwrap(); + assert_eq!(boot::wait_for_event(&mut events).unwrap(), 0); + + boot::close_event(timer_event).unwrap(); } fn test_timer(bt: &BootServices) { @@ -81,9 +121,14 @@ fn test_callback_with_ctx(bt: &BootServices) { } fn test_watchdog(bt: &BootServices) { - // Disable the UEFI watchdog timer + // There's no way to check the watchdog timer value, so just test setting it. + + // Disable the UEFI watchdog timer. bt.set_watchdog_timer(0, 0x10000, None) .expect("Could not set watchdog timer"); + + // Set the timer with the freestanding function. + boot::set_watchdog_timer(240, 0x10000, None).expect("Could not set watchdog timer"); } /// Dummy protocol for tests @@ -112,6 +157,62 @@ fn test_register_protocol_notify(bt: &BootServices) { .expect("Failed to register protocol notify fn"); } +fn test_register_protocol_notify_freestanding() { + unsafe extern "efiapi" fn callback(_event: Event, _context: Option>) { + info!("in callback for test_register_protocol_notify_freestanding") + } + + let protocol = &TestProtocol::GUID; + let event = unsafe { + boot::create_event(EventType::NOTIFY_SIGNAL, Tpl::NOTIFY, Some(callback), None).unwrap() + }; + + boot::register_protocol_notify(protocol, &event) + .expect("Failed to register protocol notify fn"); +} + +fn test_protocol_interface_management() { + let mut interface = TestProtocol { data: 123 }; + let interface_ptr: *mut _ = &mut interface; + + // Install the protocol. + let handle = unsafe { + boot::install_protocol_interface(None, &TestProtocol::GUID, interface_ptr.cast()) + } + .unwrap(); + + // Verify the handle was installed. + assert_eq!( + &*boot::locate_handle_buffer(SearchType::from_proto::()).unwrap(), + [handle] + ); + + // Re-install the protocol. + unsafe { + boot::reinstall_protocol_interface( + handle, + &TestProtocol::GUID, + interface_ptr.cast(), + interface_ptr.cast(), + ) + } + .unwrap(); + + // Uninstall the protocol. + unsafe { + boot::uninstall_protocol_interface(handle, &TestProtocol::GUID, interface_ptr.cast()) + } + .unwrap(); + + // Verify the protocol was uninstalled. + assert_eq!( + boot::locate_handle_buffer(SearchType::from_proto::()) + .unwrap_err() + .status(), + Status::NOT_FOUND + ); +} + fn test_install_protocol_interface(bt: &BootServices) { info!("Installing TestProtocol"); @@ -163,16 +264,15 @@ fn test_uninstall_protocol_interface(bt: &BootServices) { // pointer. Open the protocol to get that pointer, making sure to drop // the `ScopedProtocol` _before_ uninstalling the protocol interface. let interface_ptr: *mut TestProtocol = { - let mut sp = bt - .open_protocol::( - OpenProtocolParams { - handle, - agent: bt.image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - .unwrap(); + let mut sp = boot::open_protocol::( + OpenProtocolParams { + handle, + agent: bt.image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .unwrap(); assert_eq!(sp.data, 123); &mut *sp }; @@ -184,6 +284,35 @@ fn test_uninstall_protocol_interface(bt: &BootServices) { } } +fn test_install_configuration_table_freestanding() { + // Get the current number of entries. + let count = system::with_config_table(|t| t.len()); + + // Create the entry data. + let config = boot::allocate_pool(MemoryType::RUNTIME_SERVICES_DATA, 1) + .unwrap() + .as_ptr(); + unsafe { config.write(42) }; + + // Install the table. + const ID: Guid = guid!("4bec53c4-5fc1-48a1-ab12-df214907d29f"); + unsafe { + boot::install_configuration_table(&ID, config.cast()).unwrap(); + } + + // Verify the installation. + assert_eq!(count + 1, system::with_config_table(|t| t.len())); + system::with_config_table(|t| { + let config_entry = t.iter().find(|ct| ct.guid == ID).unwrap(); + assert_eq!(unsafe { *(config_entry.address as *const u8) }, 42); + }); + + // Uninstall the table. + unsafe { + boot::install_configuration_table(&ID, ptr::null()).unwrap(); + } +} + fn test_install_configuration_table(st: &SystemTable) { let config = st .boot_services() diff --git a/uefi-test-runner/src/boot/mod.rs b/uefi-test-runner/src/boot/mod.rs index 74e817afe..512b6ab98 100644 --- a/uefi-test-runner/src/boot/mod.rs +++ b/uefi-test-runner/src/boot/mod.rs @@ -3,9 +3,10 @@ use uefi::fs::FileSystem; use uefi::proto::console::text::Output; use uefi::proto::device_path::media::FilePath; use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath}; +use uefi::proto::BootPolicy; use uefi::table::boot::{BootServices, LoadImageSource, SearchType}; use uefi::table::{Boot, SystemTable}; -use uefi::{CString16, Identify}; +use uefi::{boot, CString16, Identify}; mod memory; mod misc; @@ -15,12 +16,12 @@ pub fn test(st: &SystemTable) { info!("Testing boot services"); memory::test(bt); misc::test(st); - test_locate_handle_buffer(bt); + test_locate_handles(bt); test_load_image(bt); } -fn test_locate_handle_buffer(bt: &BootServices) { - info!("Testing the `locate_handle_buffer` function"); +fn test_locate_handles(bt: &BootServices) { + info!("Testing the `locate_handle_buffer`/`find_handles` functions"); { // search all handles @@ -28,6 +29,12 @@ fn test_locate_handle_buffer(bt: &BootServices) { .locate_handle_buffer(SearchType::AllHandles) .expect("Failed to locate handle buffer"); assert!(!handles.is_empty(), "Could not find any handles"); + + // Compare with freestanding version. + assert_eq!( + *handles, + *boot::locate_handle_buffer(SearchType::AllHandles).unwrap() + ); } { @@ -39,6 +46,17 @@ fn test_locate_handle_buffer(bt: &BootServices) { !handles.is_empty(), "Could not find any OUTPUT protocol handles" ); + + // Compare with freestanding version. + assert_eq!( + *handles, + *boot::locate_handle_buffer(SearchType::ByProtocol(&Output::GUID)).unwrap() + ); + + // Compare with `boot::find_handles`. This implicitly tests + // `boot::locate_handle` as well. + let handles_vec = boot::find_handles::().unwrap(); + assert_eq!(*handles, handles_vec); } } @@ -105,7 +123,7 @@ fn test_load_image(bt: &BootServices) { { let load_source = LoadImageSource::FromDevicePath { device_path: image_device_path, - from_boot_manager: false, + boot_policy: BootPolicy::ExactMatch, }; let _ = bt .load_image(bt.image_handle(), load_source) diff --git a/uefi-test-runner/src/main.rs b/uefi-test-runner/src/main.rs index c61411e27..569b36e91 100644 --- a/uefi-test-runner/src/main.rs +++ b/uefi-test-runner/src/main.rs @@ -8,12 +8,12 @@ extern crate alloc; use alloc::string::ToString; use alloc::vec::Vec; +use uefi::mem::memory_map::{MemoryMap, MemoryType}; use uefi::prelude::*; use uefi::proto::console::serial::Serial; use uefi::proto::device_path::build::{self, DevicePathBuilder}; use uefi::proto::device_path::messaging::Vendor; -use uefi::table::boot::MemoryType; -use uefi::{print, println, Result}; +use uefi::{print, println, system, Result}; mod boot; mod fs; @@ -23,7 +23,7 @@ mod runtime; #[entry] fn efi_main(image: Handle, mut st: SystemTable) -> Status { // Initialize utilities (logging, memory allocation...) - uefi::helpers::init(&mut st).expect("Failed to initialize utilities"); + uefi::helpers::init().expect("Failed to initialize utilities"); // unit tests here @@ -45,12 +45,11 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { // Ensure the tests are run on a version of UEFI we support. check_revision(st.uefi_revision()); - // Test all the boot services. - let bt = st.boot_services(); + // Check the `uefi::system` module. + check_system(&st); // Try retrieving a handle to the file system the image was booted from. - bt.get_image_file_system(image) - .expect("Failed to retrieve boot file system"); + uefi::boot::get_image_file_system(image).expect("Failed to retrieve boot file system"); boot::test(&st); @@ -67,6 +66,8 @@ fn efi_main(image: Handle, mut st: SystemTable) -> Status { } fn check_revision(rev: uefi::table::Revision) { + assert_eq!(system::uefi_revision(), rev); + let (major, minor) = (rev.major(), rev.minor()); info!("UEFI {}.{}", major, minor / 10); @@ -78,6 +79,25 @@ fn check_revision(rev: uefi::table::Revision) { ); } +fn check_system(st: &SystemTable) { + assert_eq!(system::firmware_vendor(), cstr16!("EDK II")); + check_revision(system::uefi_revision()); + + assert_eq!(system::firmware_revision(), st.firmware_revision()); + system::with_config_table(|t| assert_eq!(t, st.config_table())); + + system::with_stdout(|stdout| { + stdout + .output_string(cstr16!("test system::with_stdout\n")) + .unwrap() + }); + system::with_stderr(|stdout| { + stdout + .output_string(cstr16!("test system::with_stderr\n")) + .unwrap() + }); +} + #[derive(Clone, Copy, Debug)] enum HostRequest { /// Tell the host to take a screenshot and compare against the @@ -119,7 +139,7 @@ fn send_request_helper(serial: &mut Serial, request: HostRequest) -> Result { /// This must be called after opening the serial protocol in exclusive mode, as /// that breaks the connection to the console, which in turn prevents logs from /// getting to the host. -fn reconnect_serial_to_console(boot_services: &BootServices, serial_handle: Handle) { +fn reconnect_serial_to_console(serial_handle: Handle) { let mut storage = Vec::new(); // Create a device path that specifies the terminal type. let terminal_guid = if cfg!(target_arch = "aarch64") { @@ -136,18 +156,16 @@ fn reconnect_serial_to_console(boot_services: &BootServices, serial_handle: Hand .finalize() .unwrap(); - boot_services - .connect_controller(serial_handle, None, Some(terminal_device_path), true) + uefi::boot::connect_controller(serial_handle, None, Some(terminal_device_path), true) .expect("failed to reconnect serial to console"); } /// Send the `request` string to the host via the `serial` device, then /// wait up to 10 seconds to receive a reply. Returns an error if the /// reply is not `"OK\n"`. -fn send_request_to_host(bt: &BootServices, request: HostRequest) { - let serial_handle = bt - .get_handle_for_protocol::() - .expect("Failed to get serial handle"); +fn send_request_to_host(request: HostRequest) { + let serial_handle = + uefi::boot::get_handle_for_protocol::().expect("Failed to get serial handle"); // Open the serial protocol in exclusive mode. // @@ -160,8 +178,7 @@ fn send_request_to_host(bt: &BootServices, request: HostRequest) { // end with `connect_controller`. // // [console splitter driver]: https://github.com/tianocore/edk2/blob/HEAD/MdeModulePkg/Universal/Console/ConSplitterDxe/ConSplitter.c - let mut serial = bt - .open_protocol_exclusive::(serial_handle) + let mut serial = uefi::boot::open_protocol_exclusive::(serial_handle) .expect("Could not open serial protocol"); // Send the request, but don't check the result yet so that first @@ -174,7 +191,7 @@ fn send_request_to_host(bt: &BootServices, request: HostRequest) { // device, which was broken when we opened the protocol in exclusive // mode above. drop(serial); - reconnect_serial_to_console(bt, serial_handle); + reconnect_serial_to_console(serial_handle); if let Err(err) = res { panic!("request failed: \"{request:?}\": {:?}", err.status()); @@ -188,7 +205,7 @@ fn shutdown(mut st: SystemTable) -> ! { // Tell the host that tests are done. We are about to exit boot // services, so we can't easily communicate with the host any later // than this. - send_request_to_host(st.boot_services(), HostRequest::TestsComplete); + send_request_to_host(HostRequest::TestsComplete); // Send a special log to the host so that we can verify that logging works // up until exiting boot services. See `reconnect_serial_to_console` for the diff --git a/uefi-test-runner/src/proto/console/gop.rs b/uefi-test-runner/src/proto/console/gop.rs index 94bcbda40..7afb163fd 100644 --- a/uefi-test-runner/src/proto/console/gop.rs +++ b/uefi-test-runner/src/proto/console/gop.rs @@ -28,7 +28,7 @@ pub unsafe fn test(bt: &BootServices) { // `draw_fb` is skipped on aarch64, so the screenshot doesn't match. if cfg!(not(target_arch = "aarch64")) { - send_request_to_host(bt, HostRequest::Screenshot("gop_test")); + send_request_to_host(HostRequest::Screenshot("gop_test")); } } diff --git a/uefi-test-runner/src/proto/console/serial.rs b/uefi-test-runner/src/proto/console/serial.rs index a39d448ef..6c20f6568 100644 --- a/uefi-test-runner/src/proto/console/serial.rs +++ b/uefi-test-runner/src/proto/console/serial.rs @@ -67,7 +67,7 @@ pub unsafe fn test(bt: &BootServices) { // device, which was broken when we opened the protocol in exclusive // mode above. drop(serial); - reconnect_serial_to_console(bt, handle); + reconnect_serial_to_console(handle); if let Err(err) = res { panic!("serial test failed: {:?}", err.status()); diff --git a/uefi-test-runner/src/proto/device_path.rs b/uefi-test-runner/src/proto/device_path.rs index 942837241..311b51847 100644 --- a/uefi-test-runner/src/proto/device_path.rs +++ b/uefi-test-runner/src/proto/device_path.rs @@ -1,9 +1,11 @@ use alloc::string::ToString; use alloc::vec::Vec; +use uefi::boot; use uefi::prelude::*; use uefi::proto::device_path::text::*; use uefi::proto::device_path::{DevicePath, LoadedImageDevicePath}; use uefi::proto::loaded_image::LoadedImage; +use uefi::proto::media::disk::DiskIo; pub fn test(bt: &BootServices) { info!("Running device path protocol test"); @@ -64,6 +66,10 @@ pub fn test(bt: &BootServices) { { assert_eq!(n1, n2); } + + // Test `locate_device_path`. + let mut dp = &*device_path; + boot::locate_device_path::(&mut dp).unwrap(); } // test 2/2: test high-level to-string api diff --git a/uefi-test-runner/src/proto/load.rs b/uefi-test-runner/src/proto/load.rs new file mode 100644 index 000000000..1a694d31c --- /dev/null +++ b/uefi-test-runner/src/proto/load.rs @@ -0,0 +1,144 @@ +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::ffi::c_void; +use core::pin::Pin; +use core::ptr; +use core::ptr::addr_of; +use uefi::prelude::BootServices; +use uefi::proto::device_path::build::DevicePathBuilder; +use uefi::proto::media::load_file::{LoadFile, LoadFile2}; +use uefi::proto::BootPolicy; +use uefi::{Guid, Handle}; +use uefi_raw::protocol::device_path::DevicePathProtocol; +use uefi_raw::protocol::media::{LoadFile2Protocol, LoadFileProtocol}; +use uefi_raw::Status; + +unsafe extern "efiapi" fn raw_load_file( + this: *mut LoadFile2Protocol, + _file_path: *const DevicePathProtocol, + _boot_policy: bool, + buffer_size: *mut usize, + buffer: *mut c_void, +) -> Status { + log::debug!("Called static extern \"efiapi\" `raw_load_file` glue function"); + let this = this.cast::().as_ref().unwrap(); + this.load_file(buffer_size, buffer.cast()) +} + +#[repr(C)] +struct CustomLoadFile2Protocol { + inner: LoadFile2Protocol, + file_data: Vec, +} + +impl CustomLoadFile2Protocol { + fn new(file_data: Vec) -> Pin> { + let inner = Self { + inner: LoadFile2Protocol { + load_file: raw_load_file, + }, + file_data, + }; + Box::pin(inner) + } + + fn load_file(&self, buf_len: *mut usize, buf: *mut c_void) -> Status { + if buf.is_null() || unsafe { *buf_len } < self.file_data.len() { + log::debug!("Returning buffer size"); + unsafe { *buf_len = self.file_data.len() }; + Status::BUFFER_TOO_SMALL + } else { + log::debug!("Writing file content to buffer"); + unsafe { + ptr::copy_nonoverlapping(self.file_data.as_ptr(), buf.cast(), self.file_data.len()); + } + Status::SUCCESS + } + } +} + +unsafe fn install_protocol( + bt: &BootServices, + handle: Handle, + guid: Guid, + protocol: &mut CustomLoadFile2Protocol, +) { + bt.install_protocol_interface(Some(handle), &guid, addr_of!(*protocol).cast()) + .unwrap(); +} + +unsafe fn uninstall_protocol( + bt: &BootServices, + handle: Handle, + guid: Guid, + protocol: &mut CustomLoadFile2Protocol, +) { + bt.uninstall_protocol_interface(handle, &guid, addr_of!(*protocol).cast()) + .unwrap(); +} + +/// This tests the LoadFile and LoadFile2 protocols. As this protocol is not +/// implemented in OVMF for the default handle, we implement it manually using +/// `install_protocol_interface`. Then, we load a file from our custom installed +/// protocol leveraging our protocol abstraction. +/// +/// The way we are implementing the LoadFile(2) protocol is roughly what certain +/// Linux loaders do so that Linux can find its initrd [0, 1]. +/// +/// [0] https://github.com/u-boot/u-boot/commit/ec80b4735a593961fe701cc3a5d717d4739b0fd0#diff-1f940face4d1cf74f9d2324952759404d01ee0a81612b68afdcba6b49803bdbbR171 +/// [1] https://github.com/torvalds/linux/blob/ee9a43b7cfe2d8a3520335fea7d8ce71b8cabd9d/drivers/firmware/efi/libstub/efi-stub-helper.c#L550 +pub fn test(bt: &BootServices) { + let image = bt.image_handle(); + + let load_data_msg = "Example file content."; + let load_data = load_data_msg.to_string().into_bytes(); + let mut proto_load_file = CustomLoadFile2Protocol::new(load_data); + // Get the ptr to the inner value, not the wrapping smart pointer type. + let proto_load_file_ptr = proto_load_file.as_mut().get_mut(); + + // Install our custom protocol implementation as LoadFile and LoadFile2 + // protocol. + unsafe { + install_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr); + install_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr); + } + + let mut dvp_vec = Vec::new(); + let dummy_dvp = DevicePathBuilder::with_vec(&mut dvp_vec); + let dummy_dvp = dummy_dvp.finalize().unwrap(); + + let mut load_file_protocol = bt.open_protocol_exclusive::(image).unwrap(); + let loadfile_file = load_file_protocol + .load_file(dummy_dvp, BootPolicy::BootSelection) + .unwrap(); + let loadfile_file_string = String::from_utf8(loadfile_file.to_vec()).unwrap(); + + let mut load_file2_protocol = bt.open_protocol_exclusive::(image).unwrap(); + let loadfile2_file = load_file2_protocol.load_file(dummy_dvp).unwrap(); + let loadfile2_file_string = String::from_utf8(loadfile2_file.to_vec()).unwrap(); + + assert_eq!(load_data_msg, &loadfile_file_string); + assert_eq!(load_data_msg, &loadfile2_file_string); + + // Cleanup: Uninstall protocols again. + drop(load_file_protocol); + drop(load_file2_protocol); + unsafe { + uninstall_protocol(bt, image, LoadFileProtocol::GUID, proto_load_file_ptr); + uninstall_protocol(bt, image, LoadFile2Protocol::GUID, proto_load_file_ptr); + } + // Ensure protocols have been uninstalled: + assert_eq!( + bt.open_protocol_exclusive::(image) + .map(|_| ()) // make Result Eq'able + .map_err(|e| e.status()), + Err(Status::UNSUPPORTED) + ); + assert_eq!( + bt.open_protocol_exclusive::(image) + .map(|_| ()) // make Result Eq'able + .map_err(|e| e.status()), + Err(Status::UNSUPPORTED) + ); +} diff --git a/uefi-test-runner/src/proto/media.rs b/uefi-test-runner/src/proto/media.rs index 8590ecb81..16a99c909 100644 --- a/uefi-test-runner/src/proto/media.rs +++ b/uefi-test-runner/src/proto/media.rs @@ -1,6 +1,7 @@ use alloc::string::ToString; use core::cell::RefCell; use core::ptr::NonNull; +use uefi::boot; use uefi::data_types::Align; use uefi::prelude::*; use uefi::proto::media::block::BlockIO; @@ -305,8 +306,7 @@ fn test_raw_disk_io2(handle: Handle, bt: &BootServices) { unsafe { // Create the completion event - let mut event = bt - .create_event(EventType::empty(), Tpl::NOTIFY, None, None) + let mut event = boot::create_event(EventType::empty(), Tpl::NOTIFY, None, None) .expect("Failed to create disk I/O completion event"); // Initialise the task context diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index d40a11453..09a9fd5ef 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -1,3 +1,4 @@ +use uefi::boot::{self, OpenProtocolParams}; use uefi::prelude::*; use uefi::proto::loaded_image::LoadedImage; use uefi::{proto, Identify}; @@ -10,10 +11,13 @@ pub fn test(st: &mut SystemTable) { let bt = st.boot_services(); find_protocol(bt); test_protocols_per_handle(bt); + test_protocols_per_handle_freestanding(); + test_test_protocol_freestanding(); debug::test(bt); device_path::test(bt); driver::test(bt); + load::test(bt); loaded_image::test(bt); media::test(bt); network::test(bt); @@ -55,10 +59,27 @@ fn test_protocols_per_handle(bt: &BootServices) { assert!(pph.iter().any(|guid| **guid == LoadedImage::GUID)); } +fn test_protocols_per_handle_freestanding() { + let pph = boot::protocols_per_handle(boot::image_handle()).unwrap(); + info!("Image handle has {} protocols", pph.len()); + // Check that one of the image's protocols is `LoadedImage`. + assert!(pph.iter().any(|guid| **guid == LoadedImage::GUID)); +} + +fn test_test_protocol_freestanding() { + assert!(boot::test_protocol::(OpenProtocolParams { + handle: boot::image_handle(), + agent: boot::image_handle(), + controller: None, + }) + .unwrap()); +} + mod console; mod debug; mod device_path; mod driver; +mod load; mod loaded_image; mod media; mod misc; diff --git a/uefi-test-runner/src/proto/tcg.rs b/uefi-test-runner/src/proto/tcg.rs index 21e0cce55..b74772578 100644 --- a/uefi-test-runner/src/proto/tcg.rs +++ b/uefi-test-runner/src/proto/tcg.rs @@ -1,5 +1,4 @@ use alloc::vec::Vec; -use core::mem::MaybeUninit; use uefi::proto::tcg::{v1, v2, AlgorithmId, EventType, HashAlgorithm, PcrIndex}; use uefi::table::boot::BootServices; @@ -63,7 +62,7 @@ fn test_tcg_v1(bt: &BootServices) { let pcr_index = PcrIndex(8); - let mut event_buf = [MaybeUninit::uninit(); 256]; + let mut event_buf = [0; 256]; let event = v1::PcrEvent::new_in_buffer( &mut event_buf, pcr_index, @@ -279,7 +278,7 @@ pub fn test_tcg_v2(bt: &BootServices) { // Create a PCR event. let pcr_index = PcrIndex(8); - let mut event_buf = [MaybeUninit::uninit(); 256]; + let mut event_buf = [0; 256]; let event_data = [0x12, 0x13, 0x14, 0x15]; let data_to_hash = b"some-data"; let event = diff --git a/uefi-test-runner/src/runtime/mod.rs b/uefi-test-runner/src/runtime/mod.rs index 9babae774..92769bfe4 100644 --- a/uefi-test-runner/src/runtime/mod.rs +++ b/uefi-test-runner/src/runtime/mod.rs @@ -1,8 +1,38 @@ +mod vars; + +use uefi::runtime::{self, Daylight, Time, TimeParams}; use uefi::table::runtime::RuntimeServices; pub fn test(rt: &RuntimeServices) { info!("Testing runtime services"); vars::test(rt); + test_time(); } -mod vars; +fn test_time() { + // Print the current time and time capabilities. + info!( + "Time with caps: {:?}", + runtime::get_time_and_caps().unwrap() + ); + + // Set the time. + let time = Time::new(TimeParams { + year: 2020, + month: 1, + day: 2, + hour: 3, + minute: 4, + second: 5, + nanosecond: 6, + time_zone: None, + daylight: Daylight::ADJUST_DAYLIGHT, + }) + .unwrap(); + unsafe { runtime::set_time(&time).unwrap() }; + + // Print the new time and check that the year was successfully changed. + let now = runtime::get_time().unwrap(); + info!("After setting time: {}", now); + assert_eq!(now.year(), 2020); +} diff --git a/uefi-test-runner/src/runtime/vars.rs b/uefi-test-runner/src/runtime/vars.rs index a66f7f0de..ebd57e08f 100644 --- a/uefi-test-runner/src/runtime/vars.rs +++ b/uefi-test-runner/src/runtime/vars.rs @@ -1,40 +1,46 @@ use log::info; -use uefi::guid; use uefi::prelude::*; use uefi::table::runtime::{VariableAttributes, VariableVendor}; +use uefi::{guid, runtime, CStr16, Error}; -fn test_variables(rt: &RuntimeServices) { - let name = cstr16!("UefiRsTestVar"); - let test_value = b"TestValue"; - let test_attrs = VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS; +/// Test variable name. +const NAME: &CStr16 = cstr16!("UefiRsTestVar"); + +/// Test variable vendor. +const VENDOR: &VariableVendor = &VariableVendor(guid!("9baf21cf-e187-497e-ae77-5bd8b0e09703")); + +/// Test variable value. +const VALUE: &[u8] = b"TestValue"; - // Arbitrary GUID generated for this test. - let vendor = VariableVendor(guid!("9baf21cf-e187-497e-ae77-5bd8b0e09703")); +/// Test variable attributes. +const ATTRS: VariableAttributes = + VariableAttributes::BOOTSERVICE_ACCESS.union(VariableAttributes::RUNTIME_ACCESS); +fn test_variables(rt: &RuntimeServices) { info!("Testing set_variable"); - rt.set_variable(name, &vendor, test_attrs, test_value) + rt.set_variable(NAME, VENDOR, ATTRS, VALUE) .expect("failed to set variable"); info!("Testing get_variable_size"); let size = rt - .get_variable_size(name, &vendor) + .get_variable_size(NAME, VENDOR) .expect("failed to get variable size"); - assert_eq!(size, test_value.len()); + assert_eq!(size, VALUE.len()); info!("Testing get_variable"); let mut buf = [0u8; 9]; let (data, attrs) = rt - .get_variable(name, &vendor, &mut buf) + .get_variable(NAME, VENDOR, &mut buf) .expect("failed to get variable"); - assert_eq!(data, test_value); - assert_eq!(attrs, test_attrs); + assert_eq!(data, VALUE); + assert_eq!(attrs, ATTRS); info!("Testing get_variable_boxed"); let (data, attrs) = rt - .get_variable_boxed(name, &vendor) + .get_variable_boxed(NAME, VENDOR) .expect("failed to get variable"); - assert_eq!(&*data, test_value); - assert_eq!(attrs, test_attrs); + assert_eq!(&*data, VALUE); + assert_eq!(attrs, ATTRS); info!("Testing variable_keys"); let variable_keys = rt.variable_keys().expect("failed to get variable keys"); @@ -45,35 +51,85 @@ fn test_variables(rt: &RuntimeServices) { info!("First variable: {}", key); } + // Test that the `runtime::variable_keys` iterator gives exactly the same + // list as the `RuntimeServices::variable_keys` function. + assert_eq!( + runtime::variable_keys() + .map(|k| k.unwrap()) + .collect::>(), + variable_keys + ); + info!("Testing delete_variable()"); - rt.delete_variable(name, &vendor) + rt.delete_variable(NAME, VENDOR) .expect("failed to delete variable"); assert_eq!( - rt.get_variable(name, &vendor, &mut buf) + rt.get_variable(NAME, VENDOR, &mut buf) .unwrap_err() .status(), Status::NOT_FOUND ); } -fn test_variable_info(rt: &RuntimeServices) { - info!( - "Storage for non-volatile boot-services variables: {:?}", - rt.query_variable_info( - VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::NON_VOLATILE - ) - .unwrap(), +/// Test the variable functions in `uefi::runtime`. +fn test_variables_freestanding() { + // Create the test variable. + runtime::set_variable(NAME, VENDOR, ATTRS, VALUE).expect("failed to set variable"); + + // Test `get_variable` with too small of a buffer. + let mut buf = [0u8; 0]; + assert_eq!( + runtime::get_variable(NAME, VENDOR, &mut buf).unwrap_err(), + Error::new(Status::BUFFER_TOO_SMALL, Some(9)) ); - info!( - "Storage for volatile runtime variables: {:?}", - rt.query_variable_info( - VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS - ) - .unwrap(), + + // Test `get_variable`. + let mut buf = [0u8; 9]; + let (data, attrs) = + runtime::get_variable(NAME, VENDOR, &mut buf).expect("failed to get variable"); + assert_eq!(data, VALUE); + assert_eq!(attrs, ATTRS); + + // Test `get_variable_boxed`. + let (data, attrs) = runtime::get_variable_boxed(NAME, VENDOR).expect("failed to get variable"); + assert_eq!(&*data, VALUE); + assert_eq!(attrs, ATTRS); + + // Test that the variable is present in the `variable_keys` iterator. + let find_by_key = || { + runtime::variable_keys().any(|k| { + let k = k.as_ref().unwrap(); + k.name().unwrap() == NAME && &k.vendor == VENDOR + }) + }; + assert!(find_by_key()); + + // Delete the variable and verify it can no longer be read. + runtime::delete_variable(NAME, VENDOR).expect("failed to delete variable"); + assert_eq!( + runtime::get_variable(NAME, VENDOR, &mut buf) + .unwrap_err() + .status(), + Status::NOT_FOUND ); + // Variable is no longer present in the `variable_keys` iterator. + assert!(!find_by_key()); +} + +fn test_variable_info(rt: &RuntimeServices) { + let attr = VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::NON_VOLATILE; + let info = rt.query_variable_info(attr).unwrap(); + info!("Storage for non-volatile boot-services variables: {info:?}"); + assert_eq!(info, runtime::query_variable_info(attr).unwrap()); + + let attr = VariableAttributes::BOOTSERVICE_ACCESS | VariableAttributes::RUNTIME_ACCESS; + let info = rt.query_variable_info(attr).unwrap(); + info!("Storage for volatile runtime variables: {info:?}"); + assert_eq!(info, runtime::query_variable_info(attr).unwrap()); } pub fn test(rt: &RuntimeServices) { test_variables(rt); test_variable_info(rt); + test_variables_freestanding(); } diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 8d997651c..32429126e 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -1,7 +1,62 @@ # uefi - [Unreleased] +# uefi - 0.31.0 (2024-08-21) + +See [Deprecating SystemTable/BootServices/RuntimeServices][funcmigrate] for +details of the new `system`/`boot`/`runtime` modules, and upcoming deprecations. + +## Added +- `uefi::system` is a new module that provides freestanding functions for + accessing fields of the global system table. +- `uefi::boot` is a new module that provides freestanding functions for + boot services using the global system table. +- `uefi::runtime` is a new module that provides freestanding functions for + runtime services using the global system table. +- `uefi::table::system_table_raw` is a new function to retrieve a raw pointer to + the global system table. +- Add standard derives for `ConfigTableEntry`. +- `PcrEvent`/`PcrEventInputs` impl `Align`, `Eq`, and `PartialEq`. +- Added `PcrEvent::new_in_box` and `PcrEventInputs::new_in_box`. +- `VariableKey` impls `Clone`, `Eq`, `PartialEq`, `Ord`, `PartialOrd`, and `Hash`. +- The traits `MemoryMap` and `MemoryMapMut` have been introduced together with + the implementations `MemoryMapRef`, `MemoryMapRefMut`, and `MemoryMapOwned`. + This comes with some changes. Read below. We recommend to directly use the + implementations instead of the traits. +- Added `LoadFile` and `LoadFile2` which abstracts over the `LOAD_FILE` and + `LOAD_FILE2` protocols. The UEFI test runner includes an integration test + that shows how Linux loaders can use this to implement the initrd loading + mechanism used in Linux. + +## Changed +- **Breaking:** `uefi::helpers::init` no longer takes an argument. +- The lifetime of the `SearchType` returned from + `BootServices::register_protocol_notify` is now tied to the protocol GUID. + The old `MemoryMap` was renamed to `MemoryMapOwned`. + - `pub fn memory_map(&self, mt: MemoryType) -> Result` now returns + a `MemoryMapOwned`. +- **Breaking:** `PcrEvent::new_in_buffer` and `PcrEventInputs::new_in_buffer` + now take an initialized buffer (`[u8`] instead of `[MaybeUninit]`), and if + the buffer is too small the required size is returned in the error data. +- **Breaking:** The type `MemoryMap` was renamed to `MemoryMapOwned`. `MemoryMap` + is now a trait. Read the [documentation](https://docs.rs/uefi/latest/uefi/) of the + `uefi > mem > memory_map` module to learn more. +- **Breaking:** Exports of Memory Map-related types from `uefi::table::boot` are + now removed. Use `uefi::mem::memory_map` instead. The patch you have to apply + to the `use` statements of your code might look as follows: + ```diff + < use uefi::table::boot::{BootServices, MemoryMap, MemoryMapMut, MemoryType}; + --- + > use uefi::mem::memory_map::{MemoryMap, MemoryMapMut, MemoryType}; + > use uefi::table::boot::BootServices; + ``` +- **Breaking:** Added a new `BootPolicy` type which breaks existing usages + of `LoadImageSource`. + +[funcmigrate]: ../docs/funcs_migration.md + # uefi - 0.30.0 (2024-08-02) + ## Changed - **Breaking:**: Fixed a bug in the impls of `TryFrom<&[u8]>` for `&DevicePathHeader`, `&DevicePathNode` and `&DevicePath` that could lead to diff --git a/uefi/Cargo.toml b/uefi/Cargo.toml index 597fb2a30..dc5e1be0f 100644 --- a/uefi/Cargo.toml +++ b/uefi/Cargo.toml @@ -1,8 +1,11 @@ [package] name = "uefi" -version = "0.30.0" +version = "0.31.0" readme = "README.md" -description = "Safe and easy-to-use wrapper for building UEFI apps." +description = """ +This crate makes it easy to develop Rust software that leverages safe, +convenient, and performant abstractions for UEFI functionality. +""" authors.workspace = true categories.workspace = true @@ -13,6 +16,7 @@ repository.workspace = true rust-version.workspace = true [features] +# KEEP this feature list in sync with doc in lib.rs! default = [ "log-debugcon" ] alloc = [] @@ -39,8 +43,8 @@ ptr_meta.workspace = true uguid.workspace = true cfg-if = "1.0.0" ucs2 = "0.3.3" -uefi-macros = "0.14.0" -uefi-raw = "0.6.0" +uefi-macros = "0.15.0" +uefi-raw = "0.7.0" qemu-exit = { version = "3.0.2", optional = true } [package.metadata.docs.rs] diff --git a/uefi/README.md b/uefi/README.md index 625a39363..962fad270 100644 --- a/uefi/README.md +++ b/uefi/README.md @@ -1,4 +1,9 @@ -# uefi-rs +# `uefi` + +Rusty wrapper for the [Unified Extensible Firmware Interface][UEFI]. + +This crate makes it easy to develop Rust software that leverages **safe**, +**convenient**, and **performant** abstractions for [UEFI] functionality. [![Crates.io](https://img.shields.io/crates/v/uefi)](https://crates.io/crates/uefi) [![Docs.rs](https://docs.rs/uefi/badge.svg)](https://docs.rs/uefi) @@ -6,6 +11,16 @@ ![Build status](https://github.com/rust-osdev/uefi-rs/workflows/Rust/badge.svg) ![Stars](https://img.shields.io/github/stars/rust-osdev/uefi-rs) +## Value-add and Use Cases + +`uefi` supports writing code for both pre- and post-exit boot services +epochs, but its true strength shines when you create UEFI images that heavily +interact with UEFI boot services. Still, you have the flexibility to just +integrate selected types and abstractions into your project, for example to +parse the UEFI memory map. + +_Note that for producing UEFI images, you also need to use a corresponding +`uefi` compiler target of Rust, such as `x86_64-unknown-uefi`._ For an introduction to the `uefi-rs` project and documentation, please refer to our main [README]. @@ -47,3 +62,6 @@ The code in this repository is licensed under the Mozilla Public License 2. This license allows you to use the crate in proprietary programs, but any modifications to the files must be open-sourced. The full text of the license is available in the [license file](LICENSE). + + +[UEFI]: https://en.wikipedia.org/wiki/Unified_Extensible_Firmware_Interface diff --git a/uefi/src/allocator.rs b/uefi/src/allocator.rs index d1ef3dc93..9518420cb 100644 --- a/uefi/src/allocator.rs +++ b/uefi/src/allocator.rs @@ -16,8 +16,9 @@ use core::ffi::c_void; use core::ptr; use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering}; +use crate::mem::memory_map::MemoryType; use crate::proto::loaded_image::LoadedImage; -use crate::table::boot::{BootServices, MemoryType}; +use crate::table::boot::BootServices; use crate::table::{Boot, SystemTable}; /// Reference to the system table, used to call the boot services pool memory diff --git a/uefi/src/boot.rs b/uefi/src/boot.rs new file mode 100644 index 000000000..5337e5ff8 --- /dev/null +++ b/uefi/src/boot.rs @@ -0,0 +1,1428 @@ +//! UEFI boot services. +//! +//! These functions will panic if called after exiting boot services. + +pub use crate::table::boot::{ + AllocateType, EventNotifyFn, LoadImageSource, OpenProtocolAttributes, OpenProtocolParams, + ProtocolSearchKey, SearchType, TimerTrigger, +}; +pub use uefi_raw::table::boot::{EventType, MemoryAttribute, MemoryDescriptor, MemoryType, Tpl}; + +use crate::data_types::PhysicalAddress; +use crate::mem::memory_map::{MemoryMapBackingMemory, MemoryMapKey, MemoryMapMeta, MemoryMapOwned}; +use crate::polyfill::maybe_uninit_slice_assume_init_ref; +use crate::proto::device_path::DevicePath; +#[cfg(doc)] +use crate::proto::device_path::LoadedImageDevicePath; +use crate::proto::loaded_image::LoadedImage; +use crate::proto::media::fs::SimpleFileSystem; +use crate::proto::{Protocol, ProtocolPointer}; +use crate::runtime::{self, ResetType}; +use crate::table::Revision; +use crate::util::opt_nonnull_to_ptr; +use crate::{table, Char16, Error, Event, Guid, Handle, Result, Status, StatusExt}; +use core::ffi::c_void; +use core::mem::MaybeUninit; +use core::ops::{Deref, DerefMut}; +use core::ptr::{self, NonNull}; +use core::sync::atomic::{AtomicPtr, Ordering}; +use core::{mem, slice}; +use uefi_raw::table::boot::InterfaceType; +#[cfg(feature = "alloc")] +use {alloc::vec::Vec, uefi::ResultExt}; + +/// Global image handle. This is only set by [`set_image_handle`], and it is +/// only read by [`image_handle`]. +static IMAGE_HANDLE: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +/// Get the [`Handle`] of the currently-executing image. +#[must_use] +pub fn image_handle() -> Handle { + let ptr = IMAGE_HANDLE.load(Ordering::Acquire); + // Safety: the image handle must be valid. We know it is, because it was set + // by `set_image_handle`, which has that same safety requirement. + unsafe { Handle::from_ptr(ptr) }.expect("set_image_handle has not been called") +} + +/// Update the global image [`Handle`]. +/// +/// This is called automatically in the `main` entry point as part of +/// [`uefi::entry`]. It should not be called at any other point in time, unless +/// the executable does not use [`uefi::entry`], in which case it should be +/// called once before calling other boot services functions. +/// +/// # Safety +/// +/// This function should only be called as described above, and the +/// `image_handle` must be a valid image [`Handle`]. The safety guarantees of +/// [`open_protocol_exclusive`] rely on the global image handle being correct. +pub unsafe fn set_image_handle(image_handle: Handle) { + IMAGE_HANDLE.store(image_handle.as_ptr(), Ordering::Release); +} + +fn boot_services_raw_panicking() -> NonNull { + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + NonNull::new(st.boot_services).expect("boot services are not active") +} + +/// Raises a task's priority level and returns a [`TplGuard`]. +/// +/// The effect of calling `raise_tpl` with a `Tpl` that is below the current +/// one (which, sadly, cannot be queried) is undefined by the UEFI spec, +/// which also warns against remaining at high `Tpl`s for a long time. +/// +/// This function returns an RAII guard that will automatically restore the +/// original `Tpl` when dropped. +/// +/// # Safety +/// +/// Raising a task's priority level can affect other running tasks and +/// critical processes run by UEFI. The highest priority level is the +/// most dangerous, since it disables interrupts. +#[must_use] +pub unsafe fn raise_tpl(tpl: Tpl) -> TplGuard { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + TplGuard { + old_tpl: (bt.raise_tpl)(tpl), + } +} + +/// Allocates memory pages from the system. +/// +/// UEFI OS loaders should allocate memory of the type `LoaderData`. +/// +/// # Errors +/// +/// * [`Status::OUT_OF_RESOURCES`]: allocation failed. +/// * [`Status::INVALID_PARAMETER`]: `mem_ty` is [`MemoryType::PERSISTENT_MEMORY`], +/// [`MemoryType::UNACCEPTED`], or in the range [`MemoryType::MAX`]`..=0x6fff_ffff`. +/// * [`Status::NOT_FOUND`]: the requested pages could not be found. +pub fn allocate_pages(ty: AllocateType, mem_ty: MemoryType, count: usize) -> Result> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let (ty, mut addr) = match ty { + AllocateType::AnyPages => (0, 0), + AllocateType::MaxAddress(addr) => (1, addr), + AllocateType::Address(addr) => (2, addr), + }; + let addr = + unsafe { (bt.allocate_pages)(ty, mem_ty, count, &mut addr) }.to_result_with_val(|| addr)?; + let ptr = addr as *mut u8; + Ok(NonNull::new(ptr).expect("allocate_pages must not return a null pointer if successful")) +} + +/// Frees memory pages allocated by [`allocate_pages`]. +/// +/// # Safety +/// +/// The caller must ensure that no references into the allocation remain, +/// and that the memory at the allocation is not used after it is freed. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: `ptr` was not allocated by [`allocate_pages`]. +/// * [`Status::INVALID_PARAMETER`]: `ptr` is not page aligned or is otherwise invalid. +pub unsafe fn free_pages(ptr: NonNull, count: usize) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let addr = ptr.as_ptr() as PhysicalAddress; + unsafe { (bt.free_pages)(addr, count) }.to_result() +} + +/// Allocates from a memory pool. The pointer will be 8-byte aligned. +/// +/// # Errors +/// +/// * [`Status::OUT_OF_RESOURCES`]: allocation failed. +/// * [`Status::INVALID_PARAMETER`]: `mem_ty` is [`MemoryType::PERSISTENT_MEMORY`], +/// [`MemoryType::UNACCEPTED`], or in the range [`MemoryType::MAX`]`..=0x6fff_ffff`. +pub fn allocate_pool(mem_ty: MemoryType, size: usize) -> Result> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut buffer = ptr::null_mut(); + let ptr = + unsafe { (bt.allocate_pool)(mem_ty, size, &mut buffer) }.to_result_with_val(|| buffer)?; + + Ok(NonNull::new(ptr).expect("allocate_pool must not return a null pointer if successful")) +} + +/// Frees memory allocated by [`allocate_pool`]. +/// +/// # Safety +/// +/// The caller must ensure that no references into the allocation remain, +/// and that the memory at the allocation is not used after it is freed. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `ptr` is invalid. +pub unsafe fn free_pool(ptr: NonNull) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + unsafe { (bt.free_pool)(ptr.as_ptr()) }.to_result() +} + +/// Stores the current UEFI memory map in an UEFI-heap allocated buffer +/// and returns a [`MemoryMapOwned`]. +/// +/// # Parameters +/// +/// - `mt`: The memory type for the backing memory on the UEFI heap. +/// Usually, this is [`MemoryType::LOADER_DATA`]. You can also use a +/// custom type. +/// +/// # Errors +/// +/// * [`Status::BUFFER_TOO_SMALL`] +/// * [`Status::INVALID_PARAMETER`] +pub fn memory_map(mt: MemoryType) -> Result { + let mut buffer = MemoryMapBackingMemory::new(mt)?; + + let meta = get_memory_map(buffer.as_mut_slice())?; + + Ok(MemoryMapOwned::from_initialized_mem(buffer, meta)) +} + +/// Calls the underlying `GetMemoryMap` function of UEFI. On success, +/// the buffer is mutated and contains the map. The map might be shorter +/// than the buffer, which is reflected by the return value. +pub(crate) fn get_memory_map(buf: &mut [u8]) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut map_size = buf.len(); + let map_buffer = buf.as_mut_ptr().cast::(); + let mut map_key = MemoryMapKey(0); + let mut desc_size = 0; + let mut desc_version = 0; + + assert_eq!( + (map_buffer as usize) % mem::align_of::(), + 0, + "Memory map buffers must be aligned like a MemoryDescriptor" + ); + + unsafe { + (bt.get_memory_map)( + &mut map_size, + map_buffer, + &mut map_key.0, + &mut desc_size, + &mut desc_version, + ) + } + .to_result_with_val(|| MemoryMapMeta { + map_size, + desc_size, + map_key, + desc_version, + }) +} + +/// Creates an event. +/// +/// This function creates a new event of the specified type and returns it. +/// +/// Events are created in a "waiting" state, and may switch to a "signaled" +/// state. If the event type has flag `NotifySignal` set, this will result in +/// a callback for the event being immediately enqueued at the `notify_tpl` +/// priority level. If the event type has flag `NotifyWait`, the notification +/// will be delivered next time `wait_for_event` or `check_event` is called. +/// In both cases, a `notify_fn` callback must be specified. +/// +/// # Safety +/// +/// This function is unsafe because callbacks must handle exit from boot +/// services correctly. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: an invalid combination of parameters was provided. +/// * [`Status::OUT_OF_RESOURCES`]: the event could not be allocated. +pub unsafe fn create_event( + event_ty: EventType, + notify_tpl: Tpl, + notify_fn: Option, + notify_ctx: Option>, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut event = ptr::null_mut(); + + // Safety: the argument types of the function pointers are defined + // differently, but are compatible and can be safely transmuted. + let notify_fn: Option = mem::transmute(notify_fn); + + let notify_ctx = opt_nonnull_to_ptr(notify_ctx); + + // Now we're ready to call UEFI + (bt.create_event)(event_ty, notify_tpl, notify_fn, notify_ctx, &mut event).to_result_with_val( + // OK to unwrap: event is non-null for Status::SUCCESS. + || Event::from_ptr(event).unwrap(), + ) +} + +/// Creates an event in an event group. +/// +/// The event's notification function, context, and task priority are specified +/// by `notify_fn`, `notify_ctx`, and `notify_tpl`, respectively. The event will +/// be added to the group of events identified by `event_group`. +/// +/// If no group is specified by `event_group`, this function behaves as if the +/// same parameters had been passed to `create_event()`. +/// +/// Event groups are collections of events identified by a shared GUID where, +/// when one member event is signaled, all other events are signaled and their +/// individual notification actions are taken. All events are guaranteed to be +/// signaled before the first notification action is taken. All notification +/// functions will be executed in the order specified by their `Tpl`. +/// +/// An event can only be part of a single event group. An event may be removed +/// from an event group by calling [`close_event`]. +/// +/// The [`EventType`] of an event uses the same values as `create_event()`, except that +/// `EventType::SIGNAL_EXIT_BOOT_SERVICES` and `EventType::SIGNAL_VIRTUAL_ADDRESS_CHANGE` +/// are not valid. +/// +/// For events of type `NOTIFY_SIGNAL` or `NOTIFY_WAIT`, `notify_fn` must be +/// `Some` and `notify_tpl` must be a valid task priority level. For other event +/// types these parameters are ignored. +/// +/// More than one event of type `EventType::TIMER` may be part of a single event +/// group. However, there is no mechanism for determining which of the timers +/// was signaled. +/// +/// This operation is only supported starting with UEFI 2.0; earlier versions +/// will fail with [`Status::UNSUPPORTED`]. +/// +/// # Safety +/// +/// The caller must ensure they are passing a valid `Guid` as `event_group`, if applicable. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: an invalid combination of parameters was provided. +/// * [`Status::OUT_OF_RESOURCES`]: the event could not be allocated. +pub unsafe fn create_event_ex( + event_type: EventType, + notify_tpl: Tpl, + notify_fn: Option, + notify_ctx: Option>, + event_group: Option>, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + if bt.header.revision < Revision::EFI_2_00 { + return Err(Status::UNSUPPORTED.into()); + } + + let mut event = ptr::null_mut(); + + // Safety: the argument types of the function pointers are defined + // differently, but are compatible and can be safely transmuted. + let notify_fn: Option = mem::transmute(notify_fn); + + (bt.create_event_ex)( + event_type, + notify_tpl, + notify_fn, + opt_nonnull_to_ptr(notify_ctx), + opt_nonnull_to_ptr(event_group), + &mut event, + ) + .to_result_with_val( + // OK to unwrap: event is non-null for Status::SUCCESS. + || Event::from_ptr(event).unwrap(), + ) +} + +/// Checks to see if an event is signaled, without blocking execution to wait for it. +/// +/// Returns `Ok(true)` if the event is in the signaled state or `Ok(false)` +/// if the event is not in the signaled state. +/// +/// # Errors +/// +/// Note: Instead of returning [`Status::NOT_READY`] as listed in the UEFI +/// Specification, this function will return `Ok(false)`. +/// +/// * [`Status::INVALID_PARAMETER`]: `event` is of type [`NOTIFY_SIGNAL`]. +/// +/// [`NOTIFY_SIGNAL`]: EventType::NOTIFY_SIGNAL +pub fn check_event(event: Event) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let status = unsafe { (bt.check_event)(event.as_ptr()) }; + match status { + Status::SUCCESS => Ok(true), + Status::NOT_READY => Ok(false), + _ => Err(status.into()), + } +} + +/// Removes `event` from any event group to which it belongs and closes it. +/// +/// If `event` was registered with [`register_protocol_notify`], then the +/// corresponding registration will be removed. Calling this function within the +/// corresponding notify function is allowed. +/// +/// # Errors +/// +/// The specification does not list any errors, however implementations are +/// allowed to return an error if needed. +pub fn close_event(event: Event) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + unsafe { (bt.close_event)(event.as_ptr()) }.to_result() +} + +/// Sets the trigger for an event of type [`TIMER`]. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `event` is not valid. +/// +/// [`TIMER`]: EventType::TIMER +pub fn set_timer(event: &Event, trigger_time: TimerTrigger) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let (ty, time) = match trigger_time { + TimerTrigger::Cancel => (0, 0), + TimerTrigger::Periodic(hundreds_ns) => (1, hundreds_ns), + TimerTrigger::Relative(hundreds_ns) => (2, hundreds_ns), + }; + unsafe { (bt.set_timer)(event.as_ptr(), ty, time) }.to_result() +} + +/// Stops execution until an event is signaled. +/// +/// This function must be called at priority level [`Tpl::APPLICATION`]. +/// +/// The input [`Event`] slice is repeatedly iterated from first to last until +/// an event is signaled or an error is detected. The following checks are +/// performed on each event: +/// +/// * If an event is of type [`NOTIFY_SIGNAL`], then a +/// [`Status::INVALID_PARAMETER`] error is returned with the index of the +/// event that caused the failure. +/// * If an event is in the signaled state, the signaled state is cleared +/// and the index of the event that was signaled is returned. +/// * If an event is not in the signaled state but does have a notification +/// function, the notification function is queued at the event's +/// notification task priority level. If the execution of the event's +/// notification function causes the event to be signaled, then the +/// signaled state is cleared and the index of the event that was signaled +/// is returned. +/// +/// To wait for a specified time, a timer event must be included in `events`. +/// +/// To check if an event is signaled without waiting, an already signaled +/// event can be used as the last event in the slice being checked, or the +/// [`check_event`] interface may be used. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `events` is empty, or one of the events of +/// of type [`NOTIFY_SIGNAL`]. +/// * [`Status::UNSUPPORTED`]: the current TPL is not [`Tpl::APPLICATION`]. +/// +/// [`NOTIFY_SIGNAL`]: EventType::NOTIFY_SIGNAL +pub fn wait_for_event(events: &mut [Event]) -> Result> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let number_of_events = events.len(); + let events: *mut uefi_raw::Event = events.as_mut_ptr().cast(); + + let mut index = 0; + unsafe { (bt.wait_for_event)(number_of_events, events, &mut index) }.to_result_with( + || index, + |s| { + if s == Status::INVALID_PARAMETER { + Some(index) + } else { + None + } + }, + ) +} + +/// Connect one or more drivers to a controller. +/// +/// Usually one disconnects and then reconnects certain drivers +/// to make them rescan some state that changed, e.g. reconnecting +/// a block handle after your app modified disk partitions. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: there are no driver-binding protocol instances +/// present in the system, or no drivers are connected to `controller`. +/// * [`Status::SECURITY_VIOLATION`]: the caller does not have permission to +/// start drivers associated with `controller`. +pub fn connect_controller( + controller: Handle, + driver_image: Option, + remaining_device_path: Option<&DevicePath>, + recursive: bool, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + unsafe { + (bt.connect_controller)( + controller.as_ptr(), + Handle::opt_to_ptr(driver_image), + remaining_device_path + .map(|dp| dp.as_ffi_ptr()) + .unwrap_or(ptr::null()) + .cast(), + recursive, + ) + } + .to_result_with_err(|_| ()) +} + +/// Disconnect one or more drivers from a controller. +/// +/// See also [`connect_controller`]. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `driver_image` is set but does not manage +/// `controller`, or does not support the driver binding protocol, or one of +/// the handles is invalid. +/// * [`Status::OUT_OF_RESOURCES`]: not enough resources available to disconnect +/// drivers. +/// * [`Status::DEVICE_ERROR`]: the controller could not be disconnected due to +/// a device error. +pub fn disconnect_controller( + controller: Handle, + driver_image: Option, + child: Option, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + unsafe { + (bt.disconnect_controller)( + controller.as_ptr(), + Handle::opt_to_ptr(driver_image), + Handle::opt_to_ptr(child), + ) + } + .to_result_with_err(|_| ()) +} + +/// Installs a protocol interface on a device handle. +/// +/// When a protocol interface is installed, firmware will call all functions +/// that have registered to wait for that interface to be installed. +/// +/// If `handle` is `None`, a new handle will be created and returned. +/// +/// # Safety +/// +/// The caller is responsible for ensuring that they pass a valid `Guid` for `protocol`. +/// +/// # Errors +/// +/// * [`Status::OUT_OF_RESOURCES`]: failed to allocate a new handle. +/// * [`Status::INVALID_PARAMETER`]: this protocol is already installed on the handle. +pub unsafe fn install_protocol_interface( + handle: Option, + protocol: &Guid, + interface: *const c_void, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut handle = Handle::opt_to_ptr(handle); + ((bt.install_protocol_interface)( + &mut handle, + protocol, + InterfaceType::NATIVE_INTERFACE, + interface, + )) + .to_result_with_val(|| Handle::from_ptr(handle).unwrap()) +} + +/// Reinstalls a protocol interface on a device handle. `old_interface` is replaced with `new_interface`. +/// These interfaces may be the same, in which case the registered protocol notifications occur for the handle +/// without replacing the interface. +/// +/// As with `install_protocol_interface`, any process that has registered to wait for the installation of +/// the interface is notified. +/// +/// # Safety +/// +/// The caller is responsible for ensuring that there are no references to the `old_interface` that is being +/// removed. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: the old interface was not found on the handle. +/// * [`Status::ACCESS_DENIED`]: the old interface is still in use and cannot be uninstalled. +pub unsafe fn reinstall_protocol_interface( + handle: Handle, + protocol: &Guid, + old_interface: *const c_void, + new_interface: *const c_void, +) -> Result<()> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + (bt.reinstall_protocol_interface)(handle.as_ptr(), protocol, old_interface, new_interface) + .to_result() +} + +/// Removes a protocol interface from a device handle. +/// +/// # Safety +/// +/// The caller is responsible for ensuring that there are no references to a protocol interface +/// that has been removed. Some protocols may not be able to be removed as there is no information +/// available regarding the references. This includes Console I/O, Block I/O, Disk I/o, and handles +/// to device protocols. +/// +/// The caller is responsible for ensuring that they pass a valid `Guid` for `protocol`. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: the interface was not found on the handle. +/// * [`Status::ACCESS_DENIED`]: the interface is still in use and cannot be uninstalled. +pub unsafe fn uninstall_protocol_interface( + handle: Handle, + protocol: &Guid, + interface: *const c_void, +) -> Result<()> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + (bt.uninstall_protocol_interface)(handle.as_ptr(), protocol, interface).to_result() +} + +/// Registers `event` to be signaled whenever a protocol interface is registered for +/// `protocol` by [`install_protocol_interface`] or [`reinstall_protocol_interface`]. +/// +/// If successful, a [`SearchType::ByRegisterNotify`] is returned. This can be +/// used with [`locate_handle`] or [`locate_handle_buffer`] to identify the +/// newly (re)installed handles that support `protocol`. +/// +/// Events can be unregistered from protocol interface notification by calling [`close_event`]. +/// +/// # Errors +/// +/// * [`Status::OUT_OF_RESOURCES`]: the event could not be allocated. +pub fn register_protocol_notify( + protocol: &'static Guid, + event: &Event, +) -> Result> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut key = ptr::null(); + unsafe { (bt.register_protocol_notify)(protocol, event.as_ptr(), &mut key) }.to_result_with_val( + || { + // OK to unwrap: key is non-null for Status::SUCCESS. + SearchType::ByRegisterNotify(ProtocolSearchKey(NonNull::new(key.cast_mut()).unwrap())) + }, + ) +} + +/// Get the list of protocol interface [`Guids`][Guid] that are installed +/// on a [`Handle`]. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `handle` is invalid. +/// * [`Status::OUT_OF_RESOURCES`]: out of memory. +pub fn protocols_per_handle(handle: Handle) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut protocols = ptr::null_mut(); + let mut count = 0; + + unsafe { (bt.protocols_per_handle)(handle.as_ptr(), &mut protocols, &mut count) } + .to_result_with_val(|| ProtocolsPerHandle { + count, + protocols: NonNull::new(protocols) + .expect("protocols_per_handle must not return a null pointer"), + }) +} + +/// Locates the handle of a device on the device path that supports the specified protocol. +/// +/// The `device_path` is updated to point at the remaining part of the [`DevicePath`] after +/// the part that matched the protocol. For example, it can be used with a device path +/// that contains a file path to strip off the file system portion of the device path, +/// leaving the file path and handle to the file system driver needed to access the file. +/// +/// If the first node of `device_path` matches the protocol, the `device_path` +/// is advanced to the device path terminator node. If `device_path` is a +/// multi-instance device path, the function will operate on the first instance. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: no matching handles. +pub fn locate_device_path( + device_path: &mut &DevicePath, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut handle = ptr::null_mut(); + let mut device_path_ptr: *const uefi_raw::protocol::device_path::DevicePathProtocol = + device_path.as_ffi_ptr().cast(); + unsafe { + (bt.locate_device_path)(&P::GUID, &mut device_path_ptr, &mut handle).to_result_with_val( + || { + *device_path = DevicePath::from_ffi_ptr(device_path_ptr.cast()); + // OK to unwrap: handle is non-null for Status::SUCCESS. + Handle::from_ptr(handle).unwrap() + }, + ) + } +} + +/// Enumerates all handles installed on the system which match a certain query. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: no matching handles found. +/// * [`Status::BUFFER_TOO_SMALL`]: the buffer is not large enough. The required +/// size (in number of handles, not bytes) will be returned in the error data. +pub fn locate_handle<'buf>( + search_ty: SearchType, + buffer: &'buf mut [MaybeUninit], +) -> Result<&'buf [Handle], Option> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + // Obtain the needed data from the parameters. + let (ty, guid, key) = match search_ty { + SearchType::AllHandles => (0, ptr::null(), ptr::null()), + SearchType::ByRegisterNotify(registration) => { + (1, ptr::null(), registration.0.as_ptr().cast_const()) + } + SearchType::ByProtocol(guid) => (2, guid as *const Guid, ptr::null()), + }; + + let mut buffer_size = buffer.len() * mem::size_of::(); + let status = + unsafe { (bt.locate_handle)(ty, guid, key, &mut buffer_size, buffer.as_mut_ptr().cast()) }; + + let num_handles = buffer_size / mem::size_of::(); + + match status { + Status::SUCCESS => { + let buffer = &buffer[..num_handles]; + // SAFETY: the entries up to `num_handles` have been initialized. + let handles = unsafe { maybe_uninit_slice_assume_init_ref(buffer) }; + Ok(handles) + } + Status::BUFFER_TOO_SMALL => Err(Error::new(status, Some(num_handles))), + _ => Err(Error::new(status, None)), + } +} + +/// Returns an array of handles that support the requested protocol in a +/// pool-allocated buffer. +/// +/// See [`SearchType`] for details of the available search operations. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: no matching handles. +/// * [`Status::OUT_OF_RESOURCES`]: out of memory. +pub fn locate_handle_buffer(search_ty: SearchType) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let (ty, guid, key) = match search_ty { + SearchType::AllHandles => (0, ptr::null(), ptr::null()), + SearchType::ByRegisterNotify(registration) => { + (1, ptr::null(), registration.0.as_ptr().cast_const()) + } + SearchType::ByProtocol(guid) => (2, guid as *const _, ptr::null()), + }; + + let mut num_handles: usize = 0; + let mut buffer: *mut uefi_raw::Handle = ptr::null_mut(); + unsafe { (bt.locate_handle_buffer)(ty, guid, key, &mut num_handles, &mut buffer) } + .to_result_with_val(|| HandleBuffer { + count: num_handles, + buffer: NonNull::new(buffer.cast()) + .expect("locate_handle_buffer must not return a null pointer"), + }) +} + +/// Returns all the handles implementing a certain protocol. +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: no matching handles. +#[cfg(feature = "alloc")] +pub fn find_handles() -> Result> { + // Search by protocol. + let search_type = SearchType::from_proto::

(); + + // Determine how much we need to allocate. + let num_handles = match locate_handle(search_type, &mut []) { + Err(err) => { + if err.status() == Status::BUFFER_TOO_SMALL { + err.data().expect("error data is missing") + } else { + return Err(err.to_err_without_payload()); + } + } + // This should never happen: if no handles match the search then a + // `NOT_FOUND` error should be returned. + Ok(_) => panic!("locate_handle should not return success with empty buffer"), + }; + + // Allocate a large enough buffer without pointless initialization. + let mut handles = Vec::with_capacity(num_handles); + + // Perform the search. + let num_handles = locate_handle(search_type, handles.spare_capacity_mut()) + .discard_errdata()? + .len(); + + // Mark the returned number of elements as initialized. + unsafe { + handles.set_len(num_handles); + } + + // Emit output, with warnings + Ok(handles) +} + +/// Find an arbitrary handle that supports a particular [`Protocol`]. Returns +/// [`NOT_FOUND`] if no handles support the protocol. +/// +/// This method is a convenient wrapper around [`locate_handle_buffer`] for +/// getting just one handle. This is useful when you don't care which handle the +/// protocol is opened on. For example, [`DevicePathToText`] isn't tied to a +/// particular device, so only a single handle is expected to exist. +/// +/// [`NOT_FOUND`]: Status::NOT_FOUND +/// [`DevicePathToText`]: uefi::proto::device_path::text::DevicePathToText +/// +/// # Example +/// +/// ``` +/// use uefi::proto::device_path::text::DevicePathToText; +/// use uefi::{boot, Handle}; +/// # use uefi::Result; +/// +/// # fn get_fake_val() -> T { todo!() } +/// # fn test() -> Result { +/// # let image_handle: Handle = get_fake_val(); +/// let handle = boot::get_handle_for_protocol::()?; +/// let device_path_to_text = boot::open_protocol_exclusive::(handle)?; +/// # Ok(()) +/// # } +/// ``` +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: no matching handle. +/// * [`Status::OUT_OF_RESOURCES`]: out of memory. +pub fn get_handle_for_protocol() -> Result { + locate_handle_buffer(SearchType::ByProtocol(&P::GUID))? + .first() + .cloned() + .ok_or_else(|| Status::NOT_FOUND.into()) +} + +/// Opens a protocol interface for a handle. +/// +/// See also [`open_protocol_exclusive`], which provides a safe subset of this +/// functionality. +/// +/// This function attempts to get the protocol implementation of a handle, based +/// on the [protocol GUID]. +/// +/// See [`OpenProtocolParams`] and [`OpenProtocolAttributes`] for details of the +/// input parameters. +/// +/// If successful, a [`ScopedProtocol`] is returned that will automatically +/// close the protocol interface when dropped. +/// +/// [protocol GUID]: uefi::data_types::Identify::GUID +/// +/// # Safety +/// +/// This function is unsafe because it can be used to open a protocol in ways +/// that don't get tracked by the UEFI implementation. This could allow the +/// protocol to be removed from a handle, or for the handle to be deleted +/// entirely, while a reference to the protocol is still active. The caller is +/// responsible for ensuring that the handle and protocol remain valid until the +/// `ScopedProtocol` is dropped. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: an invalid combination of `params` and +/// `attributes` was provided. +/// * [`Status::UNSUPPORTED`]: the handle does not support the protocol. +/// * [`Status::ACCESS_DENIED`] or [`Status::ALREADY_STARTED`]: the protocol is +/// already open in a way that is incompatible with the new request. +pub unsafe fn open_protocol( + params: OpenProtocolParams, + attributes: OpenProtocolAttributes, +) -> Result> { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut interface = ptr::null_mut(); + (bt.open_protocol)( + params.handle.as_ptr(), + &P::GUID, + &mut interface, + params.agent.as_ptr(), + Handle::opt_to_ptr(params.controller), + attributes as u32, + ) + .to_result_with_val(|| ScopedProtocol { + interface: NonNull::new(P::mut_ptr_from_ffi(interface)), + open_params: params, + }) +} + +/// Opens a protocol interface for a handle in exclusive mode. +/// +/// If successful, a [`ScopedProtocol`] is returned that will automatically +/// close the protocol interface when dropped. +/// +/// # Errors +/// +/// * [`Status::UNSUPPORTED`]: the handle does not support the protocol. +/// * [`Status::ACCESS_DENIED`]: the protocol is already open in a way that is +/// incompatible with the new request. +pub fn open_protocol_exclusive( + handle: Handle, +) -> Result> { + // Safety: opening in exclusive mode with the correct agent + // handle set ensures that the protocol cannot be modified or + // removed while it is open, so this usage is safe. + unsafe { + open_protocol::

( + OpenProtocolParams { + handle, + agent: image_handle(), + controller: None, + }, + OpenProtocolAttributes::Exclusive, + ) + } +} + +/// Tests whether a handle supports a protocol. +/// +/// Returns `Ok(true)` if the handle supports the protocol, `Ok(false)` if not. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: one of the handles in `params` is invalid. +pub fn test_protocol(params: OpenProtocolParams) -> Result { + const TEST_PROTOCOL: u32 = 0x04; + + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let mut interface = ptr::null_mut(); + let status = unsafe { + (bt.open_protocol)( + params.handle.as_ptr(), + &P::GUID, + &mut interface, + params.agent.as_ptr(), + Handle::opt_to_ptr(params.controller), + TEST_PROTOCOL, + ) + }; + + match status { + Status::SUCCESS => Ok(true), + Status::UNSUPPORTED => Ok(false), + _ => Err(Error::from(status)), + } +} + +/// Loads a UEFI image into memory and return a [`Handle`] to the image. +/// +/// There are two ways to load the image: by copying raw image data +/// from a source buffer, or by loading the image via the +/// [`SimpleFileSystem`] protocol. See [`LoadImageSource`] for more +/// details of the `source` parameter. +/// +/// The `parent_image_handle` is used to initialize the +/// `parent_handle` field of the [`LoadedImage`] protocol for the +/// image. +/// +/// If the image is successfully loaded, a [`Handle`] supporting the +/// [`LoadedImage`] and [`LoadedImageDevicePath`] protocols is returned. The +/// image can be started with [`start_image`] and unloaded with +/// [`unload_image`]. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `source` contains an invalid value. +/// * [`Status::UNSUPPORTED`]: the image type is not supported. +/// * [`Status::OUT_OF_RESOURCES`]: insufficient resources to load the image. +/// * [`Status::LOAD_ERROR`]: the image is invalid. +/// * [`Status::DEVICE_ERROR`]: failed to load image due to a read error. +/// * [`Status::ACCESS_DENIED`]: failed to load image due to a security policy. +/// * [`Status::SECURITY_VIOLATION`]: a security policy specifies that the image +/// should not be started. +pub fn load_image(parent_image_handle: Handle, source: LoadImageSource) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let (boot_policy, device_path, source_buffer, source_size) = source.to_ffi_params(); + + let mut image_handle = ptr::null_mut(); + unsafe { + (bt.load_image)( + boot_policy.into(), + parent_image_handle.as_ptr(), + device_path.cast(), + source_buffer, + source_size, + &mut image_handle, + ) + .to_result_with_val( + // OK to unwrap: image handle is non-null for Status::SUCCESS. + || Handle::from_ptr(image_handle).unwrap(), + ) + } +} + +/// Unloads a UEFI image. +/// +/// # Errors +/// +/// * [`Status::UNSUPPORTED`]: the image has been started, and does not support unload. +/// * [`Status::INVALID_PARAMETER`]: `image_handle` is not valid. +pub fn unload_image(image_handle: Handle) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + unsafe { (bt.unload_image)(image_handle.as_ptr()) }.to_result() +} + +/// Transfers control to a loaded image's entry point. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `image_handle` is not valid, or the image +/// has already been initialized with `start_image`. +/// * [`Status::SECURITY_VIOLATION`]: a security policy specifies that the image +/// should not be started. +pub fn start_image(image_handle: Handle) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + // TODO: implement returning exit data to the caller. + let mut exit_data_size: usize = 0; + let mut exit_data: *mut u16 = ptr::null_mut(); + + unsafe { + (bt.start_image)(image_handle.as_ptr(), &mut exit_data_size, &mut exit_data).to_result() + } +} + +/// Exits the UEFI application and returns control to the UEFI component +/// that started the UEFI application. +/// +/// # Safety +/// +/// The caller must ensure that resources owned by the application are properly +/// cleaned up. +/// +/// Note that event callbacks installed by the application are not automatically +/// uninstalled. If such a callback is invoked after exiting the application, +/// the function's code may no longer be loaded in memory, leading to a crash or +/// other unexpected behavior. +pub unsafe fn exit( + image_handle: Handle, + exit_status: Status, + exit_data_size: usize, + exit_data: *mut Char16, +) -> ! { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + (bt.exit)( + image_handle.as_ptr(), + exit_status, + exit_data_size, + exit_data.cast(), + ) +} + +/// Get the current memory map and exit boot services. +unsafe fn get_memory_map_and_exit_boot_services(buf: &mut [u8]) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + // Get the memory map. + let memory_map = get_memory_map(buf)?; + + // Try to exit boot services using the memory map key. Note that after + // the first call to `exit_boot_services`, there are restrictions on + // what boot services functions can be called. In UEFI 2.8 and earlier, + // only `get_memory_map` and `exit_boot_services` are allowed. Starting + // in UEFI 2.9 other memory allocation functions may also be called. + (bt.exit_boot_services)(image_handle().as_ptr(), memory_map.map_key.0) + .to_result_with_val(|| memory_map) +} + +/// Exit UEFI boot services. +/// +/// After this function completes, UEFI hands over control of the hardware +/// to the executing OS loader, which implies that the UEFI boot services +/// are shut down and cannot be used anymore. Only UEFI configuration tables +/// and run-time services can be used. +/// +/// The memory map at the time of exiting boot services returned. The map is +/// backed by a pool allocation of the given `memory_type`. Since the boot +/// services function to free that memory is no longer available after calling +/// `exit_boot_services`, the allocation will not be freed on drop. +/// +/// Note that once the boot services are exited, associated loggers and +/// allocators can't use the boot services anymore. For the corresponding +/// abstractions provided by this crate (see the [`helpers`] module), +/// invoking this function will automatically disable them. If the +/// `global_allocator` feature is enabled, attempting to use the allocator +/// after exiting boot services will panic. +/// +/// # Safety +/// +/// The caller is responsible for ensuring that no references to +/// boot-services data remain. A non-exhaustive list of resources to check: +/// +/// * All protocols will be invalid after exiting boot services. This +/// includes the [`Output`] protocols attached to stdout/stderr. The +/// caller must ensure that no protocol references remain. +/// * The pool allocator is not usable after exiting boot services. Types +/// such as [`PoolString`] which call [`free_pool`] on drop +/// must be cleaned up before calling `exit_boot_services`, or leaked to +/// avoid drop ever being called. +/// * All data in the memory map marked as +/// [`MemoryType::BOOT_SERVICES_CODE`] and +/// [`MemoryType::BOOT_SERVICES_DATA`] will become free memory. +/// +/// # Errors +/// +/// This function will fail if it is unable to allocate memory for +/// the memory map, if it fails to retrieve the memory map, or if +/// exiting boot services fails (with up to one retry). +/// +/// All errors are treated as unrecoverable because the system is +/// now in an undefined state. Rather than returning control to the +/// caller, the system will be reset. +/// +/// [`helpers`]: crate::helpers +/// [`Output`]: crate::proto::console::text::Output +/// [`PoolString`]: crate::proto::device_path::text::PoolString +#[must_use] +pub unsafe fn exit_boot_services(memory_type: MemoryType) -> MemoryMapOwned { + crate::helpers::exit(); + + let mut buf = MemoryMapBackingMemory::new(memory_type).expect("Failed to allocate memory"); + + // Calling `exit_boot_services` can fail if the memory map key is not + // current. Retry a second time if that occurs. This matches the + // behavior of the Linux kernel: + // https://github.com/torvalds/linux/blob/e544a0743/drivers/firmware/efi/libstub/efi-stub-helper.c#L375 + let mut status = Status::ABORTED; + for _ in 0..2 { + match unsafe { get_memory_map_and_exit_boot_services(buf.as_mut_slice()) } { + Ok(memory_map) => { + return MemoryMapOwned::from_initialized_mem(buf, memory_map); + } + Err(err) => { + log::error!("Error retrieving the memory map for exiting the boot services"); + status = err.status() + } + } + } + + // Failed to exit boot services. + log::warn!("Resetting the machine"); + runtime::reset(ResetType::COLD, status, None); +} + +/// Adds, updates, or removes a configuration table entry +/// from the EFI System Table. +/// +/// # Safety +/// +/// When installing or updating a configuration table, the data pointed to by +/// `table_ptr` must be a pool allocation of type +/// [`RUNTIME_SERVICES_DATA`]. Once this table has been installed, the caller +/// should not modify or free the data. +/// +/// [`RUNTIME_SERVICES_DATA`]: MemoryType::RUNTIME_SERVICES_DATA +/// +/// # Errors +/// +/// * [`Status::NOT_FOUND`]: tried to delete a nonexistent entry. +/// * [`Status::OUT_OF_RESOURCES`]: out of memory. +pub unsafe fn install_configuration_table( + guid_entry: &'static Guid, + table_ptr: *const c_void, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + (bt.install_configuration_table)(guid_entry, table_ptr).to_result() +} + +/// Sets the watchdog timer. +/// +/// UEFI will start a 5-minute countdown after an UEFI image is loaded. The +/// image must either successfully load an OS and exit boot services in that +/// time, or disable the watchdog. +/// +/// Otherwise, the firmware will log the event using the provided numeric +/// code and data, then reset the system. +/// +/// This function allows you to change the watchdog timer's timeout to a +/// certain amount of seconds or to disable the watchdog entirely. It also +/// allows you to change what will be logged when the timer expires. +/// +/// The watchdog codes from 0 to 0xffff (65535) are reserved for internal +/// firmware use. Higher values can be used freely by applications. +/// +/// If provided, the watchdog data must be a null-terminated string optionally +/// followed by other binary data. +/// +/// # Errors +/// +/// * [`Status::INVALID_PARAMETER`]: `watchdog_code` is invalid. +/// * [`Status::UNSUPPORTED`]: the system does not have a watchdog timer. +/// * [`Status::DEVICE_ERROR`]: the watchdog timer could not be set due to a +/// hardware error. +pub fn set_watchdog_timer( + timeout_in_seconds: usize, + watchdog_code: u64, + data: Option<&mut [u16]>, +) -> Result { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let (data_len, data) = data + .map(|d| { + assert!( + d.contains(&0), + "Watchdog data must start with a null-terminated string" + ); + (d.len(), d.as_mut_ptr()) + }) + .unwrap_or((0, ptr::null_mut())); + + unsafe { (bt.set_watchdog_timer)(timeout_in_seconds, watchdog_code, data_len, data) } + .to_result() +} + +/// Stalls execution for the given number of microseconds. +pub fn stall(microseconds: usize) { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + unsafe { + // No error conditions are defined in the spec for this function, so + // ignore the status. + let _ = (bt.stall)(microseconds); + } +} + +/// Retrieves a [`SimpleFileSystem`] protocol associated with the device the given +/// image was loaded from. +/// +/// # Errors +/// +/// This function can return errors from [`open_protocol_exclusive`] and +/// [`locate_device_path`]. See those functions for more details. +/// +/// * [`Status::INVALID_PARAMETER`] +/// * [`Status::UNSUPPORTED`] +/// * [`Status::ACCESS_DENIED`] +/// * [`Status::ALREADY_STARTED`] +/// * [`Status::NOT_FOUND`] +pub fn get_image_file_system(image_handle: Handle) -> Result> { + let loaded_image = open_protocol_exclusive::(image_handle)?; + + let device_handle = loaded_image + .device() + .ok_or(Error::new(Status::UNSUPPORTED, ()))?; + let device_path = open_protocol_exclusive::(device_handle)?; + + let device_handle = locate_device_path::(&mut &*device_path)?; + + open_protocol_exclusive(device_handle) +} + +/// Protocol interface [`Guids`][Guid] that are installed on a [`Handle`] as +/// returned by [`protocols_per_handle`]. +#[derive(Debug)] +pub struct ProtocolsPerHandle { + protocols: NonNull<*const Guid>, + count: usize, +} + +impl Drop for ProtocolsPerHandle { + fn drop(&mut self) { + let _ = unsafe { free_pool(self.protocols.cast::()) }; + } +} + +impl Deref for ProtocolsPerHandle { + type Target = [&'static Guid]; + + fn deref(&self) -> &Self::Target { + let ptr: *const &'static Guid = self.protocols.as_ptr().cast(); + + // SAFETY: + // + // * The firmware is assumed to provide a correctly-aligned pointer and + // array length. + // * The firmware is assumed to provide valid GUID pointers. + // * Protocol GUIDs should be constants or statics, so a 'static + // lifetime (of the individual pointers, not the overall slice) can be + // assumed. + unsafe { slice::from_raw_parts(ptr, self.count) } + } +} + +/// A buffer returned by [`locate_handle_buffer`] that contains an array of +/// [`Handle`]s that support the requested protocol. +#[derive(Debug, Eq, PartialEq)] +pub struct HandleBuffer { + count: usize, + buffer: NonNull, +} + +impl Drop for HandleBuffer { + fn drop(&mut self) { + let _ = unsafe { free_pool(self.buffer.cast::()) }; + } +} + +impl Deref for HandleBuffer { + type Target = [Handle]; + + fn deref(&self) -> &Self::Target { + unsafe { slice::from_raw_parts(self.buffer.as_ptr(), self.count) } + } +} + +/// An open protocol interface. Automatically closes the protocol +/// interface on drop. +/// +/// Most protocols have interface data associated with them. `ScopedProtocol` +/// implements [`Deref`] and [`DerefMut`] to access this data. A few protocols +/// (such as [`DevicePath`] and [`LoadedImageDevicePath`]) may be installed with +/// null interface data, in which case [`Deref`] and [`DerefMut`] will +/// panic. The [`get`] and [`get_mut`] methods may be used to access the +/// optional interface data without panicking. +/// +/// [`DevicePath`]: crate::proto::device_path::DevicePath +/// [`LoadedImageDevicePath`]: crate::proto::device_path::LoadedImageDevicePath +/// [`get`]: ScopedProtocol::get +/// [`get_mut`]: ScopedProtocol::get_mut +#[derive(Debug)] +pub struct ScopedProtocol { + /// The protocol interface. + interface: Option>, + open_params: OpenProtocolParams, +} + +impl Drop for ScopedProtocol

{ + fn drop(&mut self) { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + let status = unsafe { + (bt.close_protocol)( + self.open_params.handle.as_ptr(), + &P::GUID, + self.open_params.agent.as_ptr(), + Handle::opt_to_ptr(self.open_params.controller), + ) + }; + // All of the error cases for close_protocol boil down to + // calling it with a different set of parameters than what was + // passed to open_protocol. The public API prevents such errors, + // and the error can't be propagated out of drop anyway, so just + // assert success. + assert_eq!(status, Status::SUCCESS); + } +} + +impl Deref for ScopedProtocol

{ + type Target = P; + + #[track_caller] + fn deref(&self) -> &Self::Target { + unsafe { self.interface.unwrap().as_ref() } + } +} + +impl DerefMut for ScopedProtocol

{ + #[track_caller] + fn deref_mut(&mut self) -> &mut Self::Target { + unsafe { self.interface.unwrap().as_mut() } + } +} + +impl ScopedProtocol

{ + /// Get the protocol interface data, or `None` if the open protocol's + /// interface is null. + #[must_use] + pub fn get(&self) -> Option<&P> { + self.interface.map(|p| unsafe { p.as_ref() }) + } + + /// Get the protocol interface data, or `None` if the open protocol's + /// interface is null. + #[must_use] + pub fn get_mut(&mut self) -> Option<&mut P> { + self.interface.map(|mut p| unsafe { p.as_mut() }) + } +} + +/// RAII guard for task priority level changes. +/// +/// Will automatically restore the former task priority level when dropped. +#[derive(Debug)] +pub struct TplGuard { + old_tpl: Tpl, +} + +impl Drop for TplGuard { + fn drop(&mut self) { + let bt = boot_services_raw_panicking(); + let bt = unsafe { bt.as_ref() }; + + unsafe { + (bt.restore_tpl)(self.old_tpl); + } + } +} diff --git a/uefi/src/data_types/chars.rs b/uefi/src/data_types/chars.rs index 60671514b..c2f5bd63f 100644 --- a/uefi/src/data_types/chars.rs +++ b/uefi/src/data_types/chars.rs @@ -35,19 +35,19 @@ impl TryFrom for Char8 { } impl From for char { - fn from(char: Char8) -> char { - char::from(char.0) + fn from(char: Char8) -> Self { + Self::from(char.0) } } impl From for Char8 { fn from(value: u8) -> Self { - Char8(value) + Self(value) } } impl From for u8 { - fn from(char: Char8) -> u8 { + fn from(char: Char8) -> Self { char.0 } } @@ -107,7 +107,7 @@ impl TryFrom for Char16 { } impl From for char { - fn from(char: Char16) -> char { + fn from(char: Char16) -> Self { u32::from(char.0).try_into().unwrap() } } @@ -127,7 +127,7 @@ impl TryFrom for Char16 { } impl From for u16 { - fn from(char: Char16) -> u16 { + fn from(char: Char16) -> Self { char.0 } } diff --git a/uefi/src/data_types/mod.rs b/uefi/src/data_types/mod.rs index 74239c025..33cd201ff 100644 --- a/uefi/src/data_types/mod.rs +++ b/uefi/src/data_types/mod.rs @@ -39,7 +39,7 @@ impl Handle { /// Get the underlying raw pointer. #[must_use] - pub fn as_ptr(&self) -> *mut c_void { + pub const fn as_ptr(&self) -> *mut c_void { self.0.as_ptr() } @@ -78,7 +78,7 @@ impl Event { /// Get the underlying raw pointer. #[must_use] - pub fn as_ptr(&self) -> *mut c_void { + pub const fn as_ptr(&self) -> *mut c_void { self.0.as_ptr() } } @@ -137,6 +137,12 @@ pub trait Align { } } +impl Align for [u8] { + fn alignment() -> usize { + 1 + } +} + mod guid; pub use guid::{Guid, Identify}; diff --git a/uefi/src/data_types/owned_strs.rs b/uefi/src/data_types/owned_strs.rs index cf11f43ae..6fe29a6bd 100644 --- a/uefi/src/data_types/owned_strs.rs +++ b/uefi/src/data_types/owned_strs.rs @@ -113,7 +113,7 @@ impl CString16 { impl Default for CString16 { fn default() -> Self { - CString16::new() + Self::new() } } @@ -141,7 +141,7 @@ impl TryFrom<&str> for CString16 { // Add trailing nul. output.push(NUL_16); - Ok(CString16(output)) + Ok(Self(output)) } } @@ -175,7 +175,7 @@ impl<'a> TryFrom<&UnalignedSlice<'a, u16>> for CString16 { fn try_from(input: &UnalignedSlice) -> Result { let v = input.to_vec(); - CString16::try_from(v) + Self::try_from(v) } } @@ -189,7 +189,7 @@ impl From<&CStr16> for CString16 { impl From<&CString16> for String { fn from(value: &CString16) -> Self { let slice: &CStr16 = value.as_ref(); - String::from(slice) + Self::from(slice) } } diff --git a/uefi/src/data_types/strs.rs b/uefi/src/data_types/strs.rs index 2855e8802..bb01120d1 100644 --- a/uefi/src/data_types/strs.rs +++ b/uefi/src/data_types/strs.rs @@ -457,7 +457,7 @@ impl CStr16 { pub fn from_unaligned_slice<'buf>( src: &UnalignedSlice<'_, u16>, buf: &'buf mut [MaybeUninit], - ) -> Result<&'buf CStr16, UnalignedCStr16Error> { + ) -> Result<&'buf Self, UnalignedCStr16Error> { // The input `buf` might be longer than needed, so get a // subslice of the required length. let buf = buf @@ -469,7 +469,7 @@ impl CStr16 { // Safety: `copy_buf` fully initializes the slice. maybe_uninit_slice_assume_init_ref(buf) }; - CStr16::from_u16_with_nul(buf).map_err(|e| match e { + Self::from_u16_with_nul(buf).map_err(|e| match e { FromSliceWithNulError::InvalidChar(v) => UnalignedCStr16Error::InvalidChar(v), FromSliceWithNulError::InteriorNul(v) => UnalignedCStr16Error::InteriorNul(v), FromSliceWithNulError::NotNulTerminated => UnalignedCStr16Error::NotNulTerminated, @@ -524,7 +524,7 @@ impl CStr16 { /// Returns if the string is empty. This ignores the null character. #[must_use] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.num_chars() == 0 } @@ -566,7 +566,7 @@ impl CStr16 { /// Returns the underlying bytes as slice including the terminating null /// character. #[must_use] - pub fn as_bytes(&self) -> &[u8] { + pub const fn as_bytes(&self) -> &[u8] { unsafe { slice::from_raw_parts(self.0.as_ptr().cast(), self.num_bytes()) } } } @@ -593,7 +593,7 @@ impl From<&CStr16> for alloc::string::String { .map(u16::from) .map(u32::from) .map(|int| char::from_u32(int).expect("Should be encodable as UTF-8")) - .collect::() + .collect::() } } @@ -615,8 +615,8 @@ impl + ?Sized> EqStrUntilNul for CStr16 { } } -impl AsRef for CStr16 { - fn as_ref(&self) -> &CStr16 { +impl AsRef for CStr16 { + fn as_ref(&self) -> &Self { self } } diff --git a/uefi/src/data_types/unaligned_slice.rs b/uefi/src/data_types/unaligned_slice.rs index 45f1eaa0c..a77fff6ce 100644 --- a/uefi/src/data_types/unaligned_slice.rs +++ b/uefi/src/data_types/unaligned_slice.rs @@ -28,7 +28,7 @@ impl<'a, T: Copy> UnalignedSlice<'a, T> { /// The `data` pointer must point to a packed array of at least /// `len` elements of type `T`. The pointer must remain valid for as /// long as the `'a` lifetime. - pub unsafe fn new(data: *const T, len: usize) -> Self { + pub const unsafe fn new(data: *const T, len: usize) -> Self { Self { data, len, diff --git a/uefi/src/fs/dir_entry_iter.rs b/uefi/src/fs/dir_entry_iter.rs index 334825e2e..dc82477a9 100644 --- a/uefi/src/fs/dir_entry_iter.rs +++ b/uefi/src/fs/dir_entry_iter.rs @@ -18,7 +18,7 @@ pub struct UefiDirectoryIter(UefiDirectoryHandle); impl UefiDirectoryIter { /// Constructor. #[must_use] - pub fn new(handle: UefiDirectoryHandle) -> Self { + pub const fn new(handle: UefiDirectoryHandle) -> Self { Self(handle) } } diff --git a/uefi/src/fs/file_system/fs.rs b/uefi/src/fs/file_system/fs.rs index 2d57fc442..8925131b9 100644 --- a/uefi/src/fs/file_system/fs.rs +++ b/uefi/src/fs/file_system/fs.rs @@ -26,7 +26,7 @@ pub struct FileSystem<'a>(ScopedProtocol<'a, SimpleFileSystemProtocol>); impl<'a> FileSystem<'a> { /// Constructor. #[must_use] - pub fn new(proto: ScopedProtocol<'a, SimpleFileSystemProtocol>) -> Self { + pub const fn new(proto: ScopedProtocol<'a, SimpleFileSystemProtocol>) -> Self { Self(proto) } diff --git a/uefi/src/fs/path/path.rs b/uefi/src/fs/path/path.rs index efa5019d5..950c344e2 100644 --- a/uefi/src/fs/path/path.rs +++ b/uefi/src/fs/path/path.rs @@ -21,7 +21,7 @@ impl Path { /// Returns the underlying string. #[must_use] - pub fn to_cstr16(&self) -> &CStr16 { + pub const fn to_cstr16(&self) -> &CStr16 { &self.0 } @@ -79,7 +79,7 @@ impl Path { /// Returns of the path is empty. #[must_use] - pub fn is_empty(&self) -> bool { + pub const fn is_empty(&self) -> bool { self.to_cstr16().is_empty() } } diff --git a/uefi/src/helpers/logger.rs b/uefi/src/helpers/logger.rs index b8d8cfa41..cb01a1ae4 100644 --- a/uefi/src/helpers/logger.rs +++ b/uefi/src/helpers/logger.rs @@ -19,14 +19,12 @@ use core::ptr; use core::sync::atomic::{AtomicPtr, Ordering}; /// Global logger object -#[cfg(feature = "logger")] static LOGGER: Logger = Logger::new(); /// Set up logging /// /// This is unsafe because you must arrange for the logger to be reset with /// disable() on exit from UEFI boot services. -#[cfg(feature = "logger")] pub unsafe fn init(st: &mut SystemTable) { // Connect the logger to stdout. LOGGER.set_output(st.stdout()); @@ -60,7 +58,7 @@ impl core::fmt::Write for DebugconWriter { fn write_str(&mut self, s: &str) -> fmt::Result { for &byte in s.as_bytes() { unsafe { - core::arch::asm!("outb %al, %dx", in("al") byte, in("dx") DebugconWriter::IO_PORT, options(att_syntax)) + core::arch::asm!("outb %al, %dx", in("al") byte, in("dx") Self::IO_PORT, options(att_syntax)) }; } Ok(()) @@ -85,7 +83,7 @@ impl Logger { /// [`set_output`]: Self::set_output #[must_use] pub const fn new() -> Self { - Logger { + Self { writer: AtomicPtr::new(ptr::null_mut()), } } diff --git a/uefi/src/helpers/mod.rs b/uefi/src/helpers/mod.rs index e7afe9a6e..650720966 100644 --- a/uefi/src/helpers/mod.rs +++ b/uefi/src/helpers/mod.rs @@ -54,23 +54,26 @@ pub fn system_table() -> SystemTable { /// **PLEASE NOTE** that these helpers are meant for the pre exit boot service /// epoch. Limited functionality might work after exiting them, such as logging /// to the debugcon device. -#[allow(unused_variables)] // `st` is unused if logger and allocator are disabled -pub fn init(st: &mut SystemTable) -> Result<()> { +#[allow(clippy::missing_const_for_fn)] +pub fn init() -> Result<()> { // Setup logging and memory allocation #[cfg(feature = "logger")] unsafe { - logger::init(st); + let mut st = table::system_table_boot().expect("boot services are not active"); + logger::init(&mut st); } #[cfg(feature = "global_allocator")] unsafe { - crate::allocator::init(st); + let mut st = table::system_table_boot().expect("boot services are not active"); + crate::allocator::init(&mut st); } Ok(()) } +#[allow(clippy::missing_const_for_fn)] pub(crate) fn exit() { #[cfg(feature = "logger")] logger::disable(); diff --git a/uefi/src/helpers/println.rs b/uefi/src/helpers/println.rs index 2a30eb1d5..2b9903e92 100644 --- a/uefi/src/helpers/println.rs +++ b/uefi/src/helpers/println.rs @@ -4,14 +4,25 @@ use core::fmt::Write; /// INTERNAL API! Helper for print macros. #[doc(hidden)] pub fn _print(args: core::fmt::Arguments) { - system_table_boot() - .expect("boot services are not active") - .stdout() - .write_fmt(args) - .expect("Failed to write to stdout"); + if let Some(mut bs) = system_table_boot() { + bs.stdout() + .write_fmt(args) + .expect("Failed to write to stdout"); + } else { + // Ease debugging: Depending on logger, this might write to serial or + // debugcon. + log::debug!("You are using `print!` after the boot services have been exited."); + } } -/// Prints to the standard output. +/// Prints to the standard output of the UEFI boot service console. +/// +/// # Usage +/// Use this similar to `print!` from the Rust standard library, but only +/// as long as boot services have not been exited. +/// +/// You should never use this macro in a custom Logger ([`log::Log`] impl) to +/// prevent a circular runtime dependency. /// /// # Panics /// Will panic if `SYSTEM_TABLE` is `None` (Before [`uefi::helpers::init()`] and @@ -28,7 +39,15 @@ macro_rules! print { ($($arg:tt)*) => ($crate::helpers::_print(core::format_args!($($arg)*))); } -/// Prints to the standard output, with a newline. +/// Prints to the standard output of the UEFI boot service console, but with a +/// newline. +/// +/// # Usage +/// Use this similar to `println!` from the Rust standard library, but only +/// as long as boot services have not been exited. +/// +/// You should never use this macro in a custom Logger ([`log::Log`] impl) to +/// prevent a circular runtime dependency. /// /// # Panics /// Will panic if `SYSTEM_TABLE` is `None` (Before [`uefi::helpers::init()`] and diff --git a/uefi/src/lib.rs b/uefi/src/lib.rs index fb658d7c3..851dd74a7 100644 --- a/uefi/src/lib.rs +++ b/uefi/src/lib.rs @@ -1,17 +1,73 @@ //! Rusty wrapper for the [Unified Extensible Firmware Interface][UEFI]. //! +//! This crate makes it easy to develop Rust software that leverages **safe**, +//! **convenient**, and **performant** abstractions for [UEFI] functionality. +//! //! See the [Rust UEFI Book] for a tutorial, how-tos, and overviews of some //! important UEFI concepts. For more details of UEFI, see the latest [UEFI //! Specification][spec]. //! -//! Feel free to file bug reports and questions in our [issue tracker], and [PR -//! contributions][contributing] are also welcome! +//! # Minimal Example +//! +//! Minimal example for an UEFI application using functionality of the +//! `uefi` crate: +//! +//! ```ignore +//! #![no_main] +//! #![no_std] +//! +//! use uefi::prelude::*; +//! +//! #[entry] +//! fn main(_handle: Handle, system_table: SystemTable) -> Status { +//! uefi::helpers::init().unwrap(); +//! +//! Status::SUCCESS +//! } +//! ``` +//! +//! Please find more info in our [Rust UEFI Book]. +//! +//! # Value-add and Use Cases +//! +//! `uefi` supports writing code for both pre- and post-exit boot services +//! epochs, but its true strength shines when you create UEFI images that heavily +//! interact with UEFI boot services. Still, you have the flexibility to just +//! integrate selected types and abstractions into your project, for example to +//! parse the UEFI memory map. +//! +//! _Note that for producing UEFI images, you also need to use a corresponding +//! `uefi` compiler target of Rust, such as `x86_64-unknown-uefi`._ //! -//! # Interaction with uefi services +//! ## Example Use Cases //! -//! With this crate you can write code for the pre- and post-exit boot services -//! epochs. However, the `uefi` crate unfolds its true potential when -//! interacting with UEFI boot services. +//! This library significantly simplifies the process of creating **UEFI images** +//! by abstracting away much of the UEFI API complexity and by providing +//! convenient wrappers. When we mention UEFI images, we are talking about UEFI +//! applications, UEFI boot service drivers, and EFI runtime service drivers, +//! which typically have the `.efi` file extension. For instance, an UEFI +//! application could be an OS-specific loader, similar to _GRUB_ or _Limine_. +//! +//! Additionally, you can use this crate in non-UEFI images (such as a kernel +//! in ELF format) to perform tasks like parsing the UEFI memory map embedded in +//! the boot information provided by a bootloader. It also enables access to +//! UEFI runtime services from a non-UEFI image kernel. +//! +//! # Supported Compiler Versions and Architectures +//! +//! `uefi` works with stable Rust, but additional nightly-only features are +//! gated behind the `unstable` Cargo feature. Please find more information +//! about additional crate features below. +//! +//! `uefi` is compatible with all platforms that both the Rust compiler and +//! UEFI support, such as `i686`, `x86_64`, and `aarch64`. Please note that we +//! can't test all possible hardware/firmware/platform combinations in CI. +//! +//! ## MSRV +//! +//! +//! The minimum supported Rust version is currently 1.70. +//! Our policy is to support at least the past two stable releases. //! //! # Crate organisation //! @@ -42,7 +98,7 @@ //! protocol, and see the [`proto`] module for protocol implementations. New //! protocols can be defined with the [`unsafe_protocol`] macro. //! -//! ## Optional crate features +//! ## Optional Cargo crate features //! //! - `alloc`: Enable functionality requiring the [`alloc`] crate from //! the Rust standard library. For example, methods that return a @@ -59,8 +115,6 @@ //! that prints output to the UEFI console. No buffering is done; this //! is not a high-performance logger. //! - `panic_handler`: Add a default panic handler that logs to `stdout`. -//! - `panic-on-logger-errors` (enabled by default): Panic if a text -//! output error occurs in the logger. //! - `unstable`: Enable functionality that depends on [unstable //! features] in the nightly compiler. //! As example, in conjunction with the `alloc`-feature, this gate allows @@ -73,70 +127,122 @@ //! only unfold their potential when you invoke `uefi::helpers::init` as soon //! as possible in your application. //! +//! # Discuss and Contribute +//! +//! For general discussions, feel free to join us in our [Zulip] and ask +//! your questions there. +//! +//! Further, you can submit bugs and also ask questions in our [issue tracker]. +//! Contributions in the form of a PR are also highly welcome. Check our +//! [contributing guide][contributing] for details. +//! +//! # Comparison to other Projects in the Ecosystem +//! +//! ## Rust `std` implementation +//! +//! There is an ongoing effort for a [`std` implementation][rustc-uefi-std] of +//! the Rust standard library, which allows you to write UEFI programs that look +//! very similar to normal Rust programs running on top of an OS. +//! +//! It is still under development. You can track the progress in the +//! corresponding [tracking issue][uefi-std-tr-issue]. +//! +//! Using the `std` implementation simplifies the overall process of producing +//! the binary. For example, our [`#[entry]`][entry-macro] macro won't be +//! required any longer. As the `std` implementation evolves over time, you'll +//! need fewer and fewer abstractions of this crate. For everything not covered +//! by the `std` implementation, you can obtain relevant structures to work with +//! our crate via: +//! - `std::os::uefi::env::boot_services()` +//! - `std::os::uefi::env::get_system_handle()` +//! - `std::os::uefi::env::get_system_table()` +//! +//! ## `r-efi` +//! +//! [`r-efi`] provides Raw UEFI bindings without high-level convenience similar +//! to our `uefi-raw` crate, which is part of this project, but more +//! feature-complete. It targets a lower-level than our `uefi` crate does. +//! +//! # License +//! +//! +//! The code in this repository is licensed under the Mozilla Public License 2. +//! This license allows you to use the crate in proprietary programs, but any +//! modifications to the files must be open-sourced. +//! +//! The full text of the license is available in the [license file][LICENSE]. +//! +//! # Terminology +//! +//! Both "EFI" and "UEFI" can be used interchangeably, such as "UEFI image" or +//! "EFI image". We prefer "UEFI" in our crate and its documentation. +//! +//! [LICENSE]: https://github.com/rust-osdev/uefi-rs/blob/main/uefi/LICENSE //! [Rust UEFI Book]: https://rust-osdev.github.io/uefi-rs/HEAD/ //! [UEFI]: https://uefi.org/ +//! [Zulip]: https://rust-osdev.zulipchat.com //! [`BootServices`]: table::boot::BootServices //! [`GlobalAlloc`]: alloc::alloc::GlobalAlloc //! [`SystemTable`]: table::SystemTable +//! [`r-efi`]: https://crates.io/crates/r-efi +//! [`entry-macro`]: uefi_macros::entry //! [`unsafe_protocol`]: proto::unsafe_protocol //! [contributing]: https://github.com/rust-osdev/uefi-rs/blob/main/CONTRIBUTING.md //! [issue tracker]: https://github.com/rust-osdev/uefi-rs/issues //! [spec]: https://uefi.org/specifications //! [unstable features]: https://doc.rust-lang.org/unstable-book/ +//! [rustc-uefi-std]: https://doc.rust-lang.org/nightly/rustc/platform-support/unknown-uefi.html +//! [uefi-std-tr-issue]: https://github.com/rust-lang/rust/issues/100499 #![cfg_attr(all(feature = "unstable", feature = "alloc"), feature(allocator_api))] #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![no_std] -// Enable some additional warnings and lints. -#![warn(clippy::ptr_as_ptr, missing_docs, unused)] -#![deny(clippy::all)] -#![deny(clippy::must_use_candidate)] -#![deny(missing_debug_implementations)] +#![deny( + clippy::all, + clippy::missing_const_for_fn, + clippy::must_use_candidate, + clippy::ptr_as_ptr, + clippy::use_self, + missing_debug_implementations, + missing_docs, + unused +)] #[cfg(feature = "alloc")] extern crate alloc; - // allow referring to self as ::uefi for macros to work universally (from this crate and from others) // see https://github.com/rust-lang/rust/issues/54647 extern crate self as uefi; - -/// Re-export ucs2_cstr so that it can be used in the implementation of the -/// cstr16 macro. It is hidden since it's not intended to be used directly. -#[doc(hidden)] -pub use ucs2::ucs2_cstr; - #[macro_use] extern crate uefi_raw; #[macro_use] pub mod data_types; -#[cfg(feature = "alloc")] -pub use data_types::CString16; -pub use data_types::{CStr16, CStr8, Char16, Char8, Event, Guid, Handle, Identify}; -pub use uefi_macros::entry; -pub use uguid::guid; - -mod result; -pub use result::{Error, Result, ResultExt, Status, StatusExt}; - -pub mod table; - -pub mod proto; - -pub mod prelude; - pub mod allocator; - +pub mod boot; #[cfg(feature = "alloc")] pub mod fs; - -// As long as this is behind "alloc", we can simplify cfg-feature attributes in this module. -#[cfg(feature = "alloc")] -pub(crate) mod mem; +pub mod helpers; +pub mod mem; +pub mod prelude; +pub mod proto; +pub mod runtime; +pub mod system; +pub mod table; pub(crate) mod polyfill; -pub mod helpers; - mod macros; +mod result; mod util; + +#[cfg(feature = "alloc")] +pub use data_types::CString16; +pub use data_types::{CStr16, CStr8, Char16, Char8, Event, Guid, Handle, Identify}; +pub use result::{Error, Result, ResultExt, Status, StatusExt}; +/// Re-export ucs2_cstr so that it can be used in the implementation of the +/// cstr16 macro. It is hidden since it's not intended to be used directly. +#[doc(hidden)] +pub use ucs2::ucs2_cstr; +pub use uefi_macros::entry; +pub use uguid::guid; diff --git a/uefi/src/mem/memory_map/api.rs b/uefi/src/mem/memory_map/api.rs new file mode 100644 index 000000000..960967d48 --- /dev/null +++ b/uefi/src/mem/memory_map/api.rs @@ -0,0 +1,123 @@ +//! Module for the traits [`MemoryMap`] and [`MemoryMapMut`]. + +use super::*; +use core::fmt::Debug; +use core::ops::{Index, IndexMut}; + +/// An accessory to the UEFI memory map and associated metadata that can be +/// either iterated or indexed like an array. +/// +/// A [`MemoryMap`] is always associated with the unique [`MemoryMapKey`] +/// bundled with the map. +/// +/// To iterate over the entries, call [`MemoryMap::entries`]. +/// +/// ## UEFI pitfalls +/// Note that a MemoryMap can quickly become outdated, as soon as any explicit +/// or hidden allocation happens. +/// +/// As soon as boot services are excited, all previous obtained memory maps must +/// be considered as outdated, except if the [`MemoryMapKey`] equals the one +/// returned by `exit_boot_services()`. +/// +/// **Please note** that when working with memory maps, the `entry_size` is +/// usually larger than `size_of:: { + /// Returns the associated [`MemoryMapMeta`]. + #[must_use] + fn meta(&self) -> MemoryMapMeta; + + /// Returns the associated [`MemoryMapKey`]. Note that this isn't + /// necessarily the key of the latest valid UEFI memory map. + #[must_use] + fn key(&self) -> MemoryMapKey; + + /// Returns the number of keys in the map. + #[must_use] + fn len(&self) -> usize; + + /// Returns if the memory map is empty. + #[must_use] + fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Returns a reference to the [`MemoryDescriptor`] at the given index, if + /// present. + #[must_use] + fn get(&self, index: usize) -> Option<&MemoryDescriptor> { + if index >= self.len() { + None + } else { + let offset = index * self.meta().desc_size; + unsafe { + self.buffer() + .as_ptr() + .add(offset) + .cast::() + .as_ref() + } + } + } + + /// Returns a reference to the underlying memory. + #[must_use] + fn buffer(&self) -> &[u8]; + + /// Returns an Iterator of type [`MemoryMapIter`]. + #[must_use] + fn entries(&self) -> MemoryMapIter<'_>; + + /// Returns if the underlying memory map is sorted regarding the physical + /// address start. + #[must_use] + fn is_sorted(&self) -> bool { + let iter = self.entries(); + let iter = iter.clone().zip(iter.skip(1)); + + for (curr, next) in iter { + if next.phys_start < curr.phys_start { + log::debug!("next.phys_start < curr.phys_start: curr={curr:?}, next={next:?}"); + return false; + } + } + true + } +} + +/// Extension to [`MemoryMap`] that adds mutable operations. This also includes +/// the ability to sort the memory map. +pub trait MemoryMapMut: MemoryMap + IndexMut { + /// Returns a mutable reference to the [`MemoryDescriptor`] at the given + /// index, if present. + #[must_use] + fn get_mut(&mut self, index: usize) -> Option<&mut MemoryDescriptor> { + if index >= self.len() { + None + } else { + let offset = index * self.meta().desc_size; + unsafe { + self.buffer_mut() + .as_mut_ptr() + .add(offset) + .cast::() + .as_mut() + } + } + } + + /// Sorts the memory map by physical address in place. This operation is + /// optional and should be invoked only once. + fn sort(&mut self); + + /// Returns a reference to the underlying memory. + /// + /// # Safety + /// + /// This is unsafe as there is a potential to create invalid entries. + unsafe fn buffer_mut(&mut self) -> &mut [u8]; +} diff --git a/uefi/src/mem/memory_map/impl_.rs b/uefi/src/mem/memory_map/impl_.rs new file mode 100644 index 000000000..a0fd463dc --- /dev/null +++ b/uefi/src/mem/memory_map/impl_.rs @@ -0,0 +1,532 @@ +//! Module for [`MemoryMapOwned`], [`MemoryMapRef`], and [`MemoryMapRefMut`], +//! as well as relevant helper types, such as [`MemoryMapBackingMemory`]. + +use super::*; +use crate::table::system_table_boot; +use core::fmt::{Debug, Display, Formatter}; +use core::ops::{Index, IndexMut}; +use core::ptr::NonNull; +use core::{mem, ptr}; +use uefi_raw::PhysicalAddress; + +/// Errors that may happen when constructing a [`MemoryMapRef`] or +/// [`MemoryMapRefMut`]. +#[derive(Copy, Clone, Debug)] +pub enum MemoryMapError { + /// The buffer is not 8-byte aligned. + Misaligned, + /// The memory map size is invalid. + InvalidSize, +} + +impl Display for MemoryMapError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + Debug::fmt(self, f) + } +} + +#[cfg(feature = "unstable")] +impl core::error::Error for MemoryMapError {} + +/// Implementation of [`MemoryMap`] for the given buffer. +#[derive(Debug)] +pub struct MemoryMapRef<'a> { + buf: &'a [u8], + meta: MemoryMapMeta, + len: usize, +} + +impl<'a> MemoryMapRef<'a> { + /// Constructs a new [`MemoryMapRef`]. + /// + /// The underlying memory might contain an invalid/malformed memory map + /// which can't be checked during construction of this type. The entry + /// iterator might yield unexpected results. + pub fn new(buffer: &'a [u8], meta: MemoryMapMeta) -> Result { + if buffer.as_ptr().align_offset(8) != 0 { + return Err(MemoryMapError::Misaligned); + } + if buffer.len() < meta.map_size { + return Err(MemoryMapError::InvalidSize); + } + Ok(Self { + buf: buffer, + meta, + len: meta.entry_count(), + }) + } +} + +impl<'a> MemoryMap for MemoryMapRef<'a> { + fn meta(&self) -> MemoryMapMeta { + self.meta + } + + fn key(&self) -> MemoryMapKey { + self.meta.map_key + } + + fn len(&self) -> usize { + self.len + } + + fn buffer(&self) -> &[u8] { + self.buf + } + + fn entries(&self) -> MemoryMapIter<'_> { + MemoryMapIter { + memory_map: self, + index: 0, + } + } +} + +impl Index for MemoryMapRef<'_> { + type Output = MemoryDescriptor; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index).unwrap() + } +} + +/// Implementation of [`MemoryMapMut`] for the given buffer. +#[derive(Debug)] +pub struct MemoryMapRefMut<'a> { + buf: &'a mut [u8], + meta: MemoryMapMeta, + len: usize, +} + +impl<'a> MemoryMapRefMut<'a> { + /// Constructs a new [`MemoryMapRefMut`]. + /// + /// The underlying memory might contain an invalid/malformed memory map + /// which can't be checked during construction of this type. The entry + /// iterator might yield unexpected results. + pub fn new(buffer: &'a mut [u8], meta: MemoryMapMeta) -> Result { + if buffer.as_ptr().align_offset(8) != 0 { + return Err(MemoryMapError::Misaligned); + } + if buffer.len() < meta.map_size { + return Err(MemoryMapError::InvalidSize); + } + Ok(Self { + buf: buffer, + meta, + len: meta.entry_count(), + }) + } +} + +impl<'a> MemoryMap for MemoryMapRefMut<'a> { + fn meta(&self) -> MemoryMapMeta { + self.meta + } + + fn key(&self) -> MemoryMapKey { + self.meta.map_key + } + + fn len(&self) -> usize { + self.len + } + + fn buffer(&self) -> &[u8] { + self.buf + } + + fn entries(&self) -> MemoryMapIter<'_> { + MemoryMapIter { + memory_map: self, + index: 0, + } + } +} + +impl<'a> MemoryMapMut for MemoryMapRefMut<'a> { + fn sort(&mut self) { + unsafe { + self.qsort(0, self.len - 1); + } + } + + unsafe fn buffer_mut(&mut self) -> &mut [u8] { + self.buf + } +} + +impl<'a> MemoryMapRefMut<'a> { + /// Hoare partition scheme for quicksort. + /// Must be called with `low` and `high` being indices within bounds. + unsafe fn qsort(&mut self, low: usize, high: usize) { + if low >= high { + return; + } + + let p = self.partition(low, high); + self.qsort(low, p); + self.qsort(p + 1, high); + } + + unsafe fn partition(&mut self, low: usize, high: usize) -> usize { + let pivot = self.get_element_phys_addr(low + (high - low) / 2); + + let mut left_index = low.wrapping_sub(1); + let mut right_index = high.wrapping_add(1); + + loop { + while { + left_index = left_index.wrapping_add(1); + + self.get_element_phys_addr(left_index) < pivot + } {} + + while { + right_index = right_index.wrapping_sub(1); + + self.get_element_phys_addr(right_index) > pivot + } {} + + if left_index >= right_index { + return right_index; + } + + self.swap(left_index, right_index); + } + } + + /// Indices must be smaller than len. + unsafe fn swap(&mut self, index1: usize, index2: usize) { + if index1 == index2 { + return; + } + + let base = self.buf.as_mut_ptr(); + + unsafe { + ptr::swap_nonoverlapping( + base.add(index1 * self.meta.desc_size), + base.add(index2 * self.meta.desc_size), + self.meta.desc_size, + ); + } + } + + fn get_element_phys_addr(&self, index: usize) -> PhysicalAddress { + let offset = index.checked_mul(self.meta.desc_size).unwrap(); + let elem = unsafe { &*self.buf.as_ptr().add(offset).cast::() }; + elem.phys_start + } +} + +impl Index for MemoryMapRefMut<'_> { + type Output = MemoryDescriptor; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl IndexMut for MemoryMapRefMut<'_> { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +/// The backing memory for the UEFI memory app on the UEFI heap, allocated using +/// the UEFI boot services allocator. This occupied memory will also be +/// reflected in the memory map itself. +/// +/// Although untyped, it is similar to the `Box` type in terms of heap +/// allocation and deallocation, as well as ownership of the corresponding +/// memory. Apart from that, this type only has the semantics of a buffer. +/// +/// The memory is untyped, which is necessary due to the nature of the UEFI +/// spec. It still ensures a correct alignment to hold [`MemoryDescriptor`]. The +/// size of the buffer is sufficient to hold the memory map at the point in time +/// where this is created. Note that due to (not obvious or asynchronous) +/// allocations/deallocations in your environment, this might be outdated at the +/// time you store the memory map in it. +/// +/// Note that due to the nature of the UEFI memory app, this buffer might +/// hold (a few) bytes more than necessary. The `map_size` reported by +/// `get_memory_map` tells the actual size. +/// +/// When this type is dropped and boot services are not exited yet, the memory +/// is freed. +/// +/// # Usage +/// The type is intended to be used like this: +/// 1. create it using [`MemoryMapBackingMemory::new`] +/// 2. pass it to [`BootServices::get_memory_map`] +/// 3. construct a [`MemoryMapOwned`] from it +/// +/// [`BootServices::get_memory_map`]: crate::table::boot::BootServices::get_memory_map +#[derive(Debug)] +#[allow(clippy::len_without_is_empty)] // this type is never empty +pub(crate) struct MemoryMapBackingMemory(NonNull<[u8]>); + +impl MemoryMapBackingMemory { + /// Constructs a new [`MemoryMapBackingMemory`]. + /// + /// # Parameters + /// - `memory_type`: The memory type for the memory map allocation. + /// Typically, [`MemoryType::LOADER_DATA`] for regular UEFI applications. + pub(crate) fn new(memory_type: MemoryType) -> crate::Result { + let st = system_table_boot().expect("Should have boot services activated"); + let bs = st.boot_services(); + + let memory_map_meta = bs.memory_map_size(); + let len = Self::safe_allocation_size_hint(memory_map_meta); + let ptr = bs.allocate_pool(memory_type, len)?.as_ptr(); + + // Should be fine as UEFI always has allocations with a guaranteed + // alignment of 8 bytes. + assert_eq!(ptr.align_offset(mem::align_of::()), 0); + + // If this panics, the UEFI implementation is broken. + assert_eq!(memory_map_meta.map_size % memory_map_meta.desc_size, 0); + + unsafe { Ok(Self::from_raw(ptr, len)) } + } + + unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self { + assert_eq!(ptr.align_offset(mem::align_of::()), 0); + + let ptr = NonNull::new(ptr).expect("UEFI should never return a null ptr. An error should have been reflected via an Err earlier."); + let slice = NonNull::slice_from_raw_parts(ptr, len); + + Self(slice) + } + + /// INTERNAL, for unit tests. + /// + /// Creates an instance from the provided memory, which is not necessarily + /// on the UEFI heap. + #[cfg(test)] + pub(crate) fn from_slice(buffer: &mut [u8]) -> Self { + let len = buffer.len(); + unsafe { Self::from_raw(buffer.as_mut_ptr(), len) } + } + + /// Returns a "safe" best-effort size hint for the memory map size with + /// some additional bytes in buffer compared to the [`MemoryMapMeta`]. This + /// takes into account that, as you go, more (small) allocations might + /// happen. + #[must_use] + const fn safe_allocation_size_hint(mmm: MemoryMapMeta) -> usize { + // Allocate space for extra entries beyond the current size of the + // memory map. The value of 8 matches the value in the Linux kernel: + // https://github.com/torvalds/linux/blob/e544a07438/drivers/firmware/efi/libstub/efistub.h#L173 + const EXTRA_ENTRIES: usize = 8; + + let extra_size = mmm.desc_size * EXTRA_ENTRIES; + mmm.map_size + extra_size + } + + /// Returns a slice to the underlying memory. + #[must_use] + pub fn as_slice(&self) -> &[u8] { + unsafe { self.0.as_ref() } + } + + /// Returns a mutable slice to the underlying memory. + #[must_use] + pub fn as_mut_slice(&mut self) -> &mut [u8] { + unsafe { self.0.as_mut() } + } +} + +// Don't drop when we use this in unit tests. +#[cfg(not(test))] +impl Drop for MemoryMapBackingMemory { + fn drop(&mut self) { + if let Some(bs) = system_table_boot() { + let res = unsafe { bs.boot_services().free_pool(self.0.as_ptr().cast()) }; + if let Err(e) = res { + log::error!("Failed to deallocate memory map: {e:?}"); + } + } else { + #[cfg(test)] + log::debug!("Boot services are not available in unit tests."); + + #[cfg(not(test))] + log::debug!("Boot services are excited. Memory map won't be freed using the UEFI boot services allocator."); + } + } +} + +/// Implementation of [`MemoryMapMut`] that owns the buffer on the UEFI heap. +#[derive(Debug)] +pub struct MemoryMapOwned { + /// Backing memory, properly initialized at this point. + pub(crate) buf: MemoryMapBackingMemory, + pub(crate) meta: MemoryMapMeta, + pub(crate) len: usize, +} + +impl MemoryMapOwned { + /// Creates a [`MemoryMapOwned`] from the given **initialized** memory map + /// (stored inside the provided buffer) and the corresponding + /// [`MemoryMapMeta`]. + pub(crate) fn from_initialized_mem(buf: MemoryMapBackingMemory, meta: MemoryMapMeta) -> Self { + assert!(meta.desc_size >= mem::size_of::()); + let len = meta.entry_count(); + Self { buf, meta, len } + } +} + +impl MemoryMap for MemoryMapOwned { + fn meta(&self) -> MemoryMapMeta { + self.meta + } + + fn key(&self) -> MemoryMapKey { + self.meta.map_key + } + + fn len(&self) -> usize { + self.len + } + + fn buffer(&self) -> &[u8] { + self.buf.as_slice() + } + + fn entries(&self) -> MemoryMapIter<'_> { + MemoryMapIter { + memory_map: self, + index: 0, + } + } +} + +impl MemoryMapMut for MemoryMapOwned { + fn sort(&mut self) { + let mut reference = MemoryMapRefMut { + buf: self.buf.as_mut_slice(), + meta: self.meta, + len: self.len, + }; + reference.sort(); + } + + unsafe fn buffer_mut(&mut self) -> &mut [u8] { + self.buf.as_mut_slice() + } +} + +impl Index for MemoryMapOwned { + type Output = MemoryDescriptor; + + fn index(&self, index: usize) -> &Self::Output { + self.get(index).unwrap() + } +} + +impl IndexMut for MemoryMapOwned { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + self.get_mut(index).unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec::Vec; + use core::mem::size_of; + + const BASE_MMAP_UNSORTED: [MemoryDescriptor; 3] = [ + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x3000, + virt_start: 0x3000, + page_count: 1, + att: MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x2000, + virt_start: 0x2000, + page_count: 1, + att: MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x1000, + virt_start: 0x1000, + page_count: 1, + att: MemoryAttribute::WRITE_BACK, + }, + ]; + + /// Returns a copy of [`BASE_MMAP_UNSORTED`] owned on the stack. + fn new_mmap_memory() -> [MemoryDescriptor; 3] { + BASE_MMAP_UNSORTED + } + + fn mmap_raw<'a>(memory: &mut [MemoryDescriptor]) -> (&'a mut [u8], MemoryMapMeta) { + let desc_size = size_of::(); + let len = memory.len() * desc_size; + let ptr = memory.as_mut_ptr().cast::(); + let slice = unsafe { core::slice::from_raw_parts_mut(ptr, len) }; + let meta = MemoryMapMeta { + map_size: len, + desc_size, + map_key: Default::default(), + desc_version: MemoryDescriptor::VERSION, + }; + (slice, meta) + } + + /// Basic sanity checks for the type [`MemoryMapRef`]. + #[test] + fn memory_map_ref() { + let mut memory = new_mmap_memory(); + let (mmap, meta) = mmap_raw(&mut memory); + let mmap = MemoryMapRef::new(mmap, meta).unwrap(); + + assert_eq!(mmap.entries().count(), 3); + assert_eq!( + mmap.entries().copied().collect::>().as_slice(), + &BASE_MMAP_UNSORTED + ); + assert!(!mmap.is_sorted()); + } + + /// Basic sanity checks for the type [`MemoryMapRefMut`]. + #[test] + fn memory_map_ref_mut() { + let mut memory = new_mmap_memory(); + let (mmap, meta) = mmap_raw(&mut memory); + let mut mmap = MemoryMapRefMut::new(mmap, meta).unwrap(); + + assert_eq!(mmap.entries().count(), 3); + assert_eq!( + mmap.entries().copied().collect::>().as_slice(), + &BASE_MMAP_UNSORTED + ); + assert!(!mmap.is_sorted()); + mmap.sort(); + assert!(mmap.is_sorted()); + } + + /// Basic sanity checks for the type [`MemoryMapOwned`]. + #[test] + fn memory_map_owned() { + let mut memory = new_mmap_memory(); + let (mmap, meta) = mmap_raw(&mut memory); + let mmap = MemoryMapBackingMemory::from_slice(mmap); + let mut mmap = MemoryMapOwned::from_initialized_mem(mmap, meta); + + assert_eq!(mmap.entries().count(), 3); + assert_eq!( + mmap.entries().copied().collect::>().as_slice(), + &BASE_MMAP_UNSORTED + ); + assert!(!mmap.is_sorted()); + mmap.sort(); + assert!(mmap.is_sorted()); + } +} diff --git a/uefi/src/mem/memory_map/iter.rs b/uefi/src/mem/memory_map/iter.rs new file mode 100644 index 000000000..049b52ddd --- /dev/null +++ b/uefi/src/mem/memory_map/iter.rs @@ -0,0 +1,36 @@ +use super::*; + +/// An iterator for [`MemoryMap`]. +/// +/// The underlying memory might contain an invalid/malformed memory map +/// which can't be checked during construction of this type. The iterator +/// might yield unexpected results. +#[derive(Debug, Clone)] +pub struct MemoryMapIter<'a> { + pub(crate) memory_map: &'a dyn MemoryMap, + pub(crate) index: usize, +} + +impl<'a> Iterator for MemoryMapIter<'a> { + type Item = &'a MemoryDescriptor; + + fn next(&mut self) -> Option { + let desc = self.memory_map.get(self.index)?; + + self.index += 1; + + Some(desc) + } + + fn size_hint(&self) -> (usize, Option) { + let sz = self.memory_map.len() - self.index; + + (sz, Some(sz)) + } +} + +impl ExactSizeIterator for MemoryMapIter<'_> { + fn len(&self) -> usize { + self.memory_map.len() + } +} diff --git a/uefi/src/mem/memory_map/mod.rs b/uefi/src/mem/memory_map/mod.rs new file mode 100644 index 000000000..45d66de41 --- /dev/null +++ b/uefi/src/mem/memory_map/mod.rs @@ -0,0 +1,375 @@ +//! Bundles all relevant types and helpers to work with the UEFI memory map. +//! +//! To work with the memory map, you should use one of the structs +//! [`MemoryMapOwned`], [`MemoryMapRef`], or [`MemoryMapRefMut`] - depending on +//! your use-case. The traits [`MemoryMap`] and [`MemoryMapMut`] mainly exist +//! to guarantee a streamlined API across these types. We recommend to work with +//! the specific implementation. +//! +//! # Usecase: Obtain UEFI Memory Map +//! +//! You can use [`SystemTable::exit_boot_services`] or +//! [`BootServices::memory_map`], which returns an properly initialized +//! [`MemoryMapOwned`]. +//! +//! # Usecase: Parse Memory Slice as UEFI Memory Map +//! +//! If you have a chunk of memory and want to parse it as UEFI memory map, which +//! might be the case if a bootloader such as GRUB or Limine passes its boot +//! information, you can use [`MemoryMapRef`] or [`MemoryMapRefMut`]. +//! +//! # All relevant exports: +//! +//! - the traits [`MemoryMap`] and [`MemoryMapMut`], +//! - the trait implementations [`MemoryMapOwned`], [`MemoryMapRef`], and +//! [`MemoryMapRefMut`], +//! - the iterator [`MemoryMapIter`] +//! - various associated helper types, such as [`MemoryMapKey`] and +//! [`MemoryMapMeta`], +//! - re-exports [`MemoryDescriptor`], [`MemoryType`], and [`MemoryAttribute`]. +//! +//! [`SystemTable::exit_boot_services`]: uefi::table::SystemTable::exit_boot_services +//! [`BootServices::memory_map`]: uefi::table::boot::BootServices::memory_map + +mod api; +mod impl_; +mod iter; + +pub use api::*; +pub use impl_::*; +pub use iter::*; +pub use uefi_raw::table::boot::{MemoryAttribute, MemoryDescriptor, MemoryType}; + +use crate::data_types::Align; +use core::mem; + +impl Align for MemoryDescriptor { + fn alignment() -> usize { + mem::align_of::() + } +} + +/// A unique identifier of a UEFI memory map, used to tell the firmware that one +/// has the latest valid memory map when exiting boot services. +/// +/// If the memory map changes, due to any allocation or deallocation, this value +/// is no longer valid, and exiting boot services will fail. +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] +#[repr(C)] +pub struct MemoryMapKey(pub(crate) usize); + +/// A structure containing the meta attributes associated with a call to +/// `GetMemoryMap` of UEFI. Note that all values refer to the time this was +/// called. All following invocations (hidden, subtle, and asynchronous ones) +/// will likely invalidate this. +#[derive(Copy, Clone, Debug)] +pub struct MemoryMapMeta { + /// The actual size of the map. + pub map_size: usize, + /// The reported memory descriptor size. Note that this is the reference + /// and never `size_of::()`! + pub desc_size: usize, + /// A unique memory key bound to a specific memory map version/state. + pub map_key: MemoryMapKey, + /// The version of the descriptor struct. + pub desc_version: u32, +} + +impl MemoryMapMeta { + /// Returns the amount of entries in the map. + #[must_use] + pub fn entry_count(&self) -> usize { + assert_eq!(self.map_size % self.desc_size, 0); + self.map_size / self.desc_size + } + + /// Runs some sanity assertions. + pub fn assert_sanity_checks(&self) { + assert!(self.desc_size > 0); + // Although very unlikely, this might fail if the memory descriptor is + // extended by a future UEFI revision by a significant amount, we + // update the struct, but an old UEFI implementation reports a small + // size. + assert!(self.desc_size >= mem::size_of::()); + assert!(self.map_size > 0); + + // Ensure the mmap size is (somehow) sane. + const ONE_GB: usize = 1024 * 1024 * 1024; + assert!(self.map_size <= ONE_GB); + } +} + +/// Comprehensive unit test of the memory map functionality with the simplified +/// data. Here, `desc_size` equals `size_of:: MemoryMapRefMut { + let mmap_len = size_of_val(buffer); + let mmap = { + unsafe { core::slice::from_raw_parts_mut(buffer.as_mut_ptr() as *mut u8, mmap_len) } + }; + + MemoryMapRefMut::new( + mmap, + MemoryMapMeta { + map_size: mmap_len, + desc_size: size_of::(), + map_key: Default::default(), + desc_version: MemoryDescriptor::VERSION, + }, + ) + .unwrap() + } + + #[test] + fn mem_map_sorting() { + // Doesn't matter what type it is. + const TY: MemoryType = MemoryType::RESERVED; + + const BASE: MemoryDescriptor = MemoryDescriptor { + ty: TY, + phys_start: 0, + virt_start: 0, + page_count: 0, + att: MemoryAttribute::empty(), + }; + + let mut buffer = [ + MemoryDescriptor { + phys_start: 2000, + ..BASE + }, + MemoryDescriptor { + phys_start: 3000, + ..BASE + }, + BASE, + MemoryDescriptor { + phys_start: 1000, + ..BASE + }, + ]; + + let mut mem_map = buffer_to_map(&mut buffer); + + mem_map.sort(); + + if !is_sorted(&mem_map.entries()) { + panic!("mem_map is not sorted: {:?}", mem_map); + } + } + + #[test] + fn mem_map_get() { + // Doesn't matter what type it is. + const TY: MemoryType = MemoryType::RESERVED; + + const BASE: MemoryDescriptor = MemoryDescriptor { + ty: TY, + phys_start: 0, + virt_start: 0, + page_count: 0, + att: MemoryAttribute::empty(), + }; + + const BUFFER: [MemoryDescriptor; 4] = [ + MemoryDescriptor { + phys_start: 2000, + ..BASE + }, + MemoryDescriptor { + phys_start: 3000, + ..BASE + }, + BASE, + MemoryDescriptor { + phys_start: 1000, + ..BASE + }, + ]; + + let mut buffer = BUFFER; + + let mut mem_map = buffer_to_map(&mut buffer); + + for index in 0..3 { + assert_eq!(mem_map.get(index), BUFFER.get(index)); + + // Test Index impl + assert_eq!(Some(&mem_map[index]), BUFFER.get(index)); + } + + let mut_desc = mem_map.get_mut(2).unwrap(); + + mut_desc.phys_start = 300; + + let desc = mem_map.get(2).unwrap(); + + assert_ne!(*desc, BUFFER[2]); + } + + fn is_sorted(iter: &MemoryMapIter) -> bool { + let mut iter = iter.clone(); + let mut curr_start; + + if let Some(val) = iter.next() { + curr_start = val.phys_start; + } else { + return true; + } + + for desc in iter { + if desc.phys_start <= curr_start { + return false; + } + curr_start = desc.phys_start + } + true + } +} + +/// Comprehensive unit test of the memory map functionality with the data from a +/// real UEFI memory map. The important property that we test here is that +/// the reported `desc_size` doesn't equal `size_of::(), + desc_size: 48, + map_key: MemoryMapKey(0), + desc_version: 1, + }; + /// Sample with 10 entries of a real UEFI memory map extracted from our + /// UEFI test runner. + const MMAP_RAW: [u64; 60] = [ + 3, 0, 0, 1, 15, 0, 7, 4096, 0, 134, 15, 0, 4, 552960, 0, 1, 15, 0, 7, 557056, 0, 24, 15, 0, + 7, 1048576, 0, 1792, 15, 0, 10, 8388608, 0, 8, 15, 0, 7, 8421376, 0, 3, 15, 0, 10, 8433664, + 0, 1, 15, 0, 7, 8437760, 0, 4, 15, 0, 10, 8454144, 0, 240, 15, 0, + ]; + + #[test] + fn basic_functionality() { + let mut buf = MMAP_RAW; + let buf = + unsafe { slice::from_raw_parts_mut(buf.as_mut_ptr().cast::(), MMAP_META.map_size) }; + let mut mmap = MemoryMapRefMut::new(buf, MMAP_META).unwrap(); + + assert!(mmap.is_sorted()); + mmap.sort(); + assert!(mmap.is_sorted()); + + let entries = mmap.entries().copied().collect::>(); + + let expected = [ + MemoryDescriptor { + ty: MemoryType::BOOT_SERVICES_CODE, + phys_start: 0x0, + virt_start: 0x0, + page_count: 0x1, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x1000, + virt_start: 0x0, + page_count: 0x86, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::BOOT_SERVICES_DATA, + phys_start: 0x87000, + virt_start: 0x0, + page_count: 0x1, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x88000, + virt_start: 0x0, + page_count: 0x18, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x100000, + virt_start: 0x0, + page_count: 0x700, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::ACPI_NON_VOLATILE, + phys_start: 0x800000, + virt_start: 0x0, + page_count: 0x8, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x808000, + virt_start: 0x0, + page_count: 0x3, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::ACPI_NON_VOLATILE, + phys_start: 0x80b000, + virt_start: 0x0, + page_count: 0x1, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::CONVENTIONAL, + phys_start: 0x80c000, + virt_start: 0x0, + page_count: 0x4, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + MemoryDescriptor { + ty: MemoryType::ACPI_NON_VOLATILE, + phys_start: 0x810000, + virt_start: 0x0, + page_count: 0xf0, + att: MemoryAttribute::UNCACHEABLE + | MemoryAttribute::WRITE_COMBINE + | MemoryAttribute::WRITE_THROUGH + | MemoryAttribute::WRITE_BACK, + }, + ]; + assert_eq!(entries.as_slice(), &expected); + } +} diff --git a/uefi/src/mem/mod.rs b/uefi/src/mem/mod.rs new file mode 100644 index 000000000..f3087cd8e --- /dev/null +++ b/uefi/src/mem/mod.rs @@ -0,0 +1,9 @@ +//! Types, functions, traits, and other helpers to work with memory in UEFI +//! libraries and applications. + +pub mod memory_map; +#[cfg(feature = "alloc")] +pub(crate) mod util; + +#[cfg(feature = "alloc")] +pub(crate) use util::*; diff --git a/uefi/src/mem.rs b/uefi/src/mem/util.rs similarity index 98% rename from uefi/src/mem.rs rename to uefi/src/mem/util.rs index 1d0d66d41..6df38d916 100644 --- a/uefi/src/mem.rs +++ b/uefi/src/mem/util.rs @@ -54,7 +54,10 @@ pub(crate) fn make_boxed< // Propagate any other error. Err((status, _)) => Err(Error::from(status)), // Success is unexpected, return an error. - Ok(_) => Err(Error::from(Status::UNSUPPORTED)), + Ok(_) => { + log::debug!("Got unexpected success status"); + Err(Error::from(Status::UNSUPPORTED)) + } }?; // We add trailing padding because the size of a rust structure must diff --git a/uefi/src/proto/boot_policy.rs b/uefi/src/proto/boot_policy.rs new file mode 100644 index 000000000..d46edf2ff --- /dev/null +++ b/uefi/src/proto/boot_policy.rs @@ -0,0 +1,108 @@ +//! Module for the [`BootPolicy`] helper type. + +use core::fmt::{Display, Formatter}; + +/// Errors that can happen when working with [`BootPolicy`]. +#[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Eq, Ord)] +pub enum BootPolicyError { + /// Only `0` and `1` are valid integers, all other values are undefined. + InvalidInteger(u8), +} + +impl Display for BootPolicyError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let s = match self { + Self::InvalidInteger(_) => { + "Only `0` and `1` are valid integers, all other values are undefined." + } + }; + f.write_str(s) + } +} + +#[cfg(feature = "unstable")] +impl core::error::Error for BootPolicyError {} + +/// The UEFI boot policy is a property that influences the behaviour of +/// various UEFI functions that load files (typically UEFI images). +/// +/// This type is not ABI compatible. On the ABI level, this is an UEFI +/// boolean. +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialEq, PartialOrd)] +pub enum BootPolicy { + /// Indicates that the request originates from the boot manager, and that + /// the boot manager is attempting to load the provided `file_path` as a + /// boot selection. + /// + /// Boot selection refers to what a user has chosen in the (GUI) boot menu. + /// + /// This corresponds to the `TRUE` value in the UEFI spec. + BootSelection, + /// The provided `file_path` must match an exact file to be loaded. + /// + /// This corresponds to the `FALSE` value in the UEFI spec. + #[default] + ExactMatch, +} + +impl From for bool { + fn from(value: BootPolicy) -> Self { + match value { + BootPolicy::BootSelection => true, + BootPolicy::ExactMatch => false, + } + } +} + +impl From for BootPolicy { + fn from(value: bool) -> Self { + match value { + true => Self::BootSelection, + false => Self::ExactMatch, + } + } +} + +impl From for u8 { + fn from(value: BootPolicy) -> Self { + match value { + BootPolicy::BootSelection => 1, + BootPolicy::ExactMatch => 0, + } + } +} + +impl TryFrom for BootPolicy { + type Error = BootPolicyError; + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Self::ExactMatch), + 1 => Ok(Self::BootSelection), + err => Err(Self::Error::InvalidInteger(err)), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn boot_policy() { + assert_eq!(bool::from(BootPolicy::ExactMatch), false); + assert_eq!(bool::from(BootPolicy::BootSelection), true); + + assert_eq!(BootPolicy::from(false), BootPolicy::ExactMatch); + assert_eq!(BootPolicy::from(true), BootPolicy::BootSelection); + + assert_eq!(u8::from(BootPolicy::ExactMatch), 0); + assert_eq!(u8::from(BootPolicy::BootSelection), 1); + + assert_eq!(BootPolicy::try_from(0), Ok(BootPolicy::ExactMatch)); + assert_eq!(BootPolicy::try_from(1), Ok(BootPolicy::BootSelection)); + assert_eq!( + BootPolicy::try_from(2), + Err(BootPolicyError::InvalidInteger(2)) + ); + } +} diff --git a/uefi/src/proto/console/gop.rs b/uefi/src/proto/console/gop.rs index 5ae81596d..0676df547 100644 --- a/uefi/src/proto/console/gop.rs +++ b/uefi/src/proto/console/gop.rs @@ -102,7 +102,7 @@ impl GraphicsOutput { /// Returns a [`ModeIter`]. #[must_use] - pub fn modes<'a>(&'a self, bs: &'a BootServices) -> ModeIter { + pub const fn modes<'a>(&'a self, bs: &'a BootServices) -> ModeIter { ModeIter { gop: self, bs, diff --git a/uefi/src/proto/console/text/input.rs b/uefi/src/proto/console/text/input.rs index 20bb2db83..e681c97cf 100644 --- a/uefi/src/proto/console/text/input.rs +++ b/uefi/src/proto/console/text/input.rs @@ -98,11 +98,11 @@ pub enum Key { } impl From for Key { - fn from(k: InputKey) -> Key { + fn from(k: InputKey) -> Self { if k.scan_code == ScanCode::NULL.0 { - Key::Printable(Char16::try_from(k.unicode_char).unwrap()) + Self::Printable(Char16::try_from(k.unicode_char).unwrap()) } else { - Key::Special(ScanCode(k.scan_code)) + Self::Special(ScanCode(k.scan_code)) } } } diff --git a/uefi/src/proto/debug/exception.rs b/uefi/src/proto/debug/exception.rs index 4298a54b6..b2d453842 100644 --- a/uefi/src/proto/debug/exception.rs +++ b/uefi/src/proto/debug/exception.rs @@ -5,27 +5,27 @@ pub struct ExceptionType(isize); impl ExceptionType { /// Undefined Exception - pub const EXCEPT_EBC_UNDEFINED: ExceptionType = ExceptionType(0); + pub const EXCEPT_EBC_UNDEFINED: Self = Self(0); /// Divide-by-zero Error - pub const EXCEPT_EBC_DIVIDE_ERROR: ExceptionType = ExceptionType(1); + pub const EXCEPT_EBC_DIVIDE_ERROR: Self = Self(1); /// Debug Exception - pub const EXCEPT_EBC_DEBUG: ExceptionType = ExceptionType(2); + pub const EXCEPT_EBC_DEBUG: Self = Self(2); /// Breakpoint - pub const EXCEPT_EBC_BREAKPOINT: ExceptionType = ExceptionType(3); + pub const EXCEPT_EBC_BREAKPOINT: Self = Self(3); /// Overflow - pub const EXCEPT_EBC_OVERFLOW: ExceptionType = ExceptionType(4); + pub const EXCEPT_EBC_OVERFLOW: Self = Self(4); /// Invalid Opcode - pub const EXCEPT_EBC_INVALID_OPCODE: ExceptionType = ExceptionType(5); + pub const EXCEPT_EBC_INVALID_OPCODE: Self = Self(5); /// Stack-Segment Fault - pub const EXCEPT_EBC_STACK_FAULT: ExceptionType = ExceptionType(6); + pub const EXCEPT_EBC_STACK_FAULT: Self = Self(6); /// Alignment Check - pub const EXCEPT_EBC_ALIGNMENT_CHECK: ExceptionType = ExceptionType(7); + pub const EXCEPT_EBC_ALIGNMENT_CHECK: Self = Self(7); /// Instruction Encoding Exception - pub const EXCEPT_EBC_INSTRUCTION_ENCODING: ExceptionType = ExceptionType(8); + pub const EXCEPT_EBC_INSTRUCTION_ENCODING: Self = Self(8); /// Bad Breakpoint Exception - pub const EXCEPT_EBC_BAD_BREAK: ExceptionType = ExceptionType(9); + pub const EXCEPT_EBC_BAD_BREAK: Self = Self(9); /// Single Step Exception - pub const EXCEPT_EBC_SINGLE_STEP: ExceptionType = ExceptionType(10); + pub const EXCEPT_EBC_SINGLE_STEP: Self = Self(10); } #[cfg(target_arch = "x86")] @@ -69,39 +69,39 @@ impl ExceptionType { #[cfg(target_arch = "x86_64")] impl ExceptionType { /// Divide-by-zero Error - pub const EXCEPT_X64_DIVIDE_ERROR: ExceptionType = ExceptionType(0); + pub const EXCEPT_X64_DIVIDE_ERROR: Self = Self(0); /// Debug Exception - pub const EXCEPT_X64_DEBUG: ExceptionType = ExceptionType(1); + pub const EXCEPT_X64_DEBUG: Self = Self(1); /// Non-maskable Interrupt - pub const EXCEPT_X64_NMI: ExceptionType = ExceptionType(2); + pub const EXCEPT_X64_NMI: Self = Self(2); /// Breakpoint - pub const EXCEPT_X64_BREAKPOINT: ExceptionType = ExceptionType(3); + pub const EXCEPT_X64_BREAKPOINT: Self = Self(3); /// Overflow - pub const EXCEPT_X64_OVERFLOW: ExceptionType = ExceptionType(4); + pub const EXCEPT_X64_OVERFLOW: Self = Self(4); /// Bound Range Exceeded - pub const EXCEPT_X64_BOUND: ExceptionType = ExceptionType(5); + pub const EXCEPT_X64_BOUND: Self = Self(5); /// Invalid Opcode - pub const EXCEPT_X64_INVALID_OPCODE: ExceptionType = ExceptionType(6); + pub const EXCEPT_X64_INVALID_OPCODE: Self = Self(6); /// Double Fault - pub const EXCEPT_X64_DOUBLE_FAULT: ExceptionType = ExceptionType(8); + pub const EXCEPT_X64_DOUBLE_FAULT: Self = Self(8); /// Invalid TSS - pub const EXCEPT_X64_INVALID_TSS: ExceptionType = ExceptionType(10); + pub const EXCEPT_X64_INVALID_TSS: Self = Self(10); /// Segment Not Present - pub const EXCEPT_X64_SEG_NOT_PRESENT: ExceptionType = ExceptionType(11); + pub const EXCEPT_X64_SEG_NOT_PRESENT: Self = Self(11); /// Stack-Segment Fault - pub const EXCEPT_X64_STACK_FAULT: ExceptionType = ExceptionType(12); + pub const EXCEPT_X64_STACK_FAULT: Self = Self(12); /// General Protection Fault - pub const EXCEPT_X64_GP_FAULT: ExceptionType = ExceptionType(13); + pub const EXCEPT_X64_GP_FAULT: Self = Self(13); /// Page Fault - pub const EXCEPT_X64_PAGE_FAULT: ExceptionType = ExceptionType(14); + pub const EXCEPT_X64_PAGE_FAULT: Self = Self(14); /// x87 Floating-Point Exception - pub const EXCEPT_X64_FP_ERROR: ExceptionType = ExceptionType(16); + pub const EXCEPT_X64_FP_ERROR: Self = Self(16); /// Alignment Check - pub const EXCEPT_X64_ALIGNMENT_CHECK: ExceptionType = ExceptionType(17); + pub const EXCEPT_X64_ALIGNMENT_CHECK: Self = Self(17); /// Machine Check - pub const EXCEPT_X64_MACHINE_CHECK: ExceptionType = ExceptionType(18); + pub const EXCEPT_X64_MACHINE_CHECK: Self = Self(18); /// SIMD Floating-Point Exception - pub const EXCEPT_X64_SIMD: ExceptionType = ExceptionType(19); + pub const EXCEPT_X64_SIMD: Self = Self(19); } #[cfg(target_arch = "arm")] diff --git a/uefi/src/proto/device_path/device_path_gen.rs b/uefi/src/proto/device_path/device_path_gen.rs index cbd855a16..9dc098c1c 100644 --- a/uefi/src/proto/device_path/device_path_gen.rs +++ b/uefi/src/proto/device_path/device_path_gen.rs @@ -4,14 +4,15 @@ // `cargo xtask gen-code` // // See `/xtask/src/device_path/README.md` for more details. +#![allow(clippy::missing_const_for_fn)] use crate::data_types::UnalignedSlice; +use crate::mem::memory_map::MemoryType; use crate::polyfill::maybe_uninit_slice_as_mut_ptr; use crate::proto::device_path::{ DevicePathHeader, DevicePathNode, DeviceSubType, DeviceType, NodeConversionError, }; use crate::proto::network::IpAddress; -use crate::table::boot::MemoryType; use crate::{guid, Guid}; use bitflags::bitflags; use core::mem::{size_of, size_of_val}; diff --git a/uefi/src/proto/device_path/mod.rs b/uefi/src/proto/device_path/mod.rs index 52ce35417..feae09d97 100644 --- a/uefi/src/proto/device_path/mod.rs +++ b/uefi/src/proto/device_path/mod.rs @@ -171,7 +171,7 @@ impl DevicePathNode { /// remain valid for the lifetime `'a`, and cannot be mutated during /// that lifetime. #[must_use] - pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a DevicePathNode { + pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a Self { let header = *ptr.cast::(); let data_len = usize::from(header.length) - mem::size_of::(); @@ -217,7 +217,7 @@ impl DevicePathNode { /// Returns the payload data of this node. #[must_use] - pub fn data(&self) -> &[u8] { + pub const fn data(&self) -> &[u8] { &self.data } @@ -339,7 +339,7 @@ impl PartialEq for DevicePathInstance { #[cfg(feature = "alloc")] impl ToOwned for DevicePathInstance { - type Owned = Box; + type Owned = Box; fn to_owned(&self) -> Self::Owned { self.to_boxed() @@ -433,7 +433,7 @@ impl DevicePath { /// remain valid for the lifetime `'a`, and cannot be mutated during /// that lifetime. #[must_use] - pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a DevicePath { + pub unsafe fn from_ffi_ptr<'a>(ptr: *const FfiDevicePath) -> &'a Self { &*Self::ptr_from_ffi(ptr.cast::()) } @@ -527,7 +527,7 @@ impl<'a> TryFrom<&'a [u8]> for &'a DevicePath { #[cfg(feature = "alloc")] impl ToOwned for DevicePath { - type Owned = Box; + type Owned = Box; fn to_owned(&self) -> Self::Owned { self.to_boxed() @@ -678,118 +678,118 @@ pub struct DeviceSubType(pub u8); impl DeviceSubType { /// PCI Device Path. - pub const HARDWARE_PCI: DeviceSubType = DeviceSubType(1); + pub const HARDWARE_PCI: Self = Self(1); /// PCCARD Device Path. - pub const HARDWARE_PCCARD: DeviceSubType = DeviceSubType(2); + pub const HARDWARE_PCCARD: Self = Self(2); /// Memory-mapped Device Path. - pub const HARDWARE_MEMORY_MAPPED: DeviceSubType = DeviceSubType(3); + pub const HARDWARE_MEMORY_MAPPED: Self = Self(3); /// Vendor-Defined Device Path. - pub const HARDWARE_VENDOR: DeviceSubType = DeviceSubType(4); + pub const HARDWARE_VENDOR: Self = Self(4); /// Controller Device Path. - pub const HARDWARE_CONTROLLER: DeviceSubType = DeviceSubType(5); + pub const HARDWARE_CONTROLLER: Self = Self(5); /// BMC Device Path. - pub const HARDWARE_BMC: DeviceSubType = DeviceSubType(6); + pub const HARDWARE_BMC: Self = Self(6); /// ACPI Device Path. - pub const ACPI: DeviceSubType = DeviceSubType(1); + pub const ACPI: Self = Self(1); /// Expanded ACPI Device Path. - pub const ACPI_EXPANDED: DeviceSubType = DeviceSubType(2); + pub const ACPI_EXPANDED: Self = Self(2); /// ACPI _ADR Device Path. - pub const ACPI_ADR: DeviceSubType = DeviceSubType(3); + pub const ACPI_ADR: Self = Self(3); /// NVDIMM Device Path. - pub const ACPI_NVDIMM: DeviceSubType = DeviceSubType(4); + pub const ACPI_NVDIMM: Self = Self(4); /// ATAPI Device Path. - pub const MESSAGING_ATAPI: DeviceSubType = DeviceSubType(1); + pub const MESSAGING_ATAPI: Self = Self(1); /// SCSI Device Path. - pub const MESSAGING_SCSI: DeviceSubType = DeviceSubType(2); + pub const MESSAGING_SCSI: Self = Self(2); /// Fibre Channel Device Path. - pub const MESSAGING_FIBRE_CHANNEL: DeviceSubType = DeviceSubType(3); + pub const MESSAGING_FIBRE_CHANNEL: Self = Self(3); /// 1394 Device Path. - pub const MESSAGING_1394: DeviceSubType = DeviceSubType(4); + pub const MESSAGING_1394: Self = Self(4); /// USB Device Path. - pub const MESSAGING_USB: DeviceSubType = DeviceSubType(5); + pub const MESSAGING_USB: Self = Self(5); /// I2O Device Path. - pub const MESSAGING_I2O: DeviceSubType = DeviceSubType(6); + pub const MESSAGING_I2O: Self = Self(6); /// Infiniband Device Path. - pub const MESSAGING_INFINIBAND: DeviceSubType = DeviceSubType(9); + pub const MESSAGING_INFINIBAND: Self = Self(9); /// Vendor-Defined Device Path. - pub const MESSAGING_VENDOR: DeviceSubType = DeviceSubType(10); + pub const MESSAGING_VENDOR: Self = Self(10); /// MAC Address Device Path. - pub const MESSAGING_MAC_ADDRESS: DeviceSubType = DeviceSubType(11); + pub const MESSAGING_MAC_ADDRESS: Self = Self(11); /// IPV4 Device Path. - pub const MESSAGING_IPV4: DeviceSubType = DeviceSubType(12); + pub const MESSAGING_IPV4: Self = Self(12); /// IPV6 Device Path. - pub const MESSAGING_IPV6: DeviceSubType = DeviceSubType(13); + pub const MESSAGING_IPV6: Self = Self(13); /// UART Device Path. - pub const MESSAGING_UART: DeviceSubType = DeviceSubType(14); + pub const MESSAGING_UART: Self = Self(14); /// USB Class Device Path. - pub const MESSAGING_USB_CLASS: DeviceSubType = DeviceSubType(15); + pub const MESSAGING_USB_CLASS: Self = Self(15); /// USB WWID Device Path. - pub const MESSAGING_USB_WWID: DeviceSubType = DeviceSubType(16); + pub const MESSAGING_USB_WWID: Self = Self(16); /// Device Logical Unit. - pub const MESSAGING_DEVICE_LOGICAL_UNIT: DeviceSubType = DeviceSubType(17); + pub const MESSAGING_DEVICE_LOGICAL_UNIT: Self = Self(17); /// SATA Device Path. - pub const MESSAGING_SATA: DeviceSubType = DeviceSubType(18); + pub const MESSAGING_SATA: Self = Self(18); /// iSCSI Device Path node (base information). - pub const MESSAGING_ISCSI: DeviceSubType = DeviceSubType(19); + pub const MESSAGING_ISCSI: Self = Self(19); /// VLAN Device Path node. - pub const MESSAGING_VLAN: DeviceSubType = DeviceSubType(20); + pub const MESSAGING_VLAN: Self = Self(20); /// Fibre Channel Ex Device Path. - pub const MESSAGING_FIBRE_CHANNEL_EX: DeviceSubType = DeviceSubType(21); + pub const MESSAGING_FIBRE_CHANNEL_EX: Self = Self(21); /// Serial Attached SCSI (SAS) Ex Device Path. - pub const MESSAGING_SCSI_SAS_EX: DeviceSubType = DeviceSubType(22); + pub const MESSAGING_SCSI_SAS_EX: Self = Self(22); /// NVM Express Namespace Device Path. - pub const MESSAGING_NVME_NAMESPACE: DeviceSubType = DeviceSubType(23); + pub const MESSAGING_NVME_NAMESPACE: Self = Self(23); /// Uniform Resource Identifiers (URI) Device Path. - pub const MESSAGING_URI: DeviceSubType = DeviceSubType(24); + pub const MESSAGING_URI: Self = Self(24); /// UFS Device Path. - pub const MESSAGING_UFS: DeviceSubType = DeviceSubType(25); + pub const MESSAGING_UFS: Self = Self(25); /// SD (Secure Digital) Device Path. - pub const MESSAGING_SD: DeviceSubType = DeviceSubType(26); + pub const MESSAGING_SD: Self = Self(26); /// Bluetooth Device Path. - pub const MESSAGING_BLUETOOTH: DeviceSubType = DeviceSubType(27); + pub const MESSAGING_BLUETOOTH: Self = Self(27); /// Wi-Fi Device Path. - pub const MESSAGING_WIFI: DeviceSubType = DeviceSubType(28); + pub const MESSAGING_WIFI: Self = Self(28); /// eMMC (Embedded Multi-Media Card) Device Path. - pub const MESSAGING_EMMC: DeviceSubType = DeviceSubType(29); + pub const MESSAGING_EMMC: Self = Self(29); /// BluetoothLE Device Path. - pub const MESSAGING_BLUETOOTH_LE: DeviceSubType = DeviceSubType(30); + pub const MESSAGING_BLUETOOTH_LE: Self = Self(30); /// DNS Device Path. - pub const MESSAGING_DNS: DeviceSubType = DeviceSubType(31); + pub const MESSAGING_DNS: Self = Self(31); /// NVDIMM Namespace Device Path. - pub const MESSAGING_NVDIMM_NAMESPACE: DeviceSubType = DeviceSubType(32); + pub const MESSAGING_NVDIMM_NAMESPACE: Self = Self(32); /// REST Service Device Path. - pub const MESSAGING_REST_SERVICE: DeviceSubType = DeviceSubType(33); + pub const MESSAGING_REST_SERVICE: Self = Self(33); /// NVME over Fabric (NVMe-oF) Namespace Device Path. - pub const MESSAGING_NVME_OF_NAMESPACE: DeviceSubType = DeviceSubType(34); + pub const MESSAGING_NVME_OF_NAMESPACE: Self = Self(34); /// Hard Drive Media Device Path. - pub const MEDIA_HARD_DRIVE: DeviceSubType = DeviceSubType(1); + pub const MEDIA_HARD_DRIVE: Self = Self(1); /// CD-ROM Media Device Path. - pub const MEDIA_CD_ROM: DeviceSubType = DeviceSubType(2); + pub const MEDIA_CD_ROM: Self = Self(2); /// Vendor-Defined Media Device Path. - pub const MEDIA_VENDOR: DeviceSubType = DeviceSubType(3); + pub const MEDIA_VENDOR: Self = Self(3); /// File Path Media Device Path. - pub const MEDIA_FILE_PATH: DeviceSubType = DeviceSubType(4); + pub const MEDIA_FILE_PATH: Self = Self(4); /// Media Protocol Device Path. - pub const MEDIA_PROTOCOL: DeviceSubType = DeviceSubType(5); + pub const MEDIA_PROTOCOL: Self = Self(5); /// PIWG Firmware File. - pub const MEDIA_PIWG_FIRMWARE_FILE: DeviceSubType = DeviceSubType(6); + pub const MEDIA_PIWG_FIRMWARE_FILE: Self = Self(6); /// PIWG Firmware Volume. - pub const MEDIA_PIWG_FIRMWARE_VOLUME: DeviceSubType = DeviceSubType(7); + pub const MEDIA_PIWG_FIRMWARE_VOLUME: Self = Self(7); /// Relative Offset Range. - pub const MEDIA_RELATIVE_OFFSET_RANGE: DeviceSubType = DeviceSubType(8); + pub const MEDIA_RELATIVE_OFFSET_RANGE: Self = Self(8); /// RAM Disk Device Path. - pub const MEDIA_RAM_DISK: DeviceSubType = DeviceSubType(9); + pub const MEDIA_RAM_DISK: Self = Self(9); /// BIOS Boot Specification Device Path. - pub const BIOS_BOOT_SPECIFICATION: DeviceSubType = DeviceSubType(1); + pub const BIOS_BOOT_SPECIFICATION: Self = Self(1); /// End this instance of a Device Path and start a new one. - pub const END_INSTANCE: DeviceSubType = DeviceSubType(0x01); + pub const END_INSTANCE: Self = Self(0x01); /// End entire Device Path. - pub const END_ENTIRE: DeviceSubType = DeviceSubType(0xff); + pub const END_ENTIRE: Self = Self(0xff); } /// Error returned when attempting to convert from a `&[u8]` to a diff --git a/uefi/src/proto/loaded_image.rs b/uefi/src/proto/loaded_image.rs index 0070b668d..7e1bf2071 100644 --- a/uefi/src/proto/loaded_image.rs +++ b/uefi/src/proto/loaded_image.rs @@ -1,9 +1,9 @@ //! `LoadedImage` protocol. use crate::data_types::FromSliceWithNulError; +use crate::mem::memory_map::MemoryType; use crate::proto::device_path::DevicePath; use crate::proto::unsafe_protocol; -use crate::table::boot::MemoryType; use crate::util::usize_from_u32; use crate::{CStr16, Handle, Status}; use core::ffi::c_void; @@ -185,7 +185,7 @@ impl LoadedImage { /// - `MemoryType::BOOT_SERVICES_CODE` for UEFI boot drivers /// - `MemoryType::RUNTIME_SERVICES_CODE` for UEFI runtime drivers #[must_use] - pub fn code_type(&self) -> MemoryType { + pub const fn code_type(&self) -> MemoryType { self.0.image_code_type } @@ -196,7 +196,7 @@ impl LoadedImage { /// - `MemoryType::BOOT_SERVICES_DATA` for UEFI boot drivers /// - `MemoryType::RUNTIME_SERVICES_DATA` for UEFI runtime drivers #[must_use] - pub fn data_type(&self) -> MemoryType { + pub const fn data_type(&self) -> MemoryType { self.0.image_data_type } } diff --git a/uefi/src/proto/media/file/dir.rs b/uefi/src/proto/media/file/dir.rs index 1ba826963..9ab3b49c2 100644 --- a/uefi/src/proto/media/file/dir.rs +++ b/uefi/src/proto/media/file/dir.rs @@ -22,7 +22,7 @@ impl Directory { /// This function should only be called on files which ARE directories, /// doing otherwise is unsafe. #[must_use] - pub unsafe fn new(handle: FileHandle) -> Self { + pub const unsafe fn new(handle: FileHandle) -> Self { Self(RegularFile::new(handle)) } diff --git a/uefi/src/proto/media/file/info.rs b/uefi/src/proto/media/file/info.rs index 847c02d95..adce16c4d 100644 --- a/uefi/src/proto/media/file/info.rs +++ b/uefi/src/proto/media/file/info.rs @@ -248,13 +248,13 @@ impl FileInfo { /// Returns if the file is a directory. #[must_use] - pub fn is_directory(&self) -> bool { + pub const fn is_directory(&self) -> bool { self.attribute.contains(FileAttribute::DIRECTORY) } /// Returns if the file is a regular file. #[must_use] - pub fn is_regular_file(&self) -> bool { + pub const fn is_regular_file(&self) -> bool { !self.is_directory() } } diff --git a/uefi/src/proto/media/file/regular.rs b/uefi/src/proto/media/file/regular.rs index c3f23a5d8..1656b9006 100644 --- a/uefi/src/proto/media/file/regular.rs +++ b/uefi/src/proto/media/file/regular.rs @@ -19,7 +19,7 @@ impl RegularFile { /// This function should only be called on handles which ARE NOT directories, /// doing otherwise is unsafe. #[must_use] - pub unsafe fn new(handle: FileHandle) -> Self { + pub const unsafe fn new(handle: FileHandle) -> Self { Self(handle) } diff --git a/uefi/src/proto/media/load_file.rs b/uefi/src/proto/media/load_file.rs new file mode 100644 index 000000000..6f5d74634 --- /dev/null +++ b/uefi/src/proto/media/load_file.rs @@ -0,0 +1,160 @@ +//! LoadFile and LoadFile2 protocols. + +use crate::proto::unsafe_protocol; +#[cfg(all(feature = "alloc", feature = "unstable"))] +use alloc::alloc::Global; +use uefi_raw::protocol::media::{LoadFile2Protocol, LoadFileProtocol}; +#[cfg(feature = "alloc")] +use { + crate::{mem::make_boxed, proto::device_path::DevicePath, Result, StatusExt}, + alloc::boxed::Box, + uefi::proto::BootPolicy, +}; + +/// Load File Protocol. +/// +/// Used to obtain files, that are primarily boot options, from arbitrary +/// devices. +/// +/// # UEFI Spec Description +/// The EFI_LOAD_FILE_PROTOCOL is a simple protocol used to obtain files from +/// arbitrary devices. +/// +/// When the firmware is attempting to load a file, it first attempts to use the +/// device’s Simple File System protocol to read the file. If the file system +/// protocol is found, the firmware implements the policy of interpreting the +/// File Path value of the file being loaded. If the device does not support the +/// file system protocol, the firmware then attempts to read the file via the +/// EFI_LOAD_FILE_PROTOCOL and the LoadFile() function. In this case the +/// LoadFile() function implements the policy of interpreting the File Path +/// value. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(LoadFileProtocol::GUID)] +pub struct LoadFile(LoadFileProtocol); + +impl LoadFile { + /// Causes the driver to load a specified file. + /// + /// # Parameters + /// - `file_path` The device specific path of the file to load. + /// - `boot_policy` The [`BootPolicy`] to use. + /// + /// # Errors + /// - `uefi::status::EFI_SUCCESS` The file was loaded. + /// - `uefi::status::EFI_UNSUPPORTED` The device does not support the + /// provided BootPolicy. + /// - `uefi::status::EFI_INVALID_PARAMETER` FilePath is not a valid device + /// path, or BufferSize is NULL. + /// - `uefi::status::EFI_NO_MEDIA` No medium was present to load the file. + /// - `uefi::status::EFI_DEVICE_ERROR` The file was not loaded due to a + /// device error. + /// - `uefi::status::EFI_NO_RESPONSE` The remote system did not respond. + /// - `uefi::status::EFI_NOT_FOUND` The file was not found. + /// - `uefi::status::EFI_ABORTED` The file load process was manually + /// cancelled. + /// - `uefi::status::EFI_BUFFER_TOO_SMALL` The BufferSize is too small to + /// read the current directory entry. BufferSize has been updated with the + /// size needed to complete the request. + /// - `uefi::status::EFI_WARN_FILE_SYSTEM` The resulting Buffer contains + /// UEFI-compliant file system. + /// + /// [`BootPolicy`]: uefi::proto::BootPolicy + #[cfg(feature = "alloc")] + #[allow(clippy::extra_unused_lifetimes)] // false positive, it is used + pub fn load_file<'a>( + &mut self, + file_path: &DevicePath, + boot_policy: BootPolicy, + ) -> Result> { + let this = core::ptr::addr_of_mut!(*self).cast(); + + let fetch_data_fn = |buf: &'a mut [u8]| { + let mut size = buf.len(); + let status = unsafe { + (self.0.load_file)( + this, + file_path.as_ffi_ptr().cast(), + boot_policy.into(), + &mut size, + buf.as_mut_ptr().cast(), + ) + }; + status.to_result_with_err(|_| Some(size)).map(|_| buf) + }; + + #[cfg(not(feature = "unstable"))] + let file: Box<[u8]> = make_boxed::<[u8], _>(fetch_data_fn)?; + + #[cfg(feature = "unstable")] + let file = make_boxed::<[u8], _, _>(fetch_data_fn, Global)?; + + Ok(file) + } +} + +/// Load File2 Protocol. +/// +/// The Load File2 protocol is used to obtain files from arbitrary devices that +/// are not boot options. +/// +/// # UEFI Spec Description +/// +/// The EFI_LOAD_FILE2_PROTOCOL is a simple protocol used to obtain files from +/// arbitrary devices that are not boot options. It is used by LoadImage() when +/// its BootOption parameter is FALSE and the FilePath does not have an instance +/// of the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(LoadFile2Protocol::GUID)] +pub struct LoadFile2(LoadFile2Protocol); + +impl LoadFile2 { + /// Causes the driver to load a specified file. + /// + /// # Parameters + /// - `file_path` The device specific path of the file to load. + /// + /// # Errors + /// - `uefi::status::EFI_SUCCESS` The file was loaded. + /// - `uefi::status::EFI_UNSUPPORTED` BootPolicy is TRUE. + /// - `uefi::status::EFI_INVALID_PARAMETER` FilePath is not a valid device + /// path, or BufferSize is NULL. + /// - `uefi::status::EFI_NO_MEDIA` No medium was present to load the file. + /// - `uefi::status::EFI_DEVICE_ERROR` The file was not loaded due to a + /// device error. + /// - `uefi::status::EFI_NO_RESPONSE` The remote system did not respond. + /// - `uefi::status::EFI_NOT_FOUND` The file was not found. + /// - `uefi::status::EFI_ABORTED` The file load process was manually + /// cancelled. + /// - `uefi::status::EFI_BUFFER_TOO_SMALL` The BufferSize is too small to + /// read the current directory entry. BufferSize has been updated with the + /// size needed to complete the request. + #[cfg(feature = "alloc")] + #[allow(clippy::extra_unused_lifetimes)] // false positive, it is used + pub fn load_file<'a>(&mut self, file_path: &DevicePath) -> Result> { + let this = core::ptr::addr_of_mut!(*self).cast(); + + let fetch_data_fn = |buf: &'a mut [u8]| { + let mut size = buf.len(); + let status = unsafe { + (self.0.load_file)( + this, + file_path.as_ffi_ptr().cast(), + false, /* always false - see spec */ + &mut size, + buf.as_mut_ptr().cast(), + ) + }; + status.to_result_with_err(|_| Some(size)).map(|_| buf) + }; + + #[cfg(not(feature = "unstable"))] + let file: Box<[u8]> = make_boxed::<[u8], _>(fetch_data_fn)?; + + #[cfg(feature = "unstable")] + let file = make_boxed::<[u8], _, _>(fetch_data_fn, Global)?; + + Ok(file) + } +} diff --git a/uefi/src/proto/media/mod.rs b/uefi/src/proto/media/mod.rs index 6750875a6..cd1473a73 100644 --- a/uefi/src/proto/media/mod.rs +++ b/uefi/src/proto/media/mod.rs @@ -9,4 +9,5 @@ pub mod file; pub mod block; pub mod disk; pub mod fs; +pub mod load_file; pub mod partition; diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 5919b7afc..218f3ffb0 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -9,6 +9,27 @@ //! //! [`BootServices`]: crate::table::boot::BootServices#accessing-protocols +pub mod console; +pub mod debug; +pub mod device_path; +pub mod driver; +pub mod loaded_image; +pub mod media; +pub mod misc; +pub mod network; +pub mod pi; +pub mod rng; +pub mod security; +pub mod shell_params; +pub mod shim; +pub mod string; +pub mod tcg; + +mod boot_policy; + +pub use boot_policy::{BootPolicy, BootPolicyError}; +pub use uefi_macros::unsafe_protocol; + use crate::Identify; use core::ffi::c_void; @@ -63,21 +84,3 @@ where ptr.cast::() } } - -pub use uefi_macros::unsafe_protocol; - -pub mod console; -pub mod debug; -pub mod device_path; -pub mod driver; -pub mod loaded_image; -pub mod media; -pub mod misc; -pub mod network; -pub mod pi; -pub mod rng; -pub mod security; -pub mod shell_params; -pub mod shim; -pub mod string; -pub mod tcg; diff --git a/uefi/src/proto/network/snp.rs b/uefi/src/proto/network/snp.rs index ee9c2a999..05a7ffd7f 100644 --- a/uefi/src/proto/network/snp.rs +++ b/uefi/src/proto/network/snp.rs @@ -261,13 +261,13 @@ impl SimpleNetwork { /// On QEMU, this event seems to never fire; it is suggested to verify that your implementation /// of UEFI properly implements this event before using it. #[must_use] - pub fn wait_for_packet(&self) -> &Event { + pub const fn wait_for_packet(&self) -> &Event { &self.wait_for_packet } /// Returns a reference to the Simple Network mode. #[must_use] - pub fn mode(&self) -> &NetworkMode { + pub const fn mode(&self) -> &NetworkMode { unsafe { &*self.mode } } } @@ -347,14 +347,14 @@ pub struct NetworkStats { impl NetworkStats { /// Any statistic value of -1 is not available - fn available(&self, stat: u64) -> bool { + const fn available(&self, stat: u64) -> bool { stat as i64 != -1 } /// Takes a statistic and converts it to an option /// /// When the statistic is not available, `None` is returned - fn to_option(&self, stat: u64) -> Option { + const fn to_option(&self, stat: u64) -> Option { match self.available(stat) { true => Some(stat), false => None, @@ -364,175 +364,175 @@ impl NetworkStats { /// The total number of frames received, including error frames /// and dropped frames #[must_use] - pub fn rx_total_frames(&self) -> Option { + pub const fn rx_total_frames(&self) -> Option { self.to_option(self.rx_total_frames) } /// The total number of good frames received and copied /// into receive buffers #[must_use] - pub fn rx_good_frames(&self) -> Option { + pub const fn rx_good_frames(&self) -> Option { self.to_option(self.rx_good_frames) } /// The number of frames below the minimum length for the /// communications device #[must_use] - pub fn rx_undersize_frames(&self) -> Option { + pub const fn rx_undersize_frames(&self) -> Option { self.to_option(self.rx_undersize_frames) } /// The number of frames longer than the maximum length for /// the communications length device #[must_use] - pub fn rx_oversize_frames(&self) -> Option { + pub const fn rx_oversize_frames(&self) -> Option { self.to_option(self.rx_oversize_frames) } /// The number of valid frames that were dropped because /// the receive buffers were full #[must_use] - pub fn rx_dropped_frames(&self) -> Option { + pub const fn rx_dropped_frames(&self) -> Option { self.to_option(self.rx_dropped_frames) } /// The number of valid unicast frames received and not dropped #[must_use] - pub fn rx_unicast_frames(&self) -> Option { + pub const fn rx_unicast_frames(&self) -> Option { self.to_option(self.rx_unicast_frames) } /// The number of valid broadcast frames received and not dropped #[must_use] - pub fn rx_broadcast_frames(&self) -> Option { + pub const fn rx_broadcast_frames(&self) -> Option { self.to_option(self.rx_broadcast_frames) } /// The number of valid multicast frames received and not dropped #[must_use] - pub fn rx_multicast_frames(&self) -> Option { + pub const fn rx_multicast_frames(&self) -> Option { self.to_option(self.rx_multicast_frames) } /// Number of frames with CRC or alignment errors #[must_use] - pub fn rx_crc_error_frames(&self) -> Option { + pub const fn rx_crc_error_frames(&self) -> Option { self.to_option(self.rx_crc_error_frames) } /// The total number of bytes received including frames with errors /// and dropped frames #[must_use] - pub fn rx_total_bytes(&self) -> Option { + pub const fn rx_total_bytes(&self) -> Option { self.to_option(self.rx_total_bytes) } /// The total number of frames transmitted including frames /// with errors and dropped frames #[must_use] - pub fn tx_total_frames(&self) -> Option { + pub const fn tx_total_frames(&self) -> Option { self.to_option(self.tx_total_frames) } /// The total number of valid frames transmitted and copied /// into receive buffers #[must_use] - pub fn tx_good_frames(&self) -> Option { + pub const fn tx_good_frames(&self) -> Option { self.to_option(self.tx_good_frames) } /// The number of frames below the minimum length for /// the media. This would be less than 64 for Ethernet #[must_use] - pub fn tx_undersize_frames(&self) -> Option { + pub const fn tx_undersize_frames(&self) -> Option { self.to_option(self.tx_undersize_frames) } /// The number of frames longer than the maximum length for /// the media. This would be 1500 for Ethernet #[must_use] - pub fn tx_oversize_frames(&self) -> Option { + pub const fn tx_oversize_frames(&self) -> Option { self.to_option(self.tx_oversize_frames) } /// The number of valid frames that were dropped because /// received buffers were full #[must_use] - pub fn tx_dropped_frames(&self) -> Option { + pub const fn tx_dropped_frames(&self) -> Option { self.to_option(self.tx_dropped_frames) } /// The number of valid unicast frames transmitted and not /// dropped #[must_use] - pub fn tx_unicast_frames(&self) -> Option { + pub const fn tx_unicast_frames(&self) -> Option { self.to_option(self.tx_unicast_frames) } /// The number of valid broadcast frames transmitted and /// not dropped #[must_use] - pub fn tx_broadcast_frames(&self) -> Option { + pub const fn tx_broadcast_frames(&self) -> Option { self.to_option(self.tx_broadcast_frames) } /// The number of valid multicast frames transmitted /// and not dropped #[must_use] - pub fn tx_multicast_frames(&self) -> Option { + pub const fn tx_multicast_frames(&self) -> Option { self.to_option(self.tx_multicast_frames) } /// The number of transmitted frames with CRC or /// alignment errors #[must_use] - pub fn tx_crc_error_frames(&self) -> Option { + pub const fn tx_crc_error_frames(&self) -> Option { self.to_option(self.tx_crc_error_frames) } /// The total number of bytes transmitted including /// error frames and dropped frames #[must_use] - pub fn tx_total_bytes(&self) -> Option { + pub const fn tx_total_bytes(&self) -> Option { self.to_option(self.tx_total_bytes) } /// The number of collisions detected on this subnet #[must_use] - pub fn collisions(&self) -> Option { + pub const fn collisions(&self) -> Option { self.to_option(self.collisions) } /// The number of frames destined for unsupported protocol #[must_use] - pub fn unsupported_protocol(&self) -> Option { + pub const fn unsupported_protocol(&self) -> Option { self.to_option(self.unsupported_protocol) } /// The number of valid frames received that were duplicated #[must_use] - pub fn rx_duplicated_frames(&self) -> Option { + pub const fn rx_duplicated_frames(&self) -> Option { self.to_option(self.rx_duplicated_frames) } /// The number of encrypted frames received that failed /// to decrypt #[must_use] - pub fn rx_decrypt_error_frames(&self) -> Option { + pub const fn rx_decrypt_error_frames(&self) -> Option { self.to_option(self.rx_decrypt_error_frames) } /// The number of frames that failed to transmit after /// exceeding the retry limit #[must_use] - pub fn tx_error_frames(&self) -> Option { + pub const fn tx_error_frames(&self) -> Option { self.to_option(self.tx_error_frames) } /// The number of frames that transmitted successfully /// after more than one attempt #[must_use] - pub fn tx_retry_frames(&self) -> Option { + pub const fn tx_retry_frames(&self) -> Option { self.to_option(self.tx_retry_frames) } } diff --git a/uefi/src/proto/security/memory_protection.rs b/uefi/src/proto/security/memory_protection.rs index 1b7375aa9..fa94452d2 100644 --- a/uefi/src/proto/security/memory_protection.rs +++ b/uefi/src/proto/security/memory_protection.rs @@ -1,6 +1,6 @@ use crate::data_types::PhysicalAddress; +use crate::mem::memory_map::MemoryAttribute; use crate::proto::unsafe_protocol; -use crate::table::boot::MemoryAttribute; use crate::{Result, StatusExt}; use core::ops::Range; use uefi_raw::protocol::memory_protection::MemoryAttributeProtocol; diff --git a/uefi/src/proto/shell_params.rs b/uefi/src/proto/shell_params.rs index e1f084f8f..b0aaec81c 100644 --- a/uefi/src/proto/shell_params.rs +++ b/uefi/src/proto/shell_params.rs @@ -16,7 +16,7 @@ pub struct ShellParameters(ShellParametersProtocol); impl ShellParameters { /// Get the number of shell parameter arguments #[must_use] - pub fn args_len(&self) -> usize { + pub const fn args_len(&self) -> usize { self.0.argc } @@ -29,7 +29,7 @@ impl ShellParameters { /// Get a slice of the args, as Char16 pointers #[must_use] - fn args_slice(&self) -> &[*const Char16] { + const fn args_slice(&self) -> &[*const Char16] { unsafe { from_raw_parts( self.0.argv.cast::<*const data_types::chars::Char16>(), diff --git a/uefi/src/proto/tcg/v1.rs b/uefi/src/proto/tcg/v1.rs index 7f0226806..40ddf6731 100644 --- a/uefi/src/proto/tcg/v1.rs +++ b/uefi/src/proto/tcg/v1.rs @@ -9,17 +9,21 @@ //! [TPM]: https://en.wikipedia.org/wiki/Trusted_Platform_Module use super::{AlgorithmId, EventType, HashAlgorithm, PcrIndex}; -use crate::data_types::PhysicalAddress; -use crate::polyfill::maybe_uninit_slice_as_mut_ptr; +use crate::data_types::{Align, PhysicalAddress}; use crate::proto::unsafe_protocol; use crate::util::{ptr_write_unaligned_and_add, usize_from_u32}; use crate::{Error, Result, Status, StatusExt}; use core::fmt::{self, Debug, Formatter}; use core::marker::PhantomData; -use core::mem::{self, MaybeUninit}; -use core::ptr; +use core::{mem, ptr}; use ptr_meta::Pointee; +#[cfg(feature = "alloc")] +use {crate::mem::make_boxed, alloc::boxed::Box}; + +#[cfg(all(feature = "unstable", feature = "alloc"))] +use alloc::alloc::Global; + /// 20-byte SHA-1 digest. pub type Sha1Digest = [u8; 20]; @@ -45,13 +49,13 @@ pub struct BootServiceCapability { impl BootServiceCapability { /// Version of the `BootServiceCapability` structure. #[must_use] - pub fn structure_version(&self) -> Version { + pub const fn structure_version(&self) -> Version { self.structure_version } /// Version of the `Tcg` protocol. #[must_use] - pub fn protocol_spec_version(&self) -> Version { + pub const fn protocol_spec_version(&self) -> Version { self.protocol_spec_version } @@ -65,13 +69,13 @@ impl BootServiceCapability { /// Whether the TPM device is present. #[must_use] - pub fn tpm_present(&self) -> bool { + pub const fn tpm_present(&self) -> bool { self.tpm_present_flag != 0 } /// Whether the TPM device is deactivated. #[must_use] - pub fn tpm_deactivated(&self) -> bool { + pub const fn tpm_deactivated(&self) -> bool { self.tpm_deactivated_flag != 0 } } @@ -128,19 +132,19 @@ impl PcrEvent { /// # Errors /// /// Returns [`Status::BUFFER_TOO_SMALL`] if the `buffer` is not large - /// enough. + /// enough. The required size will be returned in the error data. /// /// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too /// large. pub fn new_in_buffer<'buf>( - buffer: &'buf mut [MaybeUninit], + buffer: &'buf mut [u8], pcr_index: PcrIndex, event_type: EventType, digest: Sha1Digest, event_data: &[u8], - ) -> Result<&'buf mut Self> { - let event_data_size = - u32::try_from(event_data.len()).map_err(|_| Error::from(Status::INVALID_PARAMETER))?; + ) -> Result<&'buf mut Self, Option> { + let event_data_size = u32::try_from(event_data.len()) + .map_err(|_| Error::new(Status::INVALID_PARAMETER, None))?; let required_size = mem::size_of::() + mem::size_of::() @@ -149,10 +153,10 @@ impl PcrEvent { + event_data.len(); if buffer.len() < required_size { - return Err(Status::BUFFER_TOO_SMALL.into()); + return Err(Error::new(Status::BUFFER_TOO_SMALL, Some(required_size))); } - let mut ptr: *mut u8 = maybe_uninit_slice_as_mut_ptr(buffer); + let mut ptr: *mut u8 = buffer.as_mut_ptr().cast(); unsafe { ptr_write_unaligned_and_add(&mut ptr, pcr_index); @@ -161,15 +165,41 @@ impl PcrEvent { ptr_write_unaligned_and_add(&mut ptr, event_data_size); ptr::copy(event_data.as_ptr(), ptr, event_data.len()); - let ptr: *mut PcrEvent = + let ptr: *mut Self = ptr_meta::from_raw_parts_mut(buffer.as_mut_ptr().cast(), event_data.len()); Ok(&mut *ptr) } } + /// Create a new `PcrEvent` in a [`Box`]. + /// + /// # Errors + /// + /// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too + /// large. + #[cfg(feature = "alloc")] + pub fn new_in_box( + pcr_index: PcrIndex, + event_type: EventType, + digest: Sha1Digest, + event_data: &[u8], + ) -> Result> { + #[cfg(not(feature = "unstable"))] + { + make_boxed(|buf| Self::new_in_buffer(buf, pcr_index, event_type, digest, event_data)) + } + #[cfg(feature = "unstable")] + { + make_boxed( + |buf| Self::new_in_buffer(buf, pcr_index, event_type, digest, event_data), + Global, + ) + } + } + /// PCR index for the event. #[must_use] - pub fn pcr_index(&self) -> PcrIndex { + pub const fn pcr_index(&self) -> PcrIndex { self.pcr_index } @@ -177,7 +207,7 @@ impl PcrEvent { /// /// [`event_data`]: Self::event_data #[must_use] - pub fn event_type(&self) -> EventType { + pub const fn event_type(&self) -> EventType { self.event_type } @@ -189,17 +219,23 @@ impl PcrEvent { /// [`digest`]: Self::digest /// [`event_type`]: Self::event_type #[must_use] - pub fn event_data(&self) -> &[u8] { + pub const fn event_data(&self) -> &[u8] { &self.event_data } /// SHA-1 digest of the data hashed for this event. #[must_use] - pub fn digest(&self) -> Sha1Digest { + pub const fn digest(&self) -> Sha1Digest { self.digest } } +impl Align for PcrEvent { + fn alignment() -> usize { + 1 + } +} + // Manual `Debug` implementation since it can't be derived for a packed DST. impl Debug for PcrEvent { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { @@ -252,7 +288,7 @@ pub struct EventLog<'a> { } impl<'a> EventLog<'a> { - pub(super) unsafe fn new( + pub(super) const unsafe fn new( location: *const u8, last_entry: *const u8, is_truncated: bool, @@ -267,7 +303,7 @@ impl<'a> EventLog<'a> { /// Iterator of events in the log. #[must_use] - pub fn iter(&self) -> EventLogIter { + pub const fn iter(&self) -> EventLogIter { EventLogIter { log: self, location: self.location, @@ -283,7 +319,7 @@ impl<'a> EventLog<'a> { /// /// [`v1::Tcg`]: Tcg #[must_use] - pub fn is_truncated(&self) -> bool { + pub const fn is_truncated(&self) -> bool { self.is_truncated } } @@ -533,7 +569,7 @@ mod tests { #[test] fn test_new_pcr_event() { - let mut event_buf = [MaybeUninit::uninit(); 256]; + let mut event_buf = [0; 256]; #[rustfmt::skip] let digest = [ 0x00, 0x01, 0x02, 0x03, @@ -571,6 +607,12 @@ mod tests { // Event data 0x14, 0x15, 0x16, 0x17, ]); + + // Check that `new_in_box` gives the same value. + assert_eq!( + event, + &*PcrEvent::new_in_box(PcrIndex(4), EventType::IPL, digest, &data).unwrap() + ); } #[test] diff --git a/uefi/src/proto/tcg/v2.rs b/uefi/src/proto/tcg/v2.rs index 86b3bb39c..ba9b98fdd 100644 --- a/uefi/src/proto/tcg/v2.rs +++ b/uefi/src/proto/tcg/v2.rs @@ -11,17 +11,22 @@ //! [TPM]: https://en.wikipedia.org/wiki/Trusted_Platform_Module use super::{v1, AlgorithmId, EventType, HashAlgorithm, PcrIndex}; -use crate::data_types::{PhysicalAddress, UnalignedSlice}; +use crate::data_types::{Align, PhysicalAddress, UnalignedSlice}; use crate::proto::unsafe_protocol; use crate::util::{ptr_write_unaligned_and_add, usize_from_u32}; use crate::{Error, Result, Status, StatusExt}; use bitflags::bitflags; use core::fmt::{self, Debug, Formatter}; use core::marker::PhantomData; -use core::mem::MaybeUninit; use core::{mem, ptr, slice}; use ptr_meta::{Pointee, PtrExt}; +#[cfg(feature = "alloc")] +use {crate::mem::make_boxed, alloc::boxed::Box}; + +#[cfg(all(feature = "unstable", feature = "alloc"))] +use alloc::alloc::Global; + /// Version information. /// /// Layout compatible with the C type `EFI_TG2_VERSION`. @@ -97,7 +102,7 @@ pub struct BootServiceCapability { impl Default for BootServiceCapability { fn default() -> Self { // OK to unwrap, the size is less than u8. - let struct_size = u8::try_from(mem::size_of::()).unwrap(); + let struct_size = u8::try_from(mem::size_of::()).unwrap(); Self { size: struct_size, @@ -118,7 +123,7 @@ impl Default for BootServiceCapability { impl BootServiceCapability { /// Whether the TPM device is present. #[must_use] - pub fn tpm_present(&self) -> bool { + pub const fn tpm_present(&self) -> bool { self.present_flag != 0 } } @@ -158,7 +163,7 @@ struct EventHeader { /// `TCG_PCR_EVENT2` for reading events. To help clarify the usage, our /// API renames these types to `PcrEventInputs` and `PcrEvent`, /// respectively. -#[derive(Pointee)] +#[derive(Eq, Pointee)] #[repr(C, packed)] pub struct PcrEventInputs { size: u32, @@ -172,24 +177,24 @@ impl PcrEventInputs { /// # Errors /// /// Returns [`Status::BUFFER_TOO_SMALL`] if the `buffer` is not large - /// enough. + /// enough. The required size will be returned in the error data. /// /// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too /// large. pub fn new_in_buffer<'buf>( - buffer: &'buf mut [MaybeUninit], + buffer: &'buf mut [u8], pcr_index: PcrIndex, event_type: EventType, event_data: &[u8], - ) -> Result<&'buf Self> { + ) -> Result<&'buf mut Self, Option> { let required_size = mem::size_of::() + mem::size_of::() + event_data.len(); if buffer.len() < required_size { - return Err(Status::BUFFER_TOO_SMALL.into()); + return Err(Error::new(Status::BUFFER_TOO_SMALL, Some(required_size))); } - let size_field = - u32::try_from(required_size).map_err(|_| Error::from(Status::INVALID_PARAMETER))?; + let size_field = u32::try_from(required_size) + .map_err(|_| Error::new(Status::INVALID_PARAMETER, None))?; let mut ptr: *mut u8 = buffer.as_mut_ptr().cast(); @@ -206,13 +211,44 @@ impl PcrEventInputs { ); ptr::copy(event_data.as_ptr(), ptr, event_data.len()); - let ptr: *const PcrEventInputs = - ptr_meta::from_raw_parts(buffer.as_ptr().cast(), event_data.len()); - Ok(&*ptr) + let ptr: *mut Self = + ptr_meta::from_raw_parts_mut(buffer.as_mut_ptr().cast(), event_data.len()); + Ok(&mut *ptr) + } + } + + /// Create a new `PcrEventInputs` in a [`Box`]. + /// + /// # Errors + /// + /// Returns [`Status::INVALID_PARAMETER`] if the `event_data` size is too + /// large. + #[cfg(feature = "alloc")] + pub fn new_in_box( + pcr_index: PcrIndex, + event_type: EventType, + event_data: &[u8], + ) -> Result> { + #[cfg(not(feature = "unstable"))] + { + make_boxed(|buf| Self::new_in_buffer(buf, pcr_index, event_type, event_data)) + } + #[cfg(feature = "unstable")] + { + make_boxed( + |buf| Self::new_in_buffer(buf, pcr_index, event_type, event_data), + Global, + ) } } } +impl Align for PcrEventInputs { + fn alignment() -> usize { + 1 + } +} + impl Debug for PcrEventInputs { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { f.debug_struct("PcrEventInputs") @@ -223,6 +259,15 @@ impl Debug for PcrEventInputs { } } +// Manual `PartialEq` implementation since it can't be derived for a packed DST. +impl PartialEq for PcrEventInputs { + fn eq(&self, other: &Self) -> bool { + self.size == other.size + && self.event_header == other.event_header + && self.event == other.event + } +} + #[repr(C, packed)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct AlgorithmDigestSize { @@ -374,7 +419,7 @@ impl<'a> EventLog<'a> { /// Whether the event log is truncated due to not enough space in the log to /// contain some events. #[must_use] - pub fn is_truncated(&self) -> bool { + pub const fn is_truncated(&self) -> bool { self.is_truncated } } @@ -488,7 +533,7 @@ impl<'a> PcrEvent<'a> { /// PCR index for the event. #[must_use] - pub fn pcr_index(&self) -> PcrIndex { + pub const fn pcr_index(&self) -> PcrIndex { self.pcr_index } @@ -496,7 +541,7 @@ impl<'a> PcrEvent<'a> { /// /// [`event_data`]: Self::event_data #[must_use] - pub fn event_type(&self) -> EventType { + pub const fn event_type(&self) -> EventType { self.event_type } @@ -508,7 +553,7 @@ impl<'a> PcrEvent<'a> { /// [`digests`]: Self::digests /// [`event_type`]: Self::event_type #[must_use] - pub fn event_data(&self) -> &[u8] { + pub const fn event_data(&self) -> &[u8] { self.event_data } @@ -785,7 +830,7 @@ mod tests { #[test] fn test_new_event() { - let mut buf = [MaybeUninit::uninit(); 22]; + let mut buf = [0; 22]; let event_data = [0x12, 0x13, 0x14, 0x15]; let event = PcrEventInputs::new_in_buffer(&mut buf, PcrIndex(4), EventType::IPL, &event_data) @@ -824,6 +869,12 @@ mod tests { // Event data 0x12, 0x13, 0x14, 0x15, ]); + + // Check that `new_in_box` gives the same value. + assert_eq!( + event, + &*PcrEventInputs::new_in_box(PcrIndex(4), EventType::IPL, &event_data).unwrap() + ); } #[test] diff --git a/uefi/src/result/error.rs b/uefi/src/result/error.rs index 00b478f7c..39f4696be 100644 --- a/uefi/src/result/error.rs +++ b/uefi/src/result/error.rs @@ -44,7 +44,7 @@ impl Error { impl From for Error<()> { fn from(status: Status) -> Self { - Error::new(status, ()) + Self::new(status, ()) } } @@ -60,7 +60,7 @@ impl Error { /// - to retain the erroneous status code, /// - do not care about the payload, and /// - refrain from generic type complexity in a higher API level. - pub fn to_err_without_payload(&self) -> Error<()> { + pub const fn to_err_without_payload(&self) -> Error<()> { Error { status: self.status, data: (), diff --git a/uefi/src/result/mod.rs b/uefi/src/result/mod.rs index 4826c2f0c..a66fca1a8 100644 --- a/uefi/src/result/mod.rs +++ b/uefi/src/result/mod.rs @@ -80,9 +80,9 @@ impl ResultExt for Result(self, op: O) -> Result + fn handle_warning(self, op: O) -> Self where - O: FnOnce(Error) -> Result, + O: FnOnce(Error) -> Self, { match self { Ok(output) => Ok(output), diff --git a/uefi/src/result/status.rs b/uefi/src/result/status.rs index e42c767a5..808208bb0 100644 --- a/uefi/src/result/status.rs +++ b/uefi/src/result/status.rs @@ -59,7 +59,7 @@ impl StatusExt for Status { #[inline] fn to_result_with_err( self, - err: impl FnOnce(Status) -> ErrData, + err: impl FnOnce(Self) -> ErrData, ) -> Result<(), ErrData> { if self.is_success() { Ok(()) @@ -72,7 +72,7 @@ impl StatusExt for Status { fn to_result_with( self, val: impl FnOnce() -> T, - err: impl FnOnce(Status) -> ErrData, + err: impl FnOnce(Self) -> ErrData, ) -> Result { if self.is_success() { Ok(val()) diff --git a/uefi/src/runtime.rs b/uefi/src/runtime.rs new file mode 100644 index 000000000..db92de505 --- /dev/null +++ b/uefi/src/runtime.rs @@ -0,0 +1,514 @@ +//! UEFI runtime services. +//! +//! These services are available both before and after exiting boot +//! services. Note that various restrictions apply when calling runtime services +//! functions after exiting boot services; see the "Calling Convention" section +//! of the UEFI specification for details. + +use crate::data_types::PhysicalAddress; +use crate::table::{self, Revision}; +use crate::{CStr16, Error, Result, Status, StatusExt}; +use core::mem; +use core::ptr::{self, NonNull}; +use uefi_raw::table::boot::MemoryDescriptor; + +#[cfg(feature = "alloc")] +use { + crate::mem::make_boxed, crate::Guid, alloc::borrow::ToOwned, alloc::boxed::Box, alloc::vec::Vec, +}; + +#[cfg(all(feature = "unstable", feature = "alloc"))] +use alloc::alloc::Global; + +pub use crate::table::runtime::{ + CapsuleInfo, Daylight, Time, TimeCapabilities, TimeError, TimeParams, VariableStorageInfo, +}; +pub use uefi_raw::capsule::{CapsuleBlockDescriptor, CapsuleFlags, CapsuleHeader}; +pub use uefi_raw::table::runtime::{ResetType, VariableAttributes, VariableVendor}; + +#[cfg(feature = "alloc")] +pub use crate::table::runtime::VariableKey; + +fn runtime_services_raw_panicking() -> NonNull { + let st = table::system_table_raw_panicking(); + // SAFETY: valid per requirements of `set_system_table`. + let st = unsafe { st.as_ref() }; + NonNull::new(st.runtime_services).expect("runtime services are not active") +} + +/// Query the current time and date information. +pub fn get_time() -> Result