diff --git a/.envrc b/.envrc new file mode 100644 index 00000000..30da14fd --- /dev/null +++ b/.envrc @@ -0,0 +1,5 @@ +export DIRENV_WARN_TIMEOUT=20s + +eval "$(devenv direnvrc)" + +use devenv diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f8dc7d1..61c0f70a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: path: target/x86_64-unknown-freebsd/debug/framework_tool build: - name: Build Linux and UEFI + name: Build Linux runs-on: ubuntu-24.04 env: CARGO_NET_GIT_FETCH_WITH_CLI: true @@ -65,6 +65,17 @@ jobs: name: framework_tool path: target/debug/framework_tool + build-uefi: + name: Build UEFI + runs-on: ubuntu-24.04 + env: + CARGO_NET_GIT_FETCH_WITH_CLI: true + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust toolchain + run: rustup show + - name: Build UEFI application (no ESP) run: make -C framework_uefi build/x86_64-unknown-uefi/boot.efi diff --git a/.gitignore b/.gitignore index a34615ec..5e33b24d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,15 @@ build/ *.efi *.exe *.pdb + +# Devenv +.devenv* +devenv.local.nix +# Nix +result* + +# direnv +.direnv + +# pre-commit +.pre-commit-config.yaml diff --git a/Cargo.lock b/Cargo.lock index 2f9f5c9a..f6c79ef6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -408,7 +408,7 @@ dependencies = [ [[package]] name = "framework_lib" -version = "0.4.2" +version = "0.4.3" dependencies = [ "built", "clap", @@ -440,7 +440,7 @@ dependencies = [ [[package]] name = "framework_tool" -version = "0.4.2" +version = "0.4.3" dependencies = [ "embed-resource", "framework_lib", @@ -451,7 +451,7 @@ dependencies = [ [[package]] name = "framework_uefi" -version = "0.4.2" +version = "0.4.3" dependencies = [ "framework_lib", "log", diff --git a/EXAMPLES.md b/EXAMPLES.md index f688c635..61b4aac8 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -2,7 +2,7 @@ ## Check firmware versions -### BIOS (Mainboard, UEFI, EC, PD) +### BIOS (Mainboard, UEFI, EC, PD, Retimer) Example on Framework 13 AMD Ryzen AI 300 Series: @@ -23,6 +23,25 @@ PD Controllers [...] ``` +Example on Framework 13 Intel Core Ultra Series 1: + +``` +> framework_tool --versions +Mainboard Hardware + Type: Laptop 13 (AMD Ryzen AI 300 Series) + Revision: MassProduction +UEFI BIOS + Version: 03.03 + Release Date: 10/07/2024 +EC Firmware + Build version: marigold-3.0.3-278d300 2024-10-04 03:03:58 marigold1@ip-172-26-3-226 + Current image: RO +PD Controllers + Right (01): 0.0.08 (MainFw) + Left (23): 0.0.08 (MainFw) +[...] +``` + ### Camera (Framework 12, Framework 13, Framework 16) Example on Framework 12: @@ -135,6 +154,56 @@ CSME [...] ``` +### Firmware Version using ESRT (BIOS, Retimer, CSME) + +All systems have at least an entry for BIOS. Intel systems also have CSME and some Retimers. + +Example on Framework 13 Intel Core Ultra Series 1: + +``` +> sudo framework_tool --esrt +ESRT Table + ResourceCount: 4 + ResourceCountMax: 4 + ResourceVersion: 1 +ESRT Entry 0 + GUID: BDFFCE36-809C-4FA6-AECC-54536922F0E0 + GUID: MtlRetimer23 + Type: DeviceFirmware + Version: 0x270 (624) + Min FW Version: 0x0 (0) + Capsule Flags: 0x0 + Last Attempt Version: 0x270 (624) + Last Attempt Status: Success +ESRT Entry 1 + GUID: 32D8D677-EEBC-4947-8F8A-0693A45240E5 + GUID: MtlCsme + Type: DeviceFirmware + Version: 0x85D (2141) + Min FW Version: 0x3E8 (1000) + Capsule Flags: 0x0 + Last Attempt Version: 0x0 (0) + Last Attempt Status: Success +ESRT Entry 2 + GUID: C57FD615-2AC9-4154-BF34-4DC715344408 + GUID: MtlRetimer01 + Type: DeviceFirmware + Version: 0x270 (624) + Min FW Version: 0x0 (0) + Capsule Flags: 0x0 + Last Attempt Version: 0x270 (624) + Last Attempt Status: Success +ESRT Entry 3 + GUID: 72CECB9B-2B37-5EC2-A9FF-C739AABAADF3 + GUID: MtlBios + Type: SystemFirmware + Version: 0x303 (771) + Min FW Version: 0x303 (771) + Capsule Flags: 0x0 + Last Attempt Version: 0x303 (771) + Last Attempt Status: Success +``` + ## Check input deck status ### On Framework 12 @@ -248,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) ``` @@ -384,6 +458,37 @@ Keyboard backlight: 5% > framework_tool.exe --kblight 20 ``` +## Fingerprint/Powerbutton brightness + +On Framework 13 and Framework 16 the power button has an integrated fingerprint reader, hence the name. +On Framework 12 it does not, but the same command can be used. + +``` +# Check the current brightness +> framework_tool --fp-brightness +Fingerprint LED Brightness + Requested: Auto + Brightness: 55% + +# Set it to a custom perfentage +> framework_tool --fp-brightness 42 +Fingerprint LED Brightness + Requested: Custom + Brightness: 42% + +# Set to a specific level (like the BIOS setting does) +> framework_tool --fp-led-level high +Fingerprint LED Brightness + Requested: High + Brightness: 55% + +# Set it back to auto +> framework_tool --fp-led-level auto +Fingerprint LED Brightness + Requested: Auto + Brightness: 15% +``` + ## RGB LED (Framework Desktop) ``` @@ -439,3 +544,9 @@ The scancodes are the same. ### Framework 16 It's not controlled by the EC, use https://keyboard.frame.work. + +## Advanced commands + +Mostly for debugging firmware. + +See [EXAMPLES_ADVANCED.md](EXAMPLES_ADVANCED.md) diff --git a/EXAMPLES_ADVANCED.md b/EXAMPLES_ADVANCED.md new file mode 100644 index 00000000..f8fb2172 --- /dev/null +++ b/EXAMPLES_ADVANCED.md @@ -0,0 +1,102 @@ +# Advanced debugging + +## PD + +### Check PD state + +Example on Framework 13 AMD Ryzen AI 300 + +``` +> sudo framework_tool.exe --pd-info +Right / 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 +Left / 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 +``` + +## 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 +``` diff --git a/README.md b/README.md index 7fbcea32..fc913e21 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,14 @@ make -C framework_uefi ls -l framework_uefi/build/x86_64-unknown-uefi/boot.efi ``` +### Dependencies + +``` +# NixOS +nix-shell --run fish -p cargo systemd udev hidapi pkg-config +direnv shell +``` + ## Install local package ``` @@ -253,11 +261,13 @@ Capsule Header ``` ###### Running on Windows -Windows does not ship with a Chrome EC driver. However there is an open-source implementation that this tool can take advantage of. -The project is hosted on GitHub and you can download pre-built binaries -[there](https://github.com/DHowett/FrameworkWindowsUtils/releases). -The driver is not signed by Microsoft, so you will have to enable testsigning. +On newly released systems since 2025 the Framework driver installer includes the EC driver. +This includes Framework 12, Framework 13 AMD Ryzen AI 300, Framework Desktop. + +Previous platforms will be enabled next. + +Installing: `winget install FrameworkComputer.framework_tool` ##### Running on ChromeOS diff --git a/devenv.lock b/devenv.lock new file mode 100644 index 00000000..bd0c7fbf --- /dev/null +++ b/devenv.lock @@ -0,0 +1,140 @@ +{ + "nodes": { + "devenv": { + "locked": { + "dir": "src/modules", + "lastModified": 1747543288, + "owner": "cachix", + "repo": "devenv", + "rev": "3a8a52386bde1cf14fc2f4c4df80f91417348480", + "type": "github" + }, + "original": { + "dir": "src/modules", + "owner": "cachix", + "repo": "devenv", + "type": "github" + } + }, + "fenix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" + }, + "locked": { + "lastModified": 1747392669, + "owner": "nix-community", + "repo": "fenix", + "rev": "c3c27e603b0d9b5aac8a16236586696338856fbb", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "fenix", + "type": "github" + } + }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1747046372, + "owner": "edolstra", + "repo": "flake-compat", + "rev": "9100a0f413b0c601e0533d1d94ffd501ce2e7885", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, + "git-hooks": { + "inputs": { + "flake-compat": "flake-compat", + "gitignore": "gitignore", + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1747372754, + "owner": "cachix", + "repo": "git-hooks.nix", + "rev": "80479b6ec16fefd9c1db3ea13aeb038c60530f46", + "type": "github" + }, + "original": { + "owner": "cachix", + "repo": "git-hooks.nix", + "type": "github" + } + }, + "gitignore": { + "inputs": { + "nixpkgs": [ + "git-hooks", + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1709087332, + "owner": "hercules-ci", + "repo": "gitignore.nix", + "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "gitignore.nix", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1746807397, + "owner": "cachix", + "repo": "devenv-nixpkgs", + "rev": "c5208b594838ea8e6cca5997fbf784b7cca1ca90", + "type": "github" + }, + "original": { + "owner": "cachix", + "ref": "rolling", + "repo": "devenv-nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "devenv": "devenv", + "fenix": "fenix", + "git-hooks": "git-hooks", + "nixpkgs": "nixpkgs", + "pre-commit-hooks": [ + "git-hooks" + ] + } + }, + "rust-analyzer-src": { + "flake": false, + "locked": { + "lastModified": 1747557850, + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "e464ff8c755c6e12540a45b83274ec4de4829191", + "type": "github" + }, + "original": { + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/devenv.nix b/devenv.nix new file mode 100644 index 00000000..413d6a66 --- /dev/null +++ b/devenv.nix @@ -0,0 +1,11 @@ +{ pkgs, lib, config, inputs, ... }: + +{ + packages = with pkgs; [ + systemd # libudev + ]; + + languages.rust.enable = true; + # https://devenv.sh/reference/options/#languagesrustchannel + languages.rust.channel = "stable"; +} diff --git a/devenv.yaml b/devenv.yaml new file mode 100644 index 00000000..1d180750 --- /dev/null +++ b/devenv.yaml @@ -0,0 +1,8 @@ +inputs: + fenix: + url: github:nix-community/fenix + inputs: + nixpkgs: + follows: nixpkgs + nixpkgs: + url: github:cachix/devenv-nixpkgs/rolling diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index 8d362370..b3af9f25 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_lib" -version = "0.4.2" +version = "0.4.3" description = "Library to control Framework Computer systems" homepage = "https://github.com/FrameworkComputer/framework-system" repository = "https://github.com/FrameworkComputer/framework-system" diff --git a/framework_lib/src/ccgx/binary.rs b/framework_lib/src/ccgx/binary.rs index 09c16855..cb324221 100644 --- a/framework_lib/src/ccgx/binary.rs +++ b/framework_lib/src/ccgx/binary.rs @@ -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)), } @@ -172,6 +172,7 @@ pub fn read_versions(file_buffer: &[u8], ccgx: SiliconId) -> Option (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), }; diff --git a/framework_lib/src/ccgx/device.rs b/framework_lib/src/ccgx/device.rs index 532ad10e..bd13f6df 100644 --- a/framework_lib/src/ccgx/device.rs +++ b/framework_lib/src/ccgx/device.rs @@ -14,18 +14,38 @@ 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, + Right01, + Left23, + Back, } impl PdPort { @@ -33,31 +53,51 @@ impl PdPort { fn i2c_address(&self) -> EcResult { let config = Config::get(); let platform = &(*config).as_ref().unwrap().platform; + let unsupported = Err(EcError::DeviceError( + "Controller does not exist on this platform".to_string(), + )); Ok(match (platform, self) { - (Platform::GenericFramework((left, _), _), PdPort::Left01) => *left, - (Platform::GenericFramework((_, right), _), PdPort::Right23) => *right, + (Platform::GenericFramework((left, _, _), _), PdPort::Right01) => *left, + (Platform::GenericFramework((_, right, _), _), PdPort::Left23) => *right, + (Platform::GenericFramework((_, _, back), _), PdPort::Back) => *back, // Framework AMD Platforms (CCG8) ( Platform::Framework13Amd7080 | Platform::Framework13AmdAi300 | Platform::Framework16Amd7080, - PdPort::Left01, + PdPort::Right01, ) => 0x42, ( Platform::Framework13Amd7080 | Platform::Framework13AmdAi300 | Platform::Framework16Amd7080, - PdPort::Right23, + PdPort::Left23, + ) => 0x40, + (Platform::Framework16Amd7080, PdPort::Back) => 0x42, + (Platform::FrameworkDesktopAmdAiMax300, PdPort::Back) => 0x08, + (Platform::FrameworkDesktopAmdAiMax300, _) => unsupported?, + // Framework Intel Platforms (CCG5 and CCG6) + ( + Platform::Framework12IntelGen13 + | Platform::IntelGen11 + | Platform::IntelGen12 + | Platform::IntelGen13 + | Platform::IntelCoreUltra1, + PdPort::Right01, + ) => 0x08, + ( + Platform::Framework12IntelGen13 + | Platform::IntelGen11 + | Platform::IntelGen12 + | Platform::IntelGen13 + | Platform::IntelCoreUltra1, + PdPort::Left23, ) => 0x40, - // TODO: It only has a single PD controller - (Platform::FrameworkDesktopAmdAiMax300, _) => 0x08, (Platform::UnknownSystem, _) => { Err(EcError::DeviceError("Unsupported platform".to_string()))? } - // Framework Intel Platforms (CCG5 and CCG6) - (_, PdPort::Left01) => 0x08, - (_, PdPort::Right23) => 0x40, + (_, PdPort::Back) => unsupported?, }) } @@ -65,20 +105,25 @@ impl PdPort { fn i2c_port(&self) -> EcResult { let config = Config::get(); let platform = &(*config).as_ref().unwrap().platform; + let unsupported = Err(EcError::DeviceError(format!( + "Controller {:?}, does not exist on {:?}", + self, platform + ))); Ok(match (platform, self) { - (Platform::GenericFramework(_, (left, _)), PdPort::Left01) => *left, - (Platform::GenericFramework(_, (_, right)), PdPort::Right23) => *right, + (Platform::GenericFramework(_, (left, _, _)), PdPort::Right01) => *left, + (Platform::GenericFramework(_, (_, right, _)), PdPort::Left23) => *right, + (Platform::GenericFramework(_, (_, _, back)), PdPort::Back) => *back, (Platform::IntelGen11, _) => 6, - (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Left01) => 6, - (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Right23) => 7, + (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Right01) => 6, + (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Left23) => 7, ( Platform::Framework13Amd7080 | Platform::Framework16Amd7080 | Platform::IntelCoreUltra1 | Platform::Framework13AmdAi300 | Platform::Framework12IntelGen13, - PdPort::Left01, + PdPort::Right01, ) => 1, ( Platform::Framework13Amd7080 @@ -86,13 +131,15 @@ impl PdPort { | Platform::IntelCoreUltra1 | Platform::Framework13AmdAi300 | Platform::Framework12IntelGen13, - PdPort::Right23, + PdPort::Left23, ) => 2, - // TODO: It only has a single PD controller - (Platform::FrameworkDesktopAmdAiMax300, _) => 1, + (Platform::Framework16Amd7080, PdPort::Back) => 5, + (Platform::FrameworkDesktopAmdAiMax300, PdPort::Back) => 1, + (Platform::FrameworkDesktopAmdAiMax300, _) => unsupported?, (Platform::UnknownSystem, _) => { Err(EcError::DeviceError("Unsupported platform".to_string()))? } + (_, PdPort::Back) => unsupported?, }) } } @@ -102,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) @@ -154,6 +201,21 @@ impl PdController { ) } + pub fn i2c_write(&self, addr: u16, data: &[u8]) -> EcResult { + 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> { let mut data: Vec = Vec::with_capacity(len.into()); @@ -176,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 { let data = self.ccgx_read(ControlRegisters::SiliconId, 2)?; assert_win_len(data.len(), 2); @@ -267,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 { + let data = self.ccgx_read(ControlRegisters::PdPortsEnable, 1)?; + assert_win_len(data.len(), 1); + Ok(data[0]) + } } diff --git a/framework_lib/src/ccgx/mod.rs b/framework_lib/src/ccgx/mod.rs index 80b1b763..f7269b90 100644 --- a/framework_lib/src/ccgx/mod.rs +++ b/framework_lib/src/ccgx/mod.rs @@ -3,6 +3,8 @@ use alloc::format; use alloc::string::String; use alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; #[cfg(feature = "uefi")] use core::prelude::rust_2021::derive; use num_derive::FromPrimitive; @@ -103,7 +105,8 @@ struct CyAcd2Metadata { pub enum SiliconId { Ccg3 = 0x1D00, Ccg5 = 0x2100, - Ccg6 = 0x3000, + Ccg6Adl = 0x3000, + Ccg6 = 0x30A0, Ccg8 = 0x3580, } @@ -234,23 +237,32 @@ impl ControllerFirmwares { } #[derive(Debug, PartialEq)] -pub struct PdVersions { - pub controller01: ControllerFirmwares, - pub controller23: ControllerFirmwares, +pub enum PdVersions { + RightLeft((ControllerFirmwares, ControllerFirmwares)), + Single(ControllerFirmwares), + Many(Vec), } /// Same as PdVersions but only the main FW #[derive(Debug)] -pub struct MainPdVersions { - pub controller01: ControllerVersion, - pub controller23: ControllerVersion, +pub enum MainPdVersions { + RightLeft((ControllerVersion, ControllerVersion)), + Single(ControllerVersion), + Many(Vec), } pub fn get_pd_controller_versions(ec: &CrosEc) -> EcResult { - Ok(PdVersions { - controller01: PdController::new(PdPort::Left01, ec.clone()).get_fw_versions()?, - controller23: PdController::new(PdPort::Right23, ec.clone()).get_fw_versions()?, - }) + let pd01 = PdController::new(PdPort::Right01, ec.clone()).get_fw_versions(); + let pd23 = PdController::new(PdPort::Left23, ec.clone()).get_fw_versions(); + let pd_back = PdController::new(PdPort::Back, ec.clone()).get_fw_versions(); + + match (pd01, pd23, pd_back) { + (Err(_), Err(_), Ok(pd_back)) => Ok(PdVersions::Single(pd_back)), + (Ok(pd01), Ok(pd23), Err(_)) => Ok(PdVersions::RightLeft((pd01, pd23))), + (Ok(pd01), Ok(pd23), Ok(pd_back)) => Ok(PdVersions::Many(vec![pd01, pd23, pd_back])), + (Err(err), _, _) => Err(err), + (_, Err(err), _) => Err(err), + } } fn parse_metadata_ccg3(buffer: &[u8]) -> Option<(u32, u32)> { diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 4a8fd2bf..0a367ab3 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -987,18 +987,38 @@ impl EcRequest for EcRequestChassisIntrusionC } #[repr(C, packed)] -pub struct EcRequestReadPdVersion {} +pub struct EcRequestReadPdVersionV0 {} #[repr(C, packed)] -pub struct _EcResponseReadPdVersion { +pub struct _EcResponseReadPdVersionV0 { pub controller01: [u8; 8], pub controller23: [u8; 8], } -impl EcRequest<_EcResponseReadPdVersion> for EcRequestReadPdVersion { +impl EcRequest<_EcResponseReadPdVersionV0> for EcRequestReadPdVersionV0 { fn command_id() -> EcCommands { EcCommands::ReadPdVersion } + fn command_version() -> u8 { + 0 + } +} + +#[repr(C, packed)] +pub struct EcRequestReadPdVersionV1 {} +#[repr(C, packed)] +pub struct _EcResponseReadPdVersionV1 { + pub pd_chip_count: u8, + pub pd_controllers: [u8; 0], +} + +impl EcRequest<_EcResponseReadPdVersionV1> for EcRequestReadPdVersionV1 { + fn command_id() -> EcCommands { + EcCommands::ReadPdVersion + } + fn command_version() -> u8 { + 1 + } } #[repr(C, packed)] diff --git a/framework_lib/src/chromium_ec/i2c_passthrough.rs b/framework_lib/src/chromium_ec/i2c_passthrough.rs index dfebaafc..03cb974e 100644 --- a/framework_lib/src/chromium_ec/i2c_passthrough.rs +++ b/framework_lib/src/chromium_ec/i2c_passthrough.rs @@ -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, diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index 9bd367a0..0adba41b 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -276,25 +276,25 @@ impl CrosEc { } } - pub fn check_mem_magic(&self) -> Option<()> { + pub fn check_mem_magic(&self) -> EcResult<()> { match self.read_memory(EC_MEMMAP_ID, 2) { Some(ec_id) => { if ec_id.len() != 2 { - error!(" Unexpected length returned: {:?}", ec_id.len()); - return None; - } - if ec_id[0] != b'E' || ec_id[1] != b'C' { - error!(" This machine doesn't look like it has a Framework EC"); - None + Err(EcError::DeviceError(format!( + " Unexpected length returned: {:?}", + ec_id.len() + ))) + } else if ec_id[0] != b'E' || ec_id[1] != b'C' { + Err(EcError::DeviceError( + "This machine doesn't look like it has a Framework EC".to_string(), + )) } else { - println!(" Verified that Framework EC is present!"); - Some(()) + Ok(()) } } - None => { - error!(" Failed to read EC ID from memory map"); - None - } + None => Err(EcError::DeviceError( + "Failed to read EC ID from memory map".to_string(), + )), } } @@ -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); @@ -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"); @@ -740,18 +753,32 @@ 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())); } } @@ -759,17 +786,25 @@ impl CrosEc { 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())); } } @@ -777,43 +812,50 @@ impl CrosEc { 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!(); @@ -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<()> { @@ -1105,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> { + let eeprom_port = 0x05; + let eeprom_addr = 0x50; + let mut data: Vec = 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> { + 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 { + let bytes = + self.read_ec_gpu_chunk(0x00, core::mem::size_of::() 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)?; @@ -1512,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, +} diff --git a/framework_lib/src/chromium_ec/portio.rs b/framework_lib/src/chromium_ec/portio.rs index 152d5f40..d76f8a6b 100644 --- a/framework_lib/src/chromium_ec/portio.rs +++ b/framework_lib/src/chromium_ec/portio.rs @@ -90,14 +90,6 @@ fn init() -> bool { Initialized::NotYet => {} } - // First try on MEC - portio_mec::init(); - let ec_id = portio_mec::transfer_read(MEC_MEMMAP_OFFSET + EC_MEMMAP_ID, 2); - if ec_id[0] == b'E' && ec_id[1] == b'C' { - *init = Initialized::SucceededMec; - return true; - } - // In Linux userspace has to first request access to ioports // TODO: Close these again after we're done #[cfg(target_os = "linux")] @@ -106,12 +98,25 @@ fn init() -> bool { *init = Initialized::Failed; return false; } + + // First try on MEC + if !portio_mec::init() { + *init = Initialized::Failed; + return false; + } + let ec_id = portio_mec::transfer_read(MEC_MEMMAP_OFFSET + EC_MEMMAP_ID, 2); + if ec_id[0] == b'E' && ec_id[1] == b'C' { + *init = Initialized::SucceededMec; + return true; + } + #[cfg(target_os = "linux")] unsafe { // 8 for request/response header, 0xFF for response let res = ioperm(EC_LPC_ADDR_HOST_ARGS as u64, 8 + 0xFF, 1); if res != 0 { error!("ioperm failed. portio driver is likely block by Linux kernel lockdown mode"); + *init = Initialized::Failed; return false; } diff --git a/framework_lib/src/chromium_ec/portio_mec.rs b/framework_lib/src/chromium_ec/portio_mec.rs index 9d3664e2..1f855d0f 100644 --- a/framework_lib/src/chromium_ec/portio_mec.rs +++ b/framework_lib/src/chromium_ec/portio_mec.rs @@ -22,12 +22,19 @@ const _MEC_LPC_DATA_REGISTER1: u16 = 0x0805; const MEC_LPC_DATA_REGISTER2: u16 = 0x0806; const _MEC_LPC_DATA_REGISTER3: u16 = 0x0807; -pub fn init() { +pub fn init() -> bool { #[cfg(target_os = "linux")] unsafe { - ioperm(EC_LPC_ADDR_HOST_DATA as u64, 8, 1); - ioperm(MEC_LPC_ADDRESS_REGISTER0 as u64, 10, 1); + let res = ioperm(EC_LPC_ADDR_HOST_DATA as u64, 8, 1); + if res != 0 { + error!("ioperm failed. portio driver is likely block by Linux kernel lockdown mode"); + return false; + } + let res = ioperm(MEC_LPC_ADDRESS_REGISTER0 as u64, 10, 1); + assert_eq!(res, 0); } + + true } // TODO: Create a wrapper diff --git a/framework_lib/src/chromium_ec/windows.rs b/framework_lib/src/chromium_ec/windows.rs index 5b0cca68..557ab430 100644 --- a/framework_lib/src/chromium_ec/windows.rs +++ b/framework_lib/src/chromium_ec/windows.rs @@ -14,6 +14,8 @@ use windows::{ use crate::chromium_ec::protocol::HEADER_LEN; use crate::chromium_ec::EC_MEMMAP_SIZE; use crate::chromium_ec::{EcError, EcResponseStatus, EcResult}; +use crate::smbios; +use crate::util::Platform; // Create a wrapper around HANDLE to mark it as Send. // I'm not sure, but I think it's safe to do that for this type of HANDL. @@ -46,6 +48,20 @@ fn init() -> bool { let handle = match res { Ok(h) => h, Err(err) => { + let platform = smbios::get_platform(); + match platform { + Some(platform @ Platform::IntelGen11) + | Some(platform @ Platform::IntelGen12) + | Some(platform @ Platform::IntelGen13) + | Some(platform @ Platform::Framework13Amd7080) + | Some(platform @ Platform::Framework16Amd7080) => { + println!("The windows driver is not enabled on {:?}.", platform); + println!("Please stay tuned for future BIOS and driver updates."); + println!(); + } + _ => (), + } + error!("Failed to find Windows driver. {:?}", err); return false; } diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index aabd5468..6d7f50ea 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -83,6 +83,18 @@ struct ClapCli { #[arg(long)] pd_info: bool, + /// Reset a specific PD controller (for debugging only) + #[arg(long)] + pd_reset: Option, + + /// Disable all ports on a specific PD controller (for debugging only) + #[arg(long)] + pd_disable: Option, + + /// Enable all ports on a specific PD controller (for debugging only) + #[arg(long)] + pd_enable: Option, + /// Show details about connected DP or HDMI Expansion Cards #[arg(long)] dp_hdmi_info: bool, @@ -123,11 +135,11 @@ struct ClapCli { #[arg(long)] dump_ec_flash: Option, - /// 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, - /// 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, @@ -181,7 +193,7 @@ struct ClapCli { #[arg(long)] kblight: Option>, - /// Set keyboard backlight percentage or get, if no value provided + /// Remap a key by changing the scancode #[arg(long, value_parser=maybe_hex::)] #[clap(num_args = 3)] remap_key: Vec, @@ -238,18 +250,34 @@ struct ClapCli { driver: Option, /// Specify I2C addresses of the PD chips (Advanced) - #[clap(number_of_values = 2, requires("pd_ports"))] + #[clap(number_of_values = 3, requires("pd_ports"))] #[arg(long)] pd_addrs: Vec, /// Specify I2C ports of the PD chips (Advanced) - #[clap(number_of_values = 2, requires("pd_addrs"))] + #[clap(number_of_values = 3, requires("pd_addrs"))] #[arg(long)] pd_ports: Vec, /// 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, + + /// File to write to the gpu EEPROM + #[arg(long)] + flash_gpu_descriptor_file: Option, + + /// File to dump the gpu EEPROM to + #[arg(long)] + dump_gpu_descriptor_file: Option, } /// Parse a list of commandline arguments and return the struct @@ -309,13 +337,13 @@ pub fn parse(args: &[String]) -> Cli { .unwrap(); let pd_addrs = match args.pd_addrs.len() { - 2 => Some((args.pd_addrs[0], args.pd_addrs[1])), + 3 => Some((args.pd_addrs[0], args.pd_addrs[1], args.pd_addrs[2])), 0 => None, // Checked by clap _ => unreachable!(), }; let pd_ports = match args.pd_ports.len() { - 2 => Some((args.pd_ports[0], args.pd_ports[1])), + 3 => Some((args.pd_ports[0], args.pd_ports[1], args.pd_ports[2])), 0 => None, // Checked by clap _ => unreachable!(), @@ -368,6 +396,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 @@ -424,6 +455,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 @@ -432,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![], } } diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 7f11c87f..602ca09e 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -33,7 +33,7 @@ use crate::capsule_content::{ use crate::ccgx::device::{FwMode, PdController, PdPort}; #[cfg(feature = "hidapi")] use crate::ccgx::hid::{check_ccg_fw_version, find_devices, DP_CARD_PID, HDMI_CARD_PID}; -use crate::ccgx::{self, SiliconId::*}; +use crate::ccgx::{self, MainPdVersions, PdVersions, SiliconId::*}; use crate::chromium_ec; use crate::chromium_ec::commands::DeckStateMode; use crate::chromium_ec::commands::FpLedBrightnessLevel; @@ -59,8 +59,7 @@ use crate::touchpad::print_touchpad_fw_ver; use crate::touchscreen; #[cfg(feature = "uefi")] use crate::uefi::enable_page_break; -use crate::util; -use crate::util::{Config, Platform, PlatformFamily}; +use crate::util::{self, Config, Platform, PlatformFamily}; #[cfg(feature = "hidapi")] use hidapi::HidApi; use sha2::{Digest, Sha256, Sha384, Sha512}; @@ -157,6 +156,9 @@ pub struct Cli { pub pdports: bool, pub privacy: bool, pub pd_info: bool, + pub pd_reset: Option, + pub pd_disable: Option, + pub pd_enable: Option, pub dp_hdmi_info: bool, pub dp_hdmi_update: Option, pub audio_card_info: bool, @@ -171,6 +173,8 @@ pub struct Cli { pub flash_rw_ec: Option, pub driver: Option, pub test: bool, + pub dry_run: bool, + pub force: bool, pub intrusion: bool, pub inputdeck: bool, pub inputdeck_mode: Option, @@ -192,11 +196,13 @@ pub struct Cli { pub reboot_ec: Option, pub ec_hib_delay: Option>, pub hash: Option, - pub pd_addrs: Option<(u16, u16)>, - pub pd_ports: Option<(u8, u8)>, + pub pd_addrs: Option<(u16, u16, u16)>, + pub pd_ports: Option<(u8, u8, u8)>, pub help: bool, pub info: bool, pub flash_gpu_descriptor: Option<(u8, String)>, + pub flash_gpu_descriptor_file: Option, + pub dump_gpu_descriptor_file: Option, // UEFI only pub allupdate: bool, pub paginate: bool, @@ -216,6 +222,7 @@ 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); @@ -223,6 +230,17 @@ fn print_single_pd_details(pd: &PdController) { } 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(); } @@ -231,13 +249,16 @@ fn print_pd_details(ec: &CrosEc) { println!("Only supported on Framework systems"); return; } - let pd_01 = PdController::new(PdPort::Left01, ec.clone()); - let pd_23 = PdController::new(PdPort::Right23, ec.clone()); + let pd_01 = PdController::new(PdPort::Right01, ec.clone()); + let pd_23 = PdController::new(PdPort::Left23, ec.clone()); + let pd_back = PdController::new(PdPort::Back, ec.clone()); - println!("Left / Ports 01"); + println!("Right / Ports 01"); print_single_pd_details(&pd_01); - println!("Right / Ports 23"); + println!("Left / Ports 23"); print_single_pd_details(&pd_23); + println!("Back"); + print_single_pd_details(&pd_back); } #[cfg(feature = "hidapi")] @@ -398,11 +419,8 @@ fn print_versions(ec: &CrosEc) { } println!("PD Controllers"); - - if let Ok(pd_versions) = ccgx::get_pd_controller_versions(ec) { - let right = &pd_versions.controller01; - let left = &pd_versions.controller23; - // let active_mode = + let ccgx_pd_vers = ccgx::get_pd_controller_versions(ec); + if let Ok(PdVersions::RightLeft((right, left))) = ccgx_pd_vers { if let Some(Platform::IntelGen11) = smbios::get_platform() { if right.main_fw.base != right.backup_fw.base { println!(" Right (01)"); @@ -476,10 +494,64 @@ fn print_versions(ec: &CrosEc) { left.main_fw.app, left.active_fw ); } + } else if let Ok(PdVersions::Many(versions)) = ccgx_pd_vers { + for (i, version) in versions.into_iter().enumerate() { + if version.main_fw.app != version.backup_fw.app { + println!(" PD {}", 1); + println!( + " Main: {}{}", + version.main_fw.app, + active_mode(&version.active_fw, FwMode::MainFw) + ); + println!( + " Backup: {}{}", + version.backup_fw.app, + active_mode(&version.active_fw, FwMode::BackupFw) + ); + } else { + println!( + " PD {}: {} ({:?})", + i, version.main_fw.app, version.active_fw + ); + } + } + } else if let Ok(PdVersions::Single(pd)) = ccgx_pd_vers { + if pd.main_fw.app != pd.backup_fw.app { + println!( + " Main: {}{}", + pd.main_fw.app, + active_mode(&pd.active_fw, FwMode::MainFw) + ); + println!( + " Backup: {}{}", + pd.backup_fw.app, + active_mode(&pd.active_fw, FwMode::BackupFw) + ); + } else { + println!(" Version: {} ({:?})", pd.main_fw.app, pd.active_fw); + } } else if let Ok(pd_versions) = power::read_pd_version(ec) { // As fallback try to get it from the EC. But not all EC versions have this command - println!(" Right (01): {}", pd_versions.controller01.app); - println!(" Left (23): {}", pd_versions.controller23.app); + debug!(" Fallback to PD Host command"); + match pd_versions { + MainPdVersions::RightLeft((controller01, controller23)) => { + if let Some(Platform::IntelGen11) = smbios::get_platform() { + println!(" Right (01): {}", controller01.base); + println!(" Left (23): {}", controller23.base); + } else { + println!(" Right (01): {}", controller01.app); + println!(" Left (23): {}", controller23.app); + } + } + MainPdVersions::Single(version) => { + println!(" Version: {}", version.app); + } + MainPdVersions::Many(versions) => { + for (i, version) in versions.into_iter().enumerate() { + println!(" PD {}: {}", i, version.app); + } + } + } } else { println!(" Unknown") } @@ -500,13 +572,13 @@ fn print_versions(ec: &CrosEc) { | esrt::ADL_RETIMER01_GUID | esrt::RPL_RETIMER01_GUID | esrt::MTL_RETIMER01_GUID => { - left_retimer = Some(entry.fw_version); + right_retimer = Some(entry.fw_version); } esrt::TGL_RETIMER23_GUID | esrt::ADL_RETIMER23_GUID | esrt::RPL_RETIMER23_GUID | esrt::MTL_RETIMER23_GUID => { - right_retimer = Some(entry.fw_version); + left_retimer = Some(entry.fw_version); } _ => {} } @@ -527,7 +599,7 @@ fn print_versions(ec: &CrosEc) { } #[cfg(target_os = "linux")] - { + if smbios::get_platform().and_then(Platform::which_cpu_vendor) != Some(util::CpuVendor::Amd) { println!("CSME"); if let Ok(csme) = csme::csme_from_sysfs() { info!(" Enabled: {}", csme.enabled); @@ -565,7 +637,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"))] @@ -584,7 +656,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!"); @@ -609,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, version: String, ec: &CrosEc) -> i32 { println!("Target Version {:?}", version); @@ -638,8 +728,8 @@ fn compare_version(device: Option, version: String, ec: &Cro } } Some(HardwareDeviceType::PD0) => { - if let Ok(pd_versions) = ccgx::get_pd_controller_versions(ec) { - let ver = pd_versions.controller01.active_fw_ver(); + if let Ok(PdVersions::RightLeft((pd01, _pd23))) = ccgx::get_pd_controller_versions(ec) { + let ver = pd01.active_fw_ver(); println!("Comparing PD0 version {:?}", ver); if ver.contains(&version) { @@ -650,8 +740,8 @@ fn compare_version(device: Option, version: String, ec: &Cro } } Some(HardwareDeviceType::PD1) => { - if let Ok(pd_versions) = ccgx::get_pd_controller_versions(ec) { - let ver = pd_versions.controller23.active_fw_ver(); + if let Ok(PdVersions::RightLeft((_pd01, pd23))) = ccgx::get_pd_controller_versions(ec) { + let ver = pd23.active_fw_ver(); println!("Comparing PD1 version {:?}", ver); if ver.contains(&version) { @@ -774,7 +864,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else if args.version { print_tool_version(); } else if args.features { - ec.get_features().unwrap(); + print_err(ec.get_features()); } else if args.esrt { print_esrt(); } else if let Some(compare_version_ver) = &args.compare_version { @@ -813,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 { @@ -944,6 +1054,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::Right01, ec.clone()).reset_device(), + 1 => PdController::new(PdPort::Left23, 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::Right01, ec.clone()).enable_ports(false), + 1 => PdController::new(PdPort::Left23, 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::Right01, ec.clone()).enable_ports(true), + 1 => PdController::new(PdPort::Left23, 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); @@ -1078,11 +1221,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")] @@ -1109,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> = 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 @@ -1164,6 +1347,9 @@ Options: --console Get EC console, choose whether recent or to follow the output [possible values: recent, follow] --hash Hash a file of arbitrary data --flash-gpu-descriptor <18 DIGIT SN> Overwrite the GPU bay descriptor SN and type. + --flash-gpu-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 -h, --help Print help information -b Print output one screen at a time @@ -1227,7 +1413,8 @@ fn selftest(ec: &CrosEc) -> Option<()> { } println!(" Checking EC memory mapped magic bytes"); - ec.check_mem_magic()?; + print_err(ec.check_mem_magic())?; + println!(" Verified that Framework EC is present!"); println!(" Reading EC Build Version"); print_err(ec.version_info())?; @@ -1263,8 +1450,8 @@ fn selftest(ec: &CrosEc) -> Option<()> { println!(" - OK"); } - let pd_01 = PdController::new(PdPort::Left01, ec.clone()); - let pd_23 = PdController::new(PdPort::Right23, ec.clone()); + let pd_01 = PdController::new(PdPort::Right01, ec.clone()); + let pd_23 = PdController::new(PdPort::Left23, ec.clone()); print!(" Getting PD01 info through I2C tunnel"); print_err(pd_01.get_silicon_id())?; print_err(pd_01.get_device_info())?; @@ -1441,22 +1628,22 @@ pub fn analyze_capsule(data: &[u8]) -> Option { println!(" Type: Framework RPL Insyde BIOS"); } esrt::TGL_RETIMER01_GUID => { - println!(" Type: Framework TGL Retimer01 (Left)"); + println!(" Type: Framework TGL Retimer01 (Right)"); } esrt::TGL_RETIMER23_GUID => { - println!(" Type: Framework TGL Retimer23 (Right)"); + println!(" Type: Framework TGL Retimer23 (Left)"); } esrt::ADL_RETIMER01_GUID => { - println!(" Type: Framework ADL Retimer01 (Left)"); + println!(" Type: Framework ADL Retimer01 (Right)"); } esrt::ADL_RETIMER23_GUID => { - println!(" Type: Framework ADL Retimer23 (Right)"); + println!(" Type: Framework ADL Retimer23 (Left)"); } esrt::RPL_RETIMER01_GUID => { - println!(" Type: Framework RPL Retimer01 (Left)"); + println!(" Type: Framework RPL Retimer01 (Right)"); } esrt::RPL_RETIMER23_GUID => { - println!(" Type: Framework RPL Retimer23 (Right)"); + println!(" Type: Framework RPL Retimer23 (Left)"); } esrt::WINUX_GUID => { println!(" Type: Windows UX capsule"); diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 097c006f..82d6c253 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -70,6 +70,9 @@ pub fn parse(args: &[String]) -> Cli { autofanctrl: false, pdports: false, pd_info: false, + pd_reset: None, + pd_disable: None, + pd_enable: None, dp_hdmi_info: false, dp_hdmi_update: None, audio_card_info: false, @@ -109,8 +112,12 @@ 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, + flash_gpu_descriptor_file: None, + dump_gpu_descriptor_file: None, allupdate: false, info: false, raw_command: vec![], @@ -504,12 +511,66 @@ 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; } else if arg == "--pd-info" { cli.pd_info = true; found_an_option = true; + } else if arg == "--pd-reset" { + cli.pd_reset = if args.len() > i + 1 { + if let Ok(pd) = args[i + 1].parse::() { + Some(pd) + } else { + println!( + "Invalid value for --pd-reset: '{}'. Must be 0 or 1.", + args[i + 1], + ); + None + } + } else { + println!("--pd-reset requires specifying the PD controller"); + None + }; + found_an_option = true; + } else if arg == "--pd-disable" { + cli.pd_reset = if args.len() > i + 1 { + if let Ok(pd) = args[i + 1].parse::() { + Some(pd) + } else { + println!( + "Invalid value for --pd-disable: '{}'. Must be 0 or 1.", + args[i + 1], + ); + None + } + } else { + println!("--pd-disable requires specifying the PD controller"); + None + }; + found_an_option = true; + } else if arg == "--pd-enable" { + cli.pd_enable = if args.len() > i + 1 { + if let Ok(pd) = args[i + 1].parse::() { + Some(pd) + } else { + println!( + "Invalid value for --pd-enable: '{}'. Must be 0 or 1.", + args[i + 1], + ); + None + } + } else { + println!("--pd-enable requires specifying the PD controller"); + None + }; + found_an_option = true; } else if arg == "--privacy" { cli.privacy = true; found_an_option = true; @@ -594,35 +655,39 @@ pub fn parse(args: &[String]) -> Cli { }; found_an_option = true; } else if arg == "--pd-addrs" { - cli.pd_addrs = if args.len() > i + 2 { + cli.pd_addrs = if args.len() > i + 3 { let left = args[i + 1].parse::(); let right = args[i + 2].parse::(); - if left.is_ok() && right.is_ok() { - Some((left.unwrap(), right.unwrap())) + let back = args[i + 3].parse::(); + if left.is_ok() && right.is_ok() && back.is_ok() { + Some((left.unwrap(), right.unwrap(), back.unwrap())) } else { println!( - "Invalid values for --pd-addrs: '{} {}'. Must be u16 integers.", + "Invalid values for --pd-addrs: '{} {} {}'. Must be u16 integers.", args[i + 1], - args[i + 2] + args[i + 2], + args[i + 3] ); None } } else { - println!("--pd-addrs requires two arguments, one for each address"); + println!("--pd-addrs requires three arguments, one for each address"); None }; found_an_option = true; } else if arg == "--pd-ports" { - cli.pd_ports = if args.len() > i + 2 { + cli.pd_ports = if args.len() > i + 3 { let left = args[i + 1].parse::(); let right = args[i + 2].parse::(); - if left.is_ok() && right.is_ok() { - Some((left.unwrap(), right.unwrap())) + let back = args[i + 3].parse::(); + if left.is_ok() && right.is_ok() && back.is_ok() { + Some((left.unwrap(), right.unwrap(), back.unwrap())) } else { println!( - "Invalid values for --pd-ports: '{} {}'. Must be u16 integers.", + "Invalid values for --pd-ports: '{} {} {}'. Must be u16 integers.", args[i + 1], - args[i + 2] + args[i + 2], + args[i + 3] ); None } @@ -701,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; } } diff --git a/framework_lib/src/ec_binary.rs b/framework_lib/src/ec_binary.rs index 092367de..15193105 100644 --- a/framework_lib/src/ec_binary.rs +++ b/framework_lib/src/ec_binary.rs @@ -145,38 +145,47 @@ pub fn parse_ec_version_str(version: &str) -> Option { /// Parse version information from EC FW image buffer pub fn read_ec_version(data: &[u8], ro: bool) -> Option { + // First try to find the legacy EC version let offset = if ro { EC_RO_VER_OFFSET } else { EC_RW_VER_OFFSET }; - let offset_zephyr = if ro { - EC_RO_VER_OFFSET_ZEPHYR - } else { - EC_RW_VER_OFFSET_ZEPHYR - }; - if data.len() < offset + core::mem::size_of::<_ImageVersionData>() { return None; } let v: _ImageVersionData = unsafe { std::ptr::read(data[offset..].as_ptr() as *const _) }; if v.cookie1 != CROS_EC_IMAGE_DATA_COOKIE1 { - debug!("Failed to find Cookie 1. Found: {:X?}", { v.cookie1 }); + debug!("Failed to find legacy Cookie 1. Found: {:X?}", { + v.cookie1 + }); } else if v.cookie2 != CROS_EC_IMAGE_DATA_COOKIE2 { - debug!("Failed to find Cookie 2. Found: {:X?}", { v.cookie2 }); + debug!("Failed to find legacy Cookie 2. Found: {:X?}", { + v.cookie2 + }); } else { return parse_ec_version(&v); } + // If not present, find Zephyr EC version + let offset_zephyr = if ro { + EC_RO_VER_OFFSET_ZEPHYR + } else { + EC_RW_VER_OFFSET_ZEPHYR + }; if data.len() < offset_zephyr + core::mem::size_of::<_ImageVersionData>() { return None; } let v: _ImageVersionData = unsafe { std::ptr::read(data[offset_zephyr..].as_ptr() as *const _) }; if v.cookie1 != CROS_EC_IMAGE_DATA_COOKIE1 { - debug!("Failed to find Cookie 1. Found: {:X?}", { v.cookie1 }); + debug!("Failed to find Zephyr Cookie 1. Found: {:X?}", { + v.cookie1 + }); } else if v.cookie2 != CROS_EC_IMAGE_DATA_COOKIE2 { - debug!("Failed to find Cookie 2. Found: {:X?}", { v.cookie2 }); + debug!("Failed to find Zephyr Cookie 2. Found: {:X?}", { + v.cookie2 + }); } else { return parse_ec_version(&v); } diff --git a/framework_lib/src/esrt/mod.rs b/framework_lib/src/esrt/mod.rs index 15daea4c..aa517bf4 100644 --- a/framework_lib/src/esrt/mod.rs +++ b/framework_lib/src/esrt/mod.rs @@ -131,6 +131,12 @@ pub const AMD13_AI300_BIOS_GUID: GUID = GUID::build_from_components( 0x5d68, &[0xbe, 0x61, 0x6b, 0x17, 0x88, 0x10, 0x14, 0xa7], ); +pub const DESKTOP_AMD_AI300_BIOS_GUID: GUID = GUID::build_from_components( + 0xeb68dbae, + 0x3aef, + 0x5077, + &[0x92, 0xae, 0x90, 0x16, 0xd1, 0xf0, 0xc8, 0x56], +); pub const RPL_CSME_GUID: GUID = GUID::build_from_components( 0x865d322c, 0x6ac7, @@ -182,6 +188,7 @@ pub enum FrameworkGuidKind { Fl16Bios, Amd13Ryzen7040Bios, Amd13Ai300Bios, + DesktopAmdAi300Bios, WinUx, Unknown, } @@ -196,6 +203,7 @@ pub fn match_guid_kind(guid: &Guid) -> FrameworkGuidKind { FL16_BIOS_GUID => FrameworkGuidKind::Fl16Bios, AMD13_RYZEN7040_BIOS_GUID => FrameworkGuidKind::Amd13Ryzen7040Bios, AMD13_AI300_BIOS_GUID => FrameworkGuidKind::Amd13Ai300Bios, + DESKTOP_AMD_AI300_BIOS_GUID => FrameworkGuidKind::DesktopAmdAi300Bios, TGL_RETIMER01_GUID => FrameworkGuidKind::TglRetimer01, TGL_RETIMER23_GUID => FrameworkGuidKind::TglRetimer23, ADL_RETIMER01_GUID => FrameworkGuidKind::AdlRetimer01, @@ -498,8 +506,8 @@ pub fn get_esrt() -> Option { let mut table = EfiGetTableIoc { buf: std::ptr::null_mut(), uuid: SYSTEM_RESOURCE_TABLE_GUID_BYTES, - buf_len: 0, table_len: 0, + buf_len: 0, }; unsafe { let fd = file.as_raw_fd(); @@ -524,7 +532,7 @@ pub const SYSTEM_RESOURCE_TABLE_GUID: GUID = GUID::build_from_components( &[0x99, 0x29, 0x78, 0xf8, 0xb0, 0xd6, 0x21, 0x80], ); pub const SYSTEM_RESOURCE_TABLE_GUID_BYTES: [u8; 16] = [ - 0xb1, 0x22, 0xa2, 0x63, 0x36, 0x61, 0x4f, 0x68, 0x99, 0x29, 0x78, 0xf8, 0xb0, 0xd6, 0x21, 0x80, + 0x63, 0xa2, 0x22, 0xb1, 0x61, 0x36, 0x68, 0x4f, 0x99, 0x29, 0x78, 0xf8, 0xb0, 0xd6, 0x21, 0x80, ]; #[cfg(feature = "uefi")] diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index ba89af1d..91f87bda 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -1,7 +1,7 @@ //! Get information about system power (battery, AC, PD ports) use alloc::format; -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; use core::convert::TryInto; @@ -305,10 +305,11 @@ pub fn print_sensors(ec: &CrosEc) { err => _ = print_err(err), } - // If we can't detect it based on motionsense + // If we can't detect it based on motionsense, check the system family + // If family is unknown, assume it has let als_family = matches!( smbios::get_family(), - Some(PlatformFamily::Framework13) | Some(PlatformFamily::Framework16) + Some(PlatformFamily::Framework13) | Some(PlatformFamily::Framework16) | None ); if has_als || als_family { @@ -798,6 +799,11 @@ pub fn is_charging(ec: &CrosEc) -> EcResult<(bool, bool)> { Ok((port0 || port1, port2 || port3)) } +fn parse_pd_ver_slice(data: &[u8]) -> ControllerVersion { + parse_pd_ver(&[ + data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], + ]) +} fn parse_pd_ver(data: &[u8; 8]) -> ControllerVersion { ControllerVersion { base: BaseVersion { @@ -815,15 +821,37 @@ fn parse_pd_ver(data: &[u8; 8]) -> ControllerVersion { } } -// NOTE: Only works on ADL at the moment! -// TODO: Not on TGL, need to check if RPL and later have it. +// NOTE: TGL (hx20) does not have this host command pub fn read_pd_version(ec: &CrosEc) -> EcResult { - let info = EcRequestReadPdVersion {}.send_command(ec)?; + let info = EcRequestReadPdVersionV1 {}.send_command_vec(ec); - Ok(MainPdVersions { - controller01: parse_pd_ver(&info.controller01), - controller23: parse_pd_ver(&info.controller23), - }) + // If v1 not available, fall back + if let Err(EcError::Response(EcResponseStatus::InvalidVersion)) = info { + let info = EcRequestReadPdVersionV0 {}.send_command(ec)?; + + return Ok(if info.controller23 == [0, 0, 0, 0, 0, 0, 0, 0] { + MainPdVersions::Single(parse_pd_ver(&info.controller01)) + } else { + MainPdVersions::RightLeft(( + parse_pd_ver(&info.controller01), + parse_pd_ver(&info.controller23), + )) + }); + } + // If any other error, exit + let info = info?; + + let mut versions = vec![]; + let pd_count = info[0] as usize; + for i in 0..pd_count { + // TODO: Is there a safer way to check the range? + if info.len() < 1 + 8 * (i + 1) { + return Err(EcError::DeviceError("Not enough data returned".to_string())); + } + versions.push(parse_pd_ver_slice(&info[1 + 8 * i..1 + 8 * (i + 1)])); + } + + Ok(MainPdVersions::Many(versions)) } pub fn standalone_mode(ec: &CrosEc) -> bool { diff --git a/framework_lib/src/smbios.rs b/framework_lib/src/smbios.rs index 935f0dc7..144ad210 100644 --- a/framework_lib/src/smbios.rs +++ b/framework_lib/src/smbios.rs @@ -47,7 +47,7 @@ pub enum ConfigDigit0 { pub fn is_framework() -> bool { if matches!( get_platform(), - Some(Platform::GenericFramework((_, _), (_, _))) | Some(Platform::UnknownSystem) + Some(Platform::GenericFramework((_, _, _), (_, _, _))) | Some(Platform::UnknownSystem) ) { return true; } @@ -292,7 +292,7 @@ pub fn get_platform() -> Option { let platform = &(*config).as_ref().unwrap().platform; if matches!( platform, - Platform::GenericFramework((_, _), (_, _)) | Platform::UnknownSystem + Platform::GenericFramework((_, _, _), (_, _, _)) | Platform::UnknownSystem ) { return Some(*platform); } diff --git a/framework_lib/src/util.rs b/framework_lib/src/util.rs index 022475f1..1716ed65 100644 --- a/framework_lib/src/util.rs +++ b/framework_lib/src/util.rs @@ -37,7 +37,7 @@ pub enum Platform { FrameworkDesktopAmdAiMax300, /// Generic Framework device /// pd_addrs, pd_ports - GenericFramework((u16, u16), (u8, u8)), + GenericFramework((u16, u16, u16), (u8, u8, u8)), UnknownSystem, } @@ -49,7 +49,28 @@ pub enum PlatformFamily { FrameworkDesktop, } +#[derive(Debug, PartialEq, Clone, Copy)] +pub enum CpuVendor { + Intel, + Amd, +} + impl Platform { + pub fn which_cpu_vendor(self) -> Option { + match self { + Platform::Framework12IntelGen13 + | Platform::IntelGen11 + | Platform::IntelGen12 + | Platform::IntelGen13 + | Platform::IntelCoreUltra1 => Some(CpuVendor::Intel), + Platform::Framework13Amd7080 + | Platform::Framework13AmdAi300 + | Platform::Framework16Amd7080 + | Platform::FrameworkDesktopAmdAiMax300 => Some(CpuVendor::Amd), + Platform::GenericFramework(..) => None, + Platform::UnknownSystem => None, + } + } pub fn which_family(self) -> Option { match self { Platform::Framework12IntelGen13 => Some(PlatformFamily::Framework12), diff --git a/framework_tool/Cargo.toml b/framework_tool/Cargo.toml index 8da6a35c..cdd90a2a 100644 --- a/framework_tool/Cargo.toml +++ b/framework_tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_tool" -version = "0.4.2" +version = "0.4.3" description = "Tool to control Framework Computer systems" homepage = "https://github.com/FrameworkComputer/framework-system" repository = "https://github.com/FrameworkComputer/framework-system" @@ -26,3 +26,6 @@ version = "0.3.9" features = [ "wincon" ] + +[package.metadata.winresource] +LegalCopyright = "Framework Computer Inc © 2022" diff --git a/framework_uefi/Cargo.toml b/framework_uefi/Cargo.toml index 852fa7ec..adc8b14b 100644 --- a/framework_uefi/Cargo.toml +++ b/framework_uefi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_uefi" -version = "0.4.2" +version = "0.4.3" description = "UEFI Tool to control Framework Computer systems" homepage = "https://github.com/FrameworkComputer/framework-system" repository = "https://github.com/FrameworkComputer/framework-system" diff --git a/nix.md b/nix.md new file mode 100644 index 00000000..19b140ca --- /dev/null +++ b/nix.md @@ -0,0 +1,12 @@ +# Building with Nix + +## Building with devenv + + +Must have Nix and devenv installed + +``` +devenv shell +cargo build +sudo ./target/debug/framework_tool +``` pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy