From 3fbabc854c64c0d3604bf023bb01be6b5eddb07e Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Wed, 24 Jun 2026 15:18:48 +0000 Subject: [PATCH] =?UTF-8?q?feat(windows-drivers):=20IddCx=20link=20probe?= =?UTF-8?q?=20=E2=80=94=20call=20init=20DDIs=20via=20table=20dispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First USE of the iddcx binding: a minimal table-dispatch (src/iddcx_rt.rs) over wdk_sys::iddcx — IddFunctions[_IDDFUNCENUM::TableIndex] cast to PFN_*, IddDriverGlobals as implicit arg 1 (the WDF model; ModuleConsts i32 index, not the oracle NewType .0). The probe EvtDeviceAdd now calls IddCxDeviceInitConfig → WdfDeviceCreate → IddCxDeviceInitialize → IddCxAdapterInitAsync, exports IddMinimumVersionRequired=4, and build.rs links IddCxStub (globbed from the SDK Lib dir that ships iddcx). CI gate = compile + link IddCxStub. Co-Authored-By: Claude Opus 4.8 (1M context) --- packaging/windows/drivers/wdk-probe/build.rs | 39 +++++++++- .../windows/drivers/wdk-probe/src/iddcx_rt.rs | 76 +++++++++++++++++++ .../windows/drivers/wdk-probe/src/lib.rs | 38 +++++++++- 3 files changed, 147 insertions(+), 6 deletions(-) create mode 100644 packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs diff --git a/packaging/windows/drivers/wdk-probe/build.rs b/packaging/windows/drivers/wdk-probe/build.rs index 0e4d848..b294607 100644 --- a/packaging/windows/drivers/wdk-probe/build.rs +++ b/packaging/windows/drivers/wdk-probe/build.rs @@ -1,6 +1,41 @@ //! Emits the WDK link flags for the cdylib (the same call the gamepad drivers use). If wdk-build adds //! `/INTEGRITYCHECK`, it shows up in the produced DLL's PE DllCharacteristics — which the CI step -//! inspects to answer the M0 self-signed-load question. +//! inspects to answer the M0 self-signed-load question. Also links `IddCxStub` so the `iddcx` DDIs the +//! probe calls resolve (the M1 link gate). fn main() -> Result<(), wdk_build::ConfigError> { - wdk_build::configure_wdk_binary_build() + wdk_build::configure_wdk_binary_build()?; + link_iddcx_stub(); + Ok(()) +} + +/// Link `IddCxStub.lib`. It ships only under the SDK *version* that includes IddCx (e.g. 10.0.26100.0), +/// at `Lib\\um\\iddcx\\` — a newer base SDK installed alongside (e.g. 10.0.28000.0) +/// has `um\` but no `iddcx`, so we glob for the dir that actually contains the lib rather than +/// trusting the max SDK version (the same gotcha the `wdf-umdf` oracle's build script documents). x64 +/// only (the Windows host is x64-only). +fn link_iddcx_stub() { + const ARCH: &str = "x64"; + const ROOTS: [&str; 2] = [ + r"C:\Program Files (x86)\Windows Kits\10\Lib", + r"C:\Program Files\Windows Kits\10\Lib", + ]; + for root in ROOTS { + let Ok(versions) = std::fs::read_dir(root) else { + continue; + }; + for ver in versions.flatten() { + let iddcx = ver.path().join("um").join(ARCH).join("iddcx"); + let Ok(subdirs) = std::fs::read_dir(&iddcx) else { + continue; + }; + for sub in subdirs.flatten() { + if sub.path().join("IddCxStub.lib").is_file() { + println!("cargo:rustc-link-search={}", sub.path().display()); + println!("cargo:rustc-link-lib=static=IddCxStub"); + return; + } + } + } + } + panic!("IddCxStub.lib not found under any Windows Kits Lib\\\\um\\{ARCH}\\iddcx\\\\"); } diff --git a/packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs b/packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs new file mode 100644 index 0000000..9381d8c --- /dev/null +++ b/packaging/windows/drivers/wdk-probe/src/iddcx_rt.rs @@ -0,0 +1,76 @@ +//! Minimal IddCx table-dispatch over the wdk-sys `iddcx` bindings — the M1 proof that the generated +//! IddCx surface is *callable* and links against `IddCxStub`. IddCx DDIs dispatch through the +//! `IddFunctions` table (indexed by `_IDDFUNCENUM::TableIndex`, with `IddDriverGlobals` as the +//! implicit first argument) — the same model wdk-sys already uses for WDF. This mirrors the proven +//! `wdf-umdf` oracle, but on wdk-sys's ModuleConsts enums (`_IDDFUNCENUM::TableIndex`, an `i32` +//! const — not the oracle's NewType `.0`) and plain `i32` NTSTATUS. The full DDI surface + a proper +//! `wdk-iddcx` wrapper crate land with the driver port. +#![allow(non_snake_case)] + +use wdk_sys::iddcx::{ + IDARG_IN_ADAPTER_INIT, IDARG_OUT_ADAPTER_INIT, IDD_CX_CLIENT_CONFIG, IddDriverGlobals, + IddFunctions, PFN_IDDCXADAPTERINITASYNC, PFN_IDDCXDEVICEINITCONFIG, PFN_IDDCXDEVICEINITIALIZE, + PFN_IDD_CX, PIDD_DRIVER_GLOBALS, _IDDFUNCENUM, +}; +use wdk_sys::{NTSTATUS, PWDFDEVICE_INIT, WDFDEVICE}; + +/// Read the IddCx DDI at `index` from the stub-provided `IddFunctions` table and reinterpret it as the +/// concrete `PFN_*` type. `IddFunctions` is a flexible array (`[PFN_IDD_CX; 0]`) whose real entries are +/// populated by `IddCxStub` once the driver is loaded. +/// +/// # Safety +/// `index` and `T` must be the matching `_IDDFUNCENUM` index and `PFN_*` type for the same DDI, and the +/// table must be populated (true after the driver is loaded by the IddCx runtime). +#[inline] +unsafe fn ddi(index: i32) -> T { + let table = (&raw const IddFunctions).cast::(); + // SAFETY: `index` is a valid IddCx table slot; the slot holds a `PFN_*` whose layout is `T`. + let slot = unsafe { table.add(index as usize) }; + unsafe { slot.cast::().read() } +} + +/// The IddCx driver globals (set by `IddCxStub`), passed as the implicit first arg to every DDI. +/// +/// # Safety +/// Only valid once the driver is loaded by the IddCx runtime. +#[inline] +unsafe fn globals() -> PIDD_DRIVER_GLOBALS { + // SAFETY: we only read the pointer value of the stub-provided global. + unsafe { (&raw const IddDriverGlobals).read() } +} + +/// # Safety +/// `device_init`/`config` must be valid per the IddCx contract; call only during `EvtDriverDeviceAdd`. +pub unsafe fn IddCxDeviceInitConfig( + device_init: PWDFDEVICE_INIT, + config: *const IDD_CX_CLIENT_CONFIG, +) -> NTSTATUS { + let f: PFN_IDDCXDEVICEINITCONFIG = + unsafe { ddi(_IDDFUNCENUM::IddCxDeviceInitConfigTableIndex) }; + let g = unsafe { globals() }; + // SAFETY: dispatching a populated DDI with the stub globals and caller-valid args. + unsafe { (f.unwrap())(g, device_init, config) } +} + +/// # Safety +/// `device` must be a valid `WDFDEVICE` previously configured via [`IddCxDeviceInitConfig`]. +pub unsafe fn IddCxDeviceInitialize(device: WDFDEVICE) -> NTSTATUS { + let f: PFN_IDDCXDEVICEINITIALIZE = + unsafe { ddi(_IDDFUNCENUM::IddCxDeviceInitializeTableIndex) }; + let g = unsafe { globals() }; + // SAFETY: dispatching a populated DDI with the stub globals and a caller-valid device. + unsafe { (f.unwrap())(g, device) } +} + +/// # Safety +/// `in_args`/`out_args` must be valid per the IddCx contract. +pub unsafe fn IddCxAdapterInitAsync( + in_args: *const IDARG_IN_ADAPTER_INIT, + out_args: *mut IDARG_OUT_ADAPTER_INIT, +) -> NTSTATUS { + let f: PFN_IDDCXADAPTERINITASYNC = + unsafe { ddi(_IDDFUNCENUM::IddCxAdapterInitAsyncTableIndex) }; + let g = unsafe { globals() }; + // SAFETY: dispatching a populated DDI with the stub globals and caller-valid args. + unsafe { (f.unwrap())(g, in_args, out_args) } +} diff --git a/packaging/windows/drivers/wdk-probe/src/lib.rs b/packaging/windows/drivers/wdk-probe/src/lib.rs index 70c769f..364877a 100644 --- a/packaging/windows/drivers/wdk-probe/src/lib.rs +++ b/packaging/windows/drivers/wdk-probe/src/lib.rs @@ -1,10 +1,14 @@ -//! Minimal UMDF2 driver — the M0/M1 toolchain + `/INTEGRITYCHECK` probe (see this crate's Cargo.toml). -//! DriverEntry → WdfDriverCreate → (EvtDeviceAdd) WdfDeviceCreate: enough to exercise the wdk-sys WDF -//! stub link without any device logic. Also force-links the shared `pf-vdisplay-proto` ABI crate to -//! prove it compiles + links into a driver (no_std + bytemuck) across the workspace boundary. +//! Minimal UMDF2 driver — the M0/M1 toolchain + `/INTEGRITYCHECK` + IddCx-binding probe (see this +//! crate's Cargo.toml). DriverEntry → WdfDriverCreate → (EvtDeviceAdd) IddCxDeviceInitConfig → +//! WdfDeviceCreate → IddCxDeviceInitialize → IddCxAdapterInitAsync: enough to exercise the wdk-sys WDF +//! stub link AND prove the `iddcx` subset is callable + links against `IddCxStub`. Also force-links the +//! shared `pf-vdisplay-proto` ABI crate (no_std + bytemuck) across the workspace boundary. #![allow(non_snake_case, clippy::missing_safety_doc)] +mod iddcx_rt; + +use wdk_sys::iddcx::{IDARG_IN_ADAPTER_INIT, IDARG_OUT_ADAPTER_INIT, IDD_CX_CLIENT_CONFIG}; use wdk_sys::{ call_unsafe_wdf_function_binding, NTSTATUS, PCUNICODE_STRING, PDRIVER_OBJECT, PWDFDEVICE_INIT, ULONG, WDFDEVICE, WDFDRIVER, WDF_DRIVER_CONFIG, WDF_NO_HANDLE, WDF_NO_OBJECT_ATTRIBUTES, @@ -17,6 +21,12 @@ const STATUS_SUCCESS: NTSTATUS = 0; #[used] static PROTO_GUID_LO: u64 = pf_vdisplay_proto::PF_VDISPLAY_INTERFACE_GUID_U128 as u64; +/// IddCx (stub mode) requires the driver to export the minimum IddCx framework version it needs — the +/// `#ifndef IDD_STUB` branch of `IddCxFuncEnum.h` (which normally emits it) is compiled out under +/// `IDD_STUB`, so the driver provides it. `4` matches the proven `wdf-umdf` oracle. +#[unsafe(no_mangle)] +pub static IddMinimumVersionRequired: ULONG = 4; + #[unsafe(export_name = "DriverEntry")] pub unsafe extern "system" fn driver_entry( driver: PDRIVER_OBJECT, @@ -40,6 +50,16 @@ pub unsafe extern "system" fn driver_entry( } extern "C" fn evt_device_add(_driver: WDFDRIVER, mut device_init: PWDFDEVICE_INIT) -> NTSTATUS { + // Configure the device for IddCx BEFORE WdfDeviceCreate. The required `EvtIddCx*` callbacks are left + // null in this probe — its purpose is to prove the `iddcx` DDIs are callable and link against + // IddCxStub (CI), and that a self-signed IddCx driver loads + dispatches (box); the full callback + // surface + a valid adapter come with the driver port. At runtime IddCxDeviceInitConfig will reject + // the null callbacks, but the call site is what links IddCxStub and exercises table dispatch. + let mut cfg: IDD_CX_CLIENT_CONFIG = unsafe { core::mem::zeroed() }; + cfg.Size = core::mem::size_of::() as ULONG; + // SAFETY: device_init is the framework-provided init; cfg is a valid (if minimal) config. + let _ = unsafe { iddcx_rt::IddCxDeviceInitConfig(device_init, &cfg) }; + let mut device: WDFDEVICE = core::ptr::null_mut(); // SAFETY: device_init is the framework-provided init; attributes null; device receives the handle. let _ = unsafe { @@ -50,5 +70,15 @@ extern "C" fn evt_device_add(_driver: WDFDRIVER, mut device_init: PWDFDEVICE_INI &mut device ) }; + + // SAFETY: device is the just-created WDFDEVICE. + let _ = unsafe { iddcx_rt::IddCxDeviceInitialize(device) }; + + // SAFETY: zeroed adapter-init args — a link/dispatch reference, not a valid adapter (see above). + let in_args: IDARG_IN_ADAPTER_INIT = unsafe { core::mem::zeroed() }; + let mut out_args: IDARG_OUT_ADAPTER_INIT = unsafe { core::mem::zeroed() }; + // SAFETY: in/out args are valid local storage for the call. + let _ = unsafe { iddcx_rt::IddCxAdapterInitAsync(&in_args, &mut out_args) }; + STATUS_SUCCESS }