diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2a261d25..92f5299d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,7 +26,7 @@ jobs: run: cargo install cross - name: Build FreeBSD tool - run: cross build --target=x86_64-unknown-freebsd --no-default-features --features cross_freebsd + run: cross build --target=x86_64-unknown-freebsd --no-default-features -p framework_lib - name: Upload FreeBSD App uses: actions/upload-artifact@v4 @@ -93,15 +93,15 @@ jobs: # Build debug library first to fail fast - name: Build library (Windows) - run: cargo build -p framework_lib --no-default-features --features "windows" + run: cargo build -p framework_lib - name: Build Windows tool run: | - cargo build -p framework_tool --no-default-features --features "windows" - cargo build -p framework_tool --no-default-features --features "windows" --release + cargo build -p framework_tool + cargo build -p framework_tool --release - name: Check if Windows tool can start - run: cargo run --no-default-features --features "windows" -- --help --release + run: cargo run -- --help --release # Upload release build so that vcruntime is statically linked - name: Upload Windows App diff --git a/Cargo.lock b/Cargo.lock index 465dc6e0..0f0b1984 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,7 +127,7 @@ checksum = "031718ddb8f78aa5def78a09e90defe30151d1f6c672f937af4dd916429ed996" dependencies = [ "semver", "serde", - "toml", + "toml 0.5.11", "url", ] @@ -354,6 +354,20 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "embed-resource" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fbc6e0d8e0c03a655b53ca813f0463d2c956bc4db8138dbc89f120b066551e3" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.22", + "vswhom", + "winreg", +] + [[package]] name = "env_filter" version = "0.1.2" @@ -377,6 +391,12 @@ dependencies = [ "log", ] +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "form_urlencoded" version = "1.1.0" @@ -388,7 +408,7 @@ dependencies = [ [[package]] name = "framework_lib" -version = "0.4.0" +version = "0.4.1" dependencies = [ "built", "clap", @@ -420,15 +440,18 @@ dependencies = [ [[package]] name = "framework_tool" -version = "0.4.0" +version = "0.4.1" dependencies = [ + "embed-resource", "framework_lib", "static_vcruntime", + "winapi", + "winresource", ] [[package]] name = "framework_uefi" -version = "0.4.0" +version = "0.4.1" dependencies = [ "framework_lib", "log", @@ -566,6 +589,12 @@ dependencies = [ "syn 2.0.98", ] +[[package]] +name = "hashbrown" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" + [[package]] name = "heck" version = "0.5.0" @@ -625,6 +654,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "io-kit-sys" version = "0.1.0" @@ -1069,6 +1108,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1207,6 +1255,47 @@ dependencies = [ "serde", ] +[[package]] +name = "toml" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ae329d1f08c4d17a59bed7ff5b5a769d062e64a62d34a3261b219e62cd5aae" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb942dfe1d8e29a7ee7fcbde5bd2b9a25fb89aa70caea2eba3bee836ff41076" + [[package]] name = "typenum" version = "1.16.0" @@ -1312,6 +1401,26 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + [[package]] name = "wasm-bindgen" version = "0.2.84" @@ -1672,6 +1781,35 @@ version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" +[[package]] +name = "winnow" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "winresource" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4a67c78ee5782c0c1cb41bebc7e12c6e79644daa1650ebbc1de5d5b08593f7" +dependencies = [ + "toml 0.8.22", + "version_check", +] + [[package]] name = "wmi" version = "0.15.0" diff --git a/EXAMPLES.md b/EXAMPLES.md index 4f1ae29c..7b915984 100644 --- a/EXAMPLES.md +++ b/EXAMPLES.md @@ -2,6 +2,33 @@ ## Check firmware versions +### BIOS (Mainboard, UEFI, EC, PD) + +Example on Framework 13 AMD Ryzen AI 300 Series: + +``` +> framework_tool --versions +Mainboard Hardware + Type: Laptop 13 (AMD Ryzen AI 300 Series) + Revision: MassProduction +UEFI BIOS + Version: 03.00 + Release Date: 03/10/2025 +EC Firmware + Build version: "lilac-3.0.0-1541dc6 2025-05-05 11:31:24 zoid@localhost" + RO Version: "lilac-3.0.0-1541dc6" + RW Version: "lilac-3.0.0-1541dc6" + Current image: RO +PD Controllers + Right (01) + Main: 0.0.0E (Active) + Backup: 0.0.0E + Left (23) + Main: 0.0.0E (Active) + Backup: 0.0.0E +[...] +``` + ### Camera (Framework 12, Framework 13, Framework 16) Example on Framework 12: @@ -128,7 +155,7 @@ Positions: ## Check temperatures and fan speed ``` -> sudo ./target/debug/framework_tool --thermal +> sudo framework_tool --thermal F75303_Local: 43 C F75303_CPU: 44 C F75303_DDR: 39 C @@ -136,13 +163,25 @@ Positions: Fan Speed: 0 RPM ``` -## Check sensors (ALS and G-Sensor) +## Check sensors + +### Ambient Light (Framework 13, Framework 16) ``` -> sudo ./target/debug/framework_tool --sensors +> sudo framework_tool --sensors ALS: 76 Lux ``` +### Accelerometer (Framework 12) +``` +> sudo framework_tool --sensors +ALS: 0 Lux +Accelerometers: + Lid Angle: 122 Deg + Sensor 1: X=+0.00G Y=+0.84G, Z=+0.52G + Sensor 2: X=-0.03G Y=+0.00G, Z=+1.01G +``` + ## Set custom fan duty/RPM ``` @@ -186,6 +225,7 @@ Expansion Bay Door closed: true Board: DualInterposer Serial Number: FRAXXXXXXXXXXXXXXX +``` ## Check charger and battery status (Framework 12/13/16) @@ -291,8 +331,47 @@ Battery Status Battery charging # Set charge rate/current limit only if battery is >80% charged -> sudo framework_tool --charge-rate-limit 80 0.8 -> sudo framework_tool --charge-current-limit 80 2000 +> sudo framework_tool --charge-rate-limit 0.8 80 +> sudo framework_tool --charge-current-limit 2000 80 +``` + +## EC Console + +``` +# Get recent EC console logs and watch for more +> framework_tool.exe --console follow +[53694.741000 Battery 62% (Display 61.1 %) / 3h:18 to empty] +[53715.010000 Battery 62% (Display 61.0 %) / 3h:21 to empty] +[53734.281200 Battery 62% (Display 60.9 %) / 3h:18 to empty] +[53738.037200 Battery 61% (Display 60.9 %) / 3h:6 to empty] +[53752.301500 Battery 61% (Display 60.8 %) / 3h:15 to empty] +``` + +## Keyboard backlight + +``` +# Check current keyboard backlight brightness +> framework_tool.exe --kblight +Keyboard backlight: 5% + +# Set keyboard backlight brightness +# Off +> framework_tool.exe --kblight 0 +# 20% +> framework_tool.exe --kblight 20 +``` + +## RGB LED (Framework Desktop) + +``` +# To set three LEDs to red, green, blue +sudo framework_tool --rgbkbd 0 0xFF0000 0x00FF00 0x0000FF + +# To clear 8 LEDs +sudo framework_tool --rgbkbd 0 0 0 0 0 0 0 0 0 + +# Just turn the 3rd LED red +sudo framework_tool --rgbkbd 2 0xFF0000 ``` ## Stylus (Framework 12) diff --git a/README.md b/README.md index 0e89f34b..7c41c4ca 100644 --- a/README.md +++ b/README.md @@ -130,23 +130,10 @@ Building on Windows or in general with fewer features: ```ps1 # Build the library and tool -cargo build --no-default-features --features "windows" +cargo build # Running the tool -cargo run --no-default-features --features "windows" -``` - -Cross compile from Linux to FreeBSD: - -```sh -# One time, install cross tool -cargo install cross - -# Make sure docker is started as well -sudo systemctl start docker - -# Build -cross build --target=x86_64-unknown-freebsd --no-default-features --features unix +cargo run ``` ## Running @@ -202,8 +189,6 @@ Options: Specify I2C addresses of the PD chips (Advanced) --pd-ports Specify I2C ports of the PD chips (Advanced) - --has-mec - Specify the type of EC chip (MEC/MCHP or other) [possible values: true, false] -t, --test Run self-test to check if interaction with EC is possible -h, --help Print help information ``` @@ -377,8 +362,8 @@ Keyboard backlight: 0% sudo pkg install hidapi # Build the library and tool -cargo build --no-default-features --features freebsd +cargo build # Running the tool -cargo run --no-default-features --features freebsd +cargo run ``` diff --git a/completions/bash/framework_tool b/completions/bash/framework_tool index f96fcc23..7031b8d5 100755 --- a/completions/bash/framework_tool +++ b/completions/bash/framework_tool @@ -50,7 +50,6 @@ _framework_tool() { "--driver" "--pd-addrs" "--pd-ports" - "--has-mec" "-t" "--test" "-h" "--help" ) @@ -59,7 +58,6 @@ _framework_tool() { local inputdeck_modes=("auto" "off" "on") local console_modes=("recent" "follow") local drivers=("portio" "cros-ec" "windows") - local has_mec_options=("true" "false") local brightness_options=("high" "medium" "low" "ultra-low") local current_word prev_word @@ -77,8 +75,6 @@ _framework_tool() { COMPREPLY=( $(compgen -W "${console_modes[*]}" -- "$current_word") ) elif [[ $prev_word == "--driver" ]]; then COMPREPLY=( $(compgen -W "${drivers[*]}" -- "$current_word") ) - elif [[ $prev_word == "--has-mec" ]]; then - COMPREPLY=( $(compgen -W "${has_mec_options[*]}" -- "$current_word") ) elif [[ $prev_word == "--fp-brightness" ]]; then COMPREPLY=( $(compgen -W "${brightness_options[*]}" -- "$current_word") ) fi diff --git a/completions/zsh/_framework_tool b/completions/zsh/_framework_tool index ad6aa668..2d473c8f 100644 --- a/completions/zsh/_framework_tool +++ b/completions/zsh/_framework_tool @@ -48,7 +48,6 @@ options=( '--driver[Select which driver is used]:driver:(portio cros-ec windows)' '--pd-addrs[Specify I2C addresses of the PD chips (Advanced)]:pd_addrs' '--pd-ports[Specify I2C ports of the PD chips (Advanced)]:pd_ports' - '--has-mec[Specify the type of EC chip (MEC/MCHP or other)]:has_mec:(true false)' '-t[Run self-test to check if interaction with EC is possible]' '-h[Print help]' ) diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index b9a7cc91..f0477678 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_lib" -version = "0.4.0" +version = "0.4.1" edition = "2021" # Minimum Supported Rust Version # Ubuntu 24.04 LTS ships 1.75 @@ -8,38 +8,10 @@ rust-version = "1.74" build = "build.rs" [features] -default = ["linux"] -# Linux/FreeBSD -unix = ["std", "raw_pio", "smbios", "dep:nix", "dep:libc"] -linux = ["unix", "linux_pio", "cros_ec_driver", "hidapi", "rusb"] -freebsd = ["unix", "freebsd_pio", "hidapi", "rusb"] -# hidapi and rusb don't seem to build in the cross container at the moment -cross_freebsd = ["unix", "freebsd_pio"] -# Windows does not have the cros_ec driver nor raw port I/O access to userspace -windows = ["std", "smbios", "dep:windows", "win_driver", "raw_pio", "hidapi", "rusb", "dep:wmi"] -smbios = ["dep:smbios-lib"] -std = ["dep:clap", "dep:clap-num", "dep:clap-verbosity-flag", "dep:env_logger", "smbios-lib?/std"] +default = ["hidapi", "rusb"] rusb = ["dep:rusb"] hidapi = ["dep:hidapi"] -uefi = [ - "dep:plain", "raw_pio", "smbios", "lazy_static/spin_no_std", "dep:uefi", "dep:uefi-services", - # Otherwise I get: `LLVM ERROR: Do not know how to split the result of this operator!` - # Seems to be a Ruset/LLVM bug when SSE is enabled. - # See: https://github.com/rust-lang/rust/issues/61721 - "sha2/force-soft" -] - -# EC communication via Port I/O on FreeBSD -freebsd_pio = ["redox_hwio/std"] -# EC communication via Port I/O on Linux -linux_pio = ["dep:libc", "redox_hwio/std"] -# EC communication via raw Port I/O (e.g. UEFI or other ring 0 code) -raw_pio = [] -# EC communication via cros_ec driver on Linux -cros_ec_driver = [] - -# Chromium EC driver by DHowett -win_driver = [] +uefi = [ "lazy_static/spin_no_std" ] [build-dependencies] built = { version = "0.5", features = ["chrono", "git2"] } @@ -48,39 +20,42 @@ built = { version = "0.5", features = ["chrono", "git2"] } lazy_static = "1.4.0" sha2 = { version = "0.10.8", default-features = false, features = [ "force-soft" ] } regex = { version = "1.11.1", default-features = false } -redox_hwio = { git = "https://github.com/FrameworkComputer/rust-hwio", branch = "freebsd", default-features = false } -libc = { version = "0.2.155", optional = true } -clap = { version = "4.5", features = ["derive", "cargo"], optional = true } -clap-num = { version = "1.2.0", optional = true } -clap-verbosity-flag = { version = "2.2.1", optional = true } -nix = { version = "0.29.0", features = ["ioctl", "user"], optional = true } num = { version = "0.4", default-features = false } num-derive = { version = "0.4", default-features = false } num-traits = { version = "0.2", default-features = false } -env_logger = { version = "0.11", optional = true } log = { version = "0.4", default-features = true } -uefi = { version = "0.20", features = ["alloc"], optional = true } -uefi-services = { version = "0.17", optional = true } -plain = { version = "0.2.3", optional = true } -spin = { version = "0.9.8", optional = false } -hidapi = { version = "2.6.3", optional = true, features = [ "windows-native" ] } -rusb = { version = "0.9.4", optional = true } +spin = { version = "0.9.8" } no-std-compat = { version = "0.4.1", features = [ "alloc" ] } guid_macros = { path = "../guid_macros" } -wmi = { version = "0.15.0", optional = true } +hidapi = { version = "2.6.3", features = [ "windows-native" ], optional = true } +rusb = { version = "0.9.4", optional = true } + +[target.'cfg(target_os = "uefi")'.dependencies] +uefi = { version = "0.20", features = ["alloc"] } +uefi-services = "0.17" +plain = "0.2.3" +redox_hwio = { git = "https://github.com/FrameworkComputer/rust-hwio", branch = "freebsd", default-features = false } +smbios-lib = { git = "https://github.com/FrameworkComputer/smbios-lib.git", branch = "no-std", default-features = false } + +[target.'cfg(windows)'.dependencies] +wmi = "0.15.0" +smbios-lib = { git = "https://github.com/FrameworkComputer/smbios-lib.git", branch = "no-std" } +env_logger = "0.11" +clap = { version = "4.5", features = ["derive", "cargo"] } +clap-num = { version = "1.2.0" } +clap-verbosity-flag = { version = "2.2.1" } -[dependencies.smbios-lib] -git = "https://github.com/FrameworkComputer/smbios-lib.git" -branch = "no-std" -optional = true -default-features = false -# Local development -#path = "../../smbios-lib" -# After my changes are upstreamed -#version = "0.9.0" +[target.'cfg(unix)'.dependencies] +libc = "0.2.155" +nix = { version = "0.29.0", features = ["ioctl", "user"] } +redox_hwio = { git = "https://github.com/FrameworkComputer/rust-hwio", branch = "freebsd" } +smbios-lib = { git = "https://github.com/FrameworkComputer/smbios-lib.git", branch = "no-std" } +env_logger = "0.11" +clap = { version = "4.5", features = ["derive", "cargo"] } +clap-num = { version = "1.2.0" } +clap-verbosity-flag = { version = "2.2.1" } -[dependencies.windows] -optional = true +[target.'cfg(windows)'.dependencies.windows] version = "0.59.0" features = [ "Win32_Foundation", diff --git a/framework_lib/src/capsule.rs b/framework_lib/src/capsule.rs index 9e6a35bc..971db631 100644 --- a/framework_lib/src/capsule.rs +++ b/framework_lib/src/capsule.rs @@ -11,9 +11,9 @@ use std::prelude::v1::*; use core::prelude::rust_2021::derive; -#[cfg(all(not(feature = "uefi"), feature = "std"))] +#[cfg(not(feature = "uefi"))] use std::fs::File; -#[cfg(all(not(feature = "uefi"), feature = "std"))] +#[cfg(not(feature = "uefi"))] use std::io::prelude::*; #[cfg(not(feature = "uefi"))] @@ -180,7 +180,7 @@ pub fn dump_winux_image(data: &[u8], header: &DisplayCapsule, filename: &str) { let image = &data[header_len..image_size]; - #[cfg(all(not(feature = "uefi"), feature = "std"))] + #[cfg(not(feature = "uefi"))] { let mut file = File::create(filename).unwrap(); file.write_all(image).unwrap(); diff --git a/framework_lib/src/ccgx/device.rs b/framework_lib/src/ccgx/device.rs index aa1af5d0..532ad10e 100644 --- a/framework_lib/src/ccgx/device.rs +++ b/framework_lib/src/ccgx/device.rs @@ -3,23 +3,17 @@ //! The current implementation talks to them by tunneling I2C through EC host commands. use alloc::format; -use alloc::string::ToString; -use alloc::vec; use alloc::vec::Vec; #[cfg(feature = "uefi")] use core::prelude::rust_2021::derive; use crate::ccgx::{AppVersion, BaseVersion, ControllerVersion}; -use crate::chromium_ec::command::EcCommands; -use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResult}; -use crate::util::{self, assert_win_len, Config, Platform}; -use std::mem::size_of; +use crate::chromium_ec::i2c_passthrough::*; +use crate::chromium_ec::{CrosEc, EcError, EcResult}; +use crate::util::{assert_win_len, Config, Platform}; use super::*; -/// Maximum transfer size for one I2C transaction supported by the chip -const MAX_I2C_CHUNK: usize = 128; - enum ControlRegisters { DeviceMode = 0, SiliconId = 2, // Two bytes long, First LSB, then MSB @@ -36,13 +30,13 @@ pub enum PdPort { impl PdPort { /// SMBUS/I2C Address - fn i2c_address(&self) -> u16 { + fn i2c_address(&self) -> EcResult { let config = Config::get(); let platform = &(*config).as_ref().unwrap().platform; - match (platform, self) { - (Platform::GenericFramework((left, _), _, _), PdPort::Left01) => *left, - (Platform::GenericFramework((_, right), _, _), PdPort::Right23) => *right, + Ok(match (platform, self) { + (Platform::GenericFramework((left, _), _), PdPort::Left01) => *left, + (Platform::GenericFramework((_, right), _), PdPort::Right23) => *right, // Framework AMD Platforms (CCG8) ( Platform::Framework13Amd7080 @@ -58,10 +52,13 @@ impl PdPort { ) => 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, - } + }) } /// I2C port on the EC @@ -70,8 +67,8 @@ impl PdPort { let platform = &(*config).as_ref().unwrap().platform; Ok(match (platform, self) { - (Platform::GenericFramework(_, (left, _), _), PdPort::Left01) => *left, - (Platform::GenericFramework(_, (_, right), _), PdPort::Right23) => *right, + (Platform::GenericFramework(_, (left, _)), PdPort::Left01) => *left, + (Platform::GenericFramework(_, (_, right)), PdPort::Right23) => *right, (Platform::IntelGen11, _) => 6, (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Left01) => 6, (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Right23) => 7, @@ -93,10 +90,9 @@ impl PdPort { ) => 2, // TODO: It only has a single PD controller (Platform::FrameworkDesktopAmdAiMax300, _) => 1, - // (_, _) => Err(EcError::DeviceError(format!( - // "Unsupported platform: {:?} {:?}", - // platform, self - // )))?, + (Platform::UnknownSystem, _) => { + Err(EcError::DeviceError("Unsupported platform".to_string()))? + } }) } } @@ -106,58 +102,6 @@ pub struct PdController { ec: CrosEc, } -fn passthrough_offset(dev_index: u16) -> u16 { - dev_index * 0x4000 -} - -#[repr(C, packed)] -struct EcParamsI2cPassthruMsg { - /// Slave address and flags - addr_and_flags: u16, - transfer_len: u16, -} - -#[repr(C, packed)] -struct EcParamsI2cPassthru { - port: u8, - /// How many messages - messages: u8, - msg: [EcParamsI2cPassthruMsg; 0], -} - -#[repr(C, packed)] -struct _EcI2cPassthruResponse { - i2c_status: u8, - /// How many messages - messages: u8, - data: [u8; 0], -} - -struct EcI2cPassthruResponse { - i2c_status: u8, // TODO: Can probably use enum - data: Vec, -} - -impl EcI2cPassthruResponse { - fn is_successful(&self) -> EcResult<()> { - if self.i2c_status & 1 > 0 { - return Err(EcError::DeviceError( - "I2C Transfer not acknowledged".to_string(), - )); - } - if self.i2c_status & (1 << 1) > 0 { - return Err(EcError::DeviceError("I2C Transfer timeout".to_string())); - } - // I'm not aware of any other errors, but there might be. - // But I don't think multiple errors can be indicated at the same time - assert_eq!(self.i2c_status, 0); - Ok(()) - } -} - -/// Indicate that it's a read, not a write -const I2C_READ_FLAG: u16 = 1 << 15; - #[derive(Debug, PartialEq)] pub enum FwMode { BootLoader = 0, @@ -194,63 +138,20 @@ impl PdController { pub fn new(port: PdPort, ec: CrosEc) -> Self { PdController { port, ec } } - /// Wrapped with support for dev id - /// TODO: Should move into chromium_ec module - /// TODO: Must not call CrosEc::new() otherwise the driver isn't configurable! - fn send_ec_command(&self, code: u16, dev_index: u16, data: &[u8]) -> EcResult> { - let command_id = code + passthrough_offset(dev_index); - self.ec.send_command(command_id, 0, data) - } fn i2c_read(&self, addr: u16, len: u16) -> EcResult { trace!( "I2C passthrough from I2C Port {} to I2C Addr {}", - self.port.i2c_port().unwrap(), - self.port.i2c_address() + self.port.i2c_port()?, + self.port.i2c_address()? ); - trace!("i2c_read(addr: {}, len: {})", addr, len); - if usize::from(len) > MAX_I2C_CHUNK { - return EcResult::Err(EcError::DeviceError(format!( - "i2c_read too long. Must be <128, is: {}", - len - ))); - } - let addr_bytes = u16::to_le_bytes(addr); - let messages = vec![ - EcParamsI2cPassthruMsg { - addr_and_flags: self.port.i2c_address(), - transfer_len: addr_bytes.len() as u16, - }, - EcParamsI2cPassthruMsg { - addr_and_flags: self.port.i2c_address() + I2C_READ_FLAG, - transfer_len: len, // How much to read - }, - ]; - let msgs_len = size_of::() * messages.len(); - let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) }; - - let params = EcParamsI2cPassthru { - port: self.port.i2c_port()?, - messages: messages.len() as u8, - msg: [], // Messages are copied right after this struct - }; - let params_len = size_of::(); - let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(¶ms) }; - - let mut buffer: Vec = vec![0; params_len + msgs_len + addr_bytes.len()]; - buffer[0..params_len].copy_from_slice(params_buffer); - buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer); - buffer[params_len + msgs_len..].copy_from_slice(&addr_bytes); - - let data = self.send_ec_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?; - let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) }; - let res_data = &data[size_of::<_EcI2cPassthruResponse>()..]; - // TODO: Seems to be either one, non-deterministically - debug_assert!(res.messages as usize == messages.len() || res.messages == 0); - Ok(EcI2cPassthruResponse { - i2c_status: res.i2c_status, - data: res_data.to_vec(), - }) + i2c_read( + &self.ec, + self.port.i2c_port()?, + self.port.i2c_address()?, + addr, + len, + ) } fn ccgx_read(&self, reg: ControlRegisters, len: u16) -> EcResult> { diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index 416fd3e0..54b8db52 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -173,9 +173,9 @@ pub trait EcRequestRaw { { let response = self.send_command_vec_extra(ec, extra_data)?; // TODO: The Windows driver seems to return 20 more bytes than expected - #[cfg(feature = "win_driver")] + #[cfg(windows)] let expected = response.len() != std::mem::size_of::() + 20; - #[cfg(not(feature = "win_driver"))] + #[cfg(not(windows))] let expected = response.len() != std::mem::size_of::(); if expected { return Err(EcError::DeviceError(format!( diff --git a/framework_lib/src/chromium_ec/cros_ec.rs b/framework_lib/src/chromium_ec/cros_ec.rs index a3b4928a..372c45ff 100644 --- a/framework_lib/src/chromium_ec/cros_ec.rs +++ b/framework_lib/src/chromium_ec/cros_ec.rs @@ -55,7 +55,7 @@ struct CrosEcCommandV2 { data: [u8; IN_SIZE], } -const DEV_PATH: &str = "/dev/cros_ec"; +pub const DEV_PATH: &str = "/dev/cros_ec"; lazy_static! { static ref CROS_EC_FD: Arc>> = Arc::new(Mutex::new(None)); diff --git a/framework_lib/src/chromium_ec/i2c_passthrough.rs b/framework_lib/src/chromium_ec/i2c_passthrough.rs new file mode 100644 index 00000000..dfebaafc --- /dev/null +++ b/framework_lib/src/chromium_ec/i2c_passthrough.rs @@ -0,0 +1,164 @@ +use crate::chromium_ec::command::EcCommands; +use crate::chromium_ec::{CrosEc, CrosEcDriver, EcError, EcResult}; +use crate::util; +use alloc::format; +use alloc::string::ToString; +use alloc::vec; +use alloc::vec::Vec; +use std::mem::size_of; + +/// Maximum transfer size for one I2C transaction supported by the chip +pub const MAX_I2C_CHUNK: usize = 128; + +#[repr(C, packed)] +pub struct EcParamsI2cPassthruMsg { + /// Slave address and flags + addr_and_flags: u16, + transfer_len: u16, +} + +#[repr(C, packed)] +pub struct EcParamsI2cPassthru { + port: u8, + /// How many messages + messages: u8, + msg: [EcParamsI2cPassthruMsg; 0], +} + +#[repr(C, packed)] +struct _EcI2cPassthruResponse { + i2c_status: u8, + /// How many messages + messages: u8, + data: [u8; 0], +} + +#[derive(Debug)] +pub struct EcI2cPassthruResponse { + pub i2c_status: u8, // TODO: Can probably use enum + pub data: Vec, +} + +impl EcI2cPassthruResponse { + pub fn is_successful(&self) -> EcResult<()> { + if self.i2c_status & 1 > 0 { + return Err(EcError::DeviceError( + "I2C Transfer not acknowledged".to_string(), + )); + } + if self.i2c_status & (1 << 1) > 0 { + return Err(EcError::DeviceError("I2C Transfer timeout".to_string())); + } + // I'm not aware of any other errors, but there might be. + // But I don't think multiple errors can be indicated at the same time + assert_eq!(self.i2c_status, 0); + Ok(()) + } +} + +/// Indicate that it's a read, not a write +const I2C_READ_FLAG: u16 = 1 << 15; + +pub fn i2c_read( + ec: &CrosEc, + i2c_port: u8, + i2c_addr: u16, + addr: u16, + len: u16, +) -> EcResult { + trace!( + "i2c_read(i2c_port: 0x{:X}, i2c_addr: 0x{:X}, addr: 0x{:X}, len: 0x{:X})", + i2c_port, + i2c_addr, + addr, + len + ); + if usize::from(len) > MAX_I2C_CHUNK { + return EcResult::Err(EcError::DeviceError(format!( + "i2c_read too long. Must be <128, is: {}", + len + ))); + } + let addr_bytes = u16::to_le_bytes(addr); + let messages = vec![ + EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr, + transfer_len: addr_bytes.len() as u16, + }, + EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr + I2C_READ_FLAG, + transfer_len: len, // How much to read + }, + ]; + let msgs_len = size_of::() * messages.len(); + let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) }; + + let params = EcParamsI2cPassthru { + port: i2c_port, + messages: messages.len() as u8, + msg: [], // Messages are copied right after this struct + }; + let params_len = size_of::(); + let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(¶ms) }; + + let mut buffer: Vec = vec![0; params_len + msgs_len + addr_bytes.len()]; + buffer[0..params_len].copy_from_slice(params_buffer); + buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer); + buffer[params_len + msgs_len..].copy_from_slice(&addr_bytes); + + let data = ec.send_command(EcCommands::I2cPassthrough as u16, 0, &buffer)?; + let res: _EcI2cPassthruResponse = unsafe { std::ptr::read(data.as_ptr() as *const _) }; + let res_data = &data[size_of::<_EcI2cPassthruResponse>()..]; + // TODO: Seems to be either one, non-deterministically + debug_assert!(res.messages as usize == messages.len() || res.messages == 0); + Ok(EcI2cPassthruResponse { + i2c_status: res.i2c_status, + data: res_data.to_vec(), + }) +} + +pub fn i2c_write( + ec: &CrosEc, + i2c_port: u8, + i2c_addr: u16, + addr: u16, + data: &[u8], +) -> EcResult { + trace!( + " i2c_write(addr: {}, len: {}, data: {:?})", + addr, + data.len(), + data + ); + let addr_bytes = [addr as u8, (addr >> 8) as u8]; + let messages = vec![EcParamsI2cPassthruMsg { + addr_and_flags: i2c_addr, + transfer_len: (addr_bytes.len() + data.len()) as u16, + }]; + let msgs_len = size_of::() * messages.len(); + let msgs_buffer: &[u8] = unsafe { util::any_vec_as_u8_slice(&messages) }; + + let params = EcParamsI2cPassthru { + port: i2c_port, + messages: messages.len() as u8, + msg: [], // Messages are copied right after this struct + }; + let params_len = size_of::(); + let params_buffer: &[u8] = unsafe { util::any_as_u8_slice(¶ms) }; + + let mut buffer: Vec = vec![0; params_len + msgs_len + addr_bytes.len() + data.len()]; + buffer[0..params_len].copy_from_slice(params_buffer); + buffer[params_len..params_len + msgs_len].copy_from_slice(msgs_buffer); + buffer[params_len + msgs_len..params_len + msgs_len + addr_bytes.len()] + .copy_from_slice(&addr_bytes); + buffer[params_len + msgs_len + addr_bytes.len()..].copy_from_slice(data); + + 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 + debug_assert_eq!(res.messages as usize, messages.len()); + Ok(EcI2cPassthruResponse { + i2c_status: res.i2c_status, + data: vec![], // Writing doesn't return any data + }) +} diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index e0a3094a..afdaef0c 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -21,12 +21,17 @@ use num_derive::FromPrimitive; pub mod command; pub mod commands; -#[cfg(feature = "cros_ec_driver")] +#[cfg(target_os = "linux")] mod cros_ec; +pub mod i2c_passthrough; pub mod input_deck; +#[cfg(not(windows))] mod portio; +#[cfg(not(windows))] mod portio_mec; -#[cfg(feature = "win_driver")] +#[allow(dead_code)] +mod protocol; +#[cfg(windows)] mod windows; use alloc::format; @@ -201,24 +206,6 @@ const BOARD_VERSION_NPC_DB: [i32; BOARD_VERSION_COUNT] = [ 100, 311, 521, 721, 931, 1131, 1341, 1551, 1751, 1961, 2171, 2370, 2580, 2780, 2990, 3200, ]; -pub fn has_mec() -> bool { - let platform = smbios::get_platform().unwrap(); - if let Platform::GenericFramework(_, _, has_mec) = platform { - return has_mec; - } - - // TODO: Should turn this around - !matches!( - smbios::get_platform().unwrap(), - Platform::Framework13Amd7080 - | Platform::Framework16Amd7080 - | Platform::IntelCoreUltra1 - | Platform::Framework13AmdAi300 - | Platform::Framework12IntelGen13 - | Platform::FrameworkDesktopAmdAiMax300 - ) -} - pub trait CrosEcDriver { fn read_memory(&self, offset: u16, length: u16) -> Option>; fn send_command(&self, command: u16, command_version: u8, data: &[u8]) -> EcResult>; @@ -239,14 +226,20 @@ impl Default for CrosEc { /// /// Depending on the availability we choose the first one as default fn available_drivers() -> Vec { - vec![ - #[cfg(feature = "win_driver")] - CrosEcDriverType::Windows, - #[cfg(feature = "cros_ec_driver")] - CrosEcDriverType::CrosEc, - #[cfg(not(feature = "windows"))] - CrosEcDriverType::Portio, - ] + let mut drivers = vec![]; + + #[cfg(windows)] + drivers.push(CrosEcDriverType::Windows); + + #[cfg(target_os = "linux")] + if std::path::Path::new(cros_ec::DEV_PATH).exists() { + drivers.push(CrosEcDriverType::CrosEc); + } + + #[cfg(not(windows))] + drivers.push(CrosEcDriverType::Portio); + + drivers } impl CrosEc { @@ -262,6 +255,7 @@ impl CrosEc { return None; } debug!("Chromium EC Driver: {:?}", driver); + Some(CrosEc { driver }) } @@ -520,7 +514,7 @@ impl CrosEc { let is_present = |p| if p { "Present" } else { "Missing" }; println!("Input Deck"); - println!(" Chassis Closed: {}", intrusion.currently_open); + println!(" Chassis Closed: {}", !intrusion.currently_open); println!(" Power Button Board: {}", is_present(pwrbtn.is_some())); println!(" Audio Daughterboard: {}", is_present(audio.is_some())); println!(" Touchpad: {}", is_present(tp.is_some())); @@ -548,7 +542,7 @@ impl CrosEc { let is_present = |p| if p { "Present" } else { "Missing" }; println!("Input Deck"); - println!(" Chassis Closed: {}", intrusion.currently_open); + println!(" Chassis Closed: {}", !intrusion.currently_open); println!(" Audio Daughterboard: {}", is_present(audio.is_some())); println!(" Touchpad: {}", is_present(tp.is_some())); @@ -558,7 +552,7 @@ impl CrosEc { pub fn print_fw16_inputdeck_status(&self) -> EcResult<()> { let intrusion = self.get_intrusion_status()?; let status = self.get_input_deck_status()?; - println!("Chassis Closed: {}", intrusion.currently_open); + println!("Chassis Closed: {}", !intrusion.currently_open); println!("Input Deck State: {:?}", status.state); println!("Touchpad present: {}", status.touchpad_present); println!("Positions:"); @@ -946,30 +940,25 @@ impl CrosEc { // Everything before is probably a header. // TODO: I don't think there are magic bytes on zephyr firmware // - if has_mec() { - println!(" Check MCHP magic byte at start of firmware code."); - // Make sure we can read at an offset and with arbitrary length - let data = self.read_ec_flash(FLASH_PROGRAM_OFFSET, 16).unwrap(); - debug!("Expecting beginning with 50 48 43 4D ('PHCM' in ASCII)"); - debug!("{:02X?}", data); - println!( - " {:02X?} ASCII:{:?}", - &data[..4], - core::str::from_utf8(&data[..4]) - ); - - if data[0..4] != [0x50, 0x48, 0x43, 0x4D] { - println!(" INVALID: {:02X?}", &data[0..3]); - res = Err(EcError::DeviceError(format!( - "INVALID: {:02X?}", - &data[0..3] - ))); - } + debug!(" Check MCHP magic bytes at start of firmware code."); + // Make sure we can read at an offset and with arbitrary length + let data = self.read_ec_flash(FLASH_PROGRAM_OFFSET, 16).unwrap(); + debug!("Expecting beginning with 50 48 43 4D ('PHCM' in ASCII)"); + debug!("{:02X?}", data); + debug!( + " {:02X?} ASCII:{:?}", + &data[..4], + core::str::from_utf8(&data[..4]) + ); + + let has_mec = data[0..4] == [0x50, 0x48, 0x43, 0x4D]; + if has_mec { + debug!(" Found MCHP magic bytes at start of firmware code."); } // ===== Test 4 ===== println!(" Read flash flags"); - let data = if has_mec() { + let data = if has_mec { self.read_ec_flash(MEC_FLASH_FLAGS, 0x80).unwrap() } else { self.read_ec_flash(NPC_FLASH_FLAGS, 0x80).unwrap() @@ -1055,21 +1044,21 @@ impl CrosEc { loop { match cmd.send_command_vec(self) { Ok(data) => { - // EC Buffer is empty. That means we've read everything from the snapshot - // The windows crosecbus driver returns all NULL instead of empty response - if data.is_empty() || data.iter().all(|x| *x == 0) { + // EC Buffer is empty. That means we've read everything from the snapshot. + // The windows crosecbus driver returns all NULL with a leading 0x01 instead of + // an empty response. + if data.is_empty() || data.iter().all(|x| *x == 0 || *x == 1) { debug!("Empty EC response. Stopping console read"); - // Don't read too fast, wait a second before reading more - os_specific::sleep(1_000_000); + // Don't read too fast, wait 100ms before reading more + os_specific::sleep(100_000); EcRequestConsoleSnapshot {}.send_command(self)?; cmd.subcmd = ConsoleReadSubCommand::ConsoleReadRecent as u8; continue; } let utf8 = std::str::from_utf8(&data).unwrap(); - let ascii = utf8 - .replace(|c: char| !c.is_ascii(), "") - .replace(['\0'], ""); + let full_ascii = utf8.replace(|c: char| !c.is_ascii(), ""); + let ascii = full_ascii.replace(['\0'], ""); print!("{}", ascii); } @@ -1335,10 +1324,11 @@ impl CrosEcDriver for CrosEc { // TODO: Change this function to return EcResult instead and print the error only in UI code print_err(match self.driver { + #[cfg(not(windows))] CrosEcDriverType::Portio => portio::read_memory(offset, length), - #[cfg(feature = "win_driver")] + #[cfg(windows)] CrosEcDriverType::Windows => windows::read_memory(offset, length), - #[cfg(feature = "cros_ec_driver")] + #[cfg(target_os = "linux")] CrosEcDriverType::CrosEc => cros_ec::read_memory(offset, length), _ => Err(EcError::DeviceError("No EC driver available".to_string())), }) @@ -1356,10 +1346,11 @@ impl CrosEcDriver for CrosEc { } match self.driver { + #[cfg(not(windows))] CrosEcDriverType::Portio => portio::send_command(command, command_version, data), - #[cfg(feature = "win_driver")] + #[cfg(windows)] CrosEcDriverType::Windows => windows::send_command(command, command_version, data), - #[cfg(feature = "cros_ec_driver")] + #[cfg(target_os = "linux")] CrosEcDriverType::CrosEc => cros_ec::send_command(command, command_version, data), _ => Err(EcError::DeviceError("No EC driver available".to_string())), } diff --git a/framework_lib/src/chromium_ec/portio.rs b/framework_lib/src/chromium_ec/portio.rs index b453c41c..152d5f40 100644 --- a/framework_lib/src/chromium_ec/portio.rs +++ b/framework_lib/src/chromium_ec/portio.rs @@ -4,131 +4,21 @@ use alloc::string::ToString; use alloc::vec; use alloc::vec::Vec; use core::convert::TryInto; -#[cfg(any(feature = "linux_pio", feature = "freebsd_pio", feature = "raw_pio"))] +#[cfg(not(windows))] use hwio::{Io, Pio}; -#[cfg(all(feature = "linux_pio", target_os = "linux"))] +#[cfg(target_os = "linux")] use libc::ioperm; use log::Level; -#[cfg(feature = "linux_pio")] +#[cfg(target_os = "linux")] use nix::unistd::Uid; use num::FromPrimitive; -#[cfg(feature = "linux_pio")] -use std::sync::{Arc, Mutex}; +use spin::Mutex; -use crate::chromium_ec::{has_mec, portio_mec}; +use crate::chromium_ec::protocol::*; +use crate::chromium_ec::{portio_mec, EC_MEMMAP_ID}; use crate::os_specific; use crate::util; -/* - * Value written to legacy command port / prefix byte to indicate protocol - * 3+ structs are being used. Usage is bus-dependent. - */ -const EC_COMMAND_PROTOCOL_3: u8 = 0xda; - -// LPC command status byte masks -/// EC has written data but host hasn't consumed it yet -const _EC_LPC_STATUS_TO_HOST: u8 = 0x01; -/// Host has written data/command but EC hasn't consumed it yet -const EC_LPC_STATUS_FROM_HOST: u8 = 0x02; -/// EC is still processing a command -const EC_LPC_STATUS_PROCESSING: u8 = 0x04; -/// Previous command wasn't data but command -const _EC_LPC_STATUS_LAST_CMD: u8 = 0x08; -/// EC is in burst mode -const _EC_LPC_STATUS_BURST_MODE: u8 = 0x10; -/// SCI event is pending (requesting SCI query) -const _EC_LPC_STATUS_SCI_PENDING: u8 = 0x20; -/// SMI event is pending (requesting SMI query) -const _EC_LPC_STATUS_SMI_PENDING: u8 = 0x40; -/// Reserved -const _EC_LPC_STATUS_RESERVED: u8 = 0x80; - -/// EC is busy -const EC_LPC_STATUS_BUSY_MASK: u8 = EC_LPC_STATUS_FROM_HOST | EC_LPC_STATUS_PROCESSING; - -// I/O addresses for ACPI commands -const _EC_LPC_ADDR_ACPI_DATA: u16 = 0x62; -const _EC_LPC_ADDR_ACPI_CMD: u16 = 0x66; - -// I/O addresses for host command -const EC_LPC_ADDR_HOST_DATA: u16 = 0x200; -const EC_LPC_ADDR_HOST_CMD: u16 = 0x204; - -// I/O addresses for host command args and params -// Protocol version 2 -const EC_LPC_ADDR_HOST_ARGS: u16 = 0x800; /* And 0x801, 0x802, 0x803 */ -const _EC_LPC_ADDR_HOST_PARAM: u16 = 0x804; /* For version 2 params; size is - * EC_PROTO2_MAX_PARAM_SIZE */ -// Protocol version 3 -const _EC_LPC_ADDR_HOST_PACKET: u16 = 0x800; /* Offset of version 3 packet */ -const EC_LPC_HOST_PACKET_SIZE: u16 = 0x100; /* Max size of version 3 packet */ - -const MEC_MEMMAP_OFFSET: u16 = 0x100; -const NPC_MEMMAP_OFFSET: u16 = 0xE00; - -// The actual block is 0x800-0x8ff, but some BIOSes think it's 0x880-0x8ff -// and they tell the kernel that so we have to think of it as two parts. -const _EC_HOST_CMD_REGION0: u16 = 0x800; -const _EC_HOST_CMD_REGION1: u16 = 0x8800; -const _EC_HOST_CMD_REGION_SIZE: u16 = 0x80; - -// EC command register bit functions -const _EC_LPC_CMDR_DATA: u16 = 1 << 0; // Data ready for host to read -const _EC_LPC_CMDR_PENDING: u16 = 1 << 1; // Write pending to EC -const _EC_LPC_CMDR_BUSY: u16 = 1 << 2; // EC is busy processing a command -const _EC_LPC_CMDR_CMD: u16 = 1 << 3; // Last host write was a command -const _EC_LPC_CMDR_ACPI_BRST: u16 = 1 << 4; // Burst mode (not used) -const _EC_LPC_CMDR_SCI: u16 = 1 << 5; // SCI event is pending -const _EC_LPC_CMDR_SMI: u16 = 1 << 6; // SMI event is pending - -const EC_HOST_REQUEST_VERSION: u8 = 3; - -/// Request header of version 3 -#[repr(C, packed)] -struct EcHostRequest { - /// Version of this request structure (must be 3) - pub struct_version: u8, - - /// Checksum of entire request (header and data) - /// Everything added together adds up to 0 (wrapping around u8 limit) - pub checksum: u8, - - /// Command number - pub command: u16, - - /// Command version, usually 0 - pub command_version: u8, - - /// Reserved byte in protocol v3. Must be 0 - pub reserved: u8, - - /// Data length. Data is immediately after the header - pub data_len: u16, -} - -const EC_HOST_RESPONSE_VERSION: u8 = 3; - -/// Response header of version 3 -#[repr(C, packed)] -struct EcHostResponse { - /// Version of this request structure (must be 3) - pub struct_version: u8, - - /// Checksum of entire request (header and data) - pub checksum: u8, - - /// Status code of response. See enum _EcStatus - pub result: u16, - - /// Data length. Data is immediately after the header - pub data_len: u16, - - /// Reserved byte in protocol v3. Must be 0 - pub reserved: u16, -} -#[allow(dead_code)] -pub const HEADER_LEN: usize = std::mem::size_of::(); - fn transfer_write(buffer: &[u8]) { if has_mec() { return portio_mec::transfer_write(buffer); @@ -172,65 +62,68 @@ fn transfer_read(port: u16, address: u16, size: u16) -> Vec { buffer } -#[cfg(feature = "linux_pio")] +#[derive(PartialEq)] +#[allow(dead_code)] enum Initialized { NotYet, + SucceededMec, Succeeded, Failed, } -#[cfg(feature = "linux_pio")] lazy_static! { - static ref INITIALIZED: Arc> = Arc::new(Mutex::new(Initialized::NotYet)); + static ref INITIALIZED: Mutex = Mutex::new(Initialized::NotYet); } -#[cfg(not(feature = "linux_pio"))] -fn init() -> bool { - // Nothing to do for bare-metal (UEFI) port I/O - true +fn has_mec() -> bool { + let init = INITIALIZED.lock(); + *init != Initialized::Succeeded } -// In Linux userspace has to first request access to ioports -// TODO: Close these again after we're done -#[cfg(feature = "linux_pio")] fn init() -> bool { - let mut init = INITIALIZED.lock().unwrap(); + let mut init = INITIALIZED.lock(); match *init { // Can directly give up, trying again won't help Initialized::Failed => return false, // Already initialized, no need to do anything. - Initialized::Succeeded => return true, + Initialized::Succeeded | Initialized::SucceededMec => return true, 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")] if !Uid::effective().is_root() { error!("Must be root to use port based I/O for EC communication."); *init = Initialized::Failed; return false; } - + #[cfg(target_os = "linux")] unsafe { - if has_mec() { - portio_mec::mec_init(); - } else { - // 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" - ); - return false; - } - - let res = ioperm(EC_LPC_ADDR_HOST_CMD as u64, 1, 1); - assert_eq!(res, 0); - let res = ioperm(EC_LPC_ADDR_HOST_DATA as u64, 1, 1); - assert_eq!(res, 0); - - let res = ioperm(NPC_MEMMAP_OFFSET as u64, super::EC_MEMMAP_SIZE as u64, 1); - assert_eq!(res, 0); + // 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"); + return false; } + + let res = ioperm(EC_LPC_ADDR_HOST_CMD as u64, 1, 1); + assert_eq!(res, 0); + let res = ioperm(EC_LPC_ADDR_HOST_DATA as u64, 1, 1); + assert_eq!(res, 0); + + let res = ioperm(NPC_MEMMAP_OFFSET as u64, super::EC_MEMMAP_SIZE as u64, 1); + assert_eq!(res, 0); } + *init = Initialized::Succeeded; true } diff --git a/framework_lib/src/chromium_ec/portio_mec.rs b/framework_lib/src/chromium_ec/portio_mec.rs index 974b8686..9d3664e2 100644 --- a/framework_lib/src/chromium_ec/portio_mec.rs +++ b/framework_lib/src/chromium_ec/portio_mec.rs @@ -5,11 +5,11 @@ use alloc::vec::Vec; use log::Level; use hwio::{Io, Pio}; -#[cfg(feature = "linux_pio")] +#[cfg(target_os = "linux")] use libc::ioperm; // I/O addresses for host command -#[cfg(feature = "linux_pio")] +#[cfg(target_os = "linux")] const EC_LPC_ADDR_HOST_DATA: u16 = 0x200; const MEC_EC_BYTE_ACCESS: u16 = 0x00; @@ -22,10 +22,12 @@ const _MEC_LPC_DATA_REGISTER1: u16 = 0x0805; const MEC_LPC_DATA_REGISTER2: u16 = 0x0806; const _MEC_LPC_DATA_REGISTER3: u16 = 0x0807; -#[cfg(feature = "linux_pio")] -pub unsafe fn mec_init() { - ioperm(EC_LPC_ADDR_HOST_DATA as u64, 8, 1); - ioperm(MEC_LPC_ADDRESS_REGISTER0 as u64, 10, 1); +pub fn init() { + #[cfg(target_os = "linux")] + unsafe { + ioperm(EC_LPC_ADDR_HOST_DATA as u64, 8, 1); + ioperm(MEC_LPC_ADDRESS_REGISTER0 as u64, 10, 1); + } } // TODO: Create a wrapper diff --git a/framework_lib/src/chromium_ec/protocol.rs b/framework_lib/src/chromium_ec/protocol.rs new file mode 100644 index 00000000..80152842 --- /dev/null +++ b/framework_lib/src/chromium_ec/protocol.rs @@ -0,0 +1,108 @@ +/* + * Value written to legacy command port / prefix byte to indicate protocol + * 3+ structs are being used. Usage is bus-dependent. + */ +pub const EC_COMMAND_PROTOCOL_3: u8 = 0xda; + +// LPC command status byte masks +/// EC has written data but host hasn't consumed it yet +const _EC_LPC_STATUS_TO_HOST: u8 = 0x01; +/// Host has written data/command but EC hasn't consumed it yet +pub const EC_LPC_STATUS_FROM_HOST: u8 = 0x02; +/// EC is still processing a command +pub const EC_LPC_STATUS_PROCESSING: u8 = 0x04; +/// Previous command wasn't data but command +const _EC_LPC_STATUS_LAST_CMD: u8 = 0x08; +/// EC is in burst mode +const _EC_LPC_STATUS_BURST_MODE: u8 = 0x10; +/// SCI event is pending (requesting SCI query) +const _EC_LPC_STATUS_SCI_PENDING: u8 = 0x20; +/// SMI event is pending (requesting SMI query) +const _EC_LPC_STATUS_SMI_PENDING: u8 = 0x40; +/// Reserved +const _EC_LPC_STATUS_RESERVED: u8 = 0x80; + +/// EC is busy +pub const EC_LPC_STATUS_BUSY_MASK: u8 = EC_LPC_STATUS_FROM_HOST | EC_LPC_STATUS_PROCESSING; + +// I/O addresses for ACPI commands +const _EC_LPC_ADDR_ACPI_DATA: u16 = 0x62; +const _EC_LPC_ADDR_ACPI_CMD: u16 = 0x66; + +// I/O addresses for host command +pub const EC_LPC_ADDR_HOST_DATA: u16 = 0x200; +pub const EC_LPC_ADDR_HOST_CMD: u16 = 0x204; + +// I/O addresses for host command args and params +// Protocol version 2 +pub const EC_LPC_ADDR_HOST_ARGS: u16 = 0x800; /* And 0x801, 0x802, 0x803 */ +const _EC_LPC_ADDR_HOST_PARAM: u16 = 0x804; /* For version 2 params; size is + * EC_PROTO2_MAX_PARAM_SIZE */ +// Protocol version 3 +const _EC_LPC_ADDR_HOST_PACKET: u16 = 0x800; /* Offset of version 3 packet */ +pub const EC_LPC_HOST_PACKET_SIZE: u16 = 0x100; /* Max size of version 3 packet */ + +pub const MEC_MEMMAP_OFFSET: u16 = 0x100; +pub const NPC_MEMMAP_OFFSET: u16 = 0xE00; + +// The actual block is 0x800-0x8ff, but some BIOSes think it's 0x880-0x8ff +// and they tell the kernel that so we have to think of it as two parts. +const _EC_HOST_CMD_REGION0: u16 = 0x800; +const _EC_HOST_CMD_REGION1: u16 = 0x8800; +const _EC_HOST_CMD_REGION_SIZE: u16 = 0x80; + +// EC command register bit functions +const _EC_LPC_CMDR_DATA: u16 = 1 << 0; // Data ready for host to read +const _EC_LPC_CMDR_PENDING: u16 = 1 << 1; // Write pending to EC +const _EC_LPC_CMDR_BUSY: u16 = 1 << 2; // EC is busy processing a command +const _EC_LPC_CMDR_CMD: u16 = 1 << 3; // Last host write was a command +const _EC_LPC_CMDR_ACPI_BRST: u16 = 1 << 4; // Burst mode (not used) +const _EC_LPC_CMDR_SCI: u16 = 1 << 5; // SCI event is pending +const _EC_LPC_CMDR_SMI: u16 = 1 << 6; // SMI event is pending + +pub const EC_HOST_REQUEST_VERSION: u8 = 3; + +/// Request header of version 3 +#[repr(C, packed)] +pub struct EcHostRequest { + /// Version of this request structure (must be 3) + pub struct_version: u8, + + /// Checksum of entire request (header and data) + /// Everything added together adds up to 0 (wrapping around u8 limit) + pub checksum: u8, + + /// Command number + pub command: u16, + + /// Command version, usually 0 + pub command_version: u8, + + /// Reserved byte in protocol v3. Must be 0 + pub reserved: u8, + + /// Data length. Data is immediately after the header + pub data_len: u16, +} + +pub const EC_HOST_RESPONSE_VERSION: u8 = 3; + +/// Response header of version 3 +#[repr(C, packed)] +pub struct EcHostResponse { + /// Version of this request structure (must be 3) + pub struct_version: u8, + + /// Checksum of entire request (header and data) + pub checksum: u8, + + /// Status code of response. See enum _EcStatus + pub result: u16, + + /// Data length. Data is immediately after the header + pub data_len: u16, + + /// Reserved byte in protocol v3. Must be 0 + pub reserved: u16, +} +pub const HEADER_LEN: usize = std::mem::size_of::(); diff --git a/framework_lib/src/chromium_ec/windows.rs b/framework_lib/src/chromium_ec/windows.rs index 6cd2db33..5b0cca68 100644 --- a/framework_lib/src/chromium_ec/windows.rs +++ b/framework_lib/src/chromium_ec/windows.rs @@ -11,7 +11,7 @@ use windows::{ }, }; -use crate::chromium_ec::portio::HEADER_LEN; +use crate::chromium_ec::protocol::HEADER_LEN; use crate::chromium_ec::EC_MEMMAP_SIZE; use crate::chromium_ec::{EcError, EcResponseStatus, EcResult}; diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 3c81c755..76784a5f 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -157,12 +157,12 @@ struct ClapCli { /// Get or set max charge current limit #[arg(long)] - #[clap(num_args = ..2)] + #[clap(num_args = ..=2)] charge_current_limit: Vec, /// Get or set max charge current limit #[arg(long)] - #[clap(num_args = ..2)] + #[clap(num_args = ..=2)] charge_rate_limit: Vec, /// Get GPIO value by name @@ -228,20 +228,15 @@ struct ClapCli { driver: Option, /// Specify I2C addresses of the PD chips (Advanced) - #[clap(number_of_values = 2, requires("pd_ports"), requires("has_mec"))] + #[clap(number_of_values = 2, requires("pd_ports"))] #[arg(long)] pd_addrs: Vec, /// Specify I2C ports of the PD chips (Advanced) - #[clap(number_of_values = 2, requires("pd_addrs"), requires("has_mec"))] + #[clap(number_of_values = 2, requires("pd_addrs"))] #[arg(long)] pd_ports: Vec, - /// Specify the type of EC chip (MEC/MCHP or other) - #[clap(requires("pd_addrs"), requires("pd_ports"))] - #[arg(long)] - has_mec: Option, - /// Run self-test to check if interaction with EC is possible #[arg(long, short)] test: bool, @@ -408,7 +403,6 @@ pub fn parse(args: &[String]) -> Cli { driver: args.driver, pd_addrs, pd_ports, - has_mec: args.has_mec, test: args.test, // TODO: Set help. Not very important because Clap handles this by itself help: false, diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 87b08e3f..b2a324f1 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -17,7 +17,7 @@ pub mod uefi; #[cfg(not(feature = "uefi"))] use std::fs; -#[cfg(all(not(feature = "uefi"), feature = "std"))] +#[cfg(not(feature = "uefi"))] use std::io::prelude::*; #[cfg(feature = "rusb")] @@ -42,7 +42,7 @@ use crate::chromium_ec::commands::TabletModeOverride; use crate::chromium_ec::EcResponseStatus; use crate::chromium_ec::{print_err, EcFlashType}; use crate::chromium_ec::{EcError, EcResult}; -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] use crate::csme; use crate::ec_binary; use crate::esrt; @@ -54,7 +54,7 @@ use crate::smbios::ConfigDigit0; use crate::smbios::{dmidecode_string_val, get_smbios, is_framework}; #[cfg(feature = "hidapi")] use crate::touchpad::print_touchpad_fw_ver; -#[cfg(any(feature = "hidapi", feature = "windows"))] +#[cfg(feature = "hidapi")] use crate::touchscreen; #[cfg(feature = "uefi")] use crate::uefi::enable_page_break; @@ -191,7 +191,6 @@ pub struct Cli { pub hash: Option, pub pd_addrs: Option<(u16, u16)>, pub pd_ports: Option<(u8, u8)>, - pub has_mec: Option, pub help: bool, pub info: bool, pub flash_gpu_descriptor: Option<(u8, String)>, @@ -351,12 +350,25 @@ fn print_stylus_battery_level() { } fn print_versions(ec: &CrosEc) { + println!("Mainboard Hardware"); + if let Some(ver) = smbios::get_product_name() { + println!(" Type: {}", ver); + } else { + println!(" Type: Unknown"); + } + if let Some(ver) = smbios::get_baseboard_version() { + println!(" Revision: {:?}", ver); + } else { + println!(" Revision: Unknown"); + } println!("UEFI BIOS"); if let Some(smbios) = get_smbios() { let bios_entries = smbios.collect::(); let bios = bios_entries.first().unwrap(); println!(" Version: {}", bios.version()); println!(" Release Date: {}", bios.release_date()); + } else { + println!(" Version: Unknown"); } println!("EC Firmware"); @@ -484,7 +496,7 @@ fn print_versions(ec: &CrosEc) { } } - #[cfg(feature = "linux")] + #[cfg(target_os = "linux")] { println!("CSME"); if let Ok(csme) = csme::csme_from_sysfs() { @@ -554,7 +566,7 @@ fn flash_ec(ec: &CrosEc, ec_bin_path: &str, flash_type: EcFlashType) { fn dump_ec_flash(ec: &CrosEc, dump_path: &str) { let flash_bin = ec.get_entire_ec_flash().unwrap(); - #[cfg(all(not(feature = "uefi"), feature = "std"))] + #[cfg(not(feature = "uefi"))] { let mut file = fs::File::create(dump_path).unwrap(); file.write_all(&flash_bin).unwrap(); @@ -701,12 +713,8 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } // Must be run before any application code to set the config - if args.pd_addrs.is_some() && args.pd_ports.is_some() && args.has_mec.is_some() { - let platform = Platform::GenericFramework( - args.pd_addrs.unwrap(), - args.pd_ports.unwrap(), - args.has_mec.unwrap(), - ); + if args.pd_addrs.is_some() && args.pd_ports.is_some() { + let platform = Platform::GenericFramework(args.pd_addrs.unwrap(), args.pd_ports.unwrap()); Config::set(platform); } @@ -826,7 +834,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { }; ec.set_tablet_mode(mode); } else if let Some(_enable) = &args.touchscreen_enable { - #[cfg(any(feature = "hidapi", feature = "windows"))] + #[cfg(feature = "hidapi")] if touchscreen::enable_touch(*_enable).is_none() { error!("Failed to enable/disable touch"); } @@ -1156,11 +1164,11 @@ fn hash(data: &[u8]) { println!("Hashes"); print!(" SHA256: "); - util::print_buffer_short(sha256); + util::print_buffer(sha256); print!(" SHA384: "); - util::print_buffer_short(sha384); + util::print_buffer(sha384); print!(" SHA512: "); - util::print_buffer_short(sha512); + util::print_buffer(sha512); } fn selftest(ec: &CrosEc) -> Option<()> { @@ -1169,7 +1177,7 @@ fn selftest(ec: &CrosEc) -> Option<()> { } else { println!(" SMBIOS Platform: Unknown"); println!(); - println!("Specify custom platform parameters with --pd-ports --pd-addrs --has-mec"); + println!("Specify custom platform parameters with --pd-ports --pd-addrs"); return None; }; diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 626f0bd0..1fd9e5d8 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -106,7 +106,6 @@ pub fn parse(args: &[String]) -> Cli { driver: Some(CrosEcDriverType::Portio), pd_addrs: None, pd_ports: None, - has_mec: None, test: false, help: false, flash_gpu_descriptor: None, @@ -610,22 +609,6 @@ pub fn parse(args: &[String]) -> Cli { None }; found_an_option = true; - } else if arg == "--has-mec" { - cli.has_mec = if args.len() > i + 1 { - if let Ok(b) = args[i + 1].parse::() { - Some(b) - } else { - println!( - "Invalid value for --has-mec: '{}'. Must be 'true' or 'false'.", - args[i + 1] - ); - None - } - } else { - println!("--has-mec requires extra boolean argument."); - None - }; - found_an_option = true; } else if arg == "--raw-command" { cli.raw_command = args[1..].to_vec(); } else if arg == "--compare-version" { @@ -699,11 +682,10 @@ pub fn parse(args: &[String]) -> Cli { } } - let custom_platform = cli.pd_addrs.is_some() && cli.pd_ports.is_some() && cli.has_mec.is_some(); - let no_customization = - cli.pd_addrs.is_none() && cli.pd_ports.is_none() && cli.has_mec.is_none(); + let custom_platform = cli.pd_addrs.is_some() && cli.pd_ports.is_some(); + let no_customization = cli.pd_addrs.is_none() && cli.pd_ports.is_none(); if !(custom_platform || no_customization) { - println!("To customize the platform you need to provide all of --pd-addrs, --pd-ports and --has-mec"); + println!("To customize the platform you need to provide all of --pd-addrs, and --pd-ports"); } if args.len() == 1 && cli.paginate { diff --git a/framework_lib/src/csme.rs b/framework_lib/src/csme.rs index 610560a2..5bd6c9eb 100644 --- a/framework_lib/src/csme.rs +++ b/framework_lib/src/csme.rs @@ -3,11 +3,11 @@ //! Currently only works on Linux (from sysfs). use core::fmt; -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] use std::fs; -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] use std::io; -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] use std::path::Path; pub struct CsmeInfo { @@ -85,7 +85,7 @@ impl fmt::Display for CsmeVersion { } } -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] pub fn csme_from_sysfs() -> io::Result { let dir = Path::new("/sys/class/mei"); let mut csme_info: Option = None; diff --git a/framework_lib/src/esrt/mod.rs b/framework_lib/src/esrt/mod.rs index feb98a2a..16770df5 100644 --- a/framework_lib/src/esrt/mod.rs +++ b/framework_lib/src/esrt/mod.rs @@ -22,11 +22,11 @@ use guid_macros::guid; #[cfg(feature = "uefi")] use uefi::{guid, Guid}; -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] use std::fs; -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] use std::io; -#[cfg(feature = "linux")] +#[cfg(target_os = "linux")] use std::path::Path; #[cfg(target_os = "freebsd")] @@ -262,7 +262,7 @@ pub fn print_esrt(esrt: &Esrt) { } } -#[cfg(all(not(feature = "uefi"), feature = "std", feature = "linux"))] +#[cfg(target_os = "linux")] /// On Linux read the ESRT table from the sysfs /// resource_version and resource_count_max are reported by sysfs, so they're defaulted to reaesonable values /// capsule_flags in sysfs seems to be 0 always. Not sure why. @@ -323,7 +323,7 @@ fn esrt_from_sysfs(dir: &Path) -> io::Result { Ok(esrt_table) } -#[cfg(all(not(feature = "uefi"), feature = "linux", target_os = "linux"))] +#[cfg(target_os = "linux")] pub fn get_esrt() -> Option { let res = esrt_from_sysfs(Path::new("/sys/firmware/efi/esrt/entries")).ok(); if res.is_none() { @@ -332,7 +332,7 @@ pub fn get_esrt() -> Option { res } -#[cfg(all(not(feature = "uefi"), feature = "windows"))] +#[cfg(all(not(feature = "uefi"), windows))] pub fn get_esrt() -> Option { let mut esrt_table = Esrt { resource_count: 0, diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index a237326a..661f9e14 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -20,9 +20,9 @@ pub mod camera; pub mod inputmodule; #[cfg(feature = "hidapi")] pub mod touchpad; -#[cfg(any(feature = "hidapi", feature = "windows"))] +#[cfg(feature = "hidapi")] pub mod touchscreen; -#[cfg(feature = "windows")] +#[cfg(all(feature = "hidapi", windows))] pub mod touchscreen_win; #[cfg(feature = "uefi")] diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index bb3e9ea4..f365d57a 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -76,7 +76,7 @@ const EC_FAN_SPEED_ENTRIES: usize = 4; const EC_FAN_SPEED_STALLED_DEPRECATED: u16 = 0xFFFE; const EC_FAN_SPEED_NOT_PRESENT: u16 = 0xFFFF; -#[derive(Debug)] +#[derive(Debug, PartialEq)] enum TempSensor { Ok(u8), NotPresent, @@ -190,7 +190,7 @@ impl fmt::Display for AccelData { let x = (self.x as f32) / quarter; let y = (self.y as f32) / quarter; let z = (self.z as f32) / quarter; - write!(f, "X: {:.2}G Y: {:.2}G, Z: {:.2}G", x, y, z) + write!(f, "X={:+.2}G Y={:+.2}G, Z={:+.2}G", x, y, z) } } @@ -288,22 +288,25 @@ pub fn print_sensors(ec: &CrosEc) { let accel_1 = ec.read_memory(EC_MEMMAP_ACC_DATA + 2, 0x06).unwrap(); let accel_2 = ec.read_memory(EC_MEMMAP_ACC_DATA + 8, 0x06).unwrap(); - println!("Accelerometers:"); - println!(" Status Bit: {} 0x{:X}", acc_status, acc_status); - println!(" Present: {}", (acc_status & 0x80) > 0); - println!(" Busy: {}", (acc_status & 0x8) > 0); - print!(" Lid Angle: "); - if lid_angle == LID_ANGLE_UNRELIABLE { - println!("Unreliable"); - } else { - println!("{} Deg", lid_angle); + let present = (acc_status & 0x80) > 0; + if present { + println!("Accelerometers:"); + debug!(" Status Bit: {} 0x{:X}", acc_status, acc_status); + debug!(" Present: {}", present); + debug!(" Busy: {}", (acc_status & 0x8) > 0); + print!(" Lid Angle: "); + if lid_angle == LID_ANGLE_UNRELIABLE { + println!("Unreliable"); + } else { + println!("{} Deg", lid_angle); + } + println!(" Sensor 1: {}", AccelData::from(accel_1)); + println!(" Sensor 2: {}", AccelData::from(accel_2)); + // Accelerometers + // Lid Angle: 26 Deg + // Sensor 1: 00.00 X 00.00 Y 00.00 Z + // Sensor 2: 00.00 X 00.00 Y 00.00 Z } - println!(" Sensor 1: {}", AccelData::from(accel_1)); - println!(" Sensor 2: {}", AccelData::from(accel_2)); - // Accelerometers - // Lid Angle: 26 Deg - // Sensor 1: 00.00 X 00.00 Y 00.00 Z - // Sensor 2: 00.00 X 00.00 Y 00.00 Z } pub fn print_thermal(ec: &CrosEc) { @@ -311,7 +314,7 @@ pub fn print_thermal(ec: &CrosEc) { let fans = ec.read_memory(EC_MEMMAP_FAN, 0x08).unwrap(); let platform = smbios::get_platform(); - match platform { + let remaining_sensors = match platform { Some(Platform::IntelGen11) | Some(Platform::IntelGen12) | Some(Platform::IntelGen13) => { println!(" F75303_Local: {:>4}", TempSensor::from(temps[0])); println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1])); @@ -324,6 +327,7 @@ pub fn print_thermal(ec: &CrosEc) { ) { println!(" F57397_VCCGT: {:>4}", TempSensor::from(temps[5])); } + 2 } Some(Platform::IntelCoreUltra1) => { @@ -332,6 +336,7 @@ pub fn print_thermal(ec: &CrosEc) { println!(" Battery: {:>4}", TempSensor::from(temps[2])); println!(" F75303_DDR: {:>4}", TempSensor::from(temps[3])); println!(" PECI: {:>4}", TempSensor::from(temps[4])); + 3 } Some(Platform::Framework12IntelGen13) => { @@ -340,6 +345,8 @@ pub fn print_thermal(ec: &CrosEc) { println!(" F75303_Local: {:>4}", TempSensor::from(temps[2])); println!(" Battery: {:>4}", TempSensor::from(temps[3])); println!(" PECI: {:>4}", TempSensor::from(temps[4])); + println!(" Charger IC {:>4}", TempSensor::from(temps[5])); + 2 } Some( @@ -356,6 +363,9 @@ pub fn print_thermal(ec: &CrosEc) { println!(" dGPU VRAM: {:>4}", TempSensor::from(temps[5])); println!(" dGPU AMB: {:>4}", TempSensor::from(temps[6])); println!(" dGPU temp: {:>4}", TempSensor::from(temps[7])); + 0 + } else { + 4 } } @@ -364,6 +374,7 @@ pub fn print_thermal(ec: &CrosEc) { println!(" F75303_DDR: {:>4}", TempSensor::from(temps[1])); println!(" F75303_AMB: {:>4}", TempSensor::from(temps[2])); println!(" APU: {:>4}", TempSensor::from(temps[3])); + 4 } _ => { @@ -375,17 +386,26 @@ pub fn print_thermal(ec: &CrosEc) { println!(" Temp 5: {:>4}", TempSensor::from(temps[5])); println!(" Temp 6: {:>4}", TempSensor::from(temps[6])); println!(" Temp 7: {:>4}", TempSensor::from(temps[7])); + 0 + } + }; + + // Just in case EC has more sensors than we know about, print them + for (i, temp) in temps.iter().enumerate().take(8).skip(8 - remaining_sensors) { + let temp = TempSensor::from(*temp); + if temp != TempSensor::NotPresent { + println!(" Temp {}: {:>4}", i, temp); } } for i in 0..EC_FAN_SPEED_ENTRIES { let fan = u16::from_le_bytes([fans[i * 2], fans[1 + i * 2]]); if fan == EC_FAN_SPEED_STALLED_DEPRECATED { - println!(" Fan Speed: {:>4} RPM (Stalled)", fan); + println!(" Fan Speed: {:>4} RPM (Stalled)", fan); } else if fan == EC_FAN_SPEED_NOT_PRESENT { info!(" Fan Speed: Not present"); } else { - println!(" Fan Speed: {:>4} RPM", fan); + println!(" Fan Speed: {:>4} RPM", fan); } } } diff --git a/framework_lib/src/smbios.rs b/framework_lib/src/smbios.rs index b25ed153..c5cfef29 100644 --- a/framework_lib/src/smbios.rs +++ b/framework_lib/src/smbios.rs @@ -8,6 +8,7 @@ use std::io::ErrorKind; use crate::util::Config; pub use crate::util::Platform; use num_derive::FromPrimitive; +use num_traits::FromPrimitive; use smbioslib::*; #[cfg(feature = "uefi")] use spin::Mutex; @@ -46,7 +47,7 @@ pub enum ConfigDigit0 { pub fn is_framework() -> bool { if matches!( get_platform(), - Some(Platform::GenericFramework((_, _), (_, _), _)) + Some(Platform::GenericFramework((_, _), (_, _))) | Some(Platform::UnknownSystem) ) { return true; } @@ -215,7 +216,7 @@ pub fn get_smbios() -> Option { } } -fn get_product_name() -> Option { +pub fn get_product_name() -> Option { // On FreeBSD we can short-circuit and avoid parsing SMBIOS #[cfg(target_os = "freebsd")] if let Ok(product) = kenv_get("smbios.system.product") { @@ -225,6 +226,7 @@ fn get_product_name() -> Option { let smbios = get_smbios(); if smbios.is_none() { println!("Failed to find SMBIOS"); + return None; } let mut smbios = smbios.into_iter().flatten(); smbios.find_map(|undefined_struct| { @@ -237,6 +239,38 @@ fn get_product_name() -> Option { }) } +pub fn get_baseboard_version() -> Option { + // TODO: On FreeBSD we can short-circuit and avoid parsing SMBIOS + // #[cfg(target_os = "freebsd")] + // if let Ok(product) = kenv_get("smbios.system.product") { + // return Some(product); + // } + + let smbios = get_smbios(); + if smbios.is_none() { + error!("Failed to find SMBIOS"); + return None; + } + let mut smbios = smbios.into_iter().flatten(); + smbios.find_map(|undefined_struct| { + if let DefinedStruct::BaseBoardInformation(data) = undefined_struct.defined_struct() { + if let Some(version) = dmidecode_string_val(&data.version()) { + // Assumes it's ASCII, which is guaranteed by SMBIOS + let config_digit0 = &version[0..1]; + let config_digit0 = u8::from_str_radix(config_digit0, 16); + if let Ok(version_config) = + config_digit0.map(::from_u8) + { + return version_config; + } else { + error!(" Invalid BaseBoard Version: {}'", version); + } + } + } + None + }) +} + pub fn get_platform() -> Option { #[cfg(feature = "uefi")] let mut cached_platform = CACHED_PLATFORM.lock(); @@ -252,7 +286,10 @@ pub fn get_platform() -> Option { // Except if it's a GenericFramework platform let config = Config::get(); let platform = &(*config).as_ref().unwrap().platform; - if matches!(platform, Platform::GenericFramework((_, _), (_, _), _)) { + if matches!( + platform, + Platform::GenericFramework((_, _), (_, _)) | Platform::UnknownSystem + ) { return Some(*platform); } } @@ -270,7 +307,7 @@ pub fn get_platform() -> Option { "Laptop 13 (Intel Core Ultra Series 1)" => Some(Platform::IntelCoreUltra1), "Laptop 16 (AMD Ryzen 7040 Series)" => Some(Platform::Framework16Amd7080), "Desktop (AMD Ryzen AI Max 300 Series)" => Some(Platform::FrameworkDesktopAmdAiMax300), - _ => None, + _ => Some(Platform::UnknownSystem), }; if let Some(platform) = platform { diff --git a/framework_lib/src/touchscreen.rs b/framework_lib/src/touchscreen.rs index b06965a7..256df139 100644 --- a/framework_lib/src/touchscreen.rs +++ b/framework_lib/src/touchscreen.rs @@ -1,6 +1,6 @@ use hidapi::{HidApi, HidDevice}; -#[cfg(target_os = "windows")] +#[cfg(windows)] use crate::touchscreen_win; pub const ILI_VID: u16 = 0x222A; diff --git a/framework_lib/src/util.rs b/framework_lib/src/util.rs index cfb97368..022475f1 100644 --- a/framework_lib/src/util.rs +++ b/framework_lib/src/util.rs @@ -6,11 +6,11 @@ use std::prelude::v1::*; #[cfg(feature = "uefi")] use core::prelude::rust_2021::derive; -#[cfg(not(feature = "std"))] +#[cfg(feature = "uefi")] use alloc::sync::Arc; -#[cfg(not(feature = "std"))] +#[cfg(feature = "uefi")] use spin::{Mutex, MutexGuard}; -#[cfg(feature = "std")] +#[cfg(not(feature = "uefi"))] use std::sync::{Arc, Mutex, MutexGuard}; use crate::smbios; @@ -36,8 +36,9 @@ pub enum Platform { /// Framework Desktop - AMD Ryzen AI Max 300 FrameworkDesktopAmdAiMax300, /// Generic Framework device - /// pd_addrs, pd_ports, has_mec - GenericFramework((u16, u16), (u8, u8), bool), + /// pd_addrs, pd_ports + GenericFramework((u16, u16), (u8, u8)), + UnknownSystem, } #[derive(Debug, PartialEq, Clone, Copy)] @@ -61,6 +62,7 @@ impl Platform { Platform::Framework16Amd7080 => Some(PlatformFamily::Framework16), Platform::FrameworkDesktopAmdAiMax300 => Some(PlatformFamily::FrameworkDesktop), Platform::GenericFramework(..) => None, + Platform::UnknownSystem => None, } } } @@ -74,9 +76,9 @@ pub struct Config { impl Config { pub fn set(platform: Platform) { - #[cfg(feature = "std")] + #[cfg(not(feature = "uefi"))] let mut config = CONFIG.lock().unwrap(); - #[cfg(not(feature = "std"))] + #[cfg(feature = "uefi")] let mut config = CONFIG.lock(); if (*config).is_none() { @@ -87,9 +89,9 @@ impl Config { } } pub fn is_set() -> bool { - #[cfg(feature = "std")] + #[cfg(not(feature = "uefi"))] let config = CONFIG.lock().unwrap(); - #[cfg(not(feature = "std"))] + #[cfg(feature = "uefi")] let config = CONFIG.lock(); (*config).is_some() @@ -98,9 +100,9 @@ impl Config { pub fn get() -> MutexGuard<'static, Option> { trace!("Config::get() entry"); let unset = { - #[cfg(feature = "std")] + #[cfg(not(feature = "uefi"))] let config = CONFIG.lock().unwrap(); - #[cfg(not(feature = "std"))] + #[cfg(feature = "uefi")] let config = CONFIG.lock(); (*config).is_none() }; @@ -115,9 +117,9 @@ impl Config { None }; - #[cfg(feature = "std")] + #[cfg(not(feature = "uefi"))] let mut config = CONFIG.lock().unwrap(); - #[cfg(not(feature = "std"))] + #[cfg(feature = "uefi")] let mut config = CONFIG.lock(); if new_config.is_some() { @@ -157,7 +159,7 @@ pub unsafe fn any_vec_as_u8_slice(p: &[T]) -> &[u8] { /// Print a byte buffer as a series of hex bytes pub fn print_buffer(buffer: &[u8]) { for byte in buffer { - print!("{:#X} ", byte); + print!("{:02x}", byte); } println!(); } @@ -232,15 +234,8 @@ pub fn find_sequence(haystack: &[u8], needle: &[u8]) -> Option { /// Assert length of an EC response from the windows driver /// It's always 20 more than expected. TODO: Figure out why pub fn assert_win_len(left: N, right: N) { - #[cfg(feature = "win_driver")] + #[cfg(windows)] assert_eq!(left, right + NumCast::from(20).unwrap()); - #[cfg(not(feature = "win_driver"))] + #[cfg(not(windows))] assert_eq!(left, right); } - -pub fn print_buffer_short(buffer: &[u8]) { - for byte in buffer { - print!("{:02x}", byte); - } - println!(); -} diff --git a/framework_tool/Cargo.toml b/framework_tool/Cargo.toml index ecef1fb0..7cdfd1f6 100644 --- a/framework_tool/Cargo.toml +++ b/framework_tool/Cargo.toml @@ -1,18 +1,19 @@ [package] name = "framework_tool" -version = "0.4.0" +version = "0.4.1" edition = "2021" -[features] -default = ["linux"] -linux = ["framework_lib/linux"] -freebsd = ["framework_lib/freebsd"] -windows = ["framework_lib/windows"] - [dependencies.framework_lib] path = "../framework_lib" -default-features = false [build-dependencies] # Note: Only takes effect in release builds static_vcruntime = "2.0" +embed-resource = "3.0" +winresource = "0.1.17" + +[target.'cfg(windows)'.dependencies.winapi] +version = "0.3.9" +features = [ + "wincon" +] diff --git a/framework_tool/build.rs b/framework_tool/build.rs index 20e1c8e9..87d2140e 100644 --- a/framework_tool/build.rs +++ b/framework_tool/build.rs @@ -1,3 +1,19 @@ fn main() { - static_vcruntime::metabuild(); + // Add app icon + if std::env::var_os("CARGO_CFG_WINDOWS").is_some() { + winresource::WindowsResource::new() + .set_icon("..\\res\\framework_startmenuicon.ico") + .compile() + .unwrap(); + } + + if !cfg!(debug_assertions) { + // Statically link vcruntime to allow running on clean install + static_vcruntime::metabuild(); + + // Embed resources file to force running as admin + embed_resource::compile("framework_tool-manifest.rc", embed_resource::NONE) + .manifest_optional() + .unwrap(); + } } diff --git a/framework_tool/framework_tool-manifest.rc b/framework_tool/framework_tool-manifest.rc new file mode 100644 index 00000000..68429585 --- /dev/null +++ b/framework_tool/framework_tool-manifest.rc @@ -0,0 +1,2 @@ +#define RT_MANIFEST 24 +1 RT_MANIFEST "framework_tool.exe.manifest" diff --git a/framework_tool/framework_tool.exe.manifest b/framework_tool/framework_tool.exe.manifest new file mode 100644 index 00000000..287bba26 --- /dev/null +++ b/framework_tool/framework_tool.exe.manifest @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/framework_tool/src/main.rs b/framework_tool/src/main.rs index 3a4d54a4..d755aa77 100644 --- a/framework_tool/src/main.rs +++ b/framework_tool/src/main.rs @@ -6,9 +6,49 @@ fn get_args() -> Vec { } fn main() -> Result<(), &'static str> { - let args = commandline::parse(&get_args()); + let args = get_args(); + + // If the user double-clicks (opens from explorer/desktop), + // then we want to have the default behavior of showing a report of + // all firmware versions. + #[cfg(windows)] + let (args, double_clicked) = { + let double_clicked = unsafe { + // See https://devblogs.microsoft.com/oldnewthing/20160125-00/?p=92922 + let mut plist: winapi::shared::minwindef::DWORD = 0; + let processes = winapi::um::wincon::GetConsoleProcessList(&mut plist, 1); + + // If we're the only process that means we're in a fresh terminal + // without CMD or powershell. This happens in some cases, for example + // if the user double-clicks the app from Explorer. + processes == 1 + }; + // But it also happens if launched from the commandline and a UAC prompt is necessary, + // for example with sudo set to open "In a new windows", therefore we also have to + // check that no commandline arguments were provided. + if double_clicked && args.len() == 1 { + ( + vec![args[0].clone(), "--versions".to_string()], + double_clicked, + ) + } else { + (args, double_clicked) + } + }; + + let args = commandline::parse(&args); if (commandline::run_with_args(&args, false)) != 0 { return Err("Fail"); } + + // Prevent command prompt from auto closing + #[cfg(windows)] + if double_clicked { + println!(); + println!("Press ENTER to exit..."); + let mut line = String::new(); + let _ = std::io::stdin().read_line(&mut line).unwrap(); + } + Ok(()) } diff --git a/framework_uefi/Cargo.toml b/framework_uefi/Cargo.toml index 13d6e0b5..d0ad0cc4 100644 --- a/framework_uefi/Cargo.toml +++ b/framework_uefi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_uefi" -version = "0.4.0" +version = "0.4.1" edition = "2021" # Minimum Supported Rust Version rust-version = "1.74" diff --git a/res/framework_startmenuicon.ico b/res/framework_startmenuicon.ico new file mode 100644 index 00000000..8c6be29a Binary files /dev/null and b/res/framework_startmenuicon.ico differ diff --git a/rgbkbd.py b/rgbkbd.py index 9088b85e..c997d897 100755 --- a/rgbkbd.py +++ b/rgbkbd.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # Example invocation in fish shell # cargo build && sudo ./target/debug/framework_tool \ -# --driver portio --has-mec false --pd-ports 1 1 --pd-addrs 64 64 \ +# --driver portio --pd-ports 1 1 --pd-addrs 64 64 \ # (./rgbkbd.py | string split ' ') BRIGHTNESS = 1 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