Skip to content
Merged
41 changes: 1 addition & 40 deletions EXAMPLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,43 +544,4 @@ It's not controlled by the EC, use https://keyboard.frame.work.

Mostly for debugging firmware.

### Check EFI Resource Table

On Framework Desktop:

```
> sudo framework_tool --esrt
ESRT Table
ResourceCount: 1
ResourceCountMax: 1
ResourceVersion: 1
ESRT Entry 0
GUID: EB68DBAE-3AEF-5077-92AE-9016D1F0C856
GUID: DesktopAmdAi300Bios
Type: SystemFirmware
Version: 0x204 (516)
Min FW Version: 0x100 (256)
Capsule Flags: 0x0
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
```
See [EXAMPLES_ADVANCED.md](EXAMPLES_ADVANCED.md)
81 changes: 81 additions & 0 deletions EXAMPLES_ADVANCED.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Advanced debugging

## PD

### Check PD state

Example on Framework 13 AMD Ryzen AI 300

```
> sudo framework_tool.exe --pd-info
Left / Ports 01
Silicon ID: 0x3580
Mode: MainFw
Flash Row Size: 256 B
Ports Enabled: 0, 1
Bootloader Version: Base: 3.6.0.009, App: 0.0.01
FW1 (Backup) Version: Base: 3.7.0.197, App: 0.0.0B
FW2 (Main) Version: Base: 3.7.0.197, App: 0.0.0B
Right / Ports 23
Silicon ID: 0x3580
Mode: MainFw
Flash Row Size: 256 B
Ports Enabled: 0, 1
Bootloader Version: Base: 3.6.0.009, App: 0.0.01
FW1 (Backup) Version: Base: 3.7.0.197, App: 0.0.0B
FW2 (Main) Version: Base: 3.7.0.197, App: 0.0.0B
```

### Disable/enable/reset PD

```
# Disable all ports on PD 0
> sudo framework_tool --pd-disable 0

# Reset PD 0 (enables all ports again)
> sudo framework_tool --pd-reset 0

# Or enable all ports on PD 0 without resetting it
> sudo framework_tool --pd-enable 0
```

### Check EFI Resource Table

On Framework Desktop:

