From b140afee5cf6552b4d6c071b7d280127bf4d88f5 Mon Sep 17 00:00:00 2001 From: Kat Perez Date: Tue, 12 May 2026 11:36:42 -0400 Subject: [PATCH] Add patina_sre crate --- uefi/crates/patina_sre/Cargo.lock | 662 +++++++++++++++++++++ uefi/crates/patina_sre/Cargo.toml | 47 ++ uefi/crates/patina_sre/README.md | 46 ++ uefi/crates/patina_sre/rust-toolchain.toml | 9 + uefi/crates/patina_sre/rustfmt.toml | 4 + uefi/crates/patina_sre/src/hotkey.rs | 58 ++ uefi/crates/patina_sre/src/lib.rs | 343 +++++++++++ 7 files changed, 1169 insertions(+) create mode 100644 uefi/crates/patina_sre/Cargo.lock create mode 100644 uefi/crates/patina_sre/Cargo.toml create mode 100644 uefi/crates/patina_sre/README.md create mode 100644 uefi/crates/patina_sre/rust-toolchain.toml create mode 100644 uefi/crates/patina_sre/rustfmt.toml create mode 100644 uefi/crates/patina_sre/src/hotkey.rs create mode 100644 uefi/crates/patina_sre/src/lib.rs diff --git a/uefi/crates/patina_sre/Cargo.lock b/uefi/crates/patina_sre/Cargo.lock new file mode 100644 index 0000000..3310325 --- /dev/null +++ b/uefi/crates/patina_sre/Cargo.lock @@ -0,0 +1,662 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aarch64-cpu" +version = "10.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a21cd0131c25c438e19cd6a774adf7e3f64f7f4d723022882facc2dee0f8bc9" +dependencies = [ + "tock-registers", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bit_field" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e4b40c7323adcfc0a41c4b88143ed58346ff65a288fc144329c5c45e05d70c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "compile-time" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e55ede5279d4d7c528906853743abeb26353ae1e6c440fcd6d18316c2c2dd903" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "rustc_version", + "semver", + "time", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "downcast" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" + +[[package]] +name = "fallible-streaming-iterator" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "fragile" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8878864ba14bb86e818a412bfd6f18f9eabd4ec0f008a28e8f7eb61db532fcf9" +dependencies = [ + "futures-core", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "goblin" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "983a6aafb3b12d4c41ea78d39e189af4298ce747353945ff5105b54a056e5cd9" +dependencies = [ + "log", + "plain", + "scroll", +] + +[[package]] +name = "indoc" +version = "2.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79cf5c93f93228cf8efb3ba362535fb11199ac548a09ce117c9b1adc3030d706" +dependencies = [ + "rustversion", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "linkme" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83272d46373fb8decca684579ac3e7c8f3d71d4cc3aa693df8759e260ae41cf" +dependencies = [ + "linkme-impl", +] + +[[package]] +name = "linkme-impl" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32d59e20403c7d08fe62b4376edfe5c7fb2ef1e6b1465379686d0f21c8df444b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "mockall" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39a6bfcc6c8c7eed5ee98b9c3e33adc726054389233e201c95dab2d41a3839d2" +dependencies = [ + "cfg-if", + "downcast", + "fragile", + "mockall_derive", + "predicates", + "predicates-tree", +] + +[[package]] +name = "mockall_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ca3004c2efe9011bd4e461bd8256445052b9615405b4f7ea43fc8ca5c20898" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "mu_rust_helpers" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028b489eb1f6ce61c83feee2cc5e8fb60978115a466c74b84b78aafed6041f34" +dependencies = [ + "mu_uefi_decompress", + "mu_uefi_guid", + "mu_uefi_perf_timer", +] + +[[package]] +name = "mu_uefi_decompress" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b85df261b0b0241dee3c95a186fac29234b0e60bfabef7d57d3099ec2b7cb6" +dependencies = [ + "bitvec", + "log", +] + +[[package]] +name = "mu_uefi_guid" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "091e08bf3b952c68a5d3915d53b4221dc41fad3e00d3eb2020d2dd46f83ae5e2" +dependencies = [ + "r-efi", + "uuid", +] + +[[package]] +name = "mu_uefi_perf_timer" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df471d72676adaa480d61a0c9ed30a844a90842f876c188a0b30cb760d2444aa" +dependencies = [ + "aarch64-cpu", + "log", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "patina" +version = "21.0.2" +source = "git+https://github.com/kat-perez/patina?branch=kat-perez%2Fsre-base#7e21224aa98727b092bb8b3a67b1cc4c4f787a95" +dependencies = [ + "cfg-if", + "compile-time", + "fallible-streaming-iterator", + "fixedbitset", + "goblin", + "indoc", + "linkme", + "log", + "mockall", + "mu_rust_helpers", + "num-traits", + "patina_macro 21.0.2 (git+https://github.com/kat-perez/patina?branch=kat-perez%2Fsre-base)", + "r-efi", + "safe-mmio", + "scroll", + "spin", + "uart_16550", + "uuid", + "zerocopy", + "zerocopy-derive", +] + +[[package]] +name = "patina_boot" +version = "0.1.0" +source = "git+https://github.com/OpenDevicePartnership/patina-components?branch=main#1bcdd7a34b7558a38dacb7e2db069ff73e610188" +dependencies = [ + "log", + "patina", + "patina_macro 21.0.2 (git+https://github.com/OpenDevicePartnership/patina?branch=feature%2Fpatina-boot)", + "r-efi", + "spin", + "zerocopy", + "zerocopy-derive", +] + +[[package]] +name = "patina_macro" +version = "21.0.2" +source = "git+https://github.com/OpenDevicePartnership/patina?branch=feature%2Fpatina-boot#adc19cdf6344684796c599aa512c62b227068031" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "patina_macro" +version = "21.0.2" +source = "git+https://github.com/kat-perez/patina?branch=kat-perez%2Fsre-base#7e21224aa98727b092bb8b3a67b1cc4c4f787a95" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "uuid", +] + +[[package]] +name = "patina_nvme" +version = "0.1.0" +source = "git+https://github.com/kat-perez/patina-components?branch=kat-perez%2Fpatina-nvme#6e4608e136b6bdab5b5e64e6851f7afadb829c7e" +dependencies = [ + "log", + "patina", + "r-efi", +] + +[[package]] +name = "patina_partition" +version = "0.1.0" +source = "git+https://github.com/kat-perez/patina-components?branch=kat-perez%2Fpatina-partition#3937db8ed783678d089ffa547fb38ec20c32ffcb" +dependencies = [ + "log", + "patina", + "r-efi", +] + +[[package]] +name = "patina_ram_disk" +version = "0.1.0" +source = "git+https://github.com/kat-perez/patina-components?branch=kat-perez%2Fpatina-ram-disk#75dcf21347f053159a7d4b88d920d94dcef016a7" +dependencies = [ + "patina", + "r-efi", +] + +[[package]] +name = "patina_sre" +version = "0.1.0" +dependencies = [ + "log", + "patina", + "patina_boot", + "patina_nvme", + "patina_partition", + "patina_ram_disk", + "r-efi", + "spin", +] + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "predicates" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" +dependencies = [ + "anstyle", + "predicates-core", +] + +[[package]] +name = "predicates-core" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" + +[[package]] +name = "predicates-tree" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" +dependencies = [ + "predicates-core", + "termtree", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "raw-cpuid" +version = "10.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "safe-mmio" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0a69f884a1511aa811aa94e04559414a66b18ca63f50f84ca31e304f38bad0d" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "scroll" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1257cd4248b4132760d6524d6dda4e053bc648c9070b960929bf50cfb1e7add" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed76efe62313ab6610570951494bdaa81568026e0318eaa55f167de70eeea67d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tock-registers" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b9e2fdb3a1e862c0661768b7ed25390811df1947a8acbfbefe09b47078d93c4" + +[[package]] +name = "uart_16550" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e492212ac378a5e00da953718dafb1340d9fbaf4f27d6f3c5cab03d931d1c049" +dependencies = [ + "bitflags 2.11.1", + "rustversion", + "x86", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "uuid" +version = "1.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd74a9687298c6858e9b88ec8935ec45d22e8fd5e6394fa1bd4e99a87789c76" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "x86" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2781db97787217ad2a2845c396a5efe286f87467a5810836db6d74926e94a385" +dependencies = [ + "bit_field", + "bitflags 1.3.2", + "raw-cpuid", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/uefi/crates/patina_sre/Cargo.toml b/uefi/crates/patina_sre/Cargo.toml new file mode 100644 index 0000000..b09acb1 --- /dev/null +++ b/uefi/crates/patina_sre/Cargo.toml @@ -0,0 +1,47 @@ +# Cargo manifest for the patina_sre crate. +# +# SPDX-License-Identifier: MIT +# + +[package] +name = "patina_sre" +version = "0.1.0" +edition = "2024" +# A pinned nightly toolchain is required (see rust-toolchain.toml) for #![feature(...)] gates +# used by patina_boot and SreBootManager; no MSRV is declared because the crate is not +# buildable on stable. +license = "MIT" +repository = "https://github.com/OpenDevicePartnership/odp-platform-common" +homepage = "https://github.com/OpenDevicePartnership/odp-platform-common" +documentation = "https://docs.rs/patina_sre" +description = """ +Reference System Recovery Environment (SRE) boot orchestrator for ODP platforms. +Implements patina_boot::BootOrchestrator with the BDS-phase sequencing typical of +devices shipping a recovery image alongside the main OS. OEMs can adopt as-is or +use as the starting point for vendor-specific recovery flows. +""" +keywords = ["uefi", "patina", "sre", "boot", "recovery"] +categories = ["embedded", "no-std"] +readme = "README.md" + +[dependencies] +log = { version = "0.4", default-features = false } +r-efi = { version = "5", default-features = false } +# patina + patina-components helpers are pulled from git until the corresponding releases +# land on crates.io. See [patch] block below for cross-dep version unification. +patina = { git = "https://github.com/kat-perez/patina", branch = "kat-perez/sre-base", default-features = false, features = ["alloc", "unstable-device-path"] } +patina_boot = { git = "https://github.com/OpenDevicePartnership/patina-components", branch = "main" } +patina_nvme = { git = "https://github.com/kat-perez/patina-components", branch = "kat-perez/patina-nvme" } +patina_partition = { git = "https://github.com/kat-perez/patina-components", branch = "kat-perez/patina-partition" } +patina_ram_disk = { git = "https://github.com/kat-perez/patina-components", branch = "kat-perez/patina-ram-disk" } + +[dev-dependencies] +spin = { version = "0.9", default-features = false, features = ["spin_mutex"] } +patina = { git = "https://github.com/kat-perez/patina", branch = "kat-perez/sre-base", default-features = false, features = ["alloc", "mockall", "unstable-device-path"] } + +# Redirect transitive patina deps that point at upstream's feature/patina-boot (patina_boot, +# patina_nvme, patina_partition all pull from there) onto the combined branch that includes +# the EFI_RAM_DISK_PROTOCOL binding from patina#1490 alongside the feature/patina-boot content. +# Drop once #1490 / feature/patina-boot merge and a patina release ships. +[patch."https://github.com/OpenDevicePartnership/patina"] +patina = { git = "https://github.com/kat-perez/patina", branch = "kat-perez/sre-base" } diff --git a/uefi/crates/patina_sre/README.md b/uefi/crates/patina_sre/README.md new file mode 100644 index 0000000..1840e9c --- /dev/null +++ b/uefi/crates/patina_sre/README.md @@ -0,0 +1,46 @@ +# patina_sre + +Reference System Recovery Environment (SRE) boot orchestrator for ODP platforms. + +`patina_sre::SreBootManager` implements `patina_boot::BootOrchestrator` with the BDS-phase sequencing typical of devices shipping a recovery image alongside the main OS. OEMs can adopt as-is or use it as the starting point for vendor-specific recovery flows. + +## What it does + +The orchestrator runs the following sequence under the DXE Boot Device Selection phase: + +1. **Interleave controller connection with DXE driver dispatch** until convergence. +2. **Signal BDS phase entry** — fires the event group used by components that need to react at the transition from DXE driver dispatch into Boot Device Selection. +3. **Discover console devices** and connect them. +4. **Write-lock the NVMe boot partition** (via `patina_nvme::lock_partition_write`) so the OS cannot modify the firmware payload region post-handoff. The lock is volatile and clears on power cycle. +5. **Signal `ReadyToBoot`** — last opportunity for components to react before OS load. +6. **Boot the main OS device** from the configured device path. + +The current crate is a skeleton — only the normal path is wired. SRE-entry hotkey detection, SRE WIM RAM-disk boot, and capsule-update pre-boot hook are planned and will layer on without changing the constructor surface. + +## Usage + +```rust,ignore +use patina_boot::BootDispatcher; +use patina_sre::SreBootManager; + +Core::default() + .with_component(BootDispatcher::new(SreBootManager::new( + boot_partition_device_path, + main_os_device_path, + ))) + // ... rest of platform components +``` + +## Building + +This crate currently consumes `patina`, `patina_boot`, and `patina_nvme` from git branches because none of those releases are yet on crates.io. Once releases ship, the dependencies will move to versioned crates.io references and consumers will no longer need any `[patch.crates-io]` glue. + +## Status + +- `lock_partition_write` is exercised against a mock NVMe Pass-Thru protocol via `patina_nvme`'s tests. +- The interleave / dispatch loop is unit-tested with mock `BootServices` and `DxeDispatch` implementations. +- End-to-end boot has been verified locally on `surface_patina_intel` (Won/Maa/Pue PTL boards) builds. On-device flash validation is in progress. + +## License + +`SPDX-License-Identifier: MIT` diff --git a/uefi/crates/patina_sre/rust-toolchain.toml b/uefi/crates/patina_sre/rust-toolchain.toml new file mode 100644 index 0000000..9e754e5 --- /dev/null +++ b/uefi/crates/patina_sre/rust-toolchain.toml @@ -0,0 +1,9 @@ +# Pinned Rust toolchain for patina_sre (UEFI targets). +# +# SPDX-License-Identifier: MIT +# + +[toolchain] +channel = "nightly-2025-12-12" +targets = ["x86_64-unknown-uefi", "aarch64-unknown-uefi"] +components = ["rust-src", "clippy", "rustfmt"] diff --git a/uefi/crates/patina_sre/rustfmt.toml b/uefi/crates/patina_sre/rustfmt.toml new file mode 100644 index 0000000..a71ec54 --- /dev/null +++ b/uefi/crates/patina_sre/rustfmt.toml @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: MIT + +max_width = 120 +edition = "2024" diff --git a/uefi/crates/patina_sre/src/hotkey.rs b/uefi/crates/patina_sre/src/hotkey.rs new file mode 100644 index 0000000..2746590 --- /dev/null +++ b/uefi/crates/patina_sre/src/hotkey.rs @@ -0,0 +1,58 @@ +//! Hotkey-source abstraction. +//! +//! SPDX-License-Identifier: MIT +//! +//! [`HotkeySource`] lets [`crate::SreBootManager`] decide between the normal boot path and the +//! SRE-recovery path without depending on any specific input device. Platforms wire their +//! hotkey hardware (e.g. Surface's `MsButtonServicesProtocol`) through an implementation of this +//! trait; tests and headless QEMU runs use [`AlwaysSre`] or [`NeverSre`]. + +/// Source of SRE-entry hotkey signal. +/// +/// Implementors poll their underlying hotkey hardware and return whether the SRE-entry +/// gesture (e.g. Power + Volume Up on Surface) is currently active. The poll is invoked at +/// most once per boot, immediately before the orchestrator chooses between the normal boot +/// path and the SRE-recovery path. +pub trait HotkeySource { + /// Returns `true` if the SRE-entry hotkey is currently active. + fn sre_requested(&self) -> bool; +} + +/// `HotkeySource` impl that always reports the SRE hotkey as pressed. +/// +/// Useful for forcing the SRE-recovery path under QEMU / unit tests without a real input +/// device, and for headless integration runs that always need to validate the SRE flow. +pub struct AlwaysSre; + +impl HotkeySource for AlwaysSre { + fn sre_requested(&self) -> bool { + true + } +} + +/// `HotkeySource` impl that always reports the SRE hotkey as released. +/// +/// Use this in production builds that don't yet have hotkey hardware wired up — the +/// orchestrator will always take the normal boot path. +pub struct NeverSre; + +impl HotkeySource for NeverSre { + fn sre_requested(&self) -> bool { + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn always_sre_returns_true() { + assert!(AlwaysSre.sre_requested()); + } + + #[test] + fn never_sre_returns_false() { + assert!(!NeverSre.sre_requested()); + } +} diff --git a/uefi/crates/patina_sre/src/lib.rs b/uefi/crates/patina_sre/src/lib.rs new file mode 100644 index 0000000..0b69178 --- /dev/null +++ b/uefi/crates/patina_sre/src/lib.rs @@ -0,0 +1,343 @@ +//! Reference System Recovery Environment (SRE) boot orchestrator. +//! +//! SPDX-License-Identifier: MIT +//! +//! [`SreBootManager`] implements [`patina_boot::BootOrchestrator`] for platforms shipping a +//! System Recovery Environment alongside the main OS. At boot it runs the common BDS-phase +//! sequencing (controller-connect / dispatch interleave, BDS-phase-entry and `ReadyToBoot` signals, +//! console discovery), then asks a [`HotkeySource`] whether the SRE-entry gesture is active +//! and branches between two boot paths: +//! +//! - **Normal path.** Write-lock the NVMe boot partition, then boot the main OS device. +//! - **SRE path.** Read the SRE WIM from the boot partition, write-lock the partition, +//! publish the WIM as a RAM-disk virtual block device, and boot the RAM disk. +//! +//! Capsule-update pre-boot hook orchestration is tracked separately and will layer on without +//! changing the constructor surface. +//! +//! ## Adopting for your platform +//! +//! Construct `SreBootManager` with the device paths of the boot partition, the main OS boot +//! device, the partition-relative path of the SRE WIM, and a [`HotkeySource`] implementation +//! that talks to your platform's hotkey hardware. Register the result with the patina +//! [`BootDispatcher`](https://docs.rs/patina_boot/latest/patina_boot/struct.BootDispatcher.html): +//! +//! ```rust,ignore +//! use patina_boot::BootDispatcher; +//! use patina_sre::{NeverSre, SreBootManager}; +//! +//! Core::default() +//! .with_component(BootDispatcher::new(SreBootManager::new( +//! boot_partition_device_path, +//! main_os_device_path, +//! "\\SRE\\winvos.wim", +//! NeverSre, // replace with your platform hotkey impl +//! ))) +//! // ... rest of platform components +//! ``` +//! +//! For QEMU / unit-test runs that always need to exercise the SRE path, pass [`AlwaysSre`] +//! in place of the platform hotkey. + +#![no_std] +#![feature(coverage_attribute)] +#![feature(never_type)] + +extern crate alloc; + +pub mod hotkey; + +pub use hotkey::{AlwaysSre, HotkeySource, NeverSre}; + +use patina::{ + boot_services::{BootServices, StandardBootServices}, + component::service::dxe_dispatch::DxeDispatch, + device_path::paths::DevicePathBuf, + error::EfiError, + runtime_services::StandardRuntimeServices, +}; +use patina_boot::{BootOrchestrator, helpers}; +use r_efi::efi; + +fn interleave_connect_and_dispatch( + boot_services: &B, + dxe_services: &D, +) -> patina::error::Result<()> { + const MAX_ROUNDS: usize = 10; + + for _round in 0..MAX_ROUNDS { + helpers::connect_all(boot_services)?; + if !dxe_services.dispatch()? { + return Ok(()); + } + } + + log::warn!("connect-dispatch interleaving did not converge after {MAX_ROUNDS} rounds; proceeding anyway"); + + Ok(()) +} + +/// SRE boot manager implementing [`BootOrchestrator`]. +/// +/// Generic over the [`HotkeySource`] implementation so the same orchestrator can be wired with +/// a platform-specific hotkey impl in production, or [`AlwaysSre`] / [`NeverSre`] for testing. +pub struct SreBootManager { + boot_partition_path: DevicePathBuf, + main_os_path: DevicePathBuf, + sre_wim_path: &'static str, + hotkey: H, +} + +impl SreBootManager { + /// Construct an `SreBootManager`. + /// + /// * `boot_partition_path` — device path of the boot partition (write-locked before OS + /// hand-off; also the source partition for the SRE WIM). + /// * `main_os_path` — device path of the main OS boot device, used by the normal path. + /// * `sre_wim_path` — partition-relative file path of the SRE WIM (e.g. `\\SRE\\winvos.wim`) + /// read by the SRE path. + /// * `hotkey` — implementation of [`HotkeySource`] used to choose between normal and SRE + /// paths at boot. + pub fn new( + boot_partition_path: DevicePathBuf, + main_os_path: DevicePathBuf, + sre_wim_path: &'static str, + hotkey: H, + ) -> Self { + Self { boot_partition_path, main_os_path, sre_wim_path, hotkey } + } + + /// Normal boot path: write-lock the boot partition, then boot the main OS device. + #[coverage(off)] + fn execute_normal_path( + &self, + boot_services: &B, + image_handle: efi::Handle, + ) -> Result { + // BP write-lock is best-effort: a missing NVMe Pass-Thru protocol (QEMU without BPWPS + // emulation, USB-stick rescue media, etc.) shouldn't prevent the OS from booting in + // the current pre-release iteration. Production platforms targeting strict security + // should distinguish protocol-absent (expected) from protocol-present-but-rejected + // (anomalous) and fail closed on the latter — tracked as a follow-up before v1. + if let Err(e) = patina_nvme::lock_partition_write(boot_services, &self.boot_partition_path) { + log::warn!("BP write-lock failed (continuing): {:?}", e); + } + + if let Err(e) = helpers::signal_ready_to_boot(boot_services) { + log::error!("signal_ready_to_boot failed: {:?}", e); + } + + match helpers::boot_from_device_path(boot_services, image_handle, &self.main_os_path) { + Ok(()) => log::warn!("Main OS boot returned control unexpectedly"), + Err(e) => { + log::warn!("Main OS boot failed: {:?}", e); + return Err(e); + } + } + + log::error!("SRE normal boot exhausted main OS path"); + Err(EfiError::NotFound) + } + + /// SRE recovery path: read the SRE WIM from the boot partition, lock the partition, + /// publish the WIM as a RAM disk, and boot the RAM disk. + #[coverage(off)] + fn execute_sre_path( + &self, + boot_services: &B, + image_handle: efi::Handle, + ) -> Result { + let wim_bytes = + match patina_partition::read_partition_file(boot_services, &self.boot_partition_path, self.sre_wim_path) { + Ok(b) => { + log::info!("Read SRE WIM '{}': {} bytes", self.sre_wim_path, b.len()); + b + } + Err(e) => { + log::error!("Failed to read SRE WIM '{}': {:?}", self.sre_wim_path, e); + return Err(e); + } + }; + + // BP write-lock is best-effort: a missing NVMe Pass-Thru protocol (QEMU without BPWPS + // emulation, USB-stick rescue media, etc.) shouldn't block the SRE recovery boot. + if let Err(e) = patina_nvme::lock_partition_write(boot_services, &self.boot_partition_path) { + log::warn!("BP write-lock failed (continuing): {:?}", e); + } + + let ram_disk_path = match patina_ram_disk::install(boot_services, &wim_bytes) { + Ok(p) => p, + Err(e) => { + log::error!("Failed to install WIM as RAM disk: {:?}", e); + return Err(e); + } + }; + + if let Err(e) = helpers::signal_ready_to_boot(boot_services) { + log::error!("signal_ready_to_boot failed: {:?}", e); + } + + match helpers::boot_from_device_path(boot_services, image_handle, &ram_disk_path) { + Ok(()) => log::warn!("SRE WIM boot returned control unexpectedly"), + Err(e) => { + log::warn!("SRE WIM boot failed: {:?}", e); + return Err(e); + } + } + + log::error!("SRE recovery boot exhausted RAM disk path"); + Err(EfiError::NotFound) + } +} + +impl BootOrchestrator for SreBootManager { + #[coverage(off)] // Integration point — delegates to helper functions which are individually tested. + fn execute( + &self, + boot_services: &StandardBootServices, + runtime_services: &StandardRuntimeServices, + dxe_dispatch: &dyn DxeDispatch, + image_handle: efi::Handle, + ) -> Result { + if let Err(e) = interleave_connect_and_dispatch(boot_services, dxe_dispatch) { + log::error!("interleave_connect_and_dispatch failed: {:?}", e); + } + + if let Err(e) = helpers::signal_bds_phase_entry(boot_services) { + log::error!("signal_bds_phase_entry failed: {:?}", e); + } + + if let Err(e) = helpers::discover_console_devices(boot_services, runtime_services) { + log::error!("discover_console_devices failed: {:?}", e); + } + + if self.hotkey.sre_requested() { + log::info!("SRE hotkey detected — taking SRE recovery path"); + self.execute_sre_path(boot_services, image_handle) + } else { + log::info!("No SRE hotkey — taking normal boot path"); + self.execute_normal_path(boot_services, image_handle) + } + } +} + +#[cfg(test)] +mod tests { + extern crate alloc; + + use alloc::{boxed::Box, sync::Arc, vec::Vec}; + + use patina::{ + boot_services::{MockBootServices, boxed::BootServicesBox}, + device_path::{node_defs::EndEntire, paths::DevicePathBuf}, + }; + + use super::*; + + fn test_device_path() -> DevicePathBuf { + DevicePathBuf::from_device_path_node_iter(core::iter::once(EndEntire)) + } + + struct MockDxeDispatcher { + results: spin::Mutex>>, + } + + impl MockDxeDispatcher { + fn new(results: &[patina::error::Result]) -> Self { + Self { results: spin::Mutex::new(results.iter().cloned().collect()) } + } + } + + impl DxeDispatch for MockDxeDispatcher { + fn dispatch(&self) -> patina::error::Result { + self.results.lock().pop_front().expect("MockDxeDispatcher: unexpected dispatch call") + } + } + + fn leaked_boot_services_for_box() -> &'static MockBootServices { + Box::leak(Box::new({ + let mut m = MockBootServices::new(); + m.expect_free_pool().returning(|_| Ok(())); + m + })) + } + + fn mock_handle_buffer( + handle_addrs: &[usize], + boot_services: &'static MockBootServices, + ) -> BootServicesBox<'static, [efi::Handle], MockBootServices> { + let handles: Vec = handle_addrs.iter().map(|&a| a as efi::Handle).collect(); + let leaked = handles.leak(); + // SAFETY: leaked is a valid pointer+length from Vec::leak. + unsafe { BootServicesBox::from_raw_parts_mut(leaked.as_mut_ptr(), leaked.len(), boot_services) } + } + + #[test] + fn test_new_constructs() { + let _ = SreBootManager::new(test_device_path(), test_device_path(), "\\SRE\\winvos.wim", NeverSre); + } + + #[test] + fn test_new_with_always_sre() { + let _ = SreBootManager::new(test_device_path(), test_device_path(), "\\SRE\\winvos.wim", AlwaysSre); + } + + #[test] + fn test_interleave_single_round_no_drivers_dispatched() { + let box_mock = leaked_boot_services_for_box(); + let mut boot_mock = MockBootServices::new(); + + boot_mock.expect_locate_handle_buffer().returning(move |_| Ok(mock_handle_buffer(&[1], box_mock))); + boot_mock.expect_connect_controller().returning(|_, _, _, _| Ok(())); + + let dxe_mock = MockDxeDispatcher::new(&[Ok(false)]); + + let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock); + assert!(result.is_ok()); + } + + #[test] + fn test_interleave_dispatch_failure_propagates() { + let box_mock = leaked_boot_services_for_box(); + let mut boot_mock = MockBootServices::new(); + + boot_mock.expect_locate_handle_buffer().returning(move |_| Ok(mock_handle_buffer(&[1], box_mock))); + boot_mock.expect_connect_controller().returning(|_, _, _, _| Ok(())); + + let dxe_mock = MockDxeDispatcher::new(&[Err(EfiError::DeviceError)]); + + let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock); + assert!(result.is_err()); + } + + #[test] + fn test_interleave_stops_at_max_rounds() { + let box_mock = leaked_boot_services_for_box(); + let mut boot_mock = MockBootServices::new(); + + boot_mock.expect_locate_handle_buffer().returning(move |_| Ok(mock_handle_buffer(&[1], box_mock))); + boot_mock.expect_connect_controller().returning(|_, _, _, _| Ok(())); + + let dxe_mock = MockDxeDispatcher::new(&[Ok(true); 10]); + + let result = interleave_connect_and_dispatch(&boot_mock, &dxe_mock); + assert!(result.is_ok()); + } + + // Type-level confirmation that SreBootManager satisfies BootOrchestrator's + // Send + Sync + 'static bounds at compile time, with each HotkeySource impl shipped here. + #[test] + fn test_implements_boot_orchestrator() { + fn assert_orchestrator() {} + assert_orchestrator::>(); + assert_orchestrator::>(); + } + + // Confirm the manager is constructible behind an Arc, + // matching the BootDispatcher consumption path. + #[test] + fn test_arc_dyn_construction() { + let _: Arc = + Arc::new(SreBootManager::new(test_device_path(), test_device_path(), "\\SRE\\winvos.wim", NeverSre)); + } +}