Skip to content

chromium_ec: Erase EC in sections #160

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
142 changes: 107 additions & 35 deletions framework_lib/src/chromium_ec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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");
Expand All @@ -740,80 +753,109 @@ 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()));
}
}

if ft == EcFlashType::Full || ft == EcFlashType::Ro {
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()));
}
}

println!("Locking flash");
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!();
Expand All @@ -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<()> {
Expand Down
14 changes: 12 additions & 2 deletions framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,11 @@ struct ClapCli {
#[arg(long)]
dump_ec_flash: Option<std::path::PathBuf>,

/// 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<std::path::PathBuf>,

/// 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<std::path::PathBuf>,

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
22 changes: 17 additions & 5 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ pub struct Cli {
pub flash_rw_ec: Option<String>,
pub driver: Option<CrosEcDriverType>,
pub test: bool,
pub dry_run: bool,
pub force: bool,
pub intrusion: bool,
pub inputdeck: bool,
pub inputdeck_mode: Option<InputDeckModeArg>,
Expand Down Expand Up @@ -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"))]
Expand All @@ -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!");
Expand Down Expand Up @@ -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")]
Expand Down Expand Up @@ -1193,6 +1203,8 @@ Options:
--console <CONSOLE> Get EC console, choose whether recent or to follow the output [possible values: recent, follow]
--hash <HASH> Hash a file of arbitrary data
--flash-gpu-descriptor <MAGIC> <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
Expand Down
8 changes: 8 additions & 0 deletions framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
Loading