```
> sudo framework_tool --esrt
ESRT Table
ResourceCount: 1
ResourceCountMax: 1
ResourceVersion: 1
ESRT Entry 0
GUID: EB68DBAE-3AEF-5077-92AE-9016D1F0C856
GUID: DesktopAmdAi300Bios
Type: SystemFirmware
Version: 0x204 (516)
Min FW Version: 0x100 (256)
Capsule Flags: 0x0
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
```
3 changes: 2 additions & 1 deletion framework_lib/src/ccgx/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ fn read_metadata(
let buffer = read_256_bytes(file_buffer, metadata_offset, flash_row_size)?;
match ccgx {
SiliconId::Ccg3 => parse_metadata_ccg3(&buffer),
SiliconId::Ccg5 | SiliconId::Ccg6 => parse_metadata_cyacd(&buffer),
SiliconId::Ccg5 | SiliconId::Ccg6Adl | SiliconId::Ccg6 => parse_metadata_cyacd(&buffer),
SiliconId::Ccg8 => parse_metadata_cyacd2(&buffer)
.map(|(fw_row_start, fw_size)| (fw_row_start / (flash_row_size as u32), fw_size)),
}
Expand Down Expand Up @@ -172,6 +172,7 @@ pub fn read_versions(file_buffer: &[u8], ccgx: SiliconId) -> Option<PdFirmwareFi
let (flash_row_size, f1_metadata_row, fw2_metadata_row) = match ccgx {
SiliconId::Ccg3 => (SMALL_ROW, 0x03FF, 0x03FE),
SiliconId::Ccg5 => (LARGE_ROW, FW1_METADATA_ROW, FW2_METADATA_ROW_CCG5),
SiliconId::Ccg6Adl => (SMALL_ROW, FW1_METADATA_ROW, FW2_METADATA_ROW_CCG6),
SiliconId::Ccg6 => (SMALL_ROW, FW1_METADATA_ROW, FW2_METADATA_ROW_CCG6),
SiliconId::Ccg8 => (LARGE_ROW, FW1_METADATA_ROW_CCG8, FW2_METADATA_ROW_CCG8),
};
Expand Down
87 changes: 85 additions & 2 deletions framework_lib/src/ccgx/device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,34 @@ use crate::util::{assert_win_len, Config, Platform};

use super::*;

const _HPI_FLASH_ENTER_SIGNATURE: char = 'P';
const _HPI_JUMP_TO_ALT_SIGNATURE: char = 'A';
const _HPI_JUMP_TO_BOOT_SIGNATURE: char = 'J';
const HPI_RESET_SIGNATURE: char = 'R';
const _HPI_FLASH_RW_SIGNATURE: char = 'F';
const HPI_RESET_DEV_CMD: u8 = 1;
const _HPI_FLASH_READ_CMD: u8 = 0;
const _HPI_FLASH_WRITE_CMD: u8 = 1;

#[derive(Debug, Copy, Clone)]
enum ControlRegisters {
DeviceMode = 0,
SiliconId = 2, // Two bytes long, First LSB, then MSB
_InterruptStatus = 0x06,
_JumpToBoot = 0x07,
ResetRequest = 0x08,
_FlashmodeEnter = 0x0A,
_ValidateFw = 0x0B,
_FlashSignature = 0x0C,
BootLoaderVersion = 0x10,
Firmware1Version = 0x18,
Firmware2Version = 0x20,
PdPortsEnable = 0x2C,
_ResponseType = 0x7E,
_FlashRwMem = 0x0200,
}

#[derive(Debug)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum PdPort {
Left01,
Right23,
Expand Down Expand Up @@ -130,7 +149,7 @@ pub struct PdController {
ec: CrosEc,
}

#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone, Copy)]
pub enum FwMode {
BootLoader = 0,
/// Backup CCGX firmware (No 1)
Expand Down Expand Up @@ -182,6 +201,21 @@ impl PdController {
)
}

pub fn i2c_write(&self, addr: u16, data: &[u8]) -> EcResult<EcI2cPassthruResponse> {
trace!(
"I2C passthrough from I2C Port {} to I2C Addr {}",
self.port.i2c_port()?,
self.port.i2c_address()?
);
i2c_write(
&self.ec,
self.port.i2c_port()?,
self.port.i2c_address()?,
addr,
data,
)
}

fn ccgx_read(&self, reg: ControlRegisters, len: u16) -> EcResult<Vec<u8>> {
let mut data: Vec<u8> = Vec::with_capacity(len.into());

Expand All @@ -204,6 +238,35 @@ impl PdController {
Ok(data)
}

fn ccgx_write(&self, reg: ControlRegisters, data: &[u8]) -> EcResult<()> {
let addr = reg as u16;
trace!(
"ccgx_write(reg: {:?}, addr: {}, data.len(): {}",
reg,
addr,
data.len()
);
let mut data_written = 0;

while data_written < data.len() {
let chunk_len = std::cmp::min(MAX_I2C_CHUNK, data.len());
let buffer = &data[data_written..data_written + chunk_len];
let offset = addr + data_written as u16;

let i2c_response = self.i2c_write(offset, buffer)?;
if let Err(EcError::DeviceError(err)) = i2c_response.is_successful() {
return Err(EcError::DeviceError(format!(
"I2C write was not successful: {:?}",
err
)));
}

data_written += chunk_len;
}

Ok(())
}

pub fn get_silicon_id(&self) -> EcResult<u16> {
let data = self.ccgx_read(ControlRegisters::SiliconId, 2)?;
assert_win_len(data.len(), 2);
Expand Down Expand Up @@ -295,4 +358,24 @@ impl PdController {
base_ver, app_ver
);
}

pub fn reset_device(&self) -> EcResult<()> {
self.ccgx_write(
ControlRegisters::ResetRequest,
&[HPI_RESET_SIGNATURE as u8, HPI_RESET_DEV_CMD],
)?;
Ok(())
}

pub fn enable_ports(&self, enable: bool) -> EcResult<()> {
let mask = if enable { 0b11 } else { 0b00 };
self.ccgx_write(ControlRegisters::PdPortsEnable, &[mask])?;
Ok(())
}

pub fn get_port_status(&self) -> EcResult<u8> {
let data = self.ccgx_read(ControlRegisters::PdPortsEnable, 1)?;
assert_win_len(data.len(), 1);
Ok(data[0])
}
}
3 changes: 2 additions & 1 deletion framework_lib/src/ccgx/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ struct CyAcd2Metadata {
pub enum SiliconId {
Ccg3 = 0x1D00,
Ccg5 = 0x2100,
Ccg6 = 0x3000,
Ccg6Adl = 0x3000,
Ccg6 = 0x30A0,
Ccg8 = 0x3580,
}

Expand Down
2 changes: 1 addition & 1 deletion framework_lib/src/chromium_ec/i2c_passthrough.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ pub fn i2c_write(

let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?;
let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) };
assert_eq!(data.len(), size_of::<_EcI2cPassthruResponse>()); // No extra data other than the header
util::assert_win_len(data.len(), size_of::<_EcI2cPassthruResponse>()); // No extra data other than the header
debug_assert_eq!(res.messages as usize, messages.len());
Ok(EcI2cPassthruResponse {
i2c_status: res.i2c_status,
Expand Down
15 changes: 15 additions & 0 deletions framework_lib/src/commandline/clap_std.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,18 @@ struct ClapCli {
#[arg(long)]
pd_info: bool,

/// Reset a specific PD controller (for debugging only)
#[arg(long)]
pd_reset: Option<u8>,

/// Disable all ports on a specific PD controller (for debugging only)
#[arg(long)]
pd_disable: Option<u8>,

/// Enable all ports on a specific PD controller (for debugging only)
#[arg(long)]
pd_enable: Option<u8>,

/// Show details about connected DP or HDMI Expansion Cards
#[arg(long)]
dp_hdmi_info: bool,
Expand Down Expand Up @@ -376,6 +388,9 @@ pub fn parse(args: &[String]) -> Cli {
autofanctrl: args.autofanctrl,
pdports: args.pdports,
pd_info: args.pd_info,
pd_reset: args.pd_reset,
pd_disable: args.pd_disable,
pd_enable: args.pd_enable,
dp_hdmi_info: args.dp_hdmi_info,
dp_hdmi_update: args
.dp_hdmi_update
Expand Down
48 changes: 48 additions & 0 deletions framework_lib/src/commandline/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,9 @@ pub struct Cli {
pub pdports: bool,
pub privacy: bool,
pub pd_info: bool,
pub pd_reset: Option<u8>,
pub pd_disable: Option<u8>,
pub pd_enable: Option<u8>,
pub dp_hdmi_info: bool,
pub dp_hdmi_update: Option<String>,
pub audio_card_info: bool,
Expand Down Expand Up @@ -217,13 +220,25 @@ fn print_single_pd_details(pd: &PdController) {
println!(" Silicon ID: 0x{:X}", si);
} else {
println!(" Failed to read Silicon ID/Family");
return;
}
if let Ok((mode, frs)) = pd.get_device_info() {
println!(" Mode: {:?}", mode);
println!(" Flash Row Size: {} B", frs);
} else {
println!(" Failed to device info");
}
if let Ok(port_mask) = pd.get_port_status() {
let ports = match port_mask {
1 => "0",
2 => "1",
3 => "0, 1",
_ => "None",
};
println!(" Ports Enabled: {}", ports);
} else {
println!(" Ports Enabled: Unknown");
}
pd.print_fw_info();
}

Expand Down Expand Up @@ -999,6 +1014,39 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 {
smbios_info();
} else if args.pd_info {
print_pd_details(&ec);
} else if let Some(pd) = args.pd_reset {
println!("Resetting PD {}...", pd);
print_err(match pd {
0 => PdController::new(PdPort::Left01, ec.clone()).reset_device(),
1 => PdController::new(PdPort::Right23, ec.clone()).reset_device(),
2 => PdController::new(PdPort::Back, ec.clone()).reset_device(),
_ => {
error!("PD {} does not exist", pd);
Ok(())
}
});
} else if let Some(pd) = args.pd_disable {
println!("Disabling PD {}...", pd);
print_err(match pd {
0 => PdController::new(PdPort::Left01, ec.clone()).enable_ports(false),
1 => PdController::new(PdPort::Right23, ec.clone()).enable_ports(false),
2 => PdController::new(PdPort::Back, ec.clone()).enable_ports(false),
_ => {
error!("PD {} does not exist", pd);
Ok(())
}
});
} else if let Some(pd) = args.pd_enable {
println!("Enabling PD {}...", pd);
print_err(match pd {
0 => PdController::new(PdPort::Left01, ec.clone()).enable_ports(true),
1 => PdController::new(PdPort::Right23, ec.clone()).enable_ports(true),
2 => PdController::new(PdPort::Back, ec.clone()).enable_ports(true),
_ => {
error!("PD {} does not exist", pd);
Ok(())
}
});
} else if args.dp_hdmi_info {
#[cfg(feature = "hidapi")]
print_dp_hdmi_details(true);
Expand Down
Loading
Loading