diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a46fc070..abd44170 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -106,6 +106,25 @@ jobs: name: UEFI-Shell-fwk.iso path: framework_uefi/build/x86_64-unknown-uefi/UEFI-Shell-fwk.iso + test-uefi: + name: Test UEFI + runs-on: ubuntu-24.04 + env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust toolchain + run: rustup show + + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install -y mtools parted qemu-system-x86 + + - name: Run UEFI tests in QEMU + run: make -C framework_uefi test + build-windows: name: Build Windows runs-on: windows-2022 diff --git a/.gitignore b/.gitignore index 13b77fcd..a39b6179 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ result* # Claude Code tmpclaude* + +# UEFI test artifacts +dump.bmp diff --git a/Cargo.lock b/Cargo.lock index eff80f59..5239a7c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,7 +205,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -220,12 +220,6 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" -[[package]] -name = "convert_case" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" - [[package]] name = "core-foundation" version = "0.6.4" @@ -288,7 +282,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.114", + "syn", ] [[package]] @@ -299,20 +293,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.114", -] - -[[package]] -name = "derive_more" -version = "0.99.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "rustc_version", - "syn 2.0.114", + "syn", ] [[package]] @@ -333,7 +314,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -428,7 +409,7 @@ dependencies = [ "smbios-lib", "spin 0.10.0", "uefi", - "uefi-services", + "uefi-raw", "windows", "windows-version", "winreg", @@ -453,7 +434,6 @@ dependencies = [ "framework_lib", "log", "uefi", - "uefi-services", ] [[package]] @@ -512,7 +492,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -808,7 +788,7 @@ checksum = "e0c84ee7f197eca9a86c6fd6cb771e55eb991632f15f2bc3ca6ec838929e6e78" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1008,7 +988,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1150,22 +1130,22 @@ dependencies = [ [[package]] name = "ptr_meta" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcada80daa06c42ed5f48c9a043865edea5dc44cbf9ac009fda3b89526e28607" +checksum = "0b9a0cf95a1196af61d4f1cbdab967179516d9a4a4312af1f31948f8f6224a79" dependencies = [ "ptr_meta_derive", ] [[package]] name = "ptr_meta_derive" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca9224df2e20e7c5548aeb5f110a0f3b77ef05f8585139b7148b59056168ed2" +checksum = "7347867d0a7e1208d93b46767be83e2b8f978c3dad35f775ac8d8847551d6fe1" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -1285,7 +1265,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1394,17 +1374,6 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - [[package]] name = "syn" version = "2.0.114" @@ -1424,7 +1393,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1453,7 +1422,7 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1464,7 +1433,7 @@ checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1533,38 +1502,47 @@ dependencies = [ [[package]] name = "uefi" -version = "0.20.0" -source = "git+https://github.com/FrameworkComputer/uefi-rs?branch=merged#95165e1adde99da7a460320538d908af8b55f87f" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe9058b73ee2b6559524af9e33199c13b2485ddbf3ad1181b68051cdc50c17" dependencies = [ - "bitflags 1.3.2", - "derive_more", + "bitflags 2.10.0", + "cfg-if", "log", "ptr_meta", "ucs2", "uefi-macros", + "uefi-raw", + "uguid", ] [[package]] name = "uefi-macros" -version = "0.11.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0caeb0e7b31b9f1f347e541106be10aa8c66c76fa722a3298a4cd21433fabd4" +checksum = "4687412b5ac74d245d5bfb1733ede50c31be19bf8a4b6a967a29b451bab49e67" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] -name = "uefi-services" -version = "0.17.0" -source = "git+https://github.com/FrameworkComputer/uefi-rs?branch=merged#95165e1adde99da7a460320538d908af8b55f87f" +name = "uefi-raw" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f64fe59e11af447d12fd60a403c74106eb104309f34b4c6dbce6e927d97da9d" dependencies = [ - "cfg-if", - "log", - "uefi", + "bitflags 2.10.0", + "uguid", ] +[[package]] +name = "uguid" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8352f8c05e47892e7eaf13b34abd76a7f4aeaf817b716e88789381927f199c" + [[package]] name = "unicode-ident" version = "1.0.22" @@ -1674,7 +1652,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.114", + "syn", "wasm-bindgen-shared", ] @@ -1762,7 +1740,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -1773,7 +1751,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -2032,7 +2010,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] @@ -2060,7 +2038,7 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", "synstructure", ] @@ -2081,7 +2059,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", "synstructure", ] @@ -2115,11 +2093,11 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.114", + "syn", ] [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "94f63c051f4fe3c1509da62131a678643c5b6fbdc9273b2b79d4378ebda003d2" diff --git a/Cargo.toml b/Cargo.toml index d8108e1b..66801a46 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,5 @@ default-members = [ "framework_tool", ] -[patch.crates-io] -uefi = { git = "https://github.com/FrameworkComputer/uefi-rs", branch = "merged" } -uefi-services = { git = "https://github.com/FrameworkComputer/uefi-rs", branch = "merged" } - [profile.release] lto = true diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index 0ec2e522..46286c71 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -37,8 +37,8 @@ rusb = { version = "0.9.4", optional = true } guid-create = { version = "0.5.0", default-features = false } [target.'cfg(target_os = "uefi")'.dependencies] -uefi = { version = "0.20", features = ["alloc"] } -uefi-services = "0.17" +uefi = { version = "0.36.1", features = ["alloc", "global_allocator", "panic_handler", "logger"] } +uefi-raw = "0.13" plain = "0.2.3" redox_hwio = { git = "https://github.com/FrameworkComputer/rust-hwio", branch = "freebsd", default-features = false } smbios-lib = { git = "https://github.com/FrameworkComputer/smbios-lib.git", branch = "no-std", default-features = false } diff --git a/framework_lib/src/capsule.rs b/framework_lib/src/capsule.rs index 8ded6493..d5eac38d 100644 --- a/framework_lib/src/capsule.rs +++ b/framework_lib/src/capsule.rs @@ -183,7 +183,7 @@ pub fn dump_winux_image(data: &[u8], header: &DisplayCapsule, filename: &str) { } #[cfg(feature = "uefi")] { - let ret = crate::uefi::fs::shell_write_file(filename, image); + let ret = crate::fw_uefi::fs::shell_write_file(filename, image); if let Err(err) = ret { println!("Failed to dump winux image: {:?}", err); } diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 87badf52..94b8b93a 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -9,11 +9,11 @@ //! - `windows` - It uses [DHowett's Windows driver](https://github.com/DHowett/FrameworkWindowsUtils) use crate::ec_binary; +#[cfg(feature = "uefi")] +use crate::fw_uefi::shell_get_execution_break_flag; use crate::os_specific; use crate::power; use crate::smbios; -#[cfg(feature = "uefi")] -use crate::uefi::shell_get_execution_break_flag; use crate::util::{self, Platform}; use no_std_compat::time::Duration; diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 8f90d525..9e4dba72 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -48,6 +48,8 @@ use crate::chromium_ec::{EcError, EcResult}; use crate::csme; use crate::ec_binary; use crate::esrt; +#[cfg(feature = "uefi")] +use crate::fw_uefi::enable_page_break; #[cfg(feature = "rusb")] use crate::inputmodule::check_inputmodule_version; #[cfg(target_os = "linux")] @@ -62,8 +64,6 @@ use crate::smbios::{dmidecode_string_val, get_smbios, is_framework}; use crate::touchpad::print_touchpad_fw_ver; #[cfg(feature = "hidapi")] use crate::touchscreen; -#[cfg(feature = "uefi")] -use crate::uefi::enable_page_break; #[cfg(feature = "rusb")] use crate::usbhub::check_usbhub_version; use crate::util::{self, Config, Platform, PlatformFamily}; @@ -1022,7 +1022,7 @@ fn print_esrt() { fn flash_ec(ec: &CrosEc, ec_bin_path: &str, flash_type: EcFlashType, dry_run: bool) { #[cfg(feature = "uefi")] - let data = crate::uefi::fs::shell_read_file(ec_bin_path); + let data = crate::fw_uefi::fs::shell_read_file(ec_bin_path); #[cfg(not(feature = "uefi"))] let data: Option> = { match fs::read(ec_bin_path) { @@ -1057,7 +1057,7 @@ fn dump_ec_flash(ec: &CrosEc, dump_path: &str) { } #[cfg(feature = "uefi")] { - let ret = crate::uefi::fs::shell_write_file(dump_path, &flash_bin); + let ret = crate::fw_uefi::fs::shell_write_file(dump_path, &flash_bin); if ret.is_err() { println!("Failed to dump EC FW image."); } @@ -1122,7 +1122,7 @@ fn dump_dgpu_eeprom(ec: &CrosEc, dump_path: &str) { } #[cfg(feature = "uefi")] { - if let Err(err) = crate::uefi::fs::shell_write_file(dump_path, &flash_bin) { + if let Err(err) = crate::fw_uefi::fs::shell_write_file(dump_path, &flash_bin) { error!("Failed to dump EC FW image: {:?}", err); return; } @@ -1579,7 +1579,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { // raw_command(&args[1..]); } else if let Some(pd_bin_path) = &args.pd_bin { #[cfg(feature = "uefi")] - let data: Option> = crate::uefi::fs::shell_read_file(pd_bin_path); + let data: Option> = crate::fw_uefi::fs::shell_read_file(pd_bin_path); #[cfg(not(feature = "uefi"))] let data = match fs::read(pd_bin_path) { Ok(data) => Some(data), @@ -1598,7 +1598,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } } else if let Some(ec_bin_path) = &args.ec_bin { #[cfg(feature = "uefi")] - let data: Option> = crate::uefi::fs::shell_read_file(ec_bin_path); + let data: Option> = crate::fw_uefi::fs::shell_read_file(ec_bin_path); #[cfg(not(feature = "uefi"))] let data = match fs::read(ec_bin_path) { Ok(data) => Some(data), @@ -1617,7 +1617,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } } else if let Some(capsule_path) = &args.capsule { #[cfg(feature = "uefi")] - let data: Option> = crate::uefi::fs::shell_read_file(capsule_path); + let data: Option> = crate::fw_uefi::fs::shell_read_file(capsule_path); #[cfg(not(feature = "uefi"))] let data = match fs::read(capsule_path) { Ok(data) => Some(data), @@ -1646,7 +1646,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } } else if let Some(capsule_path) = &args.h2o_capsule { #[cfg(feature = "uefi")] - let data = crate::uefi::fs::shell_read_file(capsule_path); + let data = crate::fw_uefi::fs::shell_read_file(capsule_path); #[cfg(not(feature = "uefi"))] let data = match fs::read(capsule_path) { Ok(data) => Some(data), @@ -1699,7 +1699,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else if let Some(hash_file) = &args.hash { println!("Hashing file: {}", hash_file); #[cfg(feature = "uefi")] - let data = crate::uefi::fs::shell_read_file(hash_file); + let data = crate::fw_uefi::fs::shell_read_file(hash_file); #[cfg(not(feature = "uefi"))] let data = match fs::read(hash_file) { Ok(data) => Some(data), @@ -1728,7 +1728,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { Some(PlatformFamily::Framework16) | None ) { #[cfg(feature = "uefi")] - let data: Option> = crate::uefi::fs::shell_read_file(gpu_descriptor_file); + let data: Option> = crate::fw_uefi::fs::shell_read_file(gpu_descriptor_file); #[cfg(not(feature = "uefi"))] let data = match fs::read(gpu_descriptor_file) { Ok(data) => Some(data), diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 58490c71..19a8c019 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -4,9 +4,8 @@ use alloc::vec::Vec; #[allow(unused_imports)] use log::{debug, error, info, trace}; -use uefi::prelude::BootServices; -use uefi::proto::shell_params::*; -use uefi::Handle; +use uefi::boot; +use uefi::proto::shell_params::ShellParameters; use crate::chromium_ec::commands::SetGpuSerialMagic; use crate::chromium_ec::{CrosEcDriverType, HardwareDeviceType}; @@ -15,13 +14,19 @@ use crate::commandline::{Cli, LogLevel}; use super::{ConsoleArg, FpBrightnessArg, InputDeckModeArg, RebootEcArg, TabletModeArg}; /// Get commandline arguments from UEFI environment -pub fn get_args(bs: &BootServices, image_handle: Handle) -> Vec { - if let Ok(shell_params) = bs.open_protocol_exclusive::(image_handle) { - shell_params.get_args() - } else { - // No protocol found if the application wasn't executed by the shell - vec![] - } +pub fn get_args() -> Vec { + let shell_params = uefi::boot::open_protocol_exclusive::(boot::image_handle()); + let shell_params = match shell_params { + Ok(s) => s, + Err(e) => { + error!("Failed to get ShellParameters protocol"); + // TODO: Return result + // return e.status(); + return vec![]; + } + }; + let args: Vec = shell_params.args().map(|x| x.to_string()).collect(); + args } pub fn parse(args: &[String]) -> Cli { diff --git a/framework_lib/src/esrt/mod.rs b/framework_lib/src/esrt/mod.rs index 0ccc8a39..f39f49e5 100644 --- a/framework_lib/src/esrt/mod.rs +++ b/framework_lib/src/esrt/mod.rs @@ -38,6 +38,11 @@ use std::os::fd::AsRawFd; #[cfg(target_os = "freebsd")] use std::os::unix::fs::OpenOptionsExt; +#[cfg(feature = "uefi")] +use uefi::system::with_config_table; +#[cfg(feature = "uefi")] +use uefi::table::cfg::ConfigTableEntry; + pub const TGL_BIOS_GUID: GUID = GUID::build_from_components( 0xb3bdb2e4, 0xc5cb, @@ -511,22 +516,16 @@ pub const SYSTEM_RESOURCE_TABLE_GUID_BYTES: [u8; 16] = [ #[cfg(feature = "uefi")] pub fn get_esrt() -> Option { - let st = unsafe { uefi_services::system_table().as_ref() }; - let config_tables = st.config_table(); - - for table in config_tables { - // TODO: Why aren't they the same type? - //debug!("Table: {:?}", table); - let table_guid: CGuid = unsafe { std::mem::transmute(table.guid) }; - let table_guid = GUID::from(table_guid); - match table_guid { - SYSTEM_RESOURCE_TABLE_GUID => unsafe { - return esrt_from_buf(table.address as *const u8); - }, - _ => {} + with_config_table(|slice| { + for i in slice { + if i.guid == ConfigTableEntry::ESRT_GUID { + unsafe { + return esrt_from_buf(i.address as *const u8); + } + } } - } - None + None + }) } /// Parse the ESRT table buffer diff --git a/framework_lib/src/fw_uefi/fs.rs b/framework_lib/src/fw_uefi/fs.rs new file mode 100644 index 00000000..a58b1df3 --- /dev/null +++ b/framework_lib/src/fw_uefi/fs.rs @@ -0,0 +1,95 @@ +use alloc::vec; +use alloc::vec::Vec; +use core::mem::MaybeUninit; +use uefi::boot::{self, OpenProtocolAttributes, OpenProtocolParams}; +use uefi::proto::shell::Shell; +use uefi::{CString16, Result, Status, StatusExt}; +use uefi_raw::protocol::file_system::FileMode; +use uefi_raw::protocol::shell::ShellProtocol; +use uefi_raw::protocol::shell_params::ShellFileHandle; + +fn get_shell_protocol() -> &'static ShellProtocol { + let handle = boot::get_handle_for_protocol::().expect("No Shell handles"); + + // Use GetProtocol instead of Exclusive since we're running inside the shell + let shell = unsafe { + boot::open_protocol::( + OpenProtocolParams { + handle, + agent: boot::image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .expect("Failed to open Shell protocol") + }; + + // SAFETY: The Shell wrapper contains the raw ShellProtocol + unsafe { + let proto: &ShellProtocol = core::mem::transmute(shell.get().unwrap()); + // Leak to get 'static lifetime - protocol stays valid while shell is running + core::mem::forget(shell); + proto + } +} + +pub fn shell_read_file(path: &str) -> Option> { + let shell = get_shell_protocol(); + let c_path = CString16::try_from(path).ok()?; + + unsafe { + let mut handle: MaybeUninit = MaybeUninit::zeroed(); + let status = (shell.open_file_by_name)( + c_path.as_ptr().cast(), + handle.as_mut_ptr(), + FileMode::READ.bits(), + ); + if status.is_error() { + return None; + } + + let file_handle = handle.assume_init(); + + let mut file_size: u64 = 0; + let status = (shell.get_file_size)(file_handle, &mut file_size); + if status.is_error() { + let _ = (shell.close_file)(file_handle); + return None; + } + + let mut buffer: Vec = vec![0; file_size as usize]; + let mut read_size = file_size as usize; + let status = (shell.read_file)(file_handle, &mut read_size, buffer.as_mut_ptr().cast()); + + let _ = (shell.close_file)(file_handle); + + if status.is_error() { + return None; + } + + buffer.truncate(read_size); + Some(buffer) + } +} + +pub fn shell_write_file(path: &str, data: &[u8]) -> Result { + let shell = get_shell_protocol(); + let c_path = + CString16::try_from(path).map_err(|_| uefi::Error::from(Status::INVALID_PARAMETER))?; + + unsafe { + let mode = FileMode::READ | FileMode::WRITE | FileMode::CREATE; + let mut handle: MaybeUninit = MaybeUninit::zeroed(); + (shell.open_file_by_name)(c_path.as_ptr().cast(), handle.as_mut_ptr(), mode.bits()) + .to_result()?; + + let file_handle = handle.assume_init(); + + let mut write_size = data.len(); + let status = (shell.write_file)(file_handle, &mut write_size, data.as_ptr() as *mut _); + + let _ = (shell.close_file)(file_handle); + + status.to_result() + } +} diff --git a/framework_lib/src/fw_uefi/mod.rs b/framework_lib/src/fw_uefi/mod.rs new file mode 100644 index 00000000..82f823a4 --- /dev/null +++ b/framework_lib/src/fw_uefi/mod.rs @@ -0,0 +1,131 @@ +use alloc::vec::Vec; +use core::slice; +use uefi::system::with_config_table; +use uefi::table::cfg::ConfigTableEntry; + +#[allow(unused_imports)] +use log::{debug, error, info, trace}; +use uefi::boot::{self, OpenProtocolAttributes, OpenProtocolParams}; +use uefi::proto::shell::Shell; +use uefi_raw::protocol::shell::ShellProtocol; + +pub mod fs; + +fn get_shell_protocol() -> &'static ShellProtocol { + let handle = boot::get_handle_for_protocol::().expect("No Shell handles"); + + // Use GetProtocol instead of Exclusive since we're running inside the shell + let shell = unsafe { + boot::open_protocol::( + OpenProtocolParams { + handle, + agent: boot::image_handle(), + controller: None, + }, + OpenProtocolAttributes::GetProtocol, + ) + .expect("Failed to open Shell protocol") + }; + + // SAFETY: The Shell wrapper contains the raw ShellProtocol + unsafe { + let proto: &ShellProtocol = core::mem::transmute(shell.get().unwrap()); + // Leak to get 'static lifetime - protocol stays valid while shell is running + core::mem::forget(shell); + proto + } +} + +/// Returns true when the execution break was requested, false otherwise +pub fn shell_get_execution_break_flag() -> bool { + let shell = get_shell_protocol(); + unsafe { (shell.get_page_break)().into() } +} + +/// Enable pagination in UEFI shell +/// +/// Pagination is handled by the UEFI shell environment automatically, whenever +/// the application prints more than fits on the screen. +pub fn enable_page_break() { + let shell = get_shell_protocol(); + unsafe { (shell.enable_page_break)() } +} + +#[repr(C, packed)] +pub struct Smbios { + pub anchor: [u8; 4], + pub checksum: u8, + pub length: u8, + pub major_version: u8, + pub minor_version: u8, + pub max_structure_size: u16, + pub revision: u8, + pub formatted: [u8; 5], + pub inter_anchor: [u8; 5], + pub inter_checksum: u8, + pub table_length: u16, + pub table_address: u32, + pub structure_count: u16, + pub bcd_revision: u8, +} + +impl Smbios { + pub fn checksum_valid(&self) -> bool { + let mut sum: u8 = self.anchor.iter().sum::(); + sum += self.checksum; + sum += self.length; + sum += self.major_version; + sum += self.minor_version; + sum += self.max_structure_size as u8; + sum += self.revision; + sum += self.formatted.iter().sum::(); + sum == 0 + } +} + +pub struct Smbios3 { + pub anchor: [u8; 5], + pub checksum: u8, + pub length: u8, + pub major_version: u8, + pub minor_version: u8, + pub docrev: u8, + pub revision: u8, + _reserved: u8, + pub table_length: u32, + pub table_address: u64, +} + +pub fn smbios_data() -> Option> { + with_config_table(|slice| { + for i in slice { + let table_data = match i.guid { + ConfigTableEntry::SMBIOS3_GUID => unsafe { + let smbios = &*(i.address as *const Smbios3); + debug!("SMBIOS3 valid: {:?}", smbios.anchor == *b"_SM3_"); + Some(slice::from_raw_parts( + smbios.table_address as *const u8, + smbios.table_length as usize, + )) + }, + ConfigTableEntry::SMBIOS_GUID => unsafe { + let smbios = &*(i.address as *const Smbios); + debug!("SMBIOS valid: {:?}", smbios.checksum_valid()); + Some(slice::from_raw_parts( + smbios.table_address as *const u8, + smbios.table_length as usize, + )) + }, + _ => None, + }; + + if let Some(data) = table_data { + // Return directly here because there is only ever the old config + // table or the new V3 config table. Never both. + return Some(data.to_vec()); + } + } + + None + }) +} diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index 1e76e724..92764319 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -32,7 +32,7 @@ pub mod usbhub; #[cfg(feature = "uefi")] #[macro_use] -extern crate uefi_services; +extern crate uefi; pub mod capsule; pub mod capsule_content; @@ -42,12 +42,12 @@ pub mod commandline; pub mod csme; pub mod ec_binary; pub mod esrt; +#[cfg(feature = "uefi")] +pub mod fw_uefi; mod os_specific; pub mod parade_retimer; pub mod power; pub mod smbios; -#[cfg(feature = "uefi")] -pub mod uefi; mod util; pub mod built_info { diff --git a/framework_lib/src/os_specific.rs b/framework_lib/src/os_specific.rs index b77deb87..3566e4c8 100644 --- a/framework_lib/src/os_specific.rs +++ b/framework_lib/src/os_specific.rs @@ -1,7 +1,8 @@ //! Helper functions that need OS/platform specific implementations +use core::time::Duration; #[cfg(not(feature = "uefi"))] -use std::{thread, time}; +use std::thread; #[cfg(feature = "uefi")] use alloc::string::{String, ToString}; @@ -38,17 +39,15 @@ pub fn get_os_version() -> String { /// Sleep a number of microseconds pub fn sleep(micros: u64) { + let duration = Duration::from_micros(micros); #[cfg(not(feature = "uefi"))] { - let duration = time::Duration::from_micros(micros); thread::sleep(duration); } #[cfg(feature = "uefi")] { // TODO: It's not recommended to use this for sleep more than 10ms // Should use a one-shot timer event - let st = unsafe { uefi_services::system_table().as_ref() }; - let bs = st.boot_services(); - bs.stall(micros as usize); + uefi::boot::stall(duration); } } diff --git a/framework_lib/src/smbios.rs b/framework_lib/src/smbios.rs index aaac1cf5..76ce335e 100644 --- a/framework_lib/src/smbios.rs +++ b/framework_lib/src/smbios.rs @@ -193,7 +193,7 @@ pub fn get_smbios() -> Option { #[cfg(feature = "uefi")] pub fn get_smbios() -> Option { trace!("get_smbios() uefi entry"); - let data = crate::uefi::smbios_data().unwrap(); + let data = crate::fw_uefi::smbios_data().unwrap(); let version = None; // TODO: Maybe add the version here let smbios = SMBiosData::from_vec_and_version(data, version); Some(smbios) diff --git a/framework_lib/src/uefi/fs.rs b/framework_lib/src/uefi/fs.rs deleted file mode 100644 index a5e2ca96..00000000 --- a/framework_lib/src/uefi/fs.rs +++ /dev/null @@ -1,132 +0,0 @@ -use alloc::vec; -use alloc::vec::Vec; -use uefi::prelude::*; -use uefi::proto::shell::FileOpenMode; -use uefi::Result; - -use super::find_shell_handle; - -pub fn wstr(string: &str) -> Vec { - let mut wstring = vec![]; - - for c in string.chars() { - wstring.push(c as u16); - } - wstring.push(0); - - wstring -} - -pub fn shell_read_file(path: &str) -> Option> { - let shell = if let Some(shell) = find_shell_handle() { - shell - } else { - println!("Failed to open Shell Protocol"); - return None; - }; - - debug_assert_eq!(shell.major_version, 2); - debug_assert_eq!(shell.minor_version, 2); - - let c_path = wstr(path); - let handle = shell.open_file_by_name(c_path.as_slice(), FileOpenMode::Read as u64); - - let handle = if let Ok(handle) = handle { - handle - } else { - println!("Failed to open file: {:?}", handle); - return None; - }; - - let handle = if let Some(handle) = handle { - handle - } else { - println!("Failed to open file: {:?}", handle); - return None; - }; - let file_handle = handle; - - let res = shell.get_file_size(file_handle); - let file_size = res.unwrap(); - - let mut buffer: Vec = vec![0; file_size as usize]; - let res = shell.read_file(file_handle, &mut buffer); - res.unwrap(); - - // TODO: Make it auto-close using Rust destructors - shell.close_file(file_handle).unwrap(); - - Some(buffer) -} - -pub fn shell_write_file(path: &str, data: &[u8]) -> Result { - let shell = if let Some(shell) = find_shell_handle() { - shell - } else { - println!("Failed to open Shell Protocol"); - return Status::LOAD_ERROR.into(); - }; - - debug_assert_eq!(shell.major_version, 2); - debug_assert_eq!(shell.minor_version, 2); - - let mode = FileOpenMode::Read as u64 + FileOpenMode::Write as u64 + FileOpenMode::Create as u64; - let c_path = wstr(path); - let handle = shell.open_file_by_name(c_path.as_slice(), mode); - let handle = if let Ok(handle) = handle { - handle - } else { - println!("Failed to open file: {:?}", handle); - return Status::LOAD_ERROR.into(); - }; - let handle = if let Some(handle) = handle { - handle - } else { - println!("Failed to open file: {:?}", handle); - return Status::LOAD_ERROR.into(); - }; - let file_handle = handle; - - //// TODO: Free file_info buffer - //let file_info = (shell.0.GetFileInfo)(file_handle); - //if file_info.is_null() { - // println!("Failed to get file info"); - // return ret; - //} - - //// Not sure if it's useful to set FileInfo - ////let mut file_info = unsafe { - //// &mut *(file_info as *mut FileInfo) - ////}; - ////println!("file_info.Size: {}", file_info.Size); - - ////if file_info.Size != 0 { - //// file_info.Size = 0; - //// let ret = (shell.0.SetFileInfo)(file_handle, file_info); - //// if ret.0 != 0 { - //// println!("Failed to set file info"); - //// return ret; - //// } - ////} - - //let mut buffer_size = data.len() as usize; - //let ret = (shell.0.WriteFile)(file_handle, &mut buffer_size, data.as_ptr()); - //if ret.0 != 0 { - // println!("Failed to write file"); - // return ret; - //} - //if buffer_size != data.len() { - // println!( - // "Failed to write whole buffer. Instead of {} wrote {} bytes.", - // data.len(), - // buffer_size - // ); - // return Status(1); - //} - - shell.write_file(file_handle, data).unwrap(); - - shell.close_file(file_handle).unwrap(); - - Status::SUCCESS.into() -} diff --git a/framework_lib/src/uefi/mod.rs b/framework_lib/src/uefi/mod.rs deleted file mode 100644 index 71d85b44..00000000 --- a/framework_lib/src/uefi/mod.rs +++ /dev/null @@ -1,179 +0,0 @@ -use alloc::vec::Vec; -use core::slice; -use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams, ScopedProtocol, SearchType}; - -#[allow(unused_imports)] -use log::{debug, error, info, trace}; -use uefi::proto::shell::Shell; -use uefi::table::cfg::{SMBIOS3_GUID, SMBIOS_GUID}; -use uefi::table::{Boot, SystemTable}; -use uefi::Identify; - -pub mod fs; - -pub fn get_system_table() -> &'static SystemTable { - unsafe { uefi_services::system_table().as_ref() } -} - -fn find_shell_handle() -> Option> { - let st = unsafe { uefi_services::system_table().as_ref() }; - let boot_services = st.boot_services(); - let shell_handles = boot_services.locate_handle_buffer(SearchType::ByProtocol(&Shell::GUID)); - if let Ok(sh_buf) = shell_handles { - for handle in &*sh_buf { - return Some(unsafe { - boot_services - .open_protocol::( - OpenProtocolParams { - handle: *handle, - agent: boot_services.image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - .expect("Failed to open Shell handle") - }); - } - } else { - panic!("No shell handle found!"); - } - None -} - -/// Returns true when the execution break was requested, false otherwise -pub fn shell_get_execution_break_flag() -> bool { - let st = unsafe { uefi_services::system_table().as_ref() }; - let boot_services = st.boot_services(); - let shell_handles = boot_services.locate_handle_buffer(SearchType::ByProtocol(&Shell::GUID)); - if let Ok(sh_buf) = shell_handles { - for handle in &*sh_buf { - let shell_handle = unsafe { - boot_services - .open_protocol::( - OpenProtocolParams { - handle: *handle, - agent: boot_services.image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - .expect("Failed to open Shell handle") - }; - - let event = unsafe { shell_handle.execution_break.unsafe_clone() }; - return boot_services.check_event(event).unwrap(); - } - return false; - } else { - panic!("No shell handle found!"); - } -} - -/// Enable pagination in UEFI shell -/// -/// Pagination is handled by the UEFI shell environment automatically, whenever -/// the application prints more than fits on the screen. -pub fn enable_page_break() { - let st = unsafe { uefi_services::system_table().as_ref() }; - let boot_services = st.boot_services(); - let shell_handles = boot_services.locate_handle_buffer(SearchType::ByProtocol(&Shell::GUID)); - if let Ok(sh_buf) = shell_handles { - for handle in &*sh_buf { - //trace!("Calling enable_page_break"); - let shell_handle = unsafe { - boot_services - .open_protocol::( - OpenProtocolParams { - handle: *handle, - agent: boot_services.image_handle(), - controller: None, - }, - OpenProtocolAttributes::GetProtocol, - ) - .expect("Failed to open Shell handle") - }; - shell_handle.enable_page_break(); - } - } else { - panic!("No shell handle found!"); - } -} - -#[repr(C, packed)] -pub struct Smbios { - pub anchor: [u8; 4], - pub checksum: u8, - pub length: u8, - pub major_version: u8, - pub minor_version: u8, - pub max_structure_size: u16, - pub revision: u8, - pub formatted: [u8; 5], - pub inter_anchor: [u8; 5], - pub inter_checksum: u8, - pub table_length: u16, - pub table_address: u32, - pub structure_count: u16, - pub bcd_revision: u8, -} - -impl Smbios { - pub fn checksum_valid(&self) -> bool { - let mut sum: u8 = self.anchor.iter().sum::(); - sum += self.checksum; - sum += self.length; - sum += self.major_version; - sum += self.minor_version; - sum += self.max_structure_size as u8; - sum += self.revision; - sum += self.formatted.iter().sum::(); - sum == 0 - } -} - -pub struct Smbios3 { - pub anchor: [u8; 5], - pub checksum: u8, - pub length: u8, - pub major_version: u8, - pub minor_version: u8, - pub docrev: u8, - pub revision: u8, - _reserved: u8, - pub table_length: u32, - pub table_address: u64, -} - -pub fn smbios_data() -> Option> { - let st = unsafe { uefi_services::system_table().as_ref() }; - let config_tables = st.config_table(); - - for table in config_tables { - let table_data = match table.guid { - SMBIOS3_GUID => unsafe { - let smbios = &*(table.address as *const Smbios3); - debug!("SMBIOS3 valid: {:?}", smbios.anchor == *b"_SM3_"); - Some(slice::from_raw_parts( - smbios.table_address as *const u8, - smbios.table_length as usize, - )) - }, - SMBIOS_GUID => unsafe { - let smbios = &*(table.address as *const Smbios); - debug!("SMBIOS valid: {:?}", smbios.checksum_valid()); - Some(slice::from_raw_parts( - smbios.table_address as *const u8, - smbios.table_length as usize, - )) - }, - _ => None, - }; - - if let Some(data) = table_data { - // Return directly here because there is only ever the old config - // table or the new V3 config table. Never both. - return Some(data.to_vec()); - } - } - None -} diff --git a/framework_uefi/Cargo.toml b/framework_uefi/Cargo.toml index e984b1f5..0b3f2ec0 100644 --- a/framework_uefi/Cargo.toml +++ b/framework_uefi/Cargo.toml @@ -19,8 +19,7 @@ default = [ ] readonly = [ "framework_lib/readonly" ] [dependencies] -uefi = { version = "0.20", features = ["alloc"] } -uefi-services = "0.17" +uefi = { version = "0.36.1", features = ["alloc"] } log = { version = "0.4", default-features = true } [dependencies.framework_lib] diff --git a/framework_uefi/Makefile b/framework_uefi/Makefile index 0814ff42..418e29a0 100644 --- a/framework_uefi/Makefile +++ b/framework_uefi/Makefile @@ -5,14 +5,24 @@ FEATURES?='' SRC_DIR=. QEMU?=qemu-system-x86_64 +OVMF_CODE?=$(BUILD)/OVMF_CODE.fd +OVMF_VARS?=$(BUILD)/OVMF_VARS.fd + QEMU_FLAGS=\ -M q35 \ -m 1024 \ -net none \ -vga std \ - -bios /usr/share/OVMF/OVMF_CODE.fd + -drive if=pflash,format=raw,readonly=on,file=$(OVMF_CODE) \ + -drive if=pflash,format=raw,file=$(BUILD)/ovmf_vars.fd + +QEMU_TEST_FLAGS=\ + $(QEMU_FLAGS) \ + -display none \ + -serial file:$(BUILD)/serial.log \ + -no-reboot -.PHONY: qemu clean +.PHONY: qemu clean test test-qemu all: $(BUILD)/boot.img @@ -21,8 +31,8 @@ iso: $(BUILD)/UEFI-Shell-fwk.iso clean: rm -rf $(BUILD) -qemu: $(BUILD)/boot.img - $(QEMU) $(QEMU_FLAGS) $< +qemu: $(BUILD)/boot.img $(BUILD)/OVMF_CODE.fd $(BUILD)/ovmf_vars.fd + $(QEMU) $(QEMU_FLAGS) -drive format=raw,file=$< # Create ESP partition and filesystem $(BUILD)/boot.img: $(BUILD)/efi.img @@ -46,7 +56,14 @@ $(BUILD)/efi.img: $(BUILD)/boot.efi mv $@.tmp $@ $(BUILD)/shellx64.efi: - wget https://github.com/pbatard/UEFI-Shell/releases/download/24H2/shellx64.efi -O $@ + mkdir -p $(BUILD) + curl -L https://github.com/pbatard/UEFI-Shell/releases/download/24H2/shellx64.efi -o $@ + +# Download OVMF firmware for QEMU +$(BUILD)/OVMF_CODE.fd $(BUILD)/OVMF_VARS.fd: + mkdir -p $(BUILD) + curl -L https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF_CODE.fd -o $(BUILD)/OVMF_CODE.fd + curl -L https://retrage.github.io/edk2-nightly/bin/RELEASEX64_OVMF_VARS.fd -o $(BUILD)/OVMF_VARS.fd $(BUILD)/UEFI-Shell-fwk.iso: $(BUILD)/boot.efi $(BUILD)/shellx64.efi mkdir -p $(BUILD)/$@.tmp/efi/boot @@ -66,3 +83,55 @@ $(BUILD)/boot.efi: ../Cargo.lock $(SRC_DIR)/Cargo.toml $(SRC_DIR)/src/* --release \ -- \ --emit link=framework_uefi/$@ + +# Test targets +$(BUILD)/ovmf_vars.fd: $(BUILD)/OVMF_VARS.fd + cp $< $@ + +$(BUILD)/test.img: $(BUILD)/boot.efi $(BUILD)/shellx64.efi tests/test.nsh winux.bin + dd if=/dev/zero of=$@.tmp bs=512 count=131072 + mkfs.vfat $@.tmp + mmd -i $@.tmp efi + mmd -i $@.tmp efi/boot + # Copy shell as boot loader, our tool as fwk.efi + mcopy -i $@.tmp $(BUILD)/shellx64.efi ::efi/boot/bootx64.efi + mcopy -i $@.tmp $(BUILD)/boot.efi ::efi/boot/fwk.efi + # Also copy as bootx64 location for the test script + mcopy -i $@.tmp $(BUILD)/boot.efi ::bootx64.efi + # Copy test script as startup.nsh + mcopy -i $@.tmp tests/test.nsh ::startup.nsh + # Copy test files + mcopy -i $@.tmp winux.bin ::winux.bin + mv $@.tmp $@ + +$(BUILD)/test-boot.img: $(BUILD)/test.img + dd if=/dev/zero of=$@.tmp bs=512 count=133120 + parted $@.tmp -s -a minimal mklabel gpt + parted $@.tmp -s -a minimal mkpart EFI FAT16 2048s 131071s + parted $@.tmp -s -a minimal toggle 1 boot + dd if=$< of=$@.tmp bs=512 count=131072 seek=2048 conv=notrunc + mv $@.tmp $@ + +test-qemu: $(BUILD)/test-boot.img $(BUILD)/OVMF_CODE.fd $(BUILD)/ovmf_vars.fd + @echo "Running UEFI tests in QEMU..." + @rm -f $(BUILD)/serial.log + timeout 60 $(QEMU) $(QEMU_TEST_FLAGS) -drive format=raw,file=$< || true + @echo "" + @echo "=== Test Output ===" + @cat $(BUILD)/serial.log + @echo "" + @echo "=== Test Results ===" + @if grep -q "TESTS_COMPLETE" $(BUILD)/serial.log; then \ + echo "All tests completed."; \ + else \ + echo "ERROR: Tests did not complete!"; \ + exit 1; \ + fi + @if grep -q "TEST_FAILED" $(BUILD)/serial.log; then \ + echo "ERROR: Some tests failed!"; \ + grep "TEST_FAILED" $(BUILD)/serial.log; \ + exit 1; \ + fi + @echo "All tests passed!" + +test: test-qemu diff --git a/framework_uefi/src/main.rs b/framework_uefi/src/main.rs index caeeee2e..a9e8f581 100644 --- a/framework_uefi/src/main.rs +++ b/framework_uefi/src/main.rs @@ -5,18 +5,17 @@ use log::{debug, error, info, trace}; use uefi::prelude::*; #[allow(unused_imports)] -use uefi_services::{print, println}; +use uefi::{print, println}; extern crate alloc; use framework_lib::commandline; #[entry] -fn main(image_handle: Handle, mut system_table: SystemTable) -> Status { - uefi_services::init(&mut system_table).unwrap(); - let bs = system_table.boot_services(); +fn main() -> Status { + uefi::helpers::init().unwrap(); - let args = commandline::uefi::get_args(bs, image_handle); + let args = commandline::uefi::get_args(); let args = commandline::parse(&args); if commandline::run_with_args(&args, false) == 0 { return Status::SUCCESS; diff --git a/framework_uefi/tests/test.nsh b/framework_uefi/tests/test.nsh new file mode 100644 index 00000000..2795e091 --- /dev/null +++ b/framework_uefi/tests/test.nsh @@ -0,0 +1,82 @@ +@echo -off +echo "=== UEFI Test Suite Starting ===" + +# Clean up any files from previous test runs +if exist fs0:\dump.bmp then + del fs0:\dump.bmp +endif + +# Set path to our tool (fwk.efi is our framework tool) +set fwk fs0:\efi\boot\fwk.efi + +echo "" +echo "=== TEST: --version ===" +%fwk% --version +if %lasterror% == 0 then + echo "TEST_PASSED: version" +else + echo "TEST_FAILED: version (exit code %lasterror%)" +endif + +echo "" +echo "=== TEST: --help ===" +%fwk% --help +# --help returns 1 (LOAD_ERROR) which is expected behavior for this tool +if %lasterror% == 0x1 then + echo "TEST_PASSED: help" +else + echo "TEST_FAILED: help (expected 0x1, got %lasterror%)" +endif + +echo "" +echo "=== TEST: --hash on tool itself ===" +%fwk% --hash fs0:\efi\boot\fwk.efi +if %lasterror% == 0 then + echo "TEST_PASSED: hash" +else + echo "TEST_FAILED: hash (exit code %lasterror%)" +endif + +echo "" +echo "=== TEST: --hash on winux.bin ===" +%fwk% --hash fs0:\winux.bin +if %lasterror% == 0 then + echo "TEST_PASSED: hash_winux" +else + echo "TEST_FAILED: hash_winux (exit code %lasterror%)" +endif + +echo "" +echo "=== TEST: --capsule dump ===" +%fwk% --capsule fs0:\winux.bin --dump fs0:\dump.bmp +if %lasterror% == 0 then + echo "TEST_PASSED: capsule_dump" +else + echo "TEST_FAILED: capsule_dump (exit code %lasterror%)" +endif + +echo "" +echo "=== TEST: verify dump.bmp was created ===" +if exist fs0:\dump.bmp then + %fwk% --hash fs0:\dump.bmp + if %lasterror% == 0 then + echo "TEST_PASSED: dump_exists" + else + echo "TEST_FAILED: dump_exists (hash command failed)" + endif +else + echo "TEST_FAILED: dump_exists (dump.bmp was not created)" +endif + +echo "" +echo "=== TEST: introspect (may fail in QEMU without real SMBIOS) ===" +%fwk% --introspect +# Don't fail on this - QEMU doesn't have real Framework SMBIOS +echo "TEST_INFO: introspect exited with %lasterror%" + +echo "" +echo "=== UEFI Test Suite Complete ===" +echo "TESTS_COMPLETE" + +# Shutdown QEMU +reset -s diff --git a/framework_uefi/winux.bin b/framework_uefi/winux.bin new file mode 100644 index 00000000..7cd1956f Binary files /dev/null and b/framework_uefi/winux.bin differ