Skip to content

Commit 6c5762c

Browse files
Merge pull request #160 from FrameworkComputer/erase-ec-sections
chromium_ec: Erase EC in sections
2 parents f6fc502 + aafd4cd commit 6c5762c

File tree

5 files changed

+164
-42
lines changed

5 files changed

+164
-42
lines changed

EXAMPLES.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,3 +533,23 @@ ESRT Entry 0
533533
Last Attempt Version: 0x108 (264)
534534
Last Attempt Status: Success
535535
```
536+
537+
## Flashing EC firmware
538+
539+
**IMPORTANT** Flashing EC firmware yourself is not recommended. It may render
540+
your hardware unbootable. Please update your firmware using the official BIOS
541+
update methods (Windows .exe, LVFS/FWUPD, EFI updater)!
542+
543+
This command has not been thoroughly tested on all Framework Computer systems
544+
545+
```
546+
# Simulate flashing RW (to see which blocks are updated)
547+
> framework_tool --flash-rw-ec ec.bin --dry-run
548+
549+
# Actually flash RW
550+
> framework_tool --flash-rw-ec ec.bin
551+
552+
# Boot into EC RW firmware (will crash your OS and reboot immediately)
553+
# EC will boot back into RO if the system turned off for 30s
554+
> framework_tool --reboot-ec jump-rw
555+
```

framework_lib/src/chromium_ec/mod.rs

Lines changed: 107 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,8 @@ impl CrosEc {
703703
/// | 3C000 | 3FFFF | 04000 | Preserved |
704704
/// | 40000 | 3C000 | 39000 | RO Region |
705705
/// | 79000 | 79FFF | 01000 | Flash Flags |
706-
pub fn reflash(&self, data: &[u8], ft: EcFlashType) -> EcResult<()> {
706+
pub fn reflash(&self, data: &[u8], ft: EcFlashType, dry_run: bool) -> EcResult<()> {
707+
let mut res = Ok(());
707708
if ft == EcFlashType::Full || ft == EcFlashType::Ro {
708709
if let Some(version) = ec_binary::read_ec_version(data, true) {
709710
println!("EC RO Version in File: {:?}", version.version);
@@ -723,9 +724,21 @@ impl CrosEc {
723724
}
724725
}
725726

726-
if ft == EcFlashType::Full || ft == EcFlashType::Ro {
727-
println!("For safety reasons flashing RO firmware is disabled.");
728-
return Ok(());
727+
// Determine recommended flash parameters
728+
let info = EcRequestFlashInfo {}.send_command(self)?;
729+
730+
// Check that our hardcoded offsets are valid for the available flash
731+
if FLASH_RO_SIZE + FLASH_RW_SIZE > info.flash_size {
732+
return Err(EcError::DeviceError(format!(
733+
"RO+RW larger than flash 0x{:X}",
734+
{ info.flash_size }
735+
)));
736+
}
737+
if FLASH_RW_BASE + FLASH_RW_SIZE > info.flash_size {
738+
return Err(EcError::DeviceError(format!(
739+
"RW overruns end of flash 0x{:X}",
740+
{ info.flash_size }
741+
)));
729742
}
730743

731744
println!("Unlocking flash");
@@ -740,80 +753,109 @@ impl CrosEc {
740753
if ft == EcFlashType::Full || ft == EcFlashType::Rw {
741754
let rw_data = &data[FLASH_RW_BASE as usize..(FLASH_RW_BASE + FLASH_RW_SIZE) as usize];
742755

743-
println!("Erasing RW region");
744-
self.erase_ec_flash(FLASH_BASE + FLASH_RW_BASE, FLASH_RW_SIZE)?;
745-
746-
println!("Writing RW region");
747-
self.write_ec_flash(FLASH_BASE + FLASH_RW_BASE, rw_data)?;
756+
println!(
757+
"Erasing RW region{}",
758+
if dry_run { " (DRY RUN)" } else { "" }
759+
);
760+
self.erase_ec_flash(
761+
FLASH_BASE + FLASH_RW_BASE,
762+
FLASH_RW_SIZE,
763+
dry_run,
764+
info.erase_block_size,
765+
)?;
766+
println!(" Done");
767+
768+
println!(
769+
"Writing RW region{}",
770+
if dry_run { " (DRY RUN)" } else { "" }
771+
);
772+
self.write_ec_flash(FLASH_BASE + FLASH_RW_BASE, rw_data, dry_run)?;
773+
println!(" Done");
748774

749775
println!("Verifying RW region");
750776
let flash_rw_data = self.read_ec_flash(FLASH_BASE + FLASH_RW_BASE, FLASH_RW_SIZE)?;
751777
if rw_data == flash_rw_data {
752-
println!("RW verify success");
778+
println!(" RW verify success");
753779
} else {
754-
println!("RW verify fail");
780+
error!("RW verify fail!");
781+
res = Err(EcError::DeviceError("RW verify fail!".to_string()));
755782
}
756783
}
757784

758785
if ft == EcFlashType::Full || ft == EcFlashType::Ro {
759786
let ro_data = &data[FLASH_RO_BASE as usize..(FLASH_RO_BASE + FLASH_RO_SIZE) as usize];
760787

761788
println!("Erasing RO region");
762-
self.erase_ec_flash(FLASH_BASE + FLASH_RO_BASE, FLASH_RO_SIZE)?;
789+
self.erase_ec_flash(
790+
FLASH_BASE + FLASH_RO_BASE,
791+
FLASH_RO_SIZE,
792+
dry_run,
793+
info.erase_block_size,
794+
)?;
795+
println!(" Done");
763796

764797
println!("Writing RO region");
765-
self.write_ec_flash(FLASH_BASE + FLASH_RO_BASE, ro_data)?;
798+
self.write_ec_flash(FLASH_BASE + FLASH_RO_BASE, ro_data, dry_run)?;
799+
println!(" Done");
766800

767801
println!("Verifying RO region");
768802
let flash_ro_data = self.read_ec_flash(FLASH_BASE + FLASH_RO_BASE, FLASH_RO_SIZE)?;
769803
if ro_data == flash_ro_data {
770-
println!("RO verify success");
804+
println!(" RO verify success");
771805
} else {
772-
println!("RO verify fail");
806+
error!("RO verify fail!");
807+
res = Err(EcError::DeviceError("RW verify fail!".to_string()));
773808
}
774809
}
775810

776811
println!("Locking flash");
777812
self.flash_notify(MecFlashNotify::AccessSpiDone)?;
778813
self.flash_notify(MecFlashNotify::FirmwareDone)?;
779814

780-
println!("Flashing EC done. You can reboot the EC now");
781-
// TODO: Should we force a reboot if currently running one was reflashed?
815+
if res.is_ok() {
816+
println!("Flashing EC done. You can reboot the EC now");
817+
}
782818

783-
Ok(())
819+
res
784820
}
785821

786822
/// Write a big section of EC flash. Must be unlocked already
787-
fn write_ec_flash(&self, addr: u32, data: &[u8]) -> EcResult<()> {
788-
let info = EcRequestFlashInfo {}.send_command(self)?;
789-
println!("Flash info: {:?}", info);
823+
fn write_ec_flash(&self, addr: u32, data: &[u8], dry_run: bool) -> EcResult<()> {
824+
// TODO: Use flash info to help guide ideal chunk size
825+
// let info = EcRequestFlashInfo {}.send_command(self)?;
790826
//let chunk_size = ((0x80 / info.write_ideal_size) * info.write_ideal_size) as usize;
827+
791828
let chunk_size = 0x80;
792829

793830
let chunks = data.len() / chunk_size;
831+
println!(
832+
" Will write flash from 0x{:X} to 0x{:X} in {}*{}B chunks",
833+
addr,
834+
data.len(),
835+
chunks,
836+
chunk_size
837+
);
794838
for chunk_no in 0..chunks {
795839
let offset = chunk_no * chunk_size;
796840
// Current chunk might be smaller if it's the last
797841
let cur_chunk_size = std::cmp::min(chunk_size, data.len() - chunk_no * chunk_size);
798842

799843
if chunk_no % 100 == 0 {
800-
println!();
801-
print!(
802-
"Writing chunk {:>4}/{:>4} ({:>6}/{:>6}): X",
803-
chunk_no,
804-
chunks,
805-
offset,
806-
cur_chunk_size * chunks
807-
);
844+
if chunk_no != 0 {
845+
println!();
846+
}
847+
print!(" Chunk {:>4}: X", chunk_no);
808848
} else {
809849
print!("X");
810850
}
811851

812852
let chunk = &data[offset..offset + cur_chunk_size];
813-
let res = self.write_ec_flash_chunk(addr + offset as u32, chunk);
814-
if let Err(err) = res {
815-
println!(" Failed to write chunk: {:?}", err);
816-
return Err(err);
853+
if !dry_run {
854+
let res = self.write_ec_flash_chunk(addr + offset as u32, chunk);
855+
if let Err(err) = res {
856+
println!(" Failed to write chunk: {:?}", err);
857+
return Err(err);
858+
}
817859
}
818860
}
819861
println!();
@@ -831,8 +873,38 @@ impl CrosEc {
831873
.send_command_extra(self, data)
832874
}
833875

834-
fn erase_ec_flash(&self, offset: u32, size: u32) -> EcResult<()> {
835-
EcRequestFlashErase { offset, size }.send_command(self)
876+
fn erase_ec_flash(
877+
&self,
878+
offset: u32,
879+
size: u32,
880+
dry_run: bool,
881+
chunk_size: u32,
882+
) -> EcResult<()> {
883+
// Erasing a big section takes too long sometimes and the linux kernel driver times out, so
884+
// split it up into chunks.
885+
let mut cur_offset = offset;
886+
887+
while cur_offset < offset + size {
888+
let rem_size = offset + size - cur_offset;
889+
let cur_size = if rem_size < chunk_size {
890+
rem_size
891+
} else {
892+
chunk_size
893+
};
894+
debug!(
895+
"EcRequestFlashErase (0x{:05X}, 0x{:05X})",
896+
cur_offset, cur_size
897+
);
898+
if !dry_run {
899+
EcRequestFlashErase {
900+
offset: cur_offset,
901+
size: cur_size,
902+
}
903+
.send_command(self)?;
904+
}
905+
cur_offset += chunk_size;
906+
}
907+
Ok(())
836908
}
837909

838910
pub fn flash_notify(&self, flag: MecFlashNotify) -> EcResult<()> {

framework_lib/src/commandline/clap_std.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,11 @@ struct ClapCli {
123123
#[arg(long)]
124124
dump_ec_flash: Option<std::path::PathBuf>,
125125

126-
/// Flash EC with new firmware from file
126+
/// Flash EC (RO+RW) with new firmware from file - may render your hardware unbootable!
127127
#[arg(long)]
128128
flash_ec: Option<std::path::PathBuf>,
129129

130-
/// Flash EC with new RO firmware from file
130+
/// Flash EC with new RO firmware from file - may render your hardware unbootable!
131131
#[arg(long)]
132132
flash_ro_ec: Option<std::path::PathBuf>,
133133

@@ -250,6 +250,14 @@ struct ClapCli {
250250
/// Run self-test to check if interaction with EC is possible
251251
#[arg(long, short)]
252252
test: bool,
253+
254+
/// Force execution of an unsafe command - may render your hardware unbootable!
255+
#[arg(long, short)]
256+
force: bool,
257+
258+
/// Simulate execution of a command (e.g. --flash-ec)
259+
#[arg(long)]
260+
dry_run: bool,
253261
}
254262

255263
/// Parse a list of commandline arguments and return the struct
@@ -424,6 +432,8 @@ pub fn parse(args: &[String]) -> Cli {
424432
pd_addrs,
425433
pd_ports,
426434
test: args.test,
435+
dry_run: args.dry_run,
436+
force: args.force,
427437
// TODO: Set help. Not very important because Clap handles this by itself
428438
help: false,
429439
// UEFI only for now. Don't need to handle

framework_lib/src/commandline/mod.rs

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ pub struct Cli {
170170
pub flash_rw_ec: Option<String>,
171171
pub driver: Option<CrosEcDriverType>,
172172
pub test: bool,
173+
pub dry_run: bool,
174+
pub force: bool,
173175
pub intrusion: bool,
174176
pub inputdeck: bool,
175177
pub inputdeck_mode: Option<InputDeckModeArg>,
@@ -594,7 +596,7 @@ fn print_esrt() {
594596
}
595597
}
596598

597-
fn flash_ec(ec: &CrosEc, ec_bin_path: &str, flash_type: EcFlashType) {
599+
fn flash_ec(ec: &CrosEc, ec_bin_path: &str, flash_type: EcFlashType, dry_run: bool) {
598600
#[cfg(feature = "uefi")]
599601
let data = crate::uefi::fs::shell_read_file(ec_bin_path);
600602
#[cfg(not(feature = "uefi"))]
@@ -613,7 +615,7 @@ fn flash_ec(ec: &CrosEc, ec_bin_path: &str, flash_type: EcFlashType) {
613615
println!("File");
614616
println!(" Size: {:>20} B", data.len());
615617
println!(" Size: {:>20} KB", data.len() / 1024);
616-
if let Err(err) = ec.reflash(&data, flash_type) {
618+
if let Err(err) = ec.reflash(&data, flash_type, dry_run) {
617619
println!("Error: {:?}", err);
618620
} else {
619621
println!("Success!");
@@ -1107,11 +1109,19 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
11071109
// TODO: Should have progress indicator
11081110
dump_ec_flash(&ec, dump_path);
11091111
} else if let Some(ec_bin_path) = &args.flash_ec {
1110-
flash_ec(&ec, ec_bin_path, EcFlashType::Full);
1112+
if args.force {
1113+
flash_ec(&ec, ec_bin_path, EcFlashType::Full, args.dry_run);
1114+
} else {
1115+
error!("Flashing EC RO region is unsafe. Use --flash-ec-rw instead");
1116+
}
11111117
} else if let Some(ec_bin_path) = &args.flash_ro_ec {
1112-
flash_ec(&ec, ec_bin_path, EcFlashType::Ro);
1118+
if args.force {
1119+
flash_ec(&ec, ec_bin_path, EcFlashType::Ro, args.dry_run);
1120+
} else {
1121+
error!("Flashing EC RO region is unsafe. Use --flash-ec-rw instead");
1122+
}
11131123
} else if let Some(ec_bin_path) = &args.flash_rw_ec {
1114-
flash_ec(&ec, ec_bin_path, EcFlashType::Rw);
1124+
flash_ec(&ec, ec_bin_path, EcFlashType::Rw, args.dry_run);
11151125
} else if let Some(hash_file) = &args.hash {
11161126
println!("Hashing file: {}", hash_file);
11171127
#[cfg(feature = "uefi")]
@@ -1193,6 +1203,8 @@ Options:
11931203
--console <CONSOLE> Get EC console, choose whether recent or to follow the output [possible values: recent, follow]
11941204
--hash <HASH> Hash a file of arbitrary data
11951205
--flash-gpu-descriptor <MAGIC> <18 DIGIT SN> Overwrite the GPU bay descriptor SN and type.
1206+
-f, --force Force execution of an unsafe command - may render your hardware unbootable!
1207+
--dry-run Simulate execution of a command (e.g. --flash-ec)
11961208
-t, --test Run self-test to check if interaction with EC is possible
11971209
-h, --help Print help information
11981210
-b Print output one screen at a time

framework_lib/src/commandline/uefi.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ pub fn parse(args: &[String]) -> Cli {
109109
pd_addrs: None,
110110
pd_ports: None,
111111
test: false,
112+
dry_run: false,
113+
force: false,
112114
help: false,
113115
flash_gpu_descriptor: None,
114116
allupdate: false,
@@ -504,6 +506,12 @@ pub fn parse(args: &[String]) -> Cli {
504506
} else if arg == "-t" || arg == "--test" {
505507
cli.test = true;
506508
found_an_option = true;
509+
} else if arg == "-f" || arg == "--force" {
510+
cli.force = true;
511+
found_an_option = true;
512+
} else if arg == "--dry-run" {
513+
cli.dry_run = true;
514+
found_an_option = true;
507515
} else if arg == "-h" || arg == "--help" {
508516
cli.help = true;
509517
found_an_option = true;

0 commit comments

Comments
 (0)