feat(windows-drivers): IddCx link probe — call init DDIs via table dispatch
apple / swift (push) Failing after 1s
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 19s
windows-drivers / driver-build (push) Successful in 1m5s
windows-host / package (push) Successful in 5m19s
ci / rust (push) Successful in 4m13s
ci / web (push) Successful in 41s
ci / docs-site (push) Successful in 53s
android / android (push) Successful in 9m59s
ci / bench (push) Successful in 4m48s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
deb / build-publish (push) Successful in 2m19s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m33s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m13s
docker / deploy-docs (push) Has been cancelled
apple / swift (push) Failing after 1s
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 19s
windows-drivers / driver-build (push) Successful in 1m5s
windows-host / package (push) Successful in 5m19s
ci / rust (push) Successful in 4m13s
ci / web (push) Successful in 41s
ci / docs-site (push) Successful in 53s
android / android (push) Successful in 9m59s
ci / bench (push) Successful in 4m48s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
deb / build-publish (push) Successful in 2m19s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m33s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m13s
docker / deploy-docs (push) Has been cancelled
First USE of the iddcx binding: a minimal table-dispatch (src/iddcx_rt.rs) over wdk_sys::iddcx — IddFunctions[_IDDFUNCENUM::<Name>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) <noreply@anthropic.com>
This commit is contained in:
@@ -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\<ver>\um\<arch>\iddcx\<iddcxver>\` — a newer base SDK installed alongside (e.g. 10.0.28000.0)
|
||||
/// has `um\<arch>` 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\\<ver>\\um\\{ARCH}\\iddcx\\<iddcxver>\\");
|
||||
}
|
||||
|
||||
@@ -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::<Name>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::<Name>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<T: Copy>(index: i32) -> T {
|
||||
let table = (&raw const IddFunctions).cast::<PFN_IDD_CX>();
|
||||
// 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::<T>().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) }
|
||||
}
|
||||
@@ -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::<IDD_CX_CLIENT_CONFIG>() 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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user