From 6c57b1e168fb049b04dc3998735fa630cb1b1dd1 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Fri, 21 Mar 2025 23:22:40 +0800 Subject: [PATCH 1/3] touchscreen: Dump firmware version and protocol ``` > framework_tool --versions [...] Touchscreen Firmware Version: v7.0.0.4.0.0.0.0 USI Protocol: false MPP Protocol: true ``` Signed-off-by: Daniel Schaefer --- README.md | 6 +- framework_lib/Cargo.toml | 9 +- framework_lib/src/commandline/mod.rs | 5 + framework_lib/src/lib.rs | 4 + framework_lib/src/touchscreen.rs | 142 +++++++++++++++++++++++++++ framework_lib/src/touchscreen_win.rs | 101 +++++++++++++++++++ 6 files changed, 265 insertions(+), 2 deletions(-) create mode 100644 framework_lib/src/touchscreen.rs create mode 100644 framework_lib/src/touchscreen_win.rs diff --git a/README.md b/README.md index 771675a2..c97ad722 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ see the [Support Matrices](support-matrices.md). - [x] PD - [x] ME (Only on Linux) - [x] Retimer - - [x] Touchpad (Linux and Windows) + - [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`) @@ -69,6 +70,7 @@ 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] Disable/Enable touchscreen (`--touchscreen-enable`) ###### Communication with Embedded Controller @@ -177,6 +179,8 @@ Options: --intrusion Show status of intrusion switch --inputmodules Show status of the input modules (Framework 16 only) --kblight [] Set keyboard backlight percentage or get, if no value provided + --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] --driver Select which driver is used. By default portio is used [possible values: portio, cros-ec, windows] -t, --test Run self-test to check if interaction with EC is possible diff --git a/framework_lib/Cargo.toml b/framework_lib/Cargo.toml index fb5096ca..b0d41eec 100644 --- a/framework_lib/Cargo.toml +++ b/framework_lib/Cargo.toml @@ -63,7 +63,7 @@ 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 } +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" } @@ -89,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/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index fa284951..ef38f4fa 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -52,6 +52,8 @@ 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(feature = "hidapi")] +use crate::touchscreen::print_touchscreen_fw_ver; #[cfg(feature = "uefi")] use crate::uefi::enable_page_break; use crate::util; @@ -479,6 +481,9 @@ fn print_versions(ec: &CrosEc) { #[cfg(feature = "hidapi")] let _ignore_err = print_touchpad_fw_ver(); + + #[cfg(feature = "hidapi")] + let _ignore_err = print_touchscreen_fw_ver(); } fn print_esrt() { diff --git a/framework_lib/src/lib.rs b/framework_lib/src/lib.rs index 50fa9b03..b0c92e4d 100644 --- a/framework_lib/src/lib.rs +++ b/framework_lib/src/lib.rs @@ -18,6 +18,10 @@ pub mod audio_card; 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/touchscreen.rs b/framework_lib/src/touchscreen.rs new file mode 100644 index 00000000..a602018d --- /dev/null +++ b/framework_lib/src/touchscreen.rs @@ -0,0 +1,142 @@ +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(()) + } +} + +pub fn print_touchscreen_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() +} 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()) + } +} From e004b7d84446bec28287d4fbd1fa8221ebc92fcc Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Sun, 13 Apr 2025 23:12:38 +0800 Subject: [PATCH 2/3] touchscreen: Allow turning touch on and off Signed-off-by: Daniel Schaefer --- framework_lib/src/commandline/clap_std.rs | 6 ++++++ framework_lib/src/commandline/mod.rs | 12 +++++++++--- framework_lib/src/commandline/uefi.rs | 1 + framework_lib/src/touchscreen.rs | 16 +++++++++++++++- 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/framework_lib/src/commandline/clap_std.rs b/framework_lib/src/commandline/clap_std.rs index f9299e6d..6bbd6b40 100644 --- a/framework_lib/src/commandline/clap_std.rs +++ b/framework_lib/src/commandline/clap_std.rs @@ -162,6 +162,11 @@ struct ClapCli { #[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)] @@ -284,6 +289,7 @@ pub fn parse(args: &[String]) -> Cli { 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()), diff --git a/framework_lib/src/commandline/mod.rs b/framework_lib/src/commandline/mod.rs index ef38f4fa..03179a1c 100644 --- a/framework_lib/src/commandline/mod.rs +++ b/framework_lib/src/commandline/mod.rs @@ -52,8 +52,8 @@ 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(feature = "hidapi")] -use crate::touchscreen::print_touchscreen_fw_ver; +#[cfg(any(feature = "hidapi", feature = "windows"))] +use crate::touchscreen; #[cfg(feature = "uefi")] use crate::uefi::enable_page_break; use crate::util; @@ -175,6 +175,7 @@ pub struct Cli { pub kblight: Option>, pub rgbkbd: Vec, pub tablet_mode: Option, + pub touchscreen_enable: Option, pub console: Option, pub reboot_ec: Option, pub hash: Option, @@ -483,7 +484,7 @@ fn print_versions(ec: &CrosEc) { let _ignore_err = print_touchpad_fw_ver(); #[cfg(feature = "hidapi")] - let _ignore_err = print_touchscreen_fw_ver(); + let _ignore_err = touchscreen::print_fw_ver(); } fn print_esrt() { @@ -798,6 +799,11 @@ pub fn run_with_args(args: &Cli, _allupdate: bool) -> i32 { 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 => { diff --git a/framework_lib/src/commandline/uefi.rs b/framework_lib/src/commandline/uefi.rs index 261720fc..39ea2867 100644 --- a/framework_lib/src/commandline/uefi.rs +++ b/framework_lib/src/commandline/uefi.rs @@ -89,6 +89,7 @@ pub fn parse(args: &[String]) -> Cli { kblight: None, rgbkbd: vec![], tablet_mode: None, + touchscreen_enable: None, console: None, reboot_ec: None, hash: None, diff --git a/framework_lib/src/touchscreen.rs b/framework_lib/src/touchscreen.rs index a602018d..c155c173 100644 --- a/framework_lib/src/touchscreen.rs +++ b/framework_lib/src/touchscreen.rs @@ -130,9 +130,14 @@ pub trait TouchScreen { Some(()) } + + fn enable_touch(&self, enable: bool) -> Option<()> { + self.send_message(0x38, 0, vec![!enable as u8, 0x00])?; + Some(()) + } } -pub fn print_touchscreen_fw_ver() -> Option<()> { +pub fn print_fw_ver() -> Option<()> { #[cfg(target_os = "windows")] let device = touchscreen_win::NativeWinTouchScreen::open_device()?; #[cfg(not(target_os = "windows"))] @@ -140,3 +145,12 @@ pub fn print_touchscreen_fw_ver() -> Option<()> { 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) +} From 2ddf76239be0bc96d3f411335a9e1b5044eecba3 Mon Sep 17 00:00:00 2001 From: Daniel Schaefer Date: Mon, 14 Apr 2025 17:31:31 +0800 Subject: [PATCH 3/3] README: Update Signed-off-by: Daniel Schaefer --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c97ad722..ded23458 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ 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) @@ -69,7 +69,8 @@ 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 @@ -178,11 +179,30 @@ Options: --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 ```