Skip to content

Commit 4b4d069

Browse files
Merge pull request #171 from FrameworkComputer/fw16.dgpu_file_program
fw16: add command to write and read expansion bay eeprom
2 parents 2a5827a + d13b41b commit 4b4d069

File tree

6 files changed

+253
-1
lines changed

6 files changed

+253
-1
lines changed

EXAMPLES.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,8 +317,13 @@ Expansion Bay
317317
Serial Number: FRAXXXXXXXXXXXXXXX
318318
Config: Pcie4x2
319319
Vendor: SsdHolder
320+
Expansion Bay EEPROM
321+
Valid: true
322+
HW Version: 8.0
320323
```
321324

325+
Add `-vv` for more verbose details.
326+
322327
## Check charger and battery status (Framework 12/13/16)
323328

324329
```

EXAMPLES_ADVANCED.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,3 +79,24 @@ This command has not been thoroughly tested on all Framework Computer systems
7979
# EC will boot back into RO if the system turned off for 30s
8080
> framework_tool --reboot-ec jump-rw
8181
```
82+
83+
## Flashing Expansion Bay EEPROM (Framework 16)
84+
85+
This will render your dGPU unsuable if you flash the wrong file!
86+
It's intended for advanced users who build their own expansion bay module.
87+
The I2C address of the EEPROM is hardcoded to 0x50.
88+
89+
```
90+
# Dump current descriptor (e.g. for backup)
91+
> framework_tool --dump-gpu-descriptor-file foo.bin
92+
Dumping to foo.bin
93+
Wrote 153 bytes to foo.bin
94+
95+
# Update just the serial number
96+
> framework_tool --flash_gpu_descriptor GPU FRAKMQCP41500ASSY1
97+
> framework_tool --flash_gpu_descriptor 13 FRAKMQCP41500ASSY1
98+
> framework_tool --flash_gpu_descriptor 0x0D FRAKMQCP41500ASSY1
99+
100+
# Update everything from a file
101+
> framework_tool --flash-gpu-descriptor-file pcie_4x2.bin
102+
```

framework_lib/src/chromium_ec/mod.rs

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1177,7 +1177,103 @@ impl CrosEc {
11771177
Ok(result.valid)
11781178
}
11791179

1180-
/// Requests console output from EC and constantly asks for more
1180+
pub fn read_ec_gpu_chunk(&self, addr: u16, len: u16) -> EcResult<Vec<u8>> {
1181+
let eeprom_port = 0x05;
1182+
let eeprom_addr = 0x50;
1183+
let mut data: Vec<u8> = Vec::with_capacity(len.into());
1184+
1185+
while data.len() < len.into() {
1186+
let remaining = len - data.len() as u16;
1187+
let chunk_len = std::cmp::min(i2c_passthrough::MAX_I2C_CHUNK, remaining.into());
1188+
let offset = addr + data.len() as u16;
1189+
let i2c_response = i2c_passthrough::i2c_read(
1190+
self,
1191+
eeprom_port,
1192+
eeprom_addr,
1193+
offset,
1194+
chunk_len as u16,
1195+
)?;
1196+
if let Err(EcError::DeviceError(err)) = i2c_response.is_successful() {
1197+
return Err(EcError::DeviceError(format!(
1198+
"I2C read was not successful: {:?}",
1199+
err
1200+
)));
1201+
}
1202+
data.extend(i2c_response.data);
1203+
}
1204+
1205+
Ok(data)
1206+
}
1207+
1208+
pub fn write_ec_gpu_chunk(&self, offset: u16, data: &[u8]) -> EcResult<()> {
1209+
let result = i2c_passthrough::i2c_write(self, 5, 0x50, offset, data)?;
1210+
result.is_successful()
1211+
}
1212+
1213+
/// Writes EC GPU descriptor to the GPU EEPROM.
1214+
pub fn set_gpu_descriptor(&self, data: &[u8], dry_run: bool) -> EcResult<()> {
1215+
println!(
1216+
"Writing GPU EEPROM {}",
1217+
if dry_run { " (DRY RUN)" } else { "" }
1218+
);
1219+
// Need to program the EEPROM 32 bytes at a time.
1220+
let chunk_size = 32;
1221+
1222+
let chunks = data.len() / chunk_size;
1223+
for chunk_no in 0..chunks {
1224+
let offset = chunk_no * chunk_size;
1225+
// Current chunk might be smaller if it's the last
1226+
let cur_chunk_size = std::cmp::min(chunk_size, data.len() - chunk_no * chunk_size);
1227+
1228+
if chunk_no % 100 == 0 {
1229+
println!();
1230+
print!(
1231+
"Writing chunk {:>4}/{:>4} ({:>6}/{:>6}): X",
1232+
chunk_no,
1233+
chunks,
1234+
offset,
1235+
cur_chunk_size * chunks
1236+
);
1237+
} else {
1238+
print!("X");
1239+
}
1240+
if dry_run {
1241+
continue;
1242+
}
1243+
1244+
let chunk = &data[offset..offset + cur_chunk_size];
1245+
let res = self.write_ec_gpu_chunk((offset as u16).to_be(), chunk);
1246+
// Don't read too fast, wait 100ms before writing more to allow for page erase/write cycle.
1247+
os_specific::sleep(100_000);
1248+
if let Err(err) = res {
1249+
println!(" Failed to write chunk: {:?}", err);
1250+
return Err(err);
1251+
}
1252+
}
1253+
println!();
1254+
Ok(())
1255+
}
1256+
1257+
pub fn read_gpu_descriptor(&self) -> EcResult<Vec<u8>> {
1258+
let header = self.read_gpu_desc_header()?;
1259+
if header.magic != [0x32, 0xAC, 0x00, 0x00] {
1260+
return Err(EcError::DeviceError(
1261+
"Invalid descriptor hdr magic".to_string(),
1262+
));
1263+
}
1264+
self.read_ec_gpu_chunk(0x00, header.descriptor_length as u16)
1265+
}
1266+
1267+
pub fn read_gpu_desc_header(&self) -> EcResult<GpuCfgDescriptor> {
1268+
let bytes =
1269+
self.read_ec_gpu_chunk(0x00, core::mem::size_of::<GpuCfgDescriptor>() as u16)?;
1270+
let header: *const GpuCfgDescriptor = unsafe { std::mem::transmute(bytes.as_ptr()) };
1271+
let header = unsafe { *header };
1272+
1273+
Ok(header)
1274+
}
1275+
1276+
/// Requests recent console output from EC and constantly asks for more
11811277
/// Prints the output and returns it when an error is encountered
11821278
pub fn console_read(&self) -> EcResult<()> {
11831279
EcRequestConsoleSnapshot {}.send_command(self)?;
@@ -1584,3 +1680,28 @@ pub struct IntrusionStatus {
15841680
/// That means we only know if it was opened at least once, while off, not how many times.
15851681
pub vtr_open_count: u8,
15861682
}
1683+
1684+
#[derive(Clone, Debug, Copy, PartialEq)]
1685+
#[repr(C, packed)]
1686+
pub struct GpuCfgDescriptor {
1687+
/// Expansion bay card magic value that is unique
1688+
pub magic: [u8; 4],
1689+
/// Length of header following this field
1690+
pub length: u32,
1691+
/// descriptor version, if EC max version is lower than this, ec cannot parse
1692+
pub desc_ver_major: u16,
1693+
pub desc_ver_minor: u16,
1694+
/// Hardware major version
1695+
pub hardware_version: u16,
1696+
/// Hardware minor revision
1697+
pub hardware_revision: u16,
1698+
/// 18 digit Framework Serial that starts with FRA
1699+
/// the first 10 digits must be allocated by framework
1700+
pub serial: [u8; 20],
1701+
/// Length of descriptor following heade
1702+
pub descriptor_length: u32,
1703+
/// CRC of descriptor
1704+
pub descriptor_crc32: u32,
1705+
/// CRC of header before this value
1706+
pub crc32: u32,
1707+
}

framework_lib/src/commandline/clap_std.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,14 @@ struct ClapCli {
270270
/// Simulate execution of a command (e.g. --flash-ec)
271271
#[arg(long)]
272272
dry_run: bool,
273+
274+
/// File to write to the gpu EEPROM
275+
#[arg(long)]
276+
flash_gpu_descriptor_file: Option<std::path::PathBuf>,
277+
278+
/// File to dump the gpu EEPROM to
279+
#[arg(long)]
280+
dump_gpu_descriptor_file: Option<std::path::PathBuf>,
273281
}
274282

275283
/// Parse a list of commandline arguments and return the struct
@@ -457,6 +465,12 @@ pub fn parse(args: &[String]) -> Cli {
457465
paginate: false,
458466
info: args.info,
459467
flash_gpu_descriptor,
468+
flash_gpu_descriptor_file: args
469+
.flash_gpu_descriptor_file
470+
.map(|x| x.into_os_string().into_string().unwrap()),
471+
dump_gpu_descriptor_file: args
472+
.dump_gpu_descriptor_file
473+
.map(|x| x.into_os_string().into_string().unwrap()),
460474
raw_command: vec![],
461475
}
462476
}

framework_lib/src/commandline/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,8 @@ pub struct Cli {
201201
pub help: bool,
202202
pub info: bool,
203203
pub flash_gpu_descriptor: Option<(u8, String)>,
204+
pub flash_gpu_descriptor_file: Option<String>,
205+
pub dump_gpu_descriptor_file: Option<String>,
204206
// UEFI only
205207
pub allupdate: bool,
206208
pub paginate: bool,
@@ -679,6 +681,24 @@ fn dump_ec_flash(ec: &CrosEc, dump_path: &str) {
679681
}
680682
}
681683

684+
fn dump_dgpu_eeprom(ec: &CrosEc, dump_path: &str) {
685+
let flash_bin = ec.read_gpu_descriptor().unwrap();
686+
687+
#[cfg(not(feature = "uefi"))]
688+
{
689+
let mut file = fs::File::create(dump_path).unwrap();
690+
file.write_all(&flash_bin).unwrap();
691+
}
692+
#[cfg(feature = "uefi")]
693+
{
694+
let ret = crate::uefi::fs::shell_write_file(dump_path, &flash_bin);
695+
if ret.is_err() {
696+
println!("Failed to dump EC FW image.");
697+
}
698+
}
699+
println!("Wrote {} bytes to {}", flash_bin.len(), dump_path);
700+
}
701+
682702
fn compare_version(device: Option<HardwareDeviceType>, version: String, ec: &CrosEc) -> i32 {
683703
println!("Target Version {:?}", version);
684704

@@ -883,6 +903,26 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
883903
if let Err(err) = ec.check_bay_status() {
884904
error!("{:?}", err);
885905
}
906+
if let Ok(header) = ec.read_gpu_desc_header() {
907+
println!(" Expansion Bay EEPROM");
908+
println!(
909+
" Valid: {:?}",
910+
header.magic == [0x32, 0xAC, 0x00, 0x00]
911+
);
912+
println!(" HW Version: {}.{}", { header.hardware_version }, {
913+
header.hardware_revision
914+
});
915+
if log_enabled!(Level::Info) {
916+
println!(" Hdr Length {} B", { header.length });
917+
println!(" Desc Ver: {}.{}", { header.desc_ver_major }, {
918+
header.desc_ver_minor
919+
});
920+
println!(" Serialnumber:{:X?}", { header.serial });
921+
println!(" Desc Length: {} B", { header.descriptor_length });
922+
println!(" Desc CRC: {:X}", { header.descriptor_crc32 });
923+
println!(" Hdr CRC: {:X}", { header.crc32 });
924+
}
925+
}
886926
} else if let Some(maybe_limit) = args.charge_limit {
887927
print_err(handle_charge_limit(&ec, maybe_limit));
888928
} else if let Some((limit, soc)) = args.charge_current_limit {
@@ -1220,6 +1260,38 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
12201260
Ok(x) => println!("GPU Descriptor write failed with status code: {}", x),
12211261
Err(err) => println!("GPU Descriptor write failed with error: {:?}", err),
12221262
}
1263+
} else if let Some(gpu_descriptor_file) = &args.flash_gpu_descriptor_file {
1264+
if matches!(
1265+
smbios::get_family(),
1266+
Some(PlatformFamily::Framework16) | None
1267+
) {
1268+
#[cfg(feature = "uefi")]
1269+
let data: Option<Vec<u8>> = crate::uefi::fs::shell_read_file(gpu_descriptor_file);
1270+
#[cfg(not(feature = "uefi"))]
1271+
let data = match fs::read(gpu_descriptor_file) {
1272+
Ok(data) => Some(data),
1273+
// TODO: Perhaps a more user-friendly error
1274+
Err(e) => {
1275+
println!("Error {:?}", e);
1276+
None
1277+
}
1278+
};
1279+
if let Some(data) = data {
1280+
println!("File");
1281+
println!(" Size: {:>20} B", data.len());
1282+
println!(" Size: {:>20} KB", data.len() / 1024);
1283+
let res = ec.set_gpu_descriptor(&data, args.dry_run);
1284+
match res {
1285+
Ok(()) => println!("GPU Descriptor successfully written"),
1286+
Err(err) => println!("GPU Descriptor write failed with error: {:?}", err),
1287+
}
1288+
}
1289+
} else {
1290+
println!("Unsupported on this platform");
1291+
}
1292+
} else if let Some(dump_path) = &args.dump_gpu_descriptor_file {
1293+
println!("Dumping to {}", dump_path);
1294+
dump_dgpu_eeprom(&ec, dump_path);
12231295
}
12241296

12251297
0
@@ -1275,6 +1347,7 @@ Options:
12751347
--console <CONSOLE> Get EC console, choose whether recent or to follow the output [possible values: recent, follow]
12761348
--hash <HASH> Hash a file of arbitrary data
12771349
--flash-gpu-descriptor <MAGIC> <18 DIGIT SN> Overwrite the GPU bay descriptor SN and type.
1350+
--flash-gpu-descriptor-file <DESCRIPTOR_FILE> Write the GPU bay descriptor with a descriptor file.
12781351
-f, --force Force execution of an unsafe command - may render your hardware unbootable!
12791352
--dry-run Simulate execution of a command (e.g. --flash-ec)
12801353
-t, --test Run self-test to check if interaction with EC is possible

framework_lib/src/commandline/uefi.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ pub fn parse(args: &[String]) -> Cli {
116116
force: false,
117117
help: false,
118118
flash_gpu_descriptor: None,
119+
flash_gpu_descriptor_file: None,
120+
dump_gpu_descriptor_file: None,
119121
allupdate: false,
120122
info: false,
121123
raw_command: vec![],
@@ -764,6 +766,22 @@ pub fn parse(args: &[String]) -> Cli {
764766
None
765767
};
766768
found_an_option = true;
769+
} else if arg == "--flash-gpu-descriptor-file" {
770+
cli.flash_gpu_descriptor_file = if args.len() > i + 1 {
771+
Some(args[i + 1].clone())
772+
} else {
773+
println!("Need to provide a value for --flash_gpu_descriptor_file. PATH");
774+
None
775+
};
776+
found_an_option = true;
777+
} else if arg == "--dump-gpu-descriptor-file" {
778+
cli.dump_gpu_descriptor_file = if args.len() > i + 1 {
779+
Some(args[i + 1].clone())
780+
} else {
781+
println!("Need to provide a value for --dump_gpu_descriptor_file. PATH");
782+
None
783+
};
784+
found_an_option = true;
767785
}
768786
}
769787

0 commit comments

Comments
 (0)