Skip to content
Merged
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
5 changes: 5 additions & 0 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,8 +317,13 @@ Expansion Bay
Serial Number: FRAXXXXXXXXXXXXXXX
Config: Pcie4x2
Vendor: SsdHolder
Expansion Bay EEPROM
Valid: true
HW Version: 8.0
```

Add `-vv` for more verbose details.

## Check charger and battery status (Framework 12/13/16)

```
Expand Down
21 changes: 21 additions & 0 deletions EXAMPLES_ADVANCED.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,24 @@ This command has not been thoroughly tested on all Framework Computer systems
# EC will boot back into RO if the system turned off for 30s
> framework_tool --reboot-ec jump-rw
```

## Flashing Expansion Bay EEPROM (Framework 16)

This will render your dGPU unsuable if you flash the wrong file!
It's intended for advanced users who build their own expansion bay module.
The I2C address of the EEPROM is hardcoded to 0x50.

```
# Dump current descriptor (e.g. for backup)
> framework_tool --dump-gpu-descriptor-file foo.bin
Dumping to foo.bin
Wrote 153 bytes to foo.bin

# Update just the serial number
> framework_tool --flash_gpu_descriptor GPU FRAKMQCP41500ASSY1
> framework_tool --flash_gpu_descriptor 13 FRAKMQCP41500ASSY1
> framework_tool --flash_gpu_descriptor 0x0D FRAKMQCP41500ASSY1

