diff --git a/EXAMPLES.md b/EXAMPLES.md index 6c3d50a..51c2b0e 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -464,3 +464,23 @@ ESRT Entry 0 Last Attempt Version: 0x108 (264) Last Attempt Status: Success ``` + +## Flashing EC firmware + +**IMPORTANT** Flashing EC firmware yourself is not recommended. It may render +your hardware unbootable. Please update your firmware using the official BIOS +update methods (Windows .exe, LVFS/FWUPD, EFI updater)! + +This command has not been thoroughly tested on all Framework Computer systems + +``` +# Simulate flashing RW (to see which blocks are updated) +> framework_tool --flash-rw-ec ec.bin --dry-run + +# Actually flash RW +> framework_tool --flash-rw-ec ec.bin + +# Boot into EC RW firmware (will crash your OS and reboot immediately) +# EC will boot back into RO if the system turned off for 30s +> framework_tool --reboot-ec jump-rw +``` diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 9bd367a..02631e5 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -703,7 +703,8 @@ impl CrosEc { /// | 3C000 | 3FFFF | 04000 | Preserved | /// | 40000 | 3C000 | 39000 | RO Region | /// | 79000 | 79FFF | 01000 | Flash Flags | - pub fn reflash(&self, data: &[u8], ft: EcFlashType) -> EcResult<()> { + pub fn reflash(&self, data: &[u8], ft: EcFlashType, dry_run: bool) -> EcResult<()> { + let mut res = Ok(()); if ft == EcFlashType::Full || ft == EcFlashType::Ro { if let Some(version) = ec_binary::read_ec_version(data, true) { println!("EC RO Version in File: {:?}", version.version); @@ -723,9 +724,21 @@ impl CrosEc { } } - if ft == EcFlashType::Full || ft == EcFlashType::Ro { - println!("For safety reasons flashing RO firmware is disabled."); - return Ok(()); + // Determine recommended flash parameters + let info = EcRequestFlashInfo {}.send_command(self)?; + + // Check that our hardcoded offsets are valid for the available flash + if FLASH_RO_SIZE + FLASH_RW_SIZE > info.flash_size { + return Err(EcError::DeviceError(format!( + "RO+RW larger than flash 0x{:X}", + { info.flash_size } + ))); + } + if FLASH_RW_BASE + FLASH_RW_SIZE > info.flash_size { + return Err(EcError::DeviceError(format!( + "RW overruns end of flash 0x{:X}", + { info.flash_size } + ))); } println!("Unlocking flash"); @@ -740,18 +753,32 @@ impl CrosEc { if ft == EcFlashType::Full || ft == EcFlashType::Rw { let rw_data = &data[FLASH_RW_BASE as usize..(FLASH_RW_BASE + FLASH_RW_SIZE) as usize]; - println!("Erasing RW region"); - self.erase_ec_flash(FLASH_BASE + FLASH_RW_BASE, FLASH_RW_SIZE)?; - - println!("Writing RW region"); - self.write_ec_flash(FLASH_BASE + FLASH_RW_BASE, rw_data)?; + println!( + "Erasing RW region{}", + if dry_run { " (DRY RUN)" } else { "" } + ); + self.erase_ec_flash( + FLASH_BASE + FLASH_RW_BASE, + FLASH_RW_SIZE, + dry_run, + info.erase_block_size, + )?; + println!(" Done"); + + println!( + "Writing RW region{}", + if dry_run { " (DRY RUN)" } else { "" } + ); + self.write_ec_flash(FLASH_BASE + FLASH_RW_BASE, rw_data, dry_run)?; + println!(" Done"); println!("Verifying RW region"); let flash_rw_data = self.read_ec_flash(FLASH_BASE + FLASH_RW_BASE, FLASH_RW_SIZE)?; if rw_data == flash_rw_data { - println!("RW verify success"); + println!(" RW verify success"); } else { - println!("RW verify fail"); + error!("RW verify fail!"); + res = Err(EcError::DeviceError("RW verify fail!".to_string())); } } @@ -759,17 +786,25 @@ impl CrosEc { let ro_data = &data[FLASH_RO_BASE as usize..(FLASH_RO_BASE + FLASH_RO_SIZE) as usize]; println!("Erasing RO region"); - self.erase_ec_flash(FLASH_BASE + FLASH_RO_BASE, FLASH_RO_SIZE)?; + self.erase_ec_flash( + FLASH_BASE + FLASH_RO_BASE, + FLASH_RO_SIZE, + dry_run, + info.erase_block_size, + )?; + println!(" Done"); println!("Writing RO region"); - self.write_ec_flash(FLASH_BASE + FLASH_RO_BASE, ro_data)?; + self.write_ec_flash(FLASH_BASE + FLASH_RO_BASE, ro_data, dry_run)?; + println!(" Done"); println!("Verifying RO region"); let flash_ro_data = self.read_ec_flash(FLASH_BASE + FLASH_RO_BASE, FLASH_RO_SIZE)?; if ro_data == flash_ro_data { - println!("RO verify success"); + println!(" RO verify success"); } else { - println!("RO verify fail"); + error!("RO verify fail!"); + res = Err(EcError::DeviceError("RW verify fail!".to_string())); } } @@ -777,43 +812,50 @@ impl CrosEc { self.flash_notify(MecFlashNotify::AccessSpiDone)?; self.flash_notify(MecFlashNotify::FirmwareDone)?; - println!("Flashing EC done. You can reboot the EC now"); - // TODO: Should we force a reboot if currently running one was reflashed? + if res.is_ok() { + println!("Flashing EC done. You can reboot the EC now"); + } - Ok(()) + res } /// Write a big section of EC flash. Must be unlocked already - fn write_ec_flash(&self, addr: u32, data: &[u8]) -> EcResult<()> { - let info = EcRequestFlashInfo {}.send_command(self)?; - println!("Flash info: {:?}", info); + fn write_ec_flash(&self, addr: u32, data: &[u8], dry_run: bool) -> EcResult<()> { + // TODO: Use flash info to help guide ideal chunk size + // let info = EcRequestFlashInfo {}.send_command(self)?; //let chunk_size = ((0x80 / info.write_ideal_size) * info.write_ideal_size) as usize; + let chunk_size = 0x80; let chunks = data.len() / chunk_size; + println!( + " Will write flash from 0x{:X} to 0x{:X} in {}*{}B chunks", + addr, + data.len(), + chunks, + chunk_size + ); for chunk_no in 0..chunks { let offset = chunk_no * chunk_size; // Current chunk might be smaller if it's the last let cur_chunk_size = std::cmp::min(chunk_size, data.len() - chunk_no * chunk_size); if chunk_no % 100 == 0 { - println!(); - print!( - "Writing chunk {:>4}/{:>4} ({:>6}/{:>6}): X", - chunk_no, - chunks, - offset, - cur_chunk_size * chunks - ); + if chunk_no != 0 { + println!(); + } + print!(" Chunk {:>4}: X", chunk_no); } else { print!("X"); } let chunk = &data[offset..offset + cur_chunk_size]; - let res = self.write_ec_flash_chunk(addr + offset as u32, chunk); - if let Err(err) = res { - println!(" Failed to write chunk: {:?}", err); - return Err(err); + if !dry_run { + let res = self.write_ec_flash_chunk(addr + offset as u32, chunk); + if let Err(err) = res { + println!(" Failed to write chunk: {:?}", err); + return Err(err); + } } } println!(); @@ -831,8 +873,38 @@ impl CrosEc { .send_command_extra(self, data) } - fn erase_ec_flash(&self, offset: u32, size: u32) -> EcResult<()> { - EcRequestFlashErase { offset, size }.send_command(self) + fn erase_ec_flash( + &self, + offset: u32, + size: u32, + dry_run: bool, + chunk_size: u32, + ) -> EcResult<()> { + // Erasing a big section takes too long sometimes and the linux kernel driver times out, so + // split it up into chunks. + let mut cur_offset = offset; + + while cur_offset < offset + size { + let rem_size = offset + size - cur_offset; + let cur_size = if rem_size < chunk_size { + rem_size + } else { + chunk_size + }; + debug!( + "EcRequestFlashErase (0x{:05X}, 0x{:05X})", + cur_offset, cur_size + ); + if !dry_run { + EcRequestFlashErase { + offset: cur_offset, + size: cur_size, + } + .send_command(self)?; + } + cur_offset += chunk_size; + } + Ok(()) } pub fn flash_notify(&self, flag: MecFlashNotify) -> EcResult<()> { diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index aabd546..2796057 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -123,11 +123,11 @@ struct ClapCli { #[arg(long)] dump_ec_flash: Option, - /// Flash EC with new firmware from file + /// Flash EC (RO+RW) with new firmware from file - may render your hardware unbootable! #[arg(long)] flash_ec: Option, - /// Flash EC with new RO firmware from file + /// Flash EC with new RO firmware from file - may render your hardware unbootable! #[arg(long)] flash_ro_ec: Option, @@ -250,6 +250,14 @@ struct ClapCli { /// Run self-test to check if interaction with EC is possible #[arg(long, short)] test: bool, + + /// Force execution of an unsafe command - may render your hardware unbootable! + #[arg(long, short)] + force: bool, + + /// Simulate execution of a command (e.g. --flash-ec) + #[arg(long)] + dry_run: bool, } /// Parse a list of commandline arguments and return the struct @@ -424,6 +432,8 @@ pub fn parse(args: &[String]) -> Cli { pd_addrs, pd_ports, test: args.test, + dry_run: args.dry_run, + force: args.force, // TODO: Set help. Not very important because Clap handles this by itself help: false, // UEFI only for now. Don't need to handle diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 3906cbb..ce7071e 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -170,6 +170,8 @@ pub struct Cli { pub flash_rw_ec: Option, pub driver: Option, pub test: bool, + pub dry_run: bool, + pub force: bool, pub intrusion: bool, pub inputdeck: bool, pub inputdeck_mode: Option, @@ -594,7 +596,7 @@ fn print_esrt() { } } -fn flash_ec(ec: &CrosEc, ec_bin_path: &str, flash_type: EcFlashType) { +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); #[cfg(not(feature = "uefi"))] @@ -613,7 +615,7 @@ fn flash_ec(ec: &CrosEc, ec_bin_path: &str, flash_type: EcFlashType) { println!("File"); println!(" Size: {:>20} B", data.len()); println!(" Size: {:>20} KB", data.len() / 1024); - if let Err(err) = ec.reflash(&data, flash_type) { + if let Err(err) = ec.reflash(&data, flash_type, dry_run) { println!("Error: {:?}", err); } else { println!("Success!"); @@ -1107,11 +1109,19 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { // TODO: Should have progress indicator dump_ec_flash(&ec, dump_path); } else if let Some(ec_bin_path) = &args.flash_ec { - flash_ec(&ec, ec_bin_path, EcFlashType::Full); + if args.force { + flash_ec(&ec, ec_bin_path, EcFlashType::Full, args.dry_run); + } else { + error!("Flashing EC RO region is unsafe. Use --flash-ec-rw instead"); + } } else if let Some(ec_bin_path) = &args.flash_ro_ec { - flash_ec(&ec, ec_bin_path, EcFlashType::Ro); + if args.force { + flash_ec(&ec, ec_bin_path, EcFlashType::Ro, args.dry_run); + } else { + error!("Flashing EC RO region is unsafe. Use --flash-ec-rw instead"); + } } else if let Some(ec_bin_path) = &args.flash_rw_ec { - flash_ec(&ec, ec_bin_path, EcFlashType::Rw); + flash_ec(&ec, ec_bin_path, EcFlashType::Rw, args.dry_run); } else if let Some(hash_file) = &args.hash { println!("Hashing file: {}", hash_file); #[cfg(feature = "uefi")] @@ -1193,6 +1203,8 @@ Options: --console Get EC console, choose whether recent or to follow the output [possible values: recent, follow] --hash Hash a file of arbitrary data --flash-gpu-descriptor <18 DIGIT SN> Overwrite the GPU bay descriptor SN and type. + -f, --force Force execution of an unsafe command - may render your hardware unbootable! + --dry-run Simulate execution of a command (e.g. --flash-ec) -t, --test Run self-test to check if interaction with EC is possible -h, --help Print help information -b Print output one screen at a time diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 097c006..09a4339 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -109,6 +109,8 @@ pub fn parse(args: &[String]) -> Cli { pd_addrs: None, pd_ports: None, test: false, + dry_run: false, + force: false, help: false, flash_gpu_descriptor: None, allupdate: false, @@ -504,6 +506,12 @@ pub fn parse(args: &[String]) -> Cli { } else if arg == "-t" || arg == "--test" { cli.test = true; found_an_option = true; + } else if arg == "-f" || arg == "--force" { + cli.force = true; + found_an_option = true; + } else if arg == "--dry-run" { + cli.dry_run = true; + found_an_option = true; } else if arg == "-h" || arg == "--help" { cli.help = true; found_an_option = true;