diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdadf0df..2a261d25 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - name: Setup Rust toolchain run: | rm rust-toolchain.toml - rustup toolchain install 1.75.0-x86_64-unknown-freebsd + rustup target add x86_64-unknown-freebsd rustup show - name: Install cross compilation tool @@ -91,20 +91,24 @@ jobs: - name: Setup Rust toolchain run: rustup show + # Build debug library first to fail fast - name: Build library (Windows) run: cargo build -p framework_lib --no-default-features --features "windows" - name: Build Windows tool - run: cargo build -p framework_tool --no-default-features --features "windows" + run: | + cargo build -p framework_tool --no-default-features --features "windows" + cargo build -p framework_tool --no-default-features --features "windows" --release - name: Check if Windows tool can start - run: cargo run --no-default-features --features "windows" -- --help + run: cargo run --no-default-features --features "windows" -- --help --release + # Upload release build so that vcruntime is statically linked - name: Upload Windows App uses: actions/upload-artifact@v4 with: name: framework_tool.exe - path: target/debug/framework_tool.exe + path: target/release/framework_tool.exe test: diff --git a/Cargo.lock b/Cargo.lock index f2cb24fc..228dd7f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -175,6 +175,15 @@ dependencies = [ "clap_derive", ] +[[package]] +name = "clap-num" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822c4000301ac390e65995c62207501e3ef800a1fc441df913a5e8e4dc374816" +dependencies = [ + "num-traits", +] + [[package]] name = "clap-verbosity-flag" version = "2.2.1" @@ -206,7 +215,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -302,7 +311,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -319,7 +328,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -379,10 +388,11 @@ dependencies = [ [[package]] name = "framework_lib" -version = "0.2.1" +version = "0.3.0" dependencies = [ "built", "clap", + "clap-num", "clap-verbosity-flag", "env_logger", "guid_macros", @@ -410,7 +420,7 @@ dependencies = [ [[package]] name = "framework_tool" -version = "0.2.1" +version = "0.3.0" dependencies = [ "framework_lib", "static_vcruntime", @@ -418,7 +428,7 @@ dependencies = [ [[package]] name = "framework_uefi" -version = "0.2.1" +version = "0.3.0" dependencies = [ "framework_lib", "log", @@ -482,7 +492,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -553,7 +563,7 @@ version = "0.11.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -564,9 +574,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hidapi" -version = "2.6.1" +version = "2.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e58251020fe88fe0dae5ebcc1be92b4995214af84725b375d08354d0311c23c" +checksum = "03b876ecf37e86b359573c16c8366bc3eba52b689884a0fc42ba3f67203d2a8b" dependencies = [ "cc", "cfg-if", @@ -826,7 +836,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -908,9 +918,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] @@ -937,9 +947,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] @@ -955,9 +965,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -967,9 +977,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -978,9 +988,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rusb" @@ -1135,9 +1145,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" dependencies = [ "proc-macro2", "quote", @@ -1155,22 +1165,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.40" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -1396,61 +1406,29 @@ dependencies = [ "windows-targets 0.48.0", ] -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-implement 0.52.0", - "windows-interface 0.52.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f919aee0a93304be7f62e8e5027811bbba96bcb1de84d6618be56e43f8a32a1" dependencies = [ - "windows-core 0.59.0", + "windows-core", "windows-targets 0.53.0", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "810ce18ed2112484b0d4e15d022e5f598113e220c53e373fb31e67e21670c1ce" dependencies = [ - "windows-implement 0.59.0", - "windows-interface 0.59.0", + "windows-implement", + "windows-interface", "windows-result", "windows-strings", "windows-targets 0.53.0", ] -[[package]] -name = "windows-implement" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12168c33176773b86799be25e2a2ba07c7aab9968b37541f1094dbd7a60c8946" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", -] - [[package]] name = "windows-implement" version = "0.59.0" @@ -1459,18 +1437,7 @@ checksum = "83577b051e2f49a058c308f17f273b570a6a758386fc291b5f6a934dd84e48c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", -] - -[[package]] -name = "windows-interface" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d8dc32e0095a7eeccebd0e3f09e9509365ecb3fc6ac4d6f5f14a3f6392942d1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -1481,7 +1448,7 @@ checksum = "cb26fd936d991781ea39e87c3a27285081e3c0da5ca0fcbc02d368cc6f52ff01" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.98", ] [[package]] @@ -1707,14 +1674,15 @@ checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "wmi" -version = "0.13.3" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f0a4062ca522aad4705a2948fd4061b3857537990202a8ddd5af21607f79a" +checksum = "58078b4e28f04064dae68f6e11a6b93133c83b88dfd5ae16738ded4942db6544" dependencies = [ "chrono", "futures", "log", "serde", "thiserror", - "windows 0.52.0", + "windows 0.59.0", + "windows-core", ] diff --git a/README.md b/README.md index f4927fc0..ded23458 100644 --- a/README.md +++ b/README.md @@ -25,15 +25,14 @@ see the [Support Matrices](support-matrices.md). - [x] ESRT table (UEFI, Linux, FreeBSD only) (`--esrt`) - [x] SMBIOS - [x] Get firmware version from binary file - - [x] Legacy EC (Intel 13th Gen and earlier) (`--ec-bin`) - - [x] Zephyr EC (AMD) (`--ec-bin`) + - [x] EC (Legacy and Zephyr based) (`--ec-bin`) - [x] CCG5 PD (11th Gen TigerLake) (`--pd-bin`) - - [x] CCG6 PD (12th Gen AlderLake) (`--pd-bin`) - - [x] CCG8 PD (Framework 16) (`--pd-bin`) - - [x] HO2 BIOS Capsule (`--ho2-capsule`) + - [x] CCG6 PD (Intel systems, Framework Desktop) (`--pd-bin`) + - [x] CCG8 PD (AMD Laptops) (`--pd-bin`) + - [x] H2O BIOS Capsule (`--h2o-capsule`) - [x] BIOS Version - [x] EC Version - - [x] CCG5/CCG6 PD Version + - [x] CCG5/CCG6/CCG8 PD Version - [x] UEFI Capsule (`--capsule`) - [x] Parse metadata from capsule binary - [x] Determine type (GUID) of capsule binary @@ -41,9 +40,11 @@ see the [Support Matrices](support-matrices.md). - [x] Get firmware version from system (`--versions`) - [x] BIOS - [x] EC - - [x] PD + - [x] PD Controller - [x] ME (Only on Linux) - [x] Retimer + - [x] Touchpad (Linux, Windows, FreeBSD, not UEFI) + - [x] Touchscreen (Linux, Windows, FreeBSD, not UEFI) - [x] Get Expansion Card Firmware (Not on UEFI so far) - [x] HDMI Expansion Card (`--dp-hdmi-info`) - [x] DisplayPort Expansion Card (`--dp-hdmi-info`) @@ -53,16 +54,6 @@ see the [Support Matrices](support-matrices.md). - [x] DisplayPort Expansion Card (`--dp-hdmi-update`) - [ ] Audio Expansion Card -###### Firmware Update - -Note: Use fwupd. - -- [ ] Flash firmware - - [ ] BIOS - - [ ] EC - - [ ] PD - - [ ] Expansion Cards - ###### System Status All of these need EC communication support in order to work. @@ -78,16 +69,23 @@ All of these need EC communication support in order to work. - [x] Get and set keyboard brightness (`--kblight`) - [x] Get and set battery charge limit (`--charge-limit`) -- [x] Get and set fingerprint LED brightness (`--fp-brightness`) +- [x] Get and set fingerprint LED brightness (`--fp-brightness`, `--fp-led-level`) +- [x] Override tablet mode, instead of follow G-Sensor and hall sensor (`--tablet-mode`) +- [x] Disable/Enable touchscreen (`--touchscreen-enable`) ###### Communication with Embedded Controller +- [x] Framework Laptop 12 (Intel 13th Gen) - [x] Framework Laptop 13 (Intel 11-13th Gen) -- [x] Framework Laptop 13 (AMD Ryzen) -- [x] Framework Laptop 16 (AMD Ryzen) +- [x] Framework Laptop 13 (AMD Ryzen 7080) +- [x] Framework Laptop 13 (AMD Ryzen AI 300) +- [x] Framework Laptop 16 (AMD Ryzen 7080) +- [x] Framework Desktop (AMD Ryzen AI Max 300) - [x] Port I/O communication on Linux -- [x] Port I/O communication on UEFI +- [x] Port I/O communication in UEFI +- [x] Port I/O communication on FreeBSD - [x] Using `cros_ec` driver in Linux kernel +- [x] Using [Framework EC Windows driver](https://github.com/FrameworkComputer/crosecbus) based on [coolstar's](https://github.com/coolstar/crosecbus) - [x] Using [DHowett's Windows CrosEC driver](https://github.com/DHowett/FrameworkWindowsUtils) ## Prerequisites @@ -99,8 +97,8 @@ will install the right toolchain and version for this project. MSRV (Minimum Supported Rust Version): -- 1.61 for Linux/Windows -- 1.68 for UEFI +- 1.74 for Linux/Windows +- 1.74 for UEFI ```sh # Running linter @@ -131,10 +129,6 @@ ls -l framework_uefi/build/x86_64-unknown-uefi/boot.efi Building on Windows or in general with fewer features: ```ps1 -# Because we're fetching a private dependency from git, it might be necessary -# to force cargo to use the git commandline. In powershell run: -$env:CARGO_NET_GIT_FETCH_WITH_CLI='true' - # Build the library and tool cargo build --no-default-features --features "windows" @@ -182,12 +176,33 @@ Options: --ec-bin Parse versions from EC firmware binary file --capsule Parse UEFI Capsule information from binary file --dump Dump extracted UX capsule bitmap image to a file - --ho2-capsule Parse UEFI Capsule information from binary file + --h2o-capsule Parse UEFI Capsule information from binary file --intrusion Show status of intrusion switch --inputmodules Show status of the input modules (Framework 16 only) + --input-deck-mode + Set input deck power mode [possible values: auto, off, on] (Framework 16 only) [possible values: auto, off, on] + --charge-limit [] + Get or set max charge limit + --get-gpio + Get GPIO value by name + --fp-led-level [] + Get or set fingerprint LED brightness level [possible values: high, medium, low, ultra-low, auto] + --fp-brightness [] + Get or set fingerprint LED brightness percentage --kblight [] Set keyboard backlight percentage or get, if no value provided + --tablet-mode Set tablet mode override [possible values: auto, tablet, laptop] + --touchscreen-enable + Enable/disable touchscreen [possible values: true, false] --console Get EC console, choose whether recent or to follow the output [possible values: recent, follow] + --reboot-ec Control EC RO/RW jump [possible values: reboot, jump-ro, jump-rw, cancel-jump, disable-jump] + --hash Hash a file of arbitrary data --driver Select which driver is used. By default portio is used [possible values: portio, cros-ec, windows] + --pd-addrs + 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 ``` diff --git a/completions/bash/framework_tool b/completions/bash/framework_tool new file mode 100755 index 00000000..24f41d46 --- /dev/null +++ b/completions/bash/framework_tool @@ -0,0 +1,89 @@ +#!/usr/bin/env bash + +# Bash completion for framework_tool + +_framework_tool() { + local options + options=( + "--flash-gpu-descriptor" + "-v" "--verbose" + "-q" "--quiet" + "--versions" + "--version" + "--features" + "--esrt" + "--device" + "--compare-version" + "--power" + "--thermal" + "--sensors" + "--pdports" + "--info" + "--pd-info" + "--dp-hdmi-info" + "--dp-hdmi-update" + "--audio-card-info" + "--privacy" + "--pd-bin" + "--ec-bin" + "--capsule" + "--dump" + "--ho2-capsule" + "--dump-ec-flash" + "--flash-ec" + "--flash-ro-ec" + "--flash-rw-ec" + "--intrusion" + "--inputmodules" + "--input-deck-mode" + "--charge-limit" + "--get-gpio" + "--fp-led-level" + "--fp-brightness" + "--kblight" + "--rgbkbd" + "--tablet-mode" + "--touchscreen-enable" + "--console" + "--reboot-ec" + "--hash" + "--driver" + "--pd-addrs" + "--pd-ports" + "--has-mec" + "-t" "--test" + "-h" "--help" + ) + + local devices=("bios" "ec" "pd0" "pd1" "rtm01" "rtm23" "ac-left" "ac-right") + local input_deck_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 + current_word="${COMP_WORDS[COMP_CWORD]}" + prev_word="${COMP_WORDS[COMP_CWORD-1]}" + + # Handle options + if [[ $COMP_CWORD -eq 1 ]]; then + COMPREPLY=( $(compgen -W "${options[*]}" -- "$current_word") ) + elif [[ $prev_word == "--device" ]]; then + COMPREPLY=( $(compgen -W "${devices[*]}" -- "$current_word") ) + elif [[ $prev_word == "--input-deck-mode" ]]; then + COMPREPLY=( $(compgen -W "${input_deck_modes[*]}" -- "$current_word") ) + elif [[ $prev_word == "--console" ]]; then + 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 + + return 0 +} + +complete -F _framework_tool framework_tool diff --git a/completions/zsh/_framework_tool b/completions/zsh/_framework_tool new file mode 100644 index 00000000..70ea7516 --- /dev/null +++ b/completions/zsh/_framework_tool @@ -0,0 +1,56 @@ +#compdef framework_tool + +local -a options + +options=( + '--flash-gpu-descriptor[]' + '-v[Increase logging verbosity]' + '-q[Decrease logging verbosity]' + '--versions[Show current firmware versions]' + '--version[Show tool version information (Add -vv for more details)]' + '--features[Show features supported by the firmware]' + '--esrt[Display the UEFI ESRT table]' + '--device[Specify device type]:device:(bios ec pd0 pd1 rtm01 rtm23 ac-left ac-right)' + '--compare-version[Specify version to compare]:compare_version' + '--power[Show current power status of battery and AC (Add -vv for more details)]' + '--thermal[Print thermal information (Temperatures and Fan speed)]' + '--sensors[Print sensor information (ALS, G-Sensor)]' + '--pdports[Show information about USB-C PD ports]' + '--info[Show info from SMBIOS (Only on UEFI)]' + '--pd-info[Show details about the PD controllers]' + '--dp-hdmi-info[Show details about connected DP or HDMI Expansion Cards]' + '--dp-hdmi-update[Update the DisplayPort or HDMI Expansion Card]:update_bin' + '--audio-card-info[Show details about connected Audio Expansion Cards (Needs root privileges)]' + '--privacy[Show privacy switch statuses (camera and microphone)]' + '--pd-bin[Parse versions from PD firmware binary file]:pd_bin' + '--ec-bin[Parse versions from EC firmware binary file]:ec_bin' + '--capsule[Parse UEFI Capsule information from binary file]:capsule' + '--dump[Dump extracted UX capsule bitmap image to a file]:dump' + '--ho2-capsule[Parse UEFI Capsule information from binary file]:ho2_capsule' + '--dump-ec-flash[Dump EC flash contents]:dump_ec_flash' + '--flash-ec[Flash EC with new firmware from file]:flash_ec' + '--flash-ro-ec[Flash EC with new RO firmware from file]:flash_ro_ec' + '--flash-rw-ec[Flash EC with new RW firmware from file]:flash_rw_ec' + '--intrusion[Show status of intrusion switch]' + '--inputmodules[Show status of the input modules (Framework 16 only)]' + '--input-deck-mode[Set input deck power mode]:input_deck_mode:(auto off on)' + '--charge-limit[Get or set max charge limit]:charge_limit' + '--get-gpio[Get GPIO value by name]:get_gpio' + '--fp-led-level-gpio[Get or set fingerprint LED brightness level]:fp_led_level:(high medium low ultra-low auto)' + '--fp-brightness[Get or set fingerprint LED brightness]:fp_brightness' + '--kblight[Set keyboard backlight percentage or get, if no value provided]:kblight' + '--rgbkbd[Set the color of to .]' + '--tablet-mode[Set tablet mode override]:tablet_mode:(auto tablet laptop)' + '--touchscreen-enable[Enable/disable touchscreen]:touchscreen_enable:(true false)' + '--console[Get EC console, choose whether recent or to follow the output]:console:(recent follow)' + '--reboot-ec[Control EC RO/RW jump]:reboot_ec:(reboot jump-ro jump-rw cancel-jump disable-jump)' + '--hash[Hash a file of arbitrary data]:hash' + '--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]' +) + +_arguments -s -S $options diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index 3dadedf9..27819125 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_lib" -version = "0.2.1" +version = "0.3.0" edition = "2021" # Minimum Supported Rust Version # Ubuntu 24.04 LTS ships 1.75 @@ -18,7 +18,7 @@ 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-verbosity-flag", "dep:env_logger", "smbios-lib?/std"] +std = ["dep:clap", "dep:clap-num", "dep:clap-verbosity-flag", "dep:env_logger", "smbios-lib?/std"] rusb = ["dep:rusb"] hidapi = ["dep:hidapi"] uefi = [ @@ -47,10 +47,11 @@ built = { version = "0.5", features = ["chrono", "git2"] } [dependencies] lazy_static = "1.4.0" sha2 = { version = "0.10.8", default-features = false, features = [ "force-soft" ] } -regex = { version = "1.10.6", default-features = false } +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"], 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 } @@ -62,13 +63,11 @@ 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 2.6.2 needs nightly -# See: https://github.com/ruabmbua/hidapi-rs/pull/158 -hidapi = { version = "=2.6.1", optional = true } +hidapi = { version = "2.6.3", optional = true, features = [ "windows-native" ] } rusb = { version = "0.9.4", optional = true } no-std-compat = { version = "0.4.1", features = [ "alloc" ] } guid_macros = { path = "../guid_macros" } -wmi = { version = "0.13.3", optional = true } +wmi = { version = "0.15.0", optional = true } [dependencies.smbios-lib] git = "https://github.com/FrameworkComputer/smbios-lib.git" @@ -90,5 +89,12 @@ features = [ "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_SystemServices", + # For HID devices + "Win32_Devices_DeviceAndDriverInstallation", + "Win32_Devices_HumanInterfaceDevice", + "Win32_Devices_Properties", + "Win32_Storage_EnhancedStorage", + "Win32_System_Threading", + "Win32_UI_Shell_PropertiesSystem" ] diff --git a/framework_lib/src/audio_card.rs b/framework_lib/src/audio_card.rs index 8647791d..3baa33ca 100644 --- a/framework_lib/src/audio_card.rs +++ b/framework_lib/src/audio_card.rs @@ -15,7 +15,7 @@ enum CapeCommand { GetVersion = 0x0103, } -#[repr(packed)] +#[repr(C, packed)] #[derive(Clone, Copy)] struct CapeMessage { _len: i16, @@ -25,7 +25,7 @@ struct CapeMessage { data: [u32; CAPE_DATA_LEN], } -#[repr(packed)] +#[repr(C, packed)] #[derive(Clone, Copy)] struct HidCapeMessage { _report_id: u16, diff --git a/framework_lib/src/camera.rs b/framework_lib/src/camera.rs new file mode 100644 index 00000000..c99d9915 --- /dev/null +++ b/framework_lib/src/camera.rs @@ -0,0 +1,30 @@ +pub const FRAMEWORK_VID: u16 = 0x32AC; +pub const FRAMEWORK13_16_2ND_GEN_PID: u16 = 0x001C; +pub const FRAMEWORK12_PID: u16 = 0x001D; + +/// Get and print the firmware version of the camera +pub fn check_camera_version() -> Result<(), rusb::Error> { + for dev in rusb::devices().unwrap().iter() { + let dev_descriptor = dev.device_descriptor().unwrap(); + if dev_descriptor.vendor_id() != FRAMEWORK_VID + || (dev_descriptor.product_id() != FRAMEWORK13_16_2ND_GEN_PID + && dev_descriptor.product_id() != FRAMEWORK12_PID) + { + debug!( + "Skipping {:04X}:{:04X}", + dev_descriptor.vendor_id(), + dev_descriptor.product_id() + ); + continue; + } + let handle = dev.open().unwrap(); + + let dev_descriptor = dev.device_descriptor()?; + let i_product = dev_descriptor + .product_string_index() + .and_then(|x| handle.read_string_descriptor_ascii(x).ok()); + println!("{}", i_product.unwrap_or_default()); + println!(" Firmware Version: {}", dev_descriptor.device_version()); + } + Ok(()) +} diff --git a/framework_lib/src/capsule_content.rs b/framework_lib/src/capsule_content.rs index ca9d9ec6..81199bda 100644 --- a/framework_lib/src/capsule_content.rs +++ b/framework_lib/src/capsule_content.rs @@ -8,7 +8,7 @@ use crate::alloc::string::ToString; use alloc::string::String; use core::convert::TryInto; -use crate::ccgx::binary::{CCG5_PD_LEN, CCG6_PD_LEN}; +use crate::ccgx::binary::{CCG5_PD_LEN, CCG6_PD_LEN, CCG8_PD_LEN}; use crate::ec_binary::EC_LEN; use crate::util; @@ -29,13 +29,14 @@ pub fn find_bios_version(data: &[u8]) -> Option { let needle = b"$BVDT"; let found = util::find_sequence(data, needle)?; + // One of: GFW30, HFW3T, HFW30, IFR30, KFM30, JFP30, LFK30, IFGA3, IFGP6, LFR20, LFSP0 let platform_offset = found + 0xA + needle.len() - 1; - let platform = std::str::from_utf8(&data[platform_offset..platform_offset + 4]) + let platform = std::str::from_utf8(&data[platform_offset..platform_offset + 5]) .map(|x| x.to_string()) .ok()?; - let ver_offset = found + 0xE + needle.len() - 1; - let version = std::str::from_utf8(&data[ver_offset..ver_offset + 4]) + let ver_offset = found + 0x10 + needle.len() - 1; + let version = std::str::from_utf8(&data[ver_offset..ver_offset + 5]) .map(|x| x.to_string()) .ok()?; @@ -43,12 +44,10 @@ pub fn find_bios_version(data: &[u8]) -> Option { } pub fn find_ec_in_bios_cap(data: &[u8]) -> Option<&[u8]> { - let needle = b"_IFLASH_EC_IMG_"; - let found_iflash = util::find_sequence(data, needle)?; - // The actual EC binary is a few bytes after `_IFLASH_EC_IMG_`. - // Just earch for the first 4 bytes that seem to appear in all EC images. - let found = util::find_sequence(&data[found_iflash..], &[0x10, 0x00, 0x00, 0xf7])?; - Some(&data[found_iflash + found..found_iflash + found + EC_LEN]) + let needle = b"$_IFLASH_EC_IMG_"; + let found = util::find_sequence(data, needle)?; + let ec_offset = found + 0x9 + needle.len() - 1; + Some(&data[ec_offset..ec_offset + EC_LEN]) } pub fn find_pd_in_bios_cap(data: &[u8]) -> Option<&[u8]> { @@ -57,10 +56,13 @@ pub fn find_pd_in_bios_cap(data: &[u8]) -> Option<&[u8]> { // they're the same version let ccg5_needle = &[0x00, 0x20, 0x00, 0x20, 0x11, 0x00]; let ccg6_needle = &[0x00, 0x40, 0x00, 0x20, 0x11, 0x00]; + let ccg8_needle = &[0x00, 0x80, 0x00, 0x20, 0xAD, 0x0C]; if let Some(found_pd1) = util::find_sequence(data, ccg5_needle) { Some(&data[found_pd1..found_pd1 + CCG5_PD_LEN]) } else if let Some(found_pd1) = util::find_sequence(data, ccg6_needle) { Some(&data[found_pd1..found_pd1 + CCG6_PD_LEN]) + } else if let Some(found_pd1) = util::find_sequence(data, ccg8_needle) { + Some(&data[found_pd1..found_pd1 + CCG8_PD_LEN]) } else { None } diff --git a/framework_lib/src/ccgx/binary.rs b/framework_lib/src/ccgx/binary.rs index 30d7b33d..09c16855 100644 --- a/framework_lib/src/ccgx/binary.rs +++ b/framework_lib/src/ccgx/binary.rs @@ -44,7 +44,7 @@ const FW_VERSION_OFFSET: usize = 0xE0; const SMALL_ROW: usize = 0x80; const LARGE_ROW: usize = 0x100; -#[repr(packed)] +#[repr(C, packed)] #[derive(Debug, Copy, Clone)] struct VersionInfo { base_version: u32, @@ -53,8 +53,9 @@ struct VersionInfo { silicon_family: u16, } -pub const CCG5_PD_LEN: usize = 0x2_0000; -pub const CCG6_PD_LEN: usize = 0x2_0000; +pub const CCG5_PD_LEN: usize = 0x20_000; +pub const CCG6_PD_LEN: usize = 0x20_000; +pub const CCG8_PD_LEN: usize = 0x40_000; /// Information about all the firmware in a PD binary file /// diff --git a/framework_lib/src/ccgx/device.rs b/framework_lib/src/ccgx/device.rs index 432db3b6..aa1af5d0 100644 --- a/framework_lib/src/ccgx/device.rs +++ b/framework_lib/src/ccgx/device.rs @@ -44,8 +44,20 @@ impl PdPort { (Platform::GenericFramework((left, _), _, _), PdPort::Left01) => *left, (Platform::GenericFramework((_, right), _, _), PdPort::Right23) => *right, // Framework AMD Platforms (CCG8) - (Platform::Framework13Amd | Platform::Framework16, PdPort::Left01) => 0x42, - (Platform::Framework13Amd | Platform::Framework16, PdPort::Right23) => 0x40, + ( + Platform::Framework13Amd7080 + | Platform::Framework13AmdAi300 + | Platform::Framework16Amd7080, + PdPort::Left01, + ) => 0x42, + ( + Platform::Framework13Amd7080 + | Platform::Framework13AmdAi300 + | Platform::Framework16Amd7080, + PdPort::Right23, + ) => 0x40, + // TODO: It only has a single PD controller + (Platform::FrameworkDesktopAmdAiMax300, _) => 0x08, // Framework Intel Platforms (CCG5 and CCG6) (_, PdPort::Left01) => 0x08, (_, PdPort::Right23) => 0x40, @@ -64,13 +76,23 @@ impl PdPort { (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Left01) => 6, (Platform::IntelGen12 | Platform::IntelGen13, PdPort::Right23) => 7, ( - Platform::Framework13Amd | Platform::Framework16 | Platform::IntelCoreUltra1, + Platform::Framework13Amd7080 + | Platform::Framework16Amd7080 + | Platform::IntelCoreUltra1 + | Platform::Framework13AmdAi300 + | Platform::Framework12IntelGen13, PdPort::Left01, ) => 1, ( - Platform::Framework13Amd | Platform::Framework16 | Platform::IntelCoreUltra1, + Platform::Framework13Amd7080 + | Platform::Framework16Amd7080 + | Platform::IntelCoreUltra1 + | Platform::Framework13AmdAi300 + | Platform::Framework12IntelGen13, PdPort::Right23, ) => 2, + // TODO: It only has a single PD controller + (Platform::FrameworkDesktopAmdAiMax300, _) => 1, // (_, _) => Err(EcError::DeviceError(format!( // "Unsupported platform: {:?} {:?}", // platform, self diff --git a/framework_lib/src/ccgx/hid.rs b/framework_lib/src/ccgx/hid.rs index cc46111b..e3e14dc0 100644 --- a/framework_lib/src/ccgx/hid.rs +++ b/framework_lib/src/ccgx/hid.rs @@ -25,7 +25,7 @@ const FW2_START: usize = 0x0200; const FW1_METADATA: usize = 0x03FF; const FW2_METADATA: usize = 0x03FE; -#[repr(packed)] +#[repr(C, packed)] #[derive(Debug, Copy, Clone)] struct HidFirmwareInfo { report_id: u8, diff --git a/framework_lib/src/ccgx/mod.rs b/framework_lib/src/ccgx/mod.rs index 8c8f89d2..80b1b763 100644 --- a/framework_lib/src/ccgx/mod.rs +++ b/framework_lib/src/ccgx/mod.rs @@ -30,7 +30,7 @@ const CCG3_METADATA_OFFSET: usize = 0x40; const METADATA_MAGIC: u16 = u16::from_le_bytes([b'Y', b'C']); // CY (Cypress) const CCG8_METADATA_MAGIC: u16 = u16::from_le_bytes([b'F', b'I']); // IF (Infineon) -#[repr(packed)] +#[repr(C, packed)] #[derive(Debug, Copy, Clone)] struct CyAcdMetadata { /// Offset 00: Single Byte FW Checksum @@ -64,7 +64,7 @@ struct CyAcdMetadata { } // TODO: Would be nice to check the checksums -#[repr(packed)] +#[repr(C, packed)] #[derive(Debug, Copy, Clone)] struct CyAcd2Metadata { /// Offset 00: App Firmware Start diff --git a/framework_lib/src/chromium_ec/command.rs b/framework_lib/src/chromium_ec/command.rs index a0205780..0eaa9395 100644 --- a/framework_lib/src/chromium_ec/command.rs +++ b/framework_lib/src/chromium_ec/command.rs @@ -33,18 +33,21 @@ pub enum EcCommands { PwmSetFanDuty = 0x0024, PwmSetDuty = 0x0025, PwmGetDuty = 0x0026, - GpioGet = 0x93, - I2cPassthrough = 0x9e, - ConsoleSnapshot = 0x97, - ConsoleRead = 0x98, + SetTabletMode = 0x0031, + GpioGet = 0x0093, + I2cPassthrough = 0x009e, + ConsoleSnapshot = 0x0097, + ConsoleRead = 0x0098, /// List the features supported by the firmware - GetFeatures = 0x0D, + GetFeatures = 0x000D, /// Force reboot, causes host reboot as well - Reboot = 0xD1, + Reboot = 0x00D1, /// Control EC boot - RebootEc = 0xD2, + RebootEc = 0x00D2, /// Get information about PD controller power - UsbPdPowerInfo = 0x103, + UsbPdPowerInfo = 0x0103, + RgbKbdSetColor = 0x013A, + RgbKbd = 0x013B, // Framework specific commands /// Configure the behavior of the flash notify @@ -85,6 +88,10 @@ pub enum EcCommands { ExpansionBayStatus = 0x3E1B, /// Get hardware diagnostics GetHwDiag = 0x3E1C, + /// Get gpu bay serial + GetGpuSerial = 0x3E1D, + /// Set gpu bay serial and program structure + ProgramGpuEeprom = 0x3E1F, } pub trait EcRequest { diff --git a/framework_lib/src/chromium_ec/commands.rs b/framework_lib/src/chromium_ec/commands.rs index 82057165..6bde4170 100644 --- a/framework_lib/src/chromium_ec/commands.rs +++ b/framework_lib/src/chromium_ec/commands.rs @@ -225,6 +225,24 @@ impl EcRequest for EcRequestPwmGetDuty { } } +pub enum TabletModeOverride { + Default = 0, + ForceTablet = 1, + ForceClamshell = 2, +} + +#[repr(C, packed)] +pub struct EcRequestSetTabletMode { + /// See TabletModeOverride + pub mode: u8, +} + +impl EcRequest<()> for EcRequestSetTabletMode { + fn command_id() -> EcCommands { + EcCommands::SetTabletMode + } +} + #[repr(C, packed)] pub struct EcRequestGpioGetV0 { pub name: [u8; 32], @@ -500,6 +518,37 @@ impl EcRequest for EcRequestUsbPdPowerInfo { } } +// TODO: Actually 128, but if we go above ~80 EC returns REQUEST_TRUNCATED +// At least when I use the portio driver +pub const EC_RGBKBD_MAX_KEY_COUNT: usize = 64; + +#[repr(C, packed)] +#[derive(Default, Clone, Copy, Debug)] +pub struct RgbS { + pub r: u8, + pub g: u8, + pub b: u8, +} + +#[repr(C, packed)] +pub struct EcRequestRgbKbdSetColor { + /// Specifies the starting key ID whose color is being changed + pub start_key: u8, + /// Specifies # of elements in color + pub length: u8, + /// RGB color data array of length up to MAX_KEY_COUNT + pub color: [RgbS; EC_RGBKBD_MAX_KEY_COUNT], +} + +#[repr(C, packed)] +pub struct EcResponseRgbKbdSetColor {} + +impl EcRequest for EcRequestRgbKbdSetColor { + fn command_id() -> EcCommands { + EcCommands::RgbKbdSetColor + } +} + // --- Framework Specific commands --- #[repr(C, packed)] @@ -754,6 +803,7 @@ pub struct EcRequestGetHwDiag {} pub struct EcResponseGetHwDiag { pub hw_diag: u32, pub bios_complete: u8, + pub device_complete: u8, } impl EcResponseGetHwDiag { @@ -826,10 +876,14 @@ pub enum FpLedBrightnessLevel { High = 0, Medium = 1, Low = 2, + UltraLow = 3, + /// Custom: Only get, never set + Custom = 0xFE, + Auto = 0xFF, } #[repr(C, packed)] -pub struct EcRequestFpLedLevelControl { +pub struct EcRequestFpLedLevelControlV0 { /// See enum FpLedBrightnessLevel pub set_level: u8, /// Boolean. >1 to get the level @@ -838,12 +892,88 @@ pub struct EcRequestFpLedLevelControl { #[repr(C, packed)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub struct EcResponseFpLedLevelControl { +pub struct EcResponseFpLedLevelControlV0 { + /// Current brightness, 1-100% + pub percentage: u8, +} + +impl EcRequest for EcRequestFpLedLevelControlV0 { + fn command_id() -> EcCommands { + EcCommands::FpLedLevelControl + } + fn command_version() -> u8 { + 0 + } +} + +#[repr(C, packed)] +pub struct EcRequestFpLedLevelControlV1 { + /// Percentage 1-100 + pub set_percentage: u8, + /// Boolean. >1 to get the level + pub get_level: u8, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct EcResponseFpLedLevelControlV1 { + /// Current brightness, 1-100% + pub percentage: u8, + /// Requested level. See enum FpLedBrightnessLevel pub level: u8, } -impl EcRequest for EcRequestFpLedLevelControl { +impl EcRequest for EcRequestFpLedLevelControlV1 { fn command_id() -> EcCommands { EcCommands::FpLedLevelControl } + fn command_version() -> u8 { + 1 + } +} + +#[repr(C, packed)] +pub struct EcRequestGetGpuSerial { + pub idx: u8, +} + +#[repr(C, packed)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct EcResponseGetGpuSerial { + pub idx: u8, + pub valid: u8, + pub serial: [u8; 20], +} + +impl EcRequest for EcRequestGetGpuSerial { + fn command_id() -> EcCommands { + EcCommands::GetGpuSerial + } +} + +#[repr(u8)] +pub enum SetGpuSerialMagic { + /// 7700S config magic value + WriteGPUConfig = 0x0D, + /// SSD config magic value + WriteSSDConfig = 0x55, +} + +#[repr(C, packed)] +pub struct EcRequestSetGpuSerial { + pub magic: u8, + pub idx: u8, + pub serial: [u8; 20], +} + +#[repr(C, packed)] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct EcResponseSetGpuSerial { + pub valid: u8, +} + +impl EcRequest for EcRequestSetGpuSerial { + fn command_id() -> EcCommands { + EcCommands::ProgramGpuEeprom + } } diff --git a/framework_lib/src/chromium_ec/mod.rs b/framework_lib/src/chromium_ec/mod.rs index f86fa839..acbd5c0c 100644 --- a/framework_lib/src/chromium_ec/mod.rs +++ b/framework_lib/src/chromium_ec/mod.rs @@ -33,6 +33,7 @@ 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_traits::FromPrimitive; @@ -125,9 +126,15 @@ pub fn has_mec() -> bool { return has_mec; } + // TODO: Should turn this around !matches!( smbios::get_platform().unwrap(), - Platform::Framework13Amd | Platform::Framework16 | Platform::IntelCoreUltra1 + Platform::Framework13Amd7080 + | Platform::Framework16Amd7080 + | Platform::IntelCoreUltra1 + | Platform::Framework13AmdAi300 + | Platform::Framework12IntelGen13 + | Platform::FrameworkDesktopAmdAiMax300 ) } @@ -307,6 +314,17 @@ impl CrosEc { Ok((limits.min_percentage, limits.max_percentage)) } + pub fn set_fp_led_percentage(&self, percentage: u8) -> EcResult<()> { + // Sending bytes manually because the Set command, as opposed to the Get command, + // does not return any data + let limits = &[percentage, 0x00]; + let data = self.send_command(EcCommands::FpLedLevelControl as u16, 1, limits)?; + + util::assert_win_len(data.len(), 0); + + Ok(()) + } + pub fn set_fp_led_level(&self, level: FpLedBrightnessLevel) -> EcResult<()> { // Sending bytes manually because the Set command, as opposed to the Get command, // does not return any data @@ -319,16 +337,33 @@ impl CrosEc { } /// Get fingerprint led brightness level - pub fn get_fp_led_level(&self) -> EcResult { - let res = EcRequestFpLedLevelControl { - set_level: 0xFF, + pub fn get_fp_led_level(&self) -> EcResult<(u8, Option)> { + let res = EcRequestFpLedLevelControlV1 { + set_percentage: 0xFF, get_level: 0xFF, } - .send_command(self)?; + .send_command(self); + + // If V1 does not exist, fall back + if let Err(EcError::Response(EcResponseStatus::InvalidVersion)) = res { + let res = EcRequestFpLedLevelControlV0 { + set_level: 0xFF, + get_level: 0xFF, + } + .send_command(self)?; + debug!("Current Brightness: {}%", res.percentage); + return Ok((res.percentage, None)); + } + + let res = res?; - debug!("Level Raw: {}", res.level); + debug!("Current Brightness: {}%", res.percentage); + debug!("Level Raw: {}", res.level); - Ok(res.level) + // TODO: can turn this into None and log + let level = FromPrimitive::from_u8(res.level) + .ok_or(EcError::DeviceError(format!("Invalid level {}", res.level)))?; + Ok((res.percentage, Some(level))) } /// Get the intrusion switch status (whether the chassis is open or not) @@ -381,7 +416,6 @@ impl CrosEc { } /// Check the current brightness of the keyboard backlight - /// pub fn get_keyboard_backlight(&self) -> EcResult { let kblight = EcRequestPwmGetDuty { pwm_type: PwmType::KbLight as u8, @@ -392,6 +426,13 @@ impl CrosEc { Ok((kblight.duty / (PWM_MAX_DUTY / 100)) as u8) } + /// Set tablet mode + pub fn set_tablet_mode(&self, mode: TabletModeOverride) { + let mode = mode as u8; + let res = EcRequestSetTabletMode { mode }.send_command(self); + print_err(res); + } + /// Overwrite RO and RW regions of EC flash /// MEC/Legacy EC /// | Start | End | Size | Region | @@ -749,6 +790,36 @@ impl CrosEc { res } + /// Get the GPU Serial + /// + pub fn get_gpu_serial(&self) -> EcResult { + let gpuserial: EcResponseGetGpuSerial = + EcRequestGetGpuSerial { idx: 0 }.send_command(self)?; + let serial: String = String::from_utf8(gpuserial.serial.to_vec()).unwrap(); + + if gpuserial.valid == 0 { + return Err(EcError::DeviceError("No valid GPU serial".to_string())); + } + + Ok(serial) + } + + /// Set the GPU Serial + /// + /// # Arguments + /// `newserial` - a string that is 18 characters long + pub fn set_gpu_serial(&self, magic: u8, newserial: String) -> EcResult { + let mut array_tmp: [u8; 20] = [0; 20]; + array_tmp[..18].copy_from_slice(newserial.as_bytes()); + let result = EcRequestSetGpuSerial { + magic, + idx: 0, + serial: array_tmp, + } + .send_command(self)?; + Ok(result.valid) + } + /// 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 { @@ -876,11 +947,28 @@ impl CrosEc { let mut request = EcRequestGpioGetV0 { name: [0; MAX_LEN] }; let end = MAX_LEN.min(name.len()); - request.name[..end].copy_from_slice(name[..end].as_bytes()); + request.name[..end].copy_from_slice(&name.as_bytes()[..end]); let res = request.send_command(self)?; Ok(res.val == 1) } + + pub fn rgbkbd_set_color(&self, start_key: u8, colors: Vec) -> EcResult<()> { + for (chunk, colors) in colors.chunks(EC_RGBKBD_MAX_KEY_COUNT).enumerate() { + let mut request = EcRequestRgbKbdSetColor { + start_key: start_key + ((chunk * EC_RGBKBD_MAX_KEY_COUNT) as u8), + length: colors.len() as u8, + color: [(); EC_RGBKBD_MAX_KEY_COUNT].map(|()| Default::default()), + }; + + for (i, color) in colors.iter().enumerate() { + request.color[i] = *color; + } + + let _res = request.send_command(self)?; + } + Ok(()) + } } #[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))] @@ -974,7 +1062,7 @@ pub fn print_err(something: EcResult) -> Option { } /// Which of the two EC images is currently in-use -#[derive(PartialEq)] +#[derive(PartialEq, Debug)] pub enum EcCurrentImage { Unknown = 0, RO = 1, diff --git a/framework_lib/src/chromium_ec/windows.rs b/framework_lib/src/chromium_ec/windows.rs index 4e026663..6cd2db33 100644 --- a/framework_lib/src/chromium_ec/windows.rs +++ b/framework_lib/src/chromium_ec/windows.rs @@ -25,31 +25,40 @@ lazy_static! { static ref DEVICE: Arc>> = Arc::new(Mutex::new(None)); } -fn init() { +fn init() -> bool { let mut device = DEVICE.lock().unwrap(); if (*device).is_some() { - return; + return true; } let path = w!(r"\\.\GLOBALROOT\Device\CrosEC"); - unsafe { - *device = Some(DevHandle( - CreateFileW( - path, - FILE_GENERIC_READ.0 | FILE_GENERIC_WRITE.0, - FILE_SHARE_READ | FILE_SHARE_WRITE, - None, - OPEN_EXISTING, - FILE_FLAGS_AND_ATTRIBUTES(0), - None, - ) - .unwrap(), - )); - } + let res = unsafe { + CreateFileW( + path, + FILE_GENERIC_READ.0 | FILE_GENERIC_WRITE.0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + FILE_FLAGS_AND_ATTRIBUTES(0), + None, + ) + }; + let handle = match res { + Ok(h) => h, + Err(err) => { + error!("Failed to find Windows driver. {:?}", err); + return false; + } + }; + + *device = Some(DevHandle(handle)); + true } pub fn read_memory(offset: u16, length: u16) -> EcResult> { - init(); + if !init() { + return Err(EcError::DeviceError("Failed to initialize".to_string())); + } let mut rm = CrosEcReadMem { offset: offset as u32, bytes: length as u32, diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index 30e54d2e..f75f23ef 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -1,11 +1,16 @@ //! Module to factor out commandline interaction //! This way we can use it in the regular OS commandline tool on Linux and Windows, //! as well as on the UEFI shell tool. +use clap::error::ErrorKind; use clap::Parser; +use clap::{arg, command, Arg, Args, FromArgMatches}; +use clap_num::maybe_hex; +use crate::chromium_ec::commands::SetGpuSerialMagic; use crate::chromium_ec::CrosEcDriverType; use crate::commandline::{ Cli, ConsoleArg, FpBrightnessArg, HardwareDeviceType, InputDeckModeArg, RebootEcArg, + TabletModeArg, }; /// Swiss army knife for Framework laptops @@ -98,7 +103,7 @@ struct ClapCli { /// Parse UEFI Capsule information from binary file #[arg(long)] - ho2_capsule: Option, + h2o_capsule: Option, /// Dump EC flash contents #[arg(long)] @@ -136,14 +141,35 @@ struct ClapCli { #[arg(long)] get_gpio: Option, - /// Get or set fingerprint LED brightness + /// Get or set fingerprint LED brightness level #[arg(long)] - fp_brightness: Option>, + fp_led_level: Option>, + + /// Get or set fingerprint LED brightness percentage + #[arg(long)] + fp_brightness: Option>, /// Set keyboard backlight percentage or get, if no value provided #[arg(long)] kblight: Option>, + /// Set the color of to . Multiple colors for adjacent keys can be set at once. + /// [ ...] + /// Example: 0 0xFF000 0x00FF00 0x0000FF + #[clap(num_args = 2..)] + #[arg(long, value_parser=maybe_hex::)] + rgbkbd: Vec, + + /// Set tablet mode override + #[clap(value_enum)] + #[arg(long)] + tablet_mode: Option, + + /// Enable/disable touchscreen + #[clap(value_enum)] + #[arg(long)] + touchscreen_enable: Option, + /// Get EC console, choose whether recent or to follow the output #[clap(value_enum)] #[arg(long)] @@ -185,31 +211,71 @@ struct ClapCli { /// Parse a list of commandline arguments and return the struct pub fn parse(args: &[String]) -> Cli { - let args = ClapCli::parse_from(args); + // Step 1 - Define args that can't be derived + let cli = command!() + .arg(Arg::new("fgd").long("flash-gpu-descriptor").num_args(2)) + .disable_version_flag(true); + // Step 2 - Define args from derived struct + let mut cli = ClapCli::augment_args(cli); + + // Step 3 - Parse args that can't be derived + let matches = cli.clone().get_matches_from(args); + let fgd = matches + .get_many::("fgd") + .unwrap_or_default() + .map(|v| v.as_str()) + .collect::>(); + let flash_gpu_descriptor = if !fgd.is_empty() { + let hex_magic = if let Some(hex_magic) = fgd[0].strip_prefix("0x") { + u8::from_str_radix(hex_magic, 16) + } else { + // Force parse error + u8::from_str_radix("", 16) + }; + + let magic = if let Ok(magic) = fgd[0].parse::() { + magic + } else if let Ok(hex_magic) = hex_magic { + hex_magic + } else if fgd[0].to_uppercase() == "GPU" { + SetGpuSerialMagic::WriteGPUConfig as u8 + } else if fgd[0].to_uppercase() == "SSD" { + SetGpuSerialMagic::WriteSSDConfig as u8 + } else { + cli.error( + ErrorKind::InvalidValue, + "First argument of --flash-gpu-descriptor must be an integer or one of: 'GPU', 'SSD'", + ) + .exit(); + }; + if fgd[1].len() != 18 { + cli.error( + ErrorKind::InvalidValue, + "Second argument of --flash-gpu-descriptor must be an 18 digit serial number", + ) + .exit(); + } + Some((magic, fgd[1].to_string())) + } else { + None + }; + + // Step 4 - Parse from derived struct + let args = ClapCli::from_arg_matches(&matches) + .map_err(|err| err.exit()) + .unwrap(); let pd_addrs = match args.pd_addrs.len() { 2 => Some((args.pd_addrs[0], args.pd_addrs[1])), 0 => None, - _ => { - // Actually unreachable, checked by clap - println!( - "Must provide exactly to PD Addresses. Provided: {:?}", - args.pd_addrs - ); - std::process::exit(1); - } + // Checked by clap + _ => unreachable!(), }; let pd_ports = match args.pd_ports.len() { 2 => Some((args.pd_ports[0], args.pd_ports[1])), 0 => None, - _ => { - // Actually unreachable, checked by clap - println!( - "Must provide exactly to PD Ports. Provided: {:?}", - args.pd_ports - ); - std::process::exit(1); - } + // Checked by clap + _ => unreachable!(), }; Cli { @@ -241,8 +307,8 @@ pub fn parse(args: &[String]) -> Cli { .capsule .map(|x| x.into_os_string().into_string().unwrap()), dump: args.dump.map(|x| x.into_os_string().into_string().unwrap()), - ho2_capsule: args - .ho2_capsule + h2o_capsule: args + .h2o_capsule .map(|x| x.into_os_string().into_string().unwrap()), dump_ec_flash: args .dump_ec_flash @@ -261,8 +327,12 @@ pub fn parse(args: &[String]) -> Cli { input_deck_mode: args.input_deck_mode, charge_limit: args.charge_limit, get_gpio: args.get_gpio, + fp_led_level: args.fp_led_level, fp_brightness: args.fp_brightness, kblight: args.kblight, + rgbkbd: args.rgbkbd, + tablet_mode: args.tablet_mode, + touchscreen_enable: args.touchscreen_enable, console: args.console, reboot_ec: args.reboot_ec, hash: args.hash.map(|x| x.into_os_string().into_string().unwrap()), @@ -278,6 +348,7 @@ pub fn parse(args: &[String]) -> Cli { // UEFI only - every command needs to implement a parameter to enable the pager paginate: false, info: args.info, + flash_gpu_descriptor, raw_command: vec![], } } diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index 9a28c66b..6a73d655 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -23,6 +23,8 @@ use std::io::prelude::*; #[cfg(feature = "rusb")] use crate::audio_card::check_synaptics_fw_version; use crate::built_info; +#[cfg(feature = "rusb")] +use crate::camera::check_camera_version; use crate::capsule; use crate::capsule_content::{ find_bios_version, find_ec_in_bios_cap, find_pd_in_bios_cap, find_retimer_version, @@ -35,6 +37,8 @@ use crate::chromium_ec; use crate::chromium_ec::commands::DeckStateMode; use crate::chromium_ec::commands::FpLedBrightnessLevel; use crate::chromium_ec::commands::RebootEcCmd; +use crate::chromium_ec::commands::RgbS; +use crate::chromium_ec::commands::TabletModeOverride; use crate::chromium_ec::EcResponseStatus; use crate::chromium_ec::{print_err, EcFlashType}; use crate::chromium_ec::{EcError, EcResult}; @@ -46,6 +50,10 @@ use crate::power; use crate::smbios; 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"))] +use crate::touchscreen; #[cfg(feature = "uefi")] use crate::uefi::enable_page_break; use crate::util; @@ -61,6 +69,14 @@ use crate::chromium_ec::{CrosEc, CrosEcDriverType, HardwareDeviceType}; #[cfg(feature = "uefi")] use core::prelude::rust_2021::derive; +#[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))] +#[derive(Clone, Debug, PartialEq)] +pub enum TabletModeArg { + Auto, + Tablet, + Laptop, +} + #[cfg_attr(not(feature = "uefi"), derive(clap::ValueEnum))] #[derive(Clone, Debug, PartialEq)] pub enum ConsoleArg { @@ -84,6 +100,8 @@ pub enum FpBrightnessArg { High, Medium, Low, + UltraLow, + Auto, } impl From for FpLedBrightnessLevel { fn from(w: FpBrightnessArg) -> FpLedBrightnessLevel { @@ -91,6 +109,8 @@ impl From for FpLedBrightnessLevel { FpBrightnessArg::High => FpLedBrightnessLevel::High, FpBrightnessArg::Medium => FpLedBrightnessLevel::Medium, FpBrightnessArg::Low => FpLedBrightnessLevel::Low, + FpBrightnessArg::UltraLow => FpLedBrightnessLevel::UltraLow, + FpBrightnessArg::Auto => FpLedBrightnessLevel::Auto, } } } @@ -138,7 +158,7 @@ pub struct Cli { pub ec_bin: Option, pub capsule: Option, pub dump: Option, - pub ho2_capsule: Option, + pub h2o_capsule: Option, pub dump_ec_flash: Option, pub flash_ec: Option, pub flash_ro_ec: Option, @@ -150,8 +170,12 @@ pub struct Cli { pub input_deck_mode: Option, pub charge_limit: Option>, pub get_gpio: Option, - pub fp_brightness: Option>, + pub fp_led_level: Option>, + pub fp_brightness: Option>, pub kblight: Option>, + pub rgbkbd: Vec, + pub tablet_mode: Option, + pub touchscreen_enable: Option, pub console: Option, pub reboot_ec: Option, pub hash: Option, @@ -160,6 +184,7 @@ pub struct Cli { pub has_mec: Option, pub help: bool, pub info: bool, + pub flash_gpu_descriptor: Option<(u8, String)>, // UEFI only pub allupdate: bool, pub paginate: bool, @@ -453,6 +478,14 @@ fn print_versions(ec: &CrosEc) { println!(" Unknown"); } } + #[cfg(feature = "rusb")] + let _ignore_err = check_camera_version(); + + #[cfg(feature = "hidapi")] + let _ignore_err = print_touchpad_fw_ver(); + + #[cfg(feature = "hidapi")] + let _ignore_err = touchscreen::print_fw_ver(); } fn print_esrt() { @@ -731,11 +764,28 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else { println!("Not found"); } + } else if let Some(maybe_led_level) = &args.fp_led_level { + print_err(handle_fp_led_level(&ec, *maybe_led_level)); } else if let Some(maybe_brightness) = &args.fp_brightness { print_err(handle_fp_brightness(&ec, *maybe_brightness)); } else if let Some(Some(kblight)) = args.kblight { assert!(kblight <= 100); ec.set_keyboard_backlight(kblight); + } else if !args.rgbkbd.is_empty() { + if args.rgbkbd.len() < 2 { + println!( + "Must provide at least 2 arguments. Provided only: {}", + args.rgbkbd.len() + ); + } else { + let start_key = args.rgbkbd[0] as u8; + let colors = args.rgbkbd[1..].iter().map(|color| RgbS { + r: ((color & 0x00FF0000) >> 16) as u8, + g: ((color & 0x0000FF00) >> 8) as u8, + b: (color & 0x000000FF) as u8, + }); + ec.rgbkbd_set_color(start_key, colors.collect()).unwrap(); + } } else if let Some(None) = args.kblight { print!("Keyboard backlight: "); if let Some(percentage) = print_err(ec.get_keyboard_backlight()) { @@ -743,6 +793,18 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { } else { println!("Unable to tell"); } + } else if let Some(tablet_arg) = &args.tablet_mode { + let mode = match tablet_arg { + TabletModeArg::Auto => TabletModeOverride::Default, + TabletModeArg::Tablet => TabletModeOverride::ForceTablet, + TabletModeArg::Laptop => TabletModeOverride::ForceClamshell, + }; + ec.set_tablet_mode(mode); + } else if let Some(_enable) = &args.touchscreen_enable { + #[cfg(any(feature = "hidapi", feature = "windows"))] + if touchscreen::enable_touch(*_enable).is_none() { + error!("Failed to enable/disable touch"); + } } else if let Some(console_arg) = &args.console { match console_arg { ConsoleArg::Follow => { @@ -891,7 +953,7 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!("Capsule is invalid."); } } - } else if let Some(capsule_path) = &args.ho2_capsule { + } else if let Some(capsule_path) = &args.h2o_capsule { #[cfg(feature = "uefi")] let data = crate::uefi::fs::shell_read_file(capsule_path); #[cfg(not(feature = "uefi"))] @@ -913,10 +975,16 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!(" BIOS Version: {:>18}", cap.version); } if let Some(ec_bin) = find_ec_in_bios_cap(&data) { + debug!("Found EC binary in BIOS capsule"); analyze_ec_fw(ec_bin); + } else { + debug!("Didn't find EC binary in BIOS capsule"); } if let Some(pd_bin) = find_pd_in_bios_cap(&data) { + debug!("Found PD binary in BIOS capsule"); analyze_ccgx_pd_fw(pd_bin); + } else { + debug!("Didn't find PD binary in BIOS capsule"); } } } else if let Some(dump_path) = &args.dump_ec_flash { @@ -948,6 +1016,13 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { println!(" Size: {:>20} KB", data.len() / 1024); hash(&data); } + } else if let Some(gpu_descriptor) = &args.flash_gpu_descriptor { + let res = ec.set_gpu_serial(gpu_descriptor.0, gpu_descriptor.1.to_ascii_uppercase()); + match res { + Ok(1) => println!("GPU Descriptor successfully written"), + Ok(x) => println!("GPU Descriptor write failed with status code: {}", x), + Err(err) => println!("GPU Descriptor write failed with error: {:?}", err), + } } 0 @@ -981,7 +1056,7 @@ Options: --ec-bin Parse versions from EC firmware binary file --capsule Parse UEFI Capsule information from binary file --dump Dump extracted UX capsule bitmap image to a file - --ho2-capsule Parse UEFI Capsule information from binary file + --h2o-capsule Parse UEFI Capsule information from binary file --dump-ec-flash Dump EC flash contents --flash-ec Flash EC with new firmware from file --flash-ro-ec Flash EC with new firmware from file @@ -992,10 +1067,12 @@ Options: --input-deck-mode Set input deck power mode [possible values: auto, off, on] (Framework 16 only) --charge-limit [] Get or set battery charge limit (Percentage number as arg, e.g. '100') --get-gpio Get GPIO value by name - --fp-brightness []Get or set fingerprint LED brightness level [possible values: high, medium, low] + --fp-led-level [] Get or set fingerprint LED brightness level [possible values: high, medium, low] + --fp-brightness []Get or set fingerprint LED brightness percentage --kblight [] Set keyboard backlight percentage or get, if no value provided --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. -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 @@ -1054,7 +1131,8 @@ fn selftest(ec: &CrosEc) -> Option<()> { if let Some(mem) = ec.dump_mem_region() { util::print_multiline_buffer(&mem, 0); } else { - println!(" Failed to read EC memory region") + println!(" Failed to read EC memory region"); + return None; } println!(" Checking EC memory mapped magic bytes"); @@ -1238,7 +1316,7 @@ fn analyze_ccgx_pd_fw(data: &[u8]) { ccgx::binary::print_fw(&versions.main_fw); return; } else { - println!("Failed to read versions") + println!("Failed to read PD versions") } } @@ -1247,13 +1325,13 @@ pub fn analyze_ec_fw(data: &[u8]) { if let Some(ver) = ec_binary::read_ec_version(data, true) { ec_binary::print_ec_version(&ver, true); } else { - println!("Failed to read version") + println!("Failed to read EC version") } // Readwrite firmware if let Some(ver) = ec_binary::read_ec_version(data, false) { ec_binary::print_ec_version(&ver, false); } else { - println!("Failed to read version") + println!("Failed to read EC version") } } @@ -1338,13 +1416,34 @@ fn handle_charge_limit(ec: &CrosEc, maybe_limit: Option) -> EcResult<()> { Ok(()) } -fn handle_fp_brightness(ec: &CrosEc, maybe_brightness: Option) -> EcResult<()> { +fn handle_fp_led_level(ec: &CrosEc, maybe_led_level: Option) -> EcResult<()> { + if let Some(led_level) = maybe_led_level { + ec.set_fp_led_level(led_level.into())?; + } + + let (brightness, level) = ec.get_fp_led_level()?; + // TODO: Rename to power button + println!("Fingerprint LED Brightness"); + if let Some(level) = level { + println!(" Requested: {:?}", level); + } + println!(" Brightness: {}%", brightness); + + Ok(()) +} + +fn handle_fp_brightness(ec: &CrosEc, maybe_brightness: Option) -> EcResult<()> { if let Some(brightness) = maybe_brightness { - ec.set_fp_led_level(brightness.into())?; + ec.set_fp_led_percentage(brightness)?; } - let level = ec.get_fp_led_level()?; - println!("Fingerprint LED Brightness: {:?}%", level); + let (brightness, level) = ec.get_fp_led_level()?; + // TODO: Rename to power button + println!("Fingerprint LED Brightness"); + if let Some(level) = level { + println!(" Requested: {:?}", level); + } + println!(" Brightness: {}%", brightness); Ok(()) } diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 121fc052..54a79fc2 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -1,4 +1,4 @@ -use alloc::string::String; +use alloc::string::{String, ToString}; use alloc::vec; use alloc::vec::Vec; @@ -9,10 +9,11 @@ use uefi::proto::shell_params::*; use uefi::table::boot::{OpenProtocolAttributes, OpenProtocolParams, SearchType}; use uefi::Identify; +use crate::chromium_ec::commands::SetGpuSerialMagic; use crate::chromium_ec::{CrosEcDriverType, HardwareDeviceType}; use crate::commandline::Cli; -use super::{ConsoleArg, FpBrightnessArg, InputDeckModeArg, RebootEcArg}; +use super::{ConsoleArg, FpBrightnessArg, InputDeckModeArg, RebootEcArg, TabletModeArg}; /// Get commandline arguments from UEFI environment pub fn get_args(boot_services: &BootServices) -> Vec { @@ -78,14 +79,18 @@ pub fn parse(args: &[String]) -> Cli { flash_rw_ec: None, capsule: None, dump: None, - ho2_capsule: None, + h2o_capsule: None, intrusion: false, inputmodules: false, input_deck_mode: None, charge_limit: None, get_gpio: None, + fp_led_level: None, fp_brightness: None, kblight: None, + rgbkbd: vec![], + tablet_mode: None, + touchscreen_enable: None, console: None, reboot_ec: None, hash: None, @@ -96,6 +101,7 @@ pub fn parse(args: &[String]) -> Cli { has_mec: None, test: false, help: false, + flash_gpu_descriptor: None, allupdate: false, info: false, raw_command: vec![], @@ -214,17 +220,75 @@ pub fn parse(args: &[String]) -> Cli { Some(None) }; found_an_option = true; - } else if arg == "--fp-brightness" { - cli.fp_brightness = if args.len() > i + 1 { - let fp_brightness_arg = &args[i + 1]; - if fp_brightness_arg == "high" { + } else if arg == "--rgbkbd" { + cli.rgbkbd = if args.len() > i + 2 { + let mut colors = Vec::::new(); + for color_i in i + 1..args.len() { + // TODO: Fail parsing instead of unwrap() + colors.push(args[color_i].parse::().unwrap()); + } + colors + } else { + println!("--rgbkbd requires at least 2 arguments, the start key and an RGB value"); + vec![] + } + } else if arg == "--tablet-mode" { + cli.tablet_mode = if args.len() > i + 1 { + let tablet_mode_arg = &args[i + 1]; + if tablet_mode_arg == "auto" { + Some(TabletModeArg::Auto) + } else if tablet_mode_arg == "tablet" { + Some(TabletModeArg::Tablet) + } else if tablet_mode_arg == "laptop" { + Some(TabletModeArg::Laptop) + } else { + println!( + "Need to provide a value for --tablet-mode: '{}'. {}", + args[i + 1], + "Must be one of: `auto`, `tablet` or `laptop`", + ); + None + } + } else { + println!("Need to provide a value for --tablet-mode. One of: `auto`, `tablet` or `laptop`"); + None + }; + found_an_option = true; + } else if arg == "--fp-led-level" { + cli.fp_led_level = if args.len() > i + 1 { + let fp_led_level_arg = &args[i + 1]; + if fp_led_level_arg == "high" { Some(Some(FpBrightnessArg::High)) - } else if fp_brightness_arg == "medium" { + } else if fp_led_level_arg == "medium" { Some(Some(FpBrightnessArg::Medium)) - } else if fp_brightness_arg == "low" { + } else if fp_led_level_arg == "low" { Some(Some(FpBrightnessArg::Low)) + } else if fp_led_level_arg == "ultra-low" { + Some(Some(FpBrightnessArg::UltraLow)) + } else if fp_led_level_arg == "auto" { + Some(Some(FpBrightnessArg::Auto)) } else { - println!("Invalid value for --fp-brightness: {}", fp_brightness_arg); + println!("Invalid value for --fp-led-level: {}", fp_led_level_arg); + None + } + } else { + Some(None) + }; + found_an_option = true; + } else if arg == "--fp-brightness" { + cli.fp_brightness = if args.len() > i + 1 { + if let Ok(fp_brightness_arg) = args[i + 1].parse::() { + if fp_brightness_arg == 0 || fp_brightness_arg > 100 { + println!( + "Invalid value for --fp-brightness: {}. Must be in the range of 1-100", + fp_brightness_arg + ); + None + } else { + Some(Some(fp_brightness_arg)) + } + } else { + println!("Invalid value for --fp-brightness. Must be in the range of 1-100"); None } } else { @@ -313,11 +377,11 @@ pub fn parse(args: &[String]) -> Cli { None }; found_an_option = true; - } else if arg == "--ho2-capsule" { - cli.ho2_capsule = if args.len() > i + 1 { + } else if arg == "--h2o-capsule" { + cli.h2o_capsule = if args.len() > i + 1 { Some(args[i + 1].clone()) } else { - println!("--ho2-capsule requires extra argument to denote input file"); + println!("--h2o-capsule requires extra argument to denote input file"); None }; found_an_option = true; @@ -452,6 +516,39 @@ pub fn parse(args: &[String]) -> Cli { println!("Need to provide a value for --console. Possible values: bios, ec, pd0, pd1, rtm01, rtm23, ac-left, ac-right"); None }; + } else if arg == "--flash-gpu-descriptor" { + cli.flash_gpu_descriptor = if args.len() > i + 2 { + let sn = args[i + 2].to_string(); + let magic = &args[i + 1]; + + let hex_magic = if let Some(hex_magic) = magic.strip_prefix("0x") { + u8::from_str_radix(hex_magic, 16) + } else { + // Force parse error + u8::from_str_radix("", 16) + }; + + if let Ok(magic) = magic.parse::() { + Some((magic, sn)) + } else if let Ok(hex_magic) = hex_magic { + Some((hex_magic, sn)) + } else if magic.to_uppercase() == "GPU" { + Some((SetGpuSerialMagic::WriteGPUConfig as u8, sn)) + } else if magic.to_uppercase() == "SSD" { + Some((SetGpuSerialMagic::WriteSSDConfig as u8, sn)) + } else { + println!( + "Invalid values for --flash_gpu_descriptor: '{} {}'. Must be u8, 18 character string.", + args[i + 1], + args[i + 2] + ); + None + } + } else { + println!("Need to provide a value for --flash_gpu_descriptor. TYPE_MAGIC SERIAL"); + None + }; + found_an_option = true; } } diff --git a/framework_lib/src/esrt/mod.rs b/framework_lib/src/esrt/mod.rs index 521c6352..feb98a2a 100644 --- a/framework_lib/src/esrt/mod.rs +++ b/framework_lib/src/esrt/mod.rs @@ -144,7 +144,7 @@ pub fn match_guid_kind(guid: &Guid) -> FrameworkGuidKind { } } -#[repr(packed)] +#[repr(C, packed)] struct _Esrt { resource_count: u32, resource_count_max: u32, diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index 40be376d..b0c92e4d 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -14,6 +14,14 @@ extern crate log; #[cfg(feature = "rusb")] pub mod audio_card; +#[cfg(feature = "rusb")] +pub mod camera; +#[cfg(feature = "hidapi")] +pub mod touchpad; +#[cfg(any(feature = "hidapi", feature = "windows"))] +pub mod touchscreen; +#[cfg(feature = "windows")] +pub mod touchscreen_win; #[cfg(feature = "uefi")] #[macro_use] diff --git a/framework_lib/src/power.rs b/framework_lib/src/power.rs index 1f8835a7..d69dda24 100644 --- a/framework_lib/src/power.rs +++ b/framework_lib/src/power.rs @@ -54,12 +54,13 @@ const EC_MEMMAP_BATT_SERIAL: u16 = 0x70; // Battery Serial Number String const EC_MEMMAP_BATT_TYPE: u16 = 0x78; // Battery Type String const EC_MEMMAP_ALS: u16 = 0x80; // ALS readings in lux (2 X 16 bits) // Unused 0x84 - 0x8f -const _EC_MEMMAP_ACC_STATUS: u16 = 0x90; // Accelerometer status (8 bits ) - // Unused 0x91 -const _EC_MEMMAP_ACC_DATA: u16 = 0x92; // Accelerometers data 0x92 - 0x9f - // 0x92: u16Lid Angle if available, LID_ANGLE_UNRELIABLE otherwise - // 0x94 - 0x99: u161st Accelerometer - // 0x9a - 0x9f: u162nd Accelerometer +const EC_MEMMAP_ACC_STATUS: u16 = 0x90; // Accelerometer status (8 bits ) + // Unused 0x91 +const EC_MEMMAP_ACC_DATA: u16 = 0x92; // Accelerometers data 0x92 - 0x9f + // 0x92: u16Lid Angle if available, LID_ANGLE_UNRELIABLE otherwise + // 0x94 - 0x99: u161st Accelerometer + // 0x9a - 0x9f: u162nd Accelerometer +const LID_ANGLE_UNRELIABLE: u16 = 500; const _EC_MEMMAP_GYRO_DATA: u16 = 0xa0; // Gyroscope data 0xa0 - 0xa5 // Unused 0xa6 - 0xdf @@ -163,6 +164,53 @@ impl From for ReducedPowerInfo { } } +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccelData { + pub x: i16, + pub y: i16, + pub z: i16, +} +impl From> for AccelData { + fn from(t: Vec) -> Self { + Self { + x: i16::from_le_bytes([t[0], t[1]]), + y: i16::from_le_bytes([t[2], t[3]]), + z: i16::from_le_bytes([t[4], t[5]]), + } + } +} +impl fmt::Display for AccelData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let quarter: f32 = 0xFFFF as f32 / 4.0; + 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) + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum LidAngle { + Angle(u16), + Unreliable, +} +impl From for LidAngle { + fn from(a: u16) -> Self { + match a { + LID_ANGLE_UNRELIABLE => Self::Unreliable, + _ => Self::Angle(a), + } + } +} +impl fmt::Display for LidAngle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Angle(deg) => write!(f, "{}", deg), + Self::Unreliable => write!(f, "Unreliable"), + } + } +} + fn read_string(ec: &CrosEc, address: u16) -> String { let bytes = ec.read_memory(address, EC_MEMMAP_TEXT_MAX).unwrap(); String::from_utf8_lossy(bytes.as_slice()).replace(['\0'], "") @@ -197,9 +245,60 @@ pub fn get_als_reading(ec: &CrosEc) -> Option { Some(u32::from_le_bytes([als[0], als[1], als[2], als[3]])) } +pub fn get_accel_data(ec: &CrosEc) -> (AccelData, AccelData, LidAngle) { + // bit 4 = busy + // bit 7 = present + // #define EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK 0x0f + let _acc_status = ec.read_memory(EC_MEMMAP_ACC_STATUS, 0x01).unwrap()[0]; + // While busy, keep reading + + let lid_angle = ec.read_memory(EC_MEMMAP_ACC_DATA, 0x02).unwrap(); + let lid_angle = u16::from_le_bytes([lid_angle[0], lid_angle[1]]); + 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(); + + // TODO: Make sure we got a new sample + // println!(" Status Bit: {} 0x{:X}", acc_status, acc_status); + // println!(" Present: {}", (acc_status & 0x80) > 0); + // println!(" Busy: {}", (acc_status & 0x8) > 0); + ( + AccelData::from(accel_1), + AccelData::from(accel_2), + LidAngle::from(lid_angle), + ) +} + pub fn print_sensors(ec: &CrosEc) { let als_int = get_als_reading(ec).unwrap(); println!("ALS: {:>4} Lux", als_int); + + // bit 4 = busy + // bit 7 = present + // #define EC_MEMMAP_ACC_STATUS_SAMPLE_ID_MASK 0x0f + let acc_status = ec.read_memory(EC_MEMMAP_ACC_STATUS, 0x01).unwrap()[0]; + // While busy, keep reading + + let lid_angle = ec.read_memory(EC_MEMMAP_ACC_DATA, 0x02).unwrap(); + let lid_angle = u16::from_le_bytes([lid_angle[0], lid_angle[1]]); + 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); + } + 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) { @@ -214,20 +313,54 @@ pub fn print_thermal(ec: &CrosEc) { println!(" F75303_DDR: {:>4}", TempSensor::from(temps[2])); println!(" Battery: {:>4}", TempSensor::from(temps[3])); println!(" PECI: {:>4}", TempSensor::from(temps[4])); - println!(" F57397_VCCGT: {:>4}", TempSensor::from(temps[5])); + if matches!( + platform, + Some(Platform::IntelGen12) | Some(Platform::IntelGen13) + ) { + println!(" F57397_VCCGT: {:>4}", TempSensor::from(temps[5])); + } } - Some(Platform::Framework13Amd | Platform::Framework16) => { + + Some(Platform::IntelCoreUltra1) => { + println!(" F75303_Local: {:>4}", TempSensor::from(temps[0])); + println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1])); + println!(" Battery: {:>4}", TempSensor::from(temps[2])); + println!(" F75303_DDR: {:>4}", TempSensor::from(temps[3])); + println!(" PECI: {:>4}", TempSensor::from(temps[4])); + } + + Some(Platform::Framework12IntelGen13) => { + println!(" F75303_CPU: {:>4}", TempSensor::from(temps[0])); + println!(" F75303_Skin: {:>4}", TempSensor::from(temps[1])); + println!(" F75303_Local: {:>4}", TempSensor::from(temps[2])); + println!(" Battery: {:>4}", TempSensor::from(temps[3])); + println!(" PECI: {:>4}", TempSensor::from(temps[4])); + } + + Some( + Platform::Framework13Amd7080 + | Platform::Framework13AmdAi300 + | Platform::Framework16Amd7080, + ) => { println!(" F75303_Local: {:>4}", TempSensor::from(temps[0])); println!(" F75303_CPU: {:>4}", TempSensor::from(temps[1])); println!(" F75303_DDR: {:>4}", TempSensor::from(temps[2])); println!(" APU: {:>4}", TempSensor::from(temps[3])); - if matches!(platform, Some(Platform::Framework16)) { + if matches!(platform, Some(Platform::Framework16Amd7080)) { println!(" dGPU VR: {:>4}", TempSensor::from(temps[4])); println!(" dGPU VRAM: {:>4}", TempSensor::from(temps[5])); println!(" dGPU AMB: {:>4}", TempSensor::from(temps[6])); println!(" dGPU temp: {:>4}", TempSensor::from(temps[7])); } } + + Some(Platform::FrameworkDesktopAmdAiMax300) => { + println!(" F75303_APU: {:>4}", TempSensor::from(temps[0])); + println!(" F75303_DDR: {:>4}", TempSensor::from(temps[1])); + println!(" F75303_AMB: {:>4}", TempSensor::from(temps[2])); + println!(" APU: {:>4}", TempSensor::from(temps[3])); + } + _ => { println!(" Temp 0: {:>4}", TempSensor::from(temps[0])); println!(" Temp 1: {:>4}", TempSensor::from(temps[1])); @@ -495,7 +628,7 @@ pub fn get_pd_info(ec: &CrosEc, ports: u8) -> Vec> { } pub fn get_and_print_pd_info(ec: &CrosEc) { - let fl16 = Some(crate::util::Platform::Framework16) == get_platform(); + let fl16 = Some(crate::util::Platform::Framework16Amd7080) == get_platform(); let ports = 4; // All our platforms have 4 PD ports so far let infos = get_pd_info(ec, ports); for (port, info) in infos.iter().enumerate().take(ports.into()) { diff --git a/framework_lib/src/smbios.rs b/framework_lib/src/smbios.rs index 9c0ab1c7..b25ed153 100644 --- a/framework_lib/src/smbios.rs +++ b/framework_lib/src/smbios.rs @@ -5,7 +5,8 @@ use std::prelude::v1::*; #[cfg(all(not(feature = "uefi"), not(target_os = "freebsd")))] use std::io::ErrorKind; -use crate::util::{Config, Platform}; +use crate::util::Config; +pub use crate::util::Platform; use num_derive::FromPrimitive; use smbioslib::*; #[cfg(feature = "uefi")] @@ -106,7 +107,7 @@ pub struct Smbios3 { } #[cfg(target_os = "freebsd")] -#[repr(packed)] +#[repr(C, packed)] pub struct Smbios { pub anchor: [u8; 4], pub checksum: u8, @@ -262,10 +263,13 @@ pub fn get_platform() -> Option { "Laptop" => Some(Platform::IntelGen11), "Laptop (12th Gen Intel Core)" => Some(Platform::IntelGen12), "Laptop (13th Gen Intel Core)" => Some(Platform::IntelGen13), - "Laptop 13 (AMD Ryzen 7040Series)" => Some(Platform::Framework13Amd), - "Laptop 13 (AMD Ryzen 7040 Series)" => Some(Platform::Framework13Amd), + "Laptop 13 (AMD Ryzen 7040Series)" => Some(Platform::Framework13Amd7080), + "Laptop 13 (AMD Ryzen 7040 Series)" => Some(Platform::Framework13Amd7080), + "Laptop 13 (AMD Ryzen AI 300 Series)" => Some(Platform::Framework13AmdAi300), + "Laptop 12 (13th Gen Intel Core)" => Some(Platform::Framework12IntelGen13), "Laptop 13 (Intel Core Ultra Series 1)" => Some(Platform::IntelCoreUltra1), - "Laptop 16 (AMD Ryzen 7040 Series)" => Some(Platform::Framework16), + "Laptop 16 (AMD Ryzen 7040 Series)" => Some(Platform::Framework16Amd7080), + "Desktop (AMD Ryzen AI Max 300 Series)" => Some(Platform::FrameworkDesktopAmdAiMax300), _ => None, }; diff --git a/framework_lib/src/touchpad.rs b/framework_lib/src/touchpad.rs new file mode 100644 index 00000000..72bc30c8 --- /dev/null +++ b/framework_lib/src/touchpad.rs @@ -0,0 +1,121 @@ +use hidapi::{HidApi, HidDevice, HidError}; +use log::Level; + +pub const PIX_VID: u16 = 0x093A; +pub const P274_REPORT_ID: u8 = 0x43; +pub const P239_REPORT_ID: u8 = 0x42; + +fn read_byte(device: &HidDevice, report_id: u8, addr: u8) -> Result { + device.send_feature_report(&[report_id, addr, 0x10, 0])?; + + let mut buf = [0u8; 4]; + buf[0] = report_id; + + device.get_feature_report(&mut buf)?; + Ok(buf[3]) +} + +fn read_239_ver(device: &HidDevice) -> Result { + Ok(u16::from_le_bytes([ + read_byte(device, P239_REPORT_ID, 0x16)?, + read_byte(device, P239_REPORT_ID, 0x18)?, + ])) +} + +fn read_274_ver(device: &HidDevice) -> Result { + Ok(u16::from_le_bytes([ + read_byte(device, P274_REPORT_ID, 0xb2)?, + read_byte(device, P274_REPORT_ID, 0xb3)?, + ])) +} + +pub fn print_touchpad_fw_ver() -> Result<(), HidError> { + debug!("Looking for touchpad HID device"); + match HidApi::new() { + Ok(api) => { + for dev_info in api.device_list() { + let vid = dev_info.vendor_id(); + let pid = dev_info.product_id(); + let usage_page = dev_info.usage_page(); + let hid_ver = dev_info.release_number(); + + debug!( + " Found {:04X}:{:04X} (Usage Page {:04X})", + vid, pid, usage_page + ); + if vid != PIX_VID || (pid != 0x0274 && pid != 0x0239) { + debug!( + " Skipping VID:PID. Expected {:04X}:{:04X}/{:04X}", + PIX_VID, 0x0274, 0x0239 + ); + continue; + } + if usage_page != 0xFF00 { + debug!(" Skipping usage page. Expected {:04X}", 0xFF00); + continue; + } + + debug!(" Found matching touchpad HID device"); + let device = dev_info.open_device(&api).unwrap(); + + println!("Touchpad"); + println!(" IC Type: {:04X}", pid); + + let ver = match pid { + 0x0239 => format!("{:04X}", read_239_ver(&device)?), + 0x0274 => format!("{:04X}", read_274_ver(&device)?), + _ => "Unsupported".to_string(), + }; + println!(" Firmware Version: v{}", ver); + + if log_enabled!(Level::Debug) { + println!(" Config space 1"); + print!(" "); + for x in 0..16 { + print!("0{:X} ", x); + } + println!(); + for y in 0..16 { + print!("{:X}0 ", y); + for x in 0..16 { + print!("{:02X} ", read_byte(&device, 0x42, x + 16 * y)?); + } + println!(); + } + println!(" Config space 2"); + print!(" "); + for x in 0..16 { + print!("0{:X} ", x); + } + println!(); + for y in 0..16 { + print!("{:X}0 ", y); + for x in 0..16 { + print!("{:02X} ", read_byte(&device, 0x43, x + 16 * y)?); + } + println!(); + } + } + + // Linux does not expose a useful version number for I2C HID devices + #[cfg(target_os = "linux")] + debug!(" HID Version {:04X}", hid_ver); + #[cfg(not(target_os = "linux"))] + if ver != format!("{:04X}", hid_ver) { + println!(" HID Version v{:04X}", hid_ver); + } else if log_enabled!(Level::Debug) { + println!(" HID Version v{:04X}", hid_ver); + } + + // If we found one, there's no need to look for more + return Ok(()); + } + } + Err(e) => { + eprintln!("Failed to open hidapi. Error: {e}"); + return Err(e); + } + }; + + Ok(()) +} diff --git a/framework_lib/src/touchscreen.rs b/framework_lib/src/touchscreen.rs new file mode 100644 index 00000000..c155c173 --- /dev/null +++ b/framework_lib/src/touchscreen.rs @@ -0,0 +1,156 @@ +use hidapi::{HidApi, HidDevice}; + +#[cfg(target_os = "windows")] +use crate::touchscreen_win; + +pub const ILI_VID: u16 = 0x222A; +pub const ILI_PID: u16 = 0x5539; +pub const USI_BITMAP: u8 = 1 << 1; +pub const MPP_BITMAP: u8 = 1 << 2; + +struct HidapiTouchScreen { + device: HidDevice, +} + +impl TouchScreen for HidapiTouchScreen { + fn open_device() -> Option { + debug!("Looking for touchscreen HID device"); + match HidApi::new() { + Ok(api) => { + for dev_info in api.device_list() { + let vid = dev_info.vendor_id(); + let pid = dev_info.product_id(); + let usage_page = dev_info.usage_page(); + if vid != ILI_VID { + trace!(" Skipping VID:PID. Expected {:04X}:*", ILI_VID); + continue; + } + debug!( + " Found {:04X}:{:04X} (Usage Page {:04X})", + vid, pid, usage_page + ); + if usage_page != 0xFF00 { + debug!(" Skipping usage page. Expected {:04X}", 0xFF00); + continue; + } + if pid != ILI_PID { + debug!(" Warning: PID is {:04X}, expected {:04X}", pid, ILI_PID); + } + + debug!(" Found matching touchscreen HID device"); + debug!(" Path: {:?}", dev_info.path()); + debug!(" IC Type: {:04X}", pid); + + // Unwrapping because if we can enumerate it, we should be able to open it + let device = dev_info.open_device(&api).unwrap(); + debug!(" Opened device."); + + return Some(HidapiTouchScreen { device }); + } + } + Err(e) => { + error!("Failed to open hidapi. Error: {e}"); + } + }; + + None + } + + fn send_message(&self, message_id: u8, read_len: usize, data: Vec) -> Option> { + let report_id = 0x03; + let data_len = data.len(); + let mut msg = [0u8; 0x40]; + msg[0] = report_id; + msg[1] = 0xA3; + msg[2] = data_len as u8; + msg[3] = read_len as u8; + msg[4] = message_id; + for (i, b) in data.into_iter().enumerate() { + msg[5 + i] = b; + } + + // Not sure why, but on Windows we just have to write an output report + // HidApiError { message: "HidD_SetFeature: (0x00000057) The parameter is incorrect." } + // Still doesn't work on Windows. Need to write a byte more than the buffer is long + #[cfg(target_os = "windows")] + let send_feature_report = false; + #[cfg(not(target_os = "windows"))] + let send_feature_report = true; + + if send_feature_report { + debug!(" send_feature_report {:X?}", msg); + self.device.send_feature_report(&msg).ok()?; + } else { + debug!(" Writing {:X?}", msg); + self.device.write(&msg).ok()?; + }; + + if read_len == 0 { + return Some(vec![]); + } + + let msg_len = 3 + data_len; + let mut buf: [u8; 0x40] = [0; 0x40]; + debug!(" Reading"); + let res = self.device.read(&mut buf); + debug!(" res: {:?}", res); + debug!(" Read buf: {:X?}", buf); + Some(buf[msg_len..msg_len + read_len].to_vec()) + } +} + +pub trait TouchScreen { + fn open_device() -> Option + where + Self: std::marker::Sized; + fn send_message(&self, message_id: u8, read_len: usize, data: Vec) -> Option>; + + fn check_fw_version(&self) -> Option<()> { + println!("Touchscreen"); + let res = self.send_message(0x42, 3, vec![0])?; + let ver = res + .iter() + .skip(1) + .fold(format!("{:02X}", res[0]), |acc, &x| { + acc + "." + &format!("{:02X}", x) + }); + // Expecting 06.00.0A + debug!(" Protocol Version: v{}", ver); + + let res = self.send_message(0x40, 8, vec![0])?; + let ver = res + .iter() + .skip(1) + .fold(res[0].to_string(), |acc, &x| acc + "." + &x.to_string()); + println!(" Firmware Version: v{}", ver); + + let res = self.send_message(0x20, 16, vec![0])?; + println!(" USI Protocol: {:?}", (res[15] & USI_BITMAP) > 0); + println!(" MPP Protocol: {:?}", (res[15] & MPP_BITMAP) > 0); + + Some(()) + } + + fn enable_touch(&self, enable: bool) -> Option<()> { + self.send_message(0x38, 0, vec![!enable as u8, 0x00])?; + Some(()) + } +} + +pub fn print_fw_ver() -> Option<()> { + #[cfg(target_os = "windows")] + let device = touchscreen_win::NativeWinTouchScreen::open_device()?; + #[cfg(not(target_os = "windows"))] + let device = HidapiTouchScreen::open_device()?; + + device.check_fw_version() +} + +pub fn enable_touch(enable: bool) -> Option<()> { + #[cfg(target_os = "windows")] + let device = touchscreen_win::NativeWinTouchScreen::open_device()?; + #[cfg(not(target_os = "windows"))] + let device = HidapiTouchScreen::open_device()?; + + device.enable_touch(enable) +} diff --git a/framework_lib/src/touchscreen_win.rs b/framework_lib/src/touchscreen_win.rs new file mode 100644 index 00000000..2a1be801 --- /dev/null +++ b/framework_lib/src/touchscreen_win.rs @@ -0,0 +1,101 @@ +use crate::touchscreen::TouchScreen; +#[allow(unused_imports)] +use windows::{ + core::*, + Win32::{ + Devices::HumanInterfaceDevice::*, + Devices::Properties::*, + Foundation::*, + Storage::FileSystem::*, + System::Threading::ResetEvent, + System::IO::{CancelIoEx, DeviceIoControl}, + System::{Ioctl::*, IO::*}, + }, +}; + +pub struct NativeWinTouchScreen { + handle: HANDLE, +} + +impl TouchScreen for NativeWinTouchScreen { + fn open_device() -> Option { + // TODO: I don't know if this might be different on other systems + // Should enumerate and find the right one + // See: https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/finding-and-opening-a-hid-collection + let path = + w!(r"\\?\HID#ILIT2901&Col03#5&357cbf85&0&0002#{4d1e55b2-f16f-11cf-88cb-001111000030}"); + + let res = unsafe { + CreateFileW( + path, + FILE_GENERIC_WRITE.0 | FILE_GENERIC_READ.0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + // hidapi-rs is using FILE_FLAG_OVERLAPPED but it doesn't look like we need that + FILE_FLAGS_AND_ATTRIBUTES(0), + None, + ) + }; + let handle = match res { + Ok(h) => h, + Err(err) => { + error!("Failed to open device {:?}", err); + return None; + } + }; + + debug!("Opened {:?}", path); + + Some(NativeWinTouchScreen { handle }) + } + + fn send_message(&self, message_id: u8, read_len: usize, data: Vec) -> Option> { + let report_id = 0x03; + let data_len = data.len(); + let mut msg = [0u8; 0x40]; + let msg_len = 3 + data_len; + msg[0] = report_id; + msg[1] = 0xA3; + msg[2] = data_len as u8; + msg[3] = read_len as u8; + msg[4] = message_id; + for (i, b) in data.into_iter().enumerate() { + msg[5 + i] = b; + } + + let mut buf = [0u8; 0x40]; + buf[0] = report_id; + + unsafe { + debug!(" HidD_SetOutputReport {:X?}", msg); + let success = HidD_SetOutputReport( + self.handle, + // Microsoft docs says that the first byte of the message has to be the report ID. + // This is normal with HID implementations. + // But it seems on Windows (at least for this device's firmware) we have to set the + // length as one more than the buffer is long. + // Otherwise no data is returned in the read call later. + msg.as_mut_ptr() as _, + msg.len() as u32 + 1, + ); + debug!(" Success: {}", success); + + if read_len == 0 { + return Some(vec![]); + } + + let mut bytes_read = 0; + debug!(" ReadFile"); + // HidD_GetFeature doesn't work, have to use ReadFile + // Microsoft does recommend that + // https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/obtaining-hid-reports + let res = ReadFile(self.handle, Some(&mut buf), Some(&mut bytes_read), None); + debug!(" Success: {:?}, Bytes: {}", res, bytes_read); + debug!(" Read buf: {:X?}", buf); + debug!(" Read msg: {:X?}", msg); + } + + Some(buf[msg_len..msg_len + read_len].to_vec()) + } +} diff --git a/framework_lib/src/uefi/mod.rs b/framework_lib/src/uefi/mod.rs index 2d6ec747..71d85b44 100644 --- a/framework_lib/src/uefi/mod.rs +++ b/framework_lib/src/uefi/mod.rs @@ -99,7 +99,7 @@ pub fn enable_page_break() { } } -#[repr(packed)] +#[repr(C, packed)] pub struct Smbios { pub anchor: [u8; 4], pub checksum: u8, diff --git a/framework_lib/src/util.rs b/framework_lib/src/util.rs index 18a5a577..3fa8f505 100644 --- a/framework_lib/src/util.rs +++ b/framework_lib/src/util.rs @@ -17,6 +17,8 @@ use crate::smbios; #[derive(Debug, PartialEq, Clone, Copy)] pub enum Platform { + /// Framework 12 + Framework12IntelGen13, /// Framework 13 - Intel 11th Gen, Codenamed TigerLake IntelGen11, /// Framework 13 - Intel 11th Gen, Codenamed AlderLake @@ -25,10 +27,14 @@ pub enum Platform { IntelGen13, /// Framework 13 - Intel Core Ultra Series 1, Codenamed MeteorLake IntelCoreUltra1, - /// Framework 13 - AMD Ryzen - Framework13Amd, - /// Framework 16 - Framework16, + /// Framework 13 - AMD Ryzen 7080 Series + Framework13Amd7080, + /// Framework 13 - AMD Ryzen AI 300 Series + Framework13AmdAi300, + /// Framework 16 - AMD Ryzen 7080 Series + Framework16Amd7080, + /// Framework Desktop - AMD Ryzen AI Max 300 + FrameworkDesktopAmdAiMax300, /// Generic Framework device /// pd_addrs, pd_ports, has_mec GenericFramework((u16, u16), (u8, u8), bool), diff --git a/framework_tool/Cargo.toml b/framework_tool/Cargo.toml index f4d3bfa9..d5a8958d 100644 --- a/framework_tool/Cargo.toml +++ b/framework_tool/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_tool" -version = "0.2.1" +version = "0.3.0" edition = "2021" [features] @@ -14,4 +14,5 @@ path = "../framework_lib" default-features = false [build-dependencies] +# Note: Only takes effect in release builds static_vcruntime = "2.0" diff --git a/framework_uefi/Cargo.toml b/framework_uefi/Cargo.toml index a8e579b0..8b7755c0 100644 --- a/framework_uefi/Cargo.toml +++ b/framework_uefi/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "framework_uefi" -version = "0.2.1" +version = "0.3.0" edition = "2021" # Minimum Supported Rust Version rust-version = "1.74" diff --git a/rgbkbd.py b/rgbkbd.py new file mode 100755 index 00000000..9088b85e --- /dev/null +++ b/rgbkbd.py @@ -0,0 +1,37 @@ +#!/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 \ +# (./rgbkbd.py | string split ' ') + +BRIGHTNESS = 1 +RED = int(0xFF * BRIGHTNESS) << 16 +GREEN = int(0xFF * BRIGHTNESS) << 8 +BLUE = int(0xFF * BRIGHTNESS) +CYAN = GREEN + BLUE +YELLOW = RED + GREEN +PURPLE = BLUE + RED +WHITE = RED + GREEN + BLUE + +grid_4x4 = [ + [ YELLOW, RED, RED, RED, YELLOW ], + [ RED, WHITE, GREEN, WHITE, RED ], + [ RED, GREEN, BLUE, GREEN, RED ], + [ RED, WHITE, GREEN, WHITE, RED ], + [ YELLOW, RED, RED, RED, YELLOW ], +] + +fan_8leds = [[ + # WHITE, CYAN, BLUE, GREEN, PURPLE, RED, YELLOW, WHITE + RED, RED, RED, RED, + GREEN, GREEN, GREEN, GREEN +]] + +# colors = grid_4x4 +colors = fan_8leds + +print('--rgbkbd 0', end='') +for row in colors: + for col in row: + print(' ', end='') + print(col, end='') 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