diff --git a/crates/wdk-build/src/bindgen.rs b/crates/wdk-build/src/bindgen.rs index 071f7e6a9..200cd807b 100644 --- a/crates/wdk-build/src/bindgen.rs +++ b/crates/wdk-build/src/bindgen.rs @@ -71,6 +71,23 @@ impl BuilderExt for Builder { .blocklist_item("ExAllocatePoolWithQuotaTag") // Deprecated .blocklist_item("ExAllocatePoolWithTagPriority") // Deprecated .blocklist_item("ExAllocatePool") // Deprecated + .blocklist_item("USBD_CalculateUsbBandwidth") // Deprecated + .blocklist_item("USBD_CreateConfigurationRequest") // Deprecated + .blocklist_item("USBD_Debug_LogEntry") // Deprecated + .blocklist_item("USBD_GetUSBDIVersion") // Deprecated + .blocklist_item("USBD_ParseConfigurationDescriptor") // Deprecated + .blocklist_item("USBD_QueryBusTime") // Deprecated + .blocklist_item("USBD_RegisterHcFilter") // Deprecated + .blocklist_item("IOCTL_USB_DIAG_IGNORE_HUBS_OFF") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_DIAG_IGNORE_HUBS_ON") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_DIAGNOSTIC_MODE_OFF") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_DIAGNOSTIC_MODE_ON") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_GET_HUB_CAPABILITIES") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_HCD_DISABLE_PORT") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_HCD_ENABLE_PORT") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_HCD_GET_STATS_1") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_HCD_GET_STATS_2") // Deprecated/Internal-Use-Only + .blocklist_item("IOCTL_USB_RESET_HUB") // Deprecated/Internal-Use-Only .opaque_type("_KGDTENTRY64") // No definition in WDK .opaque_type("_KIDTENTRY64") // No definition in WDK // FIXME: bitfield generated with non-1byte alignment in _MCG_CAP diff --git a/crates/wdk-build/src/lib.rs b/crates/wdk-build/src/lib.rs index 56b854368..55c4b74cc 100644 --- a/crates/wdk-build/src/lib.rs +++ b/crates/wdk-build/src/lib.rs @@ -207,6 +207,8 @@ pub enum ApiSubset { Spb, /// API subset for Storage drivers: Storage, + /// API subset for USB (Universal Serial Bus) drivers: + Usb, } impl Default for Config { @@ -407,6 +409,21 @@ impl Config { .canonicalize()? .strip_extended_length_path_prefix()?, ); + + // `ufxclient.h` relies on `ufxbase.h` being on the headers search path. The WDK + // normally does not automatically include this search path, but it is required + // here so that the headers can be processed successfully. + let ufx_include_path = km_or_um_include_path.join("ufx/1.1"); + if !ufx_include_path.is_dir() { + return Err(ConfigError::DirectoryNotFound { + directory: ufx_include_path.to_string_lossy().into(), + }); + } + include_paths.push( + ufx_include_path + .canonicalize()? + .strip_extended_length_path_prefix()?, + ); } DriverConfig::Umdf(umdf_config) => { let umdf_include_path = include_directory.join(format!( @@ -627,8 +644,8 @@ impl Config { "--warn-=no-ignored-attributes", "--warn-=no-ignored-pragma-intrinsic", "--warn-=no-visibility", - "--warn-=no-microsoft-anon-tag", - "--warn-=no-microsoft-enum-forward-reference", + "--warn-=no-switch", + "--warn-=no-comment", // Don't warn for deprecated declarations. Deprecated items should be explicitly // blocklisted (i.e. by the bindgen invocation). Any non-blocklisted function // definitions will trigger a -WDeprecated warning @@ -637,6 +654,8 @@ impl Config { // `_variable` are separate tokens already, and don't need `##` to concatenate // them) "--warn-=no-invalid-token-paste", + // Windows SDK & DDK headers rely on Microsoft extensions to C/C++ + "--warn-=no-microsoft", ] .into_iter() .map(std::string::ToString::to_string) @@ -649,99 +668,200 @@ impl Config { /// determine which headers to yield pub fn headers(&self, api_subset: ApiSubset) -> impl Iterator { match api_subset { - ApiSubset::Base => match &self.driver_config { - DriverConfig::Wdm | DriverConfig::Kmdf(_) => { - vec!["ntifs.h", "ntddk.h", "ntstrsafe.h"] - } - DriverConfig::Umdf(_) => { - vec!["windows.h"] - } - }, - ApiSubset::Wdf => { - if let DriverConfig::Kmdf(_) | DriverConfig::Umdf(_) = self.driver_config { - vec!["wdf.h"] - } else { - vec![] - } + ApiSubset::Base => self.base_headers(), + ApiSubset::Wdf => self.wdf_headers(), + ApiSubset::Gpio => self.gpio_headers(), + ApiSubset::Hid => self.hid_headers(), + ApiSubset::ParallelPorts => self.parallel_ports_headers(), + ApiSubset::Spb => self.spb_headers(), + ApiSubset::Storage => self.storage_headers(), + ApiSubset::Usb => self.usb_headers(), + } + .into_iter() + .map(str::to_string) + } + + fn base_headers(&self) -> Vec<&'static str> { + match &self.driver_config { + DriverConfig::Wdm | DriverConfig::Kmdf(_) => { + vec!["ntifs.h", "ntddk.h", "ntstrsafe.h"] } - ApiSubset::Gpio => { - let mut gpio_headers = vec!["gpio.h"]; + DriverConfig::Umdf(_) => { + vec!["windows.h"] + } + } + } - if let DriverConfig::Kmdf(_) = self.driver_config { - gpio_headers.extend(["gpioclx.h"]); - } + fn wdf_headers(&self) -> Vec<&'static str> { + if matches!( + self.driver_config, + DriverConfig::Kmdf(_) | DriverConfig::Umdf(_) + ) { + vec!["wdf.h"] + } else { + vec![] + } + } - gpio_headers - } - ApiSubset::Hid => { - let mut hid_headers = vec!["hidclass.h", "hidsdi.h", "hidpi.h", "vhf.h"]; + fn gpio_headers(&self) -> Vec<&'static str> { + let mut headers = vec!["gpio.h"]; + if matches!(self.driver_config, DriverConfig::Kmdf(_)) { + headers.extend(["gpioclx.h"]); + } + headers + } - if let DriverConfig::Wdm | DriverConfig::Kmdf(_) = self.driver_config { - hid_headers.extend(["hidpddi.h", "hidport.h", "kbdmou.h", "ntdd8042.h"]); - } + fn hid_headers(&self) -> Vec<&'static str> { + let mut headers = vec!["hidclass.h", "hidsdi.h", "hidpi.h", "vhf.h"]; + if matches!( + self.driver_config, + DriverConfig::Wdm | DriverConfig::Kmdf(_) + ) { + headers.extend(["hidpddi.h", "hidport.h", "kbdmou.h", "ntdd8042.h"]); + } - if let DriverConfig::Kmdf(_) = self.driver_config { - hid_headers.extend(["HidSpiCx/1.0/hidspicx.h"]); - } + if matches!(self.driver_config, DriverConfig::Kmdf(_)) { + headers.extend(["HidSpiCx/1.0/hidspicx.h"]); + } + headers + } - hid_headers - } - ApiSubset::ParallelPorts => { - let mut parallel_ports_headers = vec!["ntddpar.h", "ntddser.h"]; + fn parallel_ports_headers(&self) -> Vec<&'static str> { + let mut headers = vec!["ntddpar.h", "ntddser.h"]; + if matches!( + self.driver_config, + DriverConfig::Wdm | DriverConfig::Kmdf(_) + ) { + headers.extend(["parallel.h"]); + } + headers + } - if let DriverConfig::Wdm | DriverConfig::Kmdf(_) = self.driver_config { - parallel_ports_headers.extend(["parallel.h"]); - } + fn spb_headers(&self) -> Vec<&'static str> { + let mut headers = vec!["spb.h", "reshub.h"]; + if matches!( + self.driver_config, + DriverConfig::Wdm | DriverConfig::Kmdf(_) + ) { + headers.extend(["pwmutil.h"]); + } + if matches!(self.driver_config, DriverConfig::Kmdf(_)) { + headers.extend(["spb/1.1/spbcx.h"]); + } + headers + } - parallel_ports_headers - } - ApiSubset::Spb => { - let mut spb_headers = vec!["spb.h", "reshub.h"]; + fn storage_headers(&self) -> Vec<&'static str> { + let mut headers = vec![ + "ehstorioctl.h", + "ntddcdrm.h", + "ntddcdvd.h", + "ntdddisk.h", + "ntddmmc.h", + "ntddscsi.h", + "ntddstor.h", + "ntddtape.h", + "ntddvol.h", + "ufs.h", + ]; + if matches!( + self.driver_config, + DriverConfig::Wdm | DriverConfig::Kmdf(_) + ) { + headers.extend([ + "mountdev.h", + "mountmgr.h", + "ntddchgr.h", + "ntdddump.h", + "storduid.h", + "storport.h", + ]); + } + if matches!(self.driver_config, DriverConfig::Kmdf(_)) { + headers.extend(["ehstorbandmgmt.h"]); + } + headers + } - if let DriverConfig::Wdm | DriverConfig::Kmdf(_) = self.driver_config { - spb_headers.extend(["pwmutil.h"]); - } + fn usb_headers(&self) -> Vec<&'static str> { + let mut headers = vec![ + "usb.h", + "usbfnbase.h", + "usbioctl.h", + "usbspec.h", + "Usbpmapi.h", + ]; + + if matches!( + self.driver_config, + DriverConfig::Wdm | DriverConfig::Kmdf(_) + ) { + headers.extend(["usbbusif.h", "usbdlib.h", "usbfnattach.h", "usbfnioctl.h"]); + } - if let DriverConfig::Kmdf(_) = self.driver_config { - spb_headers.extend(["spb/1.1/spbcx.h"]); - } + if matches!( + self.driver_config, + DriverConfig::Kmdf(_) | DriverConfig::Umdf(_) + ) { + headers.extend(["wdfusb.h"]); + } - spb_headers + if matches!(self.driver_config, DriverConfig::Kmdf(_)) { + headers.extend([ + "ucm/1.0/UcmCx.h", + "UcmTcpci/1.0/UcmTcpciCx.h", + "UcmUcsi/1.0/UcmucsiCx.h", + "ucx/1.6/ucxclass.h", + "ude/1.1/UdeCx.h", + "ufx/1.1/ufxbase.h", + "ufxproprietarycharger.h", + "urs/1.0/UrsCx.h", + ]); + + if Self::should_include_ufxclient() { + headers.extend(["ufx/1.1/ufxclient.h"]); } - ApiSubset::Storage => { - let mut storage_headers = vec![ - "ehstorioctl.h", - "ntddcdrm.h", - "ntddcdvd.h", - "ntdddisk.h", - "ntddmmc.h", - "ntddscsi.h", - "ntddstor.h", - "ntddtape.h", - "ntddvol.h", - "ufs.h", - ]; - - if let DriverConfig::Wdm | DriverConfig::Kmdf(_) = self.driver_config { - storage_headers.extend([ - "mountdev.h", - "mountmgr.h", - "ntddchgr.h", - "ntdddump.h", - "storduid.h", - "storport.h", - ]); - } - - if let DriverConfig::Kmdf(_) = self.driver_config { - storage_headers.extend(["ehstorbandmgmt.h"]); - } + } + headers + } - storage_headers + /// Determines whether to include the ufxclient.h header based on the Clang + /// version used by bindgen. + /// + /// The ufxclient.h header contains FORCEINLINE annotations that are invalid + /// according to the C standard. While MSVC silently ignores these in C + /// mode, older versions of Clang (pre-20.0) will error, even with MSVC + /// compatibility enabled. + /// + /// This function checks if the current Clang version is 20.0 or newer, + /// where the issue was fixed. See + /// for details. + fn should_include_ufxclient() -> bool { + const MINIMUM_CLANG_MAJOR_VERISON_WITH_INVALID_INLINE_FIX: u32 = 20; + + let clang_version = ::bindgen::clang_version(); + match clang_version.parsed { + Some((major, _minor)) + if major >= MINIMUM_CLANG_MAJOR_VERISON_WITH_INVALID_INLINE_FIX => + { + true + } + Some(_) => { + tracing::info!( + "Skipping ufxclient.h due to FORCEINLINE bug in {}", + clang_version.full + ); + false + } + None => { + tracing::warn!( + "Failed to parse semver Major and Minor components from full Clang version \ + string: {}", + clang_version.full + ); + false } } - .into_iter() - .map(std::string::ToString::to_string) } /// Returns a [`String`] containing the contents of a header file designed diff --git a/crates/wdk-sys/Cargo.toml b/crates/wdk-sys/Cargo.toml index 30561d657..7b665ae7b 100644 --- a/crates/wdk-sys/Cargo.toml +++ b/crates/wdk-sys/Cargo.toml @@ -40,6 +40,7 @@ hid = [] parallel-ports = ["gpio"] spb = [] storage = [] +usb = [] nightly = ["wdk-macros/nightly"] test-stubs = [] diff --git a/crates/wdk-sys/build.rs b/crates/wdk-sys/build.rs index 9bbc91915..15dd33aa0 100644 --- a/crates/wdk-sys/build.rs +++ b/crates/wdk-sys/build.rs @@ -136,6 +136,7 @@ const BINDGEN_FILE_GENERATORS_TUPLES: &[(&str, GenerateFn)] = &[ ("parallel_ports.rs", generate_parallel_ports), ("spb.rs", generate_spb), ("storage.rs", generate_storage), + ("usb.rs", generate_usb), ]; fn initialize_tracing() -> Result<(), ParseError> { @@ -208,6 +209,8 @@ fn generate_constants(out_path: &Path, config: &Config) -> Result<(), ConfigErro ApiSubset::Spb, #[cfg(feature = "storage")] ApiSubset::Storage, + #[cfg(feature = "usb")] + ApiSubset::Usb, ]); trace!(header_contents = ?header_contents); @@ -238,6 +241,8 @@ fn generate_types(out_path: &Path, config: &Config) -> Result<(), ConfigError> { ApiSubset::Spb, #[cfg(feature = "storage")] ApiSubset::Storage, + #[cfg(feature = "usb")] + ApiSubset::Usb, ]); trace!(header_contents = ?header_contents); @@ -489,6 +494,42 @@ fn generate_storage(out_path: &Path, config: &Config) -> Result<(), ConfigError> } } +fn generate_usb(out_path: &Path, config: &Config) -> Result<(), ConfigError> { + cfg_if::cfg_if! { + if #[cfg(feature = "usb")] { + info!("Generating bindings to WDK: usb.rs"); + + let header_contents = + config.bindgen_header_contents([ApiSubset::Base, ApiSubset::Wdf, ApiSubset::Usb]); + trace!(header_contents = ?header_contents); + + let bindgen_builder = { + let mut builder = bindgen::Builder::wdk_default(config)? + .with_codegen_config((CodegenConfig::TYPES | CodegenConfig::VARS).complement()) + .header_contents("usb-input.h", &header_contents); + + // Only allowlist files in the usb-specific files to avoid + // duplicate definitions + for header_file in config.headers(ApiSubset::Usb) { + builder = builder.allowlist_file(format!("(?i).*{header_file}.*")); + } + builder + }; + trace!(bindgen_builder = ?bindgen_builder); + + Ok(bindgen_builder + .generate() + .expect("Bindings should succeed to generate") + .write_to_file(out_path.join("usb.rs"))?) + } else { + let _ = (out_path, config); // Silence unused variable warnings when usb feature is not enabled + + info!("Skipping usb.rs generation since usb feature is not enabled"); + Ok(()) + } + } +} + /// Generates a `wdf_function_count.rs` file in `OUT_DIR` which contains the /// definition of the function `get_wdf_function_count()`. This is required to /// be generated here since the size of the table is derived from either a diff --git a/crates/wdk-sys/src/lib.rs b/crates/wdk-sys/src/lib.rs index 96c22abc4..1c84eea02 100644 --- a/crates/wdk-sys/src/lib.rs +++ b/crates/wdk-sys/src/lib.rs @@ -79,6 +79,16 @@ pub mod spb; ))] pub mod storage; +#[cfg(all( + any( + driver_model__driver_type = "WDM", + driver_model__driver_type = "KMDF", + driver_model__driver_type = "UMDF" + ), + feature = "usb" +))] +pub mod usb; + #[cfg(feature = "test-stubs")] pub mod test_stubs; diff --git a/crates/wdk-sys/src/usb.rs b/crates/wdk-sys/src/usb.rs new file mode 100644 index 000000000..2b570f2c8 --- /dev/null +++ b/crates/wdk-sys/src/usb.rs @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +//! Direct FFI bindings to USB APIs from the Windows Driver Kit (WDK) +//! +//! This module contains all bindings to functions, constants, methods, +//! constructors and destructors for USB headers. Types are not included in this +//! module, but are available in the top-level `wdk_sys` module. + +#[allow( + missing_docs, + reason = "most items in the WDK headers have no inline documentation, so bindgen is unable to \ + generate documentation for their bindings" +)] +mod bindings { + #[allow( + clippy::wildcard_imports, + reason = "the underlying c code relies on all type definitions being in scope, which \ + results in the bindgen generated code relying on the generated types being in \ + scope as well" + )] + use crate::types::*; + + include!(concat!(env!("OUT_DIR"), "/usb.rs")); +} +pub use bindings::*; diff --git a/examples/sample-kmdf-driver/Cargo.toml b/examples/sample-kmdf-driver/Cargo.toml index 183078e20..524b9d37d 100644 --- a/examples/sample-kmdf-driver/Cargo.toml +++ b/examples/sample-kmdf-driver/Cargo.toml @@ -37,6 +37,7 @@ hid = ["wdk-sys/hid"] parallel-ports = ["wdk-sys/parallel-ports"] spb = ["wdk-sys/spb"] storage = ["wdk-sys/storage"] +usb = ["wdk-sys/usb"] nightly = ["wdk/nightly", "wdk-sys/nightly"] diff --git a/examples/sample-umdf-driver/Cargo.toml b/examples/sample-umdf-driver/Cargo.toml index 4602732e6..8e5678efb 100644 --- a/examples/sample-umdf-driver/Cargo.toml +++ b/examples/sample-umdf-driver/Cargo.toml @@ -35,6 +35,7 @@ hid = ["wdk-sys/hid"] parallel-ports = ["wdk-sys/parallel-ports"] spb = ["wdk-sys/spb"] storage = ["wdk-sys/storage"] +usb = ["wdk-sys/usb"] nightly = ["wdk/nightly", "wdk-sys/nightly"] diff --git a/examples/sample-wdm-driver/Cargo.toml b/examples/sample-wdm-driver/Cargo.toml index c1f6e4bf9..7f2f7f7ec 100644 --- a/examples/sample-wdm-driver/Cargo.toml +++ b/examples/sample-wdm-driver/Cargo.toml @@ -35,6 +35,7 @@ hid = ["wdk-sys/hid"] parallel-ports = ["wdk-sys/parallel-ports"] spb = ["wdk-sys/spb"] storage = ["wdk-sys/storage"] +usb = ["wdk-sys/usb"] nightly = ["wdk/nightly", "wdk-sys/nightly"]