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 +```