# Update everything from a file
> framework_tool --flash-gpu-descriptor-file pcie_4x2.bin
```
123 changes: 122 additions & 1 deletion framework_lib/src/chromium_ec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1177,7 +1177,103 @@ impl CrosEc {
Ok(result.valid)
}

/// Requests console output from EC and constantly asks for more
pub fn read_ec_gpu_chunk(&self, addr: u16, len: u16) -> EcResult<Vec<u8>> {
let eeprom_port = 0x05;
let eeprom_addr = 0x50;
let mut data: Vec<u8> = Vec::with_capacity(len.into());

while data.len() < len.into() {
let remaining = len - data.len() as u16;
let chunk_len = std::cmp::min(i2c_passthrough::MAX_I2C_CHUNK, remaining.into());
let offset = addr + data.len() as u16;
let i2c_response = i2c_passthrough::i2c_read(
self,
eeprom_port,
eeprom_addr,
offset,
chunk_len as u16,
)?;
if let Err(EcError::DeviceError(err)) = i2c_response.is_successful() {
return Err(EcError::DeviceError(format!(
"I2C read was not successful: {:?}",
err
)));
}
data.extend(i2c_response.data);
}

Ok(data)
}

pub fn write_ec_gpu_chunk(&self, offset: u16, data: &[u8]) -> EcResult<()> {
let result = i2c_passthrough::i2c_write(self, 5, 0x50, offset, data)?;
result.is_successful()
}

/// Writes EC GPU descriptor to the GPU EEPROM.
pub fn set_gpu_descriptor(&self, data: &[u8], dry_run: bool) -> EcResult<()> {
println!(
"Writing GPU EEPROM {}",
if dry_run { " (DRY RUN)" } else { "" }
);
// Need to program the EEPROM 32 bytes at a time.
let chunk_size = 32;

let chunks = data.len() / 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
);
} else {
print!("X");
}
if dry_run {
continue;
}

let chunk = &data[offset..offset + cur_chunk_size];
let res = self.write_ec_gpu_chunk((offset as u16).to_be(), chunk);
// Don't read too fast, wait 100ms before writing more to allow for page erase/write cycle.
os_specific::sleep(100_000);
if let Err(err) = res {
println!(" Failed to write chunk: {:?}", err);
return Err(err);
}
}
println!();
Ok(())
}

pub fn read_gpu_descriptor(&self) -> EcResult<Vec<u8>> {
let header = self.read_gpu_desc_header()?;
if header.magic != [0x32, 0xAC, 0x00, 0x00] {
return Err(EcError::DeviceError(
"Invalid descriptor hdr magic".to_string(),
));
}
self.read_ec_gpu_chunk(0x00, header.descriptor_length as u16)
}

pub fn read_gpu_desc_header(&self) -> EcResult<GpuCfgDescriptor> {
let bytes =
self.read_ec_gpu_chunk(0x00, core::mem::size_of::<GpuCfgDescriptor>() as u16)?;
let header: *const GpuCfgDescriptor = unsafe { std::mem::transmute(bytes.as_ptr()) };
let header = unsafe { *header };

Ok(header)
}

/// Requests recent console output from EC and constantly asks for more
/// Prints the output and returns it when an error is encountered
pub fn console_read(&self) -> EcResult<()> {
EcRequestConsoleSnapshot {}.send_command(self)?;
Expand Down Expand Up @@ -1584,3 +1680,28 @@ pub struct IntrusionStatus {
/// That means we only know if it was opened at least once, while off, not how many times.
pub vtr_open_count: u8,
}

#[derive(Clone, Debug, Copy, PartialEq)]
#[repr(C, packed)]
pub struct GpuCfgDescriptor {
/// Expansion bay card magic value that is unique
pub magic: [u8; 4],
/// Length of header following this field
pub length: u32,
/// descriptor version, if EC max version is lower than this, ec cannot parse
pub desc_ver_major: u16,
pub desc_ver_minor: u16,
/// Hardware major version
pub hardware_version: u16,
/// Hardware minor revision
pub hardware_revision: u16,
/// 18 digit Framework Serial that starts with FRA
/// the first 10 digits must be allocated by framework
pub serial: [u8; 20],
/// Length of descriptor following heade
pub descriptor_length: u32,
/// CRC of descriptor
pub descriptor_crc32: u32,
/// CRC of header before this value
pub crc32: u32,
}
14 changes: 14 additions & 0 deletions framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,14 @@ struct ClapCli {
/// Simulate execution of a command (e.g. --flash-ec)
#[arg(long)]
dry_run: bool,

/// File to write to the gpu EEPROM
#[arg(long)]
flash_gpu_descriptor_file: Option<std::path::PathBuf>,

/// File to dump the gpu EEPROM to
#[arg(long)]
dump_gpu_descriptor_file: Option<std::path::PathBuf>,
}

/// Parse a list of commandline arguments and return the struct
Expand Down Expand Up @@ -457,6 +465,12 @@ pub fn parse(args: &[String]) -> Cli {
paginate: false,
info: args.info,
flash_gpu_descriptor,
flash_gpu_descriptor_file: args
.flash_gpu_descriptor_file
.map(|x| x.into_os_string().into_string().unwrap()),
dump_gpu_descriptor_file: args
.dump_gpu_descriptor_file
.map(|x| x.into_os_string().into_string().unwrap()),
raw_command: vec![],
}
}
73 changes: 73 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ pub struct Cli {
pub help: bool,
pub info: bool,
pub flash_gpu_descriptor: Option<(u8, String)>,
pub flash_gpu_descriptor_file: Option<String>,
pub dump_gpu_descriptor_file: Option<String>,
// UEFI only
pub allupdate: bool,
pub paginate: bool,
Expand Down Expand Up @@ -679,6 +681,24 @@ fn dump_ec_flash(ec: &CrosEc, dump_path: &str) {
}
}

fn dump_dgpu_eeprom(ec: &CrosEc, dump_path: &str) {
let flash_bin = ec.read_gpu_descriptor().unwrap();

#[cfg(not(feature = "uefi"))]
{
let mut file = fs::File::create(dump_path).unwrap();
file.write_all(&flash_bin).unwrap();
}
#[cfg(feature = "uefi")]
{
let ret = crate::uefi::fs::shell_write_file(dump_path, &flash_bin);
if ret.is_err() {
println!("Failed to dump EC FW image.");
}
}
println!("Wrote {} bytes to {}", flash_bin.len(), dump_path);
}

fn compare_version(device: Option<HardwareDeviceType>, version: String, ec: &CrosEc) -> i32 {
println!("Target Version {:?}", version);

Expand Down Expand Up @@ -883,6 +903,26 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
if let Err(err) = ec.check_bay_status() {
error!("{:?}", err);
}
if let Ok(header) = ec.read_gpu_desc_header() {
println!(" Expansion Bay EEPROM");
println!(
" Valid: {:?}",
header.magic == [0x32, 0xAC, 0x00, 0x00]
);
println!(" HW Version: {}.{}", { header.hardware_version }, {
header.hardware_revision
});
if log_enabled!(Level::Info) {
println!(" Hdr Length {} B", { header.length });
println!(" Desc Ver: {}.{}", { header.desc_ver_major }, {
header.desc_ver_minor
});
println!(" Serialnumber:{:X?}", { header.serial });
println!(" Desc Length: {} B", { header.descriptor_length });
println!(" Desc CRC: {:X}", { header.descriptor_crc32 });
println!(" Hdr CRC: {:X}", { header.crc32 });
}
}
} else if let Some(maybe_limit) = args.charge_limit {
print_err(handle_charge_limit(&ec, maybe_limit));
} else if let Some((limit, soc)) = args.charge_current_limit {
Expand Down Expand Up @@ -1220,6 +1260,38 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
Ok(x) => println!("GPU Descriptor write failed with status code: {}", x),
Err(err) => println!("GPU Descriptor write failed with error: {:?}", err),
}
} else if let Some(gpu_descriptor_file) = &args.flash_gpu_descriptor_file {
if matches!(
smbios::get_family(),
Some(PlatformFamily::Framework16) | None
) {
#[cfg(feature = "uefi")]
let data: Option<Vec<u8>> = crate::uefi::fs::shell_read_file(gpu_descriptor_file);
#[cfg(not(feature = "uefi"))]
let data = match fs::read(gpu_descriptor_file) {
Ok(data) => Some(data),
// TODO: Perhaps a more user-friendly error
Err(e) => {
println!("Error {:?}", e);
None
}
};
if let Some(data) = data {
println!("File");
println!(" Size: {:>20} B", data.len());
println!(" Size: {:>20} KB", data.len() / 1024);
let res = ec.set_gpu_descriptor(&data, args.dry_run);
match res {
Ok(()) => println!("GPU Descriptor successfully written"),
Err(err) => println!("GPU Descriptor write failed with error: {:?}", err),
}
}
} else {
println!("Unsupported on this platform");
}
} else if let Some(dump_path) = &args.dump_gpu_descriptor_file {
println!("Dumping to {}", dump_path);
dump_dgpu_eeprom(&ec, dump_path);
}

0
Expand Down Expand Up @@ -1275,6 +1347,7 @@ 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.
--flash-gpu-descriptor-file <DESCRIPTOR_FILE> Write the GPU bay descriptor with a descriptor file.
-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
Expand Down
18 changes: 18 additions & 0 deletions framework_lib/src/commandline/uefi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ pub fn parse(args: &[String]) -> Cli {
force: false,
help: false,
flash_gpu_descriptor: None,
flash_gpu_descriptor_file: None,
dump_gpu_descriptor_file: None,
allupdate: false,
info: false,
raw_command: vec![],
Expand Down Expand Up @@ -764,6 +766,22 @@ pub fn parse(args: &[String]) -> Cli {
None
};
found_an_option = true;
} else if arg == "--flash-gpu-descriptor-file" {
cli.flash_gpu_descriptor_file = if args.len() > i + 1 {
Some(args[i + 1].clone())
} else {
println!("Need to provide a value for --flash_gpu_descriptor_file. PATH");
None
};
found_an_option = true;
} else if arg == "--dump-gpu-descriptor-file" {
cli.dump_gpu_descriptor_file = if args.len() > i + 1 {
Some(args[i + 1].clone())
} else {
println!("Need to provide a value for --dump_gpu_descriptor_file. PATH");
None
};
found_an_option = true;
}
}

Expand Down
Loading