feat(windows-drivers): STEP 3 on-glass — driver LOADS + runs full IddCx init chain
apple / swift (push) Failing after 2s
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 19s
windows-drivers / driver-build (push) Successful in 1m7s
windows-host / package (push) Successful in 5m27s
android / android (push) Successful in 4m5s
ci / web (push) Successful in 47s
ci / rust (push) Successful in 4m41s
ci / docs-site (push) Successful in 57s
deb / build-publish (push) Successful in 2m26s
decky / build-publish (push) Successful in 13s
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 5s
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 5s
ci / bench (push) Successful in 5m1s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m21s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m23s
docker / deploy-docs (push) Successful in 6s
apple / swift (push) Failing after 2s
apple / screenshots (push) Has been skipped
windows-drivers / probe-and-proto (push) Successful in 19s
windows-drivers / driver-build (push) Successful in 1m7s
windows-host / package (push) Successful in 5m27s
android / android (push) Successful in 4m5s
ci / web (push) Successful in 47s
ci / rust (push) Successful in 4m41s
ci / docs-site (push) Successful in 57s
deb / build-publish (push) Successful in 2m26s
decky / build-publish (push) Successful in 13s
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 5s
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 5s
ci / bench (push) Successful in 5m1s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 9m21s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 9m23s
docker / deploy-docs (push) Successful in 6s
Major on-glass progress on the RTX box. The all-Rust wdk-sys IddCx driver now LOADS under Secure Boot and runs the ENTIRE init chain: DriverEntry -> WdfDriverCreate -> driver_add -> IddCxDeviceInitConfig(0x0) -> WdfDeviceCreate -> CreateDeviceInterface -> IddCxDeviceInitialize -> D0Entry -> init_adapter. Findings: - Signing was a RED HERRING (the driver loads); std works in WUDFHost (DualSense uses it too). - THE unblock: link the iddcx **1.10** IddCxStub (build.rs now picks the highest version-aware), not 1.0 — the 1.0 stub lacks the version-table symbols AND its dispatch table mismatched the 1.10 framework, which made IddCxDeviceInitConfig return INVALID_PARAMETER. With 1.10 the whole chain runs. - Added a file/OutputDebugString logger (log.rs, matches the DualSense driver) — the driver was silent; this is how the chain was traced. - size.rs: framework_struct_size() reads the frameworks authoritative struct sizes from IddStructures[] (the config keeps size_of=208, validated working). - adapter.rs: version ptrs + ObjectAttributes(InheritFromParent) + FP16 + framework caps/diag/version sizes — matches the oracle. KNOWN WIP: IddCxAdapterInitAsync still returns INVALID_PARAMETER though caps match the framework size table (88/56/24) + the oracle exactly — likely a subtle wdk-sys bindgen field-layout detail in IDDCX_ADAPTER_CAPS/IDDCX_ENDPOINT_DIAGNOSTIC_INFO. CI gate (compile+link) stays green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,15 +7,18 @@ fn main() -> Result<(), wdk_build::ConfigError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Link `IddCxStub.lib`. It ships only under the SDK *version* that includes IddCx, at
|
||||
/// `Lib\<ver>\um\<arch>\iddcx\<iddcxver>\` — a newer base SDK alongside it lacks the `iddcx` subdir, so
|
||||
/// glob for the dir that actually contains the lib rather than trusting the max SDK version. x64 only.
|
||||
/// Link `IddCxStub.lib`. It ships under `Lib\<sdkver>\um\<arch>\iddcx\<iddcxver>\`, and the iddcx
|
||||
/// versions are NOT equivalent: the `1.0`/`1.2` stubs lack the versioned-struct-size table symbols
|
||||
/// (`IddStructures`/`IddStructureCount`/`IddClientVersionHigherThanFramework`) that `size.rs` needs —
|
||||
/// `1.3`+ and `1.10` have them. So pick the HIGHEST `iddcx\<X.Y>` dir that has the lib (version-aware,
|
||||
/// since "1.10" < "1.2" lexically). 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",
|
||||
];
|
||||
let mut best: Option<((u32, u32), std::path::PathBuf)> = None;
|
||||
for root in ROOTS {
|
||||
let Ok(versions) = std::fs::read_dir(root) else {
|
||||
continue;
|
||||
@@ -26,13 +29,24 @@ fn link_iddcx_stub() {
|
||||
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;
|
||||
if !sub.path().join("IddCxStub.lib").is_file() {
|
||||
continue;
|
||||
}
|
||||
let name = sub.file_name().to_string_lossy().into_owned();
|
||||
let mut parts = name.split('.');
|
||||
let v = (
|
||||
parts.next().and_then(|x| x.parse().ok()).unwrap_or(0u32),
|
||||
parts.next().and_then(|x| x.parse().ok()).unwrap_or(0u32),
|
||||
);
|
||||
if best.as_ref().is_none_or(|(bv, _)| v > *bv) {
|
||||
best = Some((v, sub.path()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("IddCxStub.lib not found under any Windows Kits Lib\\<ver>\\um\\{ARCH}\\iddcx\\<iddcxver>\\");
|
||||
let Some((_, dir)) = best else {
|
||||
panic!("IddCxStub.lib not found under any Windows Kits Lib\\<ver>\\um\\{ARCH}\\iddcx\\<iddcxver>\\");
|
||||
};
|
||||
println!("cargo:rustc-link-search={}", dir.display());
|
||||
println!("cargo:rustc-link-lib=static=IddCxStub");
|
||||
}
|
||||
|
||||
@@ -43,31 +43,81 @@ pub fn init_adapter(device: WDFDEVICE) -> NTSTATUS {
|
||||
if ADAPTER.get().is_some() {
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
dbglog!("[pf-vd] init_adapter");
|
||||
|
||||
// Endpoint diagnostics (telemetry only — not used for OS runtime decisions). `pEndPointModelName`
|
||||
// must be a non-empty string; the rest are optional. GammaSupport stays NONE (zeroed).
|
||||
// The framework binds an IddCx version (the INF's UmdfExtensions) that may be OLDER than our 1.10
|
||||
// headers, so use ITS expected struct sizes — newer fields (e.g. IDDCX_ADAPTER_CAPS's
|
||||
// StaticDesktopReencodeFrameCount) make size_of too big and IddCxAdapterInitAsync rejects it. The
|
||||
// table is readable now (post-IddCxDeviceInitialize). Falls back to size_of if unavailable.
|
||||
let ver_size = crate::size::framework_struct_size(
|
||||
iddcx::_IDDSTRUCTENUM::INDEX_IDDCX_ENDPOINT_VERSION as u32,
|
||||
)
|
||||
.unwrap_or(core::mem::size_of::<iddcx::IDDCX_ENDPOINT_VERSION>() as u32);
|
||||
let diag_size = crate::size::framework_struct_size(
|
||||
iddcx::_IDDSTRUCTENUM::INDEX_IDDCX_ENDPOINT_DIAGNOSTIC_INFO as u32,
|
||||
)
|
||||
.unwrap_or(core::mem::size_of::<iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO>() as u32);
|
||||
let caps_size = crate::size::framework_struct_size(
|
||||
iddcx::_IDDSTRUCTENUM::INDEX_IDDCX_ADAPTER_CAPS as u32,
|
||||
)
|
||||
.unwrap_or(core::mem::size_of::<iddcx::IDDCX_ADAPTER_CAPS>() as u32);
|
||||
dbglog!("[pf-vd] fw sizes: caps={caps_size} diag={diag_size} ver={ver_size}");
|
||||
|
||||
// Firmware/hardware version (telemetry). The oracle points BOTH at one IDDCX_ENDPOINT_VERSION.
|
||||
// `version` is a stack local read synchronously by IddCxAdapterInitAsync (same as the oracle).
|
||||
let mut version: iddcx::IDDCX_ENDPOINT_VERSION = unsafe { core::mem::zeroed() };
|
||||
version.Size = ver_size;
|
||||
version.MajorVer = env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap_or(0);
|
||||
version.MinorVer = env!("CARGO_PKG_VERSION_MINOR").parse().unwrap_or(0);
|
||||
version.Build = env!("CARGO_PKG_VERSION_PATCH").parse().unwrap_or(0);
|
||||
|
||||
// Endpoint diagnostics. `pEndPointModelName` must be a non-empty string. GammaSupport stays NONE.
|
||||
let mut diag: iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO = unsafe { core::mem::zeroed() };
|
||||
diag.Size = core::mem::size_of::<iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO>() as u32;
|
||||
diag.Size = diag_size;
|
||||
diag.TransmissionType = iddcx::IDDCX_TRANSMISSION_TYPE::IDDCX_TRANSMISSION_TYPE_WIRED_OTHER;
|
||||
diag.pEndPointFriendlyName = wstr!("punktfunk Virtual Display Adapter");
|
||||
diag.pEndPointManufacturerName = wstr!("punktfunk");
|
||||
diag.pEndPointModelName = wstr!("Virtual Display");
|
||||
diag.pFirmwareVersion = (&raw mut version).cast();
|
||||
diag.pHardwareVersion = (&raw mut version).cast();
|
||||
|
||||
let mut caps: iddcx::IDDCX_ADAPTER_CAPS = unsafe { core::mem::zeroed() };
|
||||
caps.Size = core::mem::size_of::<iddcx::IDDCX_ADAPTER_CAPS>() as u32;
|
||||
caps.Size = caps_size;
|
||||
// CAN_PROCESS_FP16 must be CONSISTENT with the config's callbacks: the config registers the *2 / gamma
|
||||
// / set-default-hdr-metadata callbacks (FP16-obligated), so the adapter MUST advertise FP16 or the
|
||||
// framework rejects the mismatch at IddCxAdapterInitAsync. The oracle sets both.
|
||||
caps.Flags = iddcx::IDDCX_ADAPTER_FLAGS::IDDCX_ADAPTER_FLAGS_CAN_PROCESS_FP16;
|
||||
caps.MaxMonitorsSupported = 16;
|
||||
caps.EndPointDiagnostics = diag;
|
||||
|
||||
dbglog!(
|
||||
"[pf-vd] caps Size={} Flags={:#x} MaxMon={} diagSize={} cfgSizeOf={} capsSizeOf={}",
|
||||
caps.Size,
|
||||
caps.Flags,
|
||||
caps.MaxMonitorsSupported,
|
||||
caps.EndPointDiagnostics.Size,
|
||||
core::mem::size_of::<iddcx::IDD_CX_CLIENT_CONFIG>(),
|
||||
core::mem::size_of::<iddcx::IDDCX_ADAPTER_CAPS>(),
|
||||
);
|
||||
// The adapter WDF object's attributes. The oracle passes an init'd WDF_OBJECT_ATTRIBUTES (Size +
|
||||
// Synchronization/Execution = InheritFromParent — NOT zeroed, since zero = *Invalid*); a null/zeroed
|
||||
// one is what IddCxAdapterInitAsync rejected. No context type yet (STEP 3).
|
||||
let mut attr: wdk_sys::WDF_OBJECT_ATTRIBUTES = unsafe { core::mem::zeroed() };
|
||||
attr.Size = core::mem::size_of::<wdk_sys::WDF_OBJECT_ATTRIBUTES>() as u32;
|
||||
attr.ExecutionLevel = wdk_sys::_WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent;
|
||||
attr.SynchronizationScope =
|
||||
wdk_sys::_WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent;
|
||||
let init = iddcx::IDARG_IN_ADAPTER_INIT {
|
||||
WdfDevice: device,
|
||||
pCaps: &raw mut caps,
|
||||
ObjectAttributes: core::ptr::null_mut(),
|
||||
ObjectAttributes: &raw mut attr,
|
||||
};
|
||||
let mut out: iddcx::IDARG_OUT_ADAPTER_INIT = unsafe { core::mem::zeroed() };
|
||||
// SAFETY: `init`/`out` are valid local storage; IddCxAdapterInitAsync reads the caps synchronously
|
||||
// (the adapter object itself is delivered later via adapter_init_finished). Called once per device.
|
||||
unsafe { wdk_iddcx::IddCxAdapterInitAsync(&init, &mut out) }
|
||||
let st = unsafe { wdk_iddcx::IddCxAdapterInitAsync(&init, &mut out) };
|
||||
dbglog!("[pf-vd] IddCxAdapterInitAsync -> {st:#x}");
|
||||
st
|
||||
}
|
||||
|
||||
/// Stash the adapter object delivered by `EvtIddCxAdapterInitFinished` (STEP 4 reads it).
|
||||
|
||||
@@ -17,6 +17,7 @@ pub unsafe extern "C" fn device_d0_entry(
|
||||
device: WDFDEVICE,
|
||||
_previous_state: wdk_sys::WDF_POWER_DEVICE_STATE,
|
||||
) -> NTSTATUS {
|
||||
dbglog!("[pf-vd] device_d0_entry");
|
||||
crate::adapter::init_adapter(device)
|
||||
}
|
||||
|
||||
@@ -26,6 +27,7 @@ pub unsafe extern "C" fn adapter_init_finished(
|
||||
adapter: iddcx::IDDCX_ADAPTER,
|
||||
_p_in: *const iddcx::IDARG_IN_ADAPTER_INIT_FINISHED,
|
||||
) -> NTSTATUS {
|
||||
dbglog!("[pf-vd] adapter_init_finished");
|
||||
crate::adapter::set_adapter(adapter);
|
||||
STATUS_SUCCESS
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! DriverEntry + driver_add — the IddCx device bring-up (STEP 2 skeleton). wdk-build links the UMDF
|
||||
//! DriverEntry + driver_add — the IddCx device bring-up (STEP 2/3). wdk-build links the UMDF
|
||||
//! `WdfDriverStubUm` whose `FxDriverEntryUm` forwards to the exported `DriverEntry`. Adapter creation is
|
||||
//! deferred to the first `EvtDeviceD0Entry` (STEP 3); monitors are created on demand by the control
|
||||
//! plane (STEP 4).
|
||||
//! plane (STEP 4). Instrumented with `dbglog!` for on-glass bring-up.
|
||||
|
||||
use wdk_iddcx::nt_success;
|
||||
use wdk_sys::{
|
||||
@@ -17,12 +17,13 @@ pub unsafe extern "system" fn driver_entry(
|
||||
driver: PDRIVER_OBJECT,
|
||||
registry_path: PCUNICODE_STRING,
|
||||
) -> NTSTATUS {
|
||||
dbglog!("[pf-vd] DriverEntry");
|
||||
// SAFETY: zeroed then Size + the device-add callback set, per the WDF_DRIVER_CONFIG contract.
|
||||
let mut config: WDF_DRIVER_CONFIG = unsafe { core::mem::zeroed() };
|
||||
config.Size = core::mem::size_of::<WDF_DRIVER_CONFIG>() as ULONG;
|
||||
config.EvtDriverDeviceAdd = Some(driver_add);
|
||||
// SAFETY: driver + registry_path are loader-provided; config is valid for the call.
|
||||
unsafe {
|
||||
let st = unsafe {
|
||||
call_unsafe_wdf_function_binding!(
|
||||
WdfDriverCreate,
|
||||
driver,
|
||||
@@ -31,10 +32,13 @@ pub unsafe extern "system" fn driver_entry(
|
||||
&mut config,
|
||||
WDF_NO_HANDLE.cast::<WDFDRIVER>()
|
||||
)
|
||||
}
|
||||
};
|
||||
dbglog!("[pf-vd] WdfDriverCreate -> {st:#x}");
|
||||
st
|
||||
}
|
||||
|
||||
extern "C" fn driver_add(_driver: WDFDRIVER, mut init: PWDFDEVICE_INIT) -> NTSTATUS {
|
||||
dbglog!("[pf-vd] driver_add");
|
||||
// Defer adapter creation to the first D0 entry.
|
||||
let mut pnp: WDF_PNPPOWER_EVENT_CALLBACKS = unsafe { core::mem::zeroed() };
|
||||
pnp.Size = core::mem::size_of::<WDF_PNPPOWER_EVENT_CALLBACKS>() as ULONG;
|
||||
@@ -46,6 +50,7 @@ extern "C" fn driver_add(_driver: WDFDRIVER, mut init: PWDFDEVICE_INIT) -> NTSTA
|
||||
|
||||
// Build + size the IddCx client config (versioned size) and wire the 14 callbacks.
|
||||
let Some(cfg_size) = size::idd_cx_client_config_size() else {
|
||||
dbglog!("[pf-vd] config size unavailable");
|
||||
return STATUS_NOT_FOUND;
|
||||
};
|
||||
let mut cfg: iddcx::IDD_CX_CLIENT_CONFIG = unsafe { core::mem::zeroed() };
|
||||
@@ -67,6 +72,7 @@ extern "C" fn driver_add(_driver: WDFDRIVER, mut init: PWDFDEVICE_INIT) -> NTSTA
|
||||
|
||||
// SAFETY: init is the framework device-init; cfg is fully populated + sized. (Links IddCxStub.)
|
||||
let status = unsafe { wdk_iddcx::IddCxDeviceInitConfig(init, &cfg) };
|
||||
dbglog!("[pf-vd] IddCxDeviceInitConfig -> {status:#x}");
|
||||
if !nt_success(status) {
|
||||
return status;
|
||||
}
|
||||
@@ -81,6 +87,7 @@ extern "C" fn driver_add(_driver: WDFDRIVER, mut init: PWDFDEVICE_INIT) -> NTSTA
|
||||
&mut device
|
||||
)
|
||||
};
|
||||
dbglog!("[pf-vd] WdfDeviceCreate -> {status:#x}");
|
||||
if !nt_success(status) {
|
||||
return status;
|
||||
}
|
||||
@@ -103,10 +110,13 @@ extern "C" fn driver_add(_driver: WDFDRIVER, mut init: PWDFDEVICE_INIT) -> NTSTA
|
||||
core::ptr::null()
|
||||
)
|
||||
};
|
||||
dbglog!("[pf-vd] WdfDeviceCreateDeviceInterface -> {status:#x}");
|
||||
if !nt_success(status) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// SAFETY: device is the just-created WDFDEVICE.
|
||||
unsafe { wdk_iddcx::IddCxDeviceInitialize(device) }
|
||||
let status = unsafe { wdk_iddcx::IddCxDeviceInitialize(device) };
|
||||
dbglog!("[pf-vd] IddCxDeviceInitialize -> {status:#x}");
|
||||
status
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#![allow(non_snake_case, clippy::missing_safety_doc)]
|
||||
|
||||
#[macro_use]
|
||||
mod log;
|
||||
mod adapter;
|
||||
mod callbacks;
|
||||
#[allow(dead_code)] // salvaged verbatim; wired into the mode callbacks in STEP 4
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
//! Minimal driver logger (matches the DualSense driver). DebugView can't capture the UMDF host across
|
||||
//! session 0, so besides `OutputDebugStringA` we append to a world-writable file readable over SSH. Used
|
||||
//! only for bring-up/diagnostics; cheap and best-effort (ignores all errors).
|
||||
|
||||
unsafe extern "system" {
|
||||
fn OutputDebugStringA(s: *const u8);
|
||||
}
|
||||
|
||||
pub fn log(s: &str) {
|
||||
if let Ok(c) = std::ffi::CString::new(s) {
|
||||
// SAFETY: `c` is a valid NUL-terminated string for the duration of the call.
|
||||
unsafe { OutputDebugStringA(c.as_ptr().cast()) };
|
||||
}
|
||||
use std::io::Write;
|
||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("C:\\Users\\Public\\pfvd-driver.log")
|
||||
{
|
||||
let _ = writeln!(f, "{s}");
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! dbglog {
|
||||
($($a:tt)*) => { $crate::log::log(&::std::format!($($a)*)) };
|
||||
}
|
||||
@@ -1,19 +1,59 @@
|
||||
//! `.Size` for `IDD_CX_CLIENT_CONFIG`.
|
||||
//! `.Size` for `IDD_CX_CLIENT_CONFIG` — the oracle's `IDD_STRUCTURE_SIZE!`, ported.
|
||||
//!
|
||||
//! The oracle uses a *versioned* size — `IddStructures[INDEX]` when the running framework is OLDER than
|
||||
//! the (1.10) headers we built against (`IddClientVersionHigherThanFramework != 0`). That machinery
|
||||
//! (`IddClientVersionHigherThanFramework` / `IddStructureCount` / `IddStructures`) only exists in the
|
||||
//! iddcx ≥1.4 `IddCxStub`; the WDK on the runner/box links the **1.0** stub (the only `IddCxStub.lib`
|
||||
//! present), which does NOT export those symbols — referencing them is an LNK2019. We target IddCx 1.10
|
||||
//! against a current framework (framework ≥ client ⇒ `higher == false`), where `size_of` is exactly what
|
||||
//! the versioned path returns. So use `size_of` directly. (Revisit the versioned path — with a ≥1.4
|
||||
//! `IddCxStub` linked — only if pre-1.10 Windows must ever be supported, which the punktfunk Windows
|
||||
//! host does not target.)
|
||||
//! IddCx structs are versioned. If the client (our headers) is NEWER than the running framework
|
||||
//! (`IddClientVersionHigherThanFramework != 0`), `size_of` of our struct is too big, so the framework's
|
||||
//! own `IddStructures[INDEX]` size must be used. Otherwise `size_of` is correct. `IddStructures` is null
|
||||
//! until IddCx initialises, so it must ONLY be read in the `higher` branch (the framework populates it
|
||||
//! exactly then). build.rs links the `iddcx>=1.3` stub that exports these symbols. Logged so we can see
|
||||
//! which branch + value the box actually takes.
|
||||
|
||||
use wdk_sys::iddcx;
|
||||
|
||||
/// Correct `.Size` for `IDD_CX_CLIENT_CONFIG` on a framework at least as new as our headers.
|
||||
/// The correct `.Size` for `IDD_CX_CLIENT_CONFIG`, or `None` if the struct is unusable on this framework.
|
||||
#[must_use]
|
||||
pub fn idd_cx_client_config_size() -> Option<u32> {
|
||||
u32::try_from(core::mem::size_of::<iddcx::IDD_CX_CLIENT_CONFIG>()).ok()
|
||||
let local = core::mem::size_of::<iddcx::IDD_CX_CLIENT_CONFIG>();
|
||||
// SAFETY: BOOLEAN static, read-only.
|
||||
let higher = unsafe { (&raw const iddcx::IddClientVersionHigherThanFramework).read() } != 0;
|
||||
dbglog!("[pf-vd] cfg size: higher={higher} size_of={local}");
|
||||
if !higher {
|
||||
return u32::try_from(local).ok();
|
||||
}
|
||||
// SAFETY: read-only; the framework populates the size table exactly when `higher` is true.
|
||||
let count = unsafe { (&raw const iddcx::IddStructureCount).read() };
|
||||
let index = iddcx::_IDDSTRUCTENUM::INDEX_IDD_CX_CLIENT_CONFIG as u32;
|
||||
if index >= count {
|
||||
dbglog!("[pf-vd] cfg size: index {index} >= count {count} -> None");
|
||||
return None;
|
||||
}
|
||||
// SAFETY: `IddStructures` is the framework's size table; `index` validated `< count`.
|
||||
let table = unsafe { (&raw const iddcx::IddStructures).read() };
|
||||
let fw = unsafe { table.add(index as usize).read() };
|
||||
dbglog!("[pf-vd] cfg size: framework={fw}");
|
||||
u32::try_from(fw).ok()
|
||||
}
|
||||
|
||||
/// The framework's expected `.Size` for the `_IDDSTRUCTENUM` struct at `index`, or `None` if the size
|
||||
/// table isn't usable (index out of range / `IddStructures` not yet populated). The framework binds a
|
||||
/// specific IddCx version (the INF's `UmdfExtensions`), which can be OLDER than our headers — newer
|
||||
/// fields (e.g. `IDDCX_ADAPTER_CAPS::StaticDesktopReencodeFrameCount`) make `size_of` too big and
|
||||
/// `IddCxAdapterInitAsync` rejects it; this returns the size the framework actually expects.
|
||||
///
|
||||
/// Only call once the IddCx context is initialised (e.g. from `EvtDeviceD0Entry`, after
|
||||
/// `IddCxDeviceInitialize`) — `IddStructures` may be null before that.
|
||||
#[must_use]
|
||||
pub fn framework_struct_size(index: u32) -> Option<u32> {
|
||||
// SAFETY: read-only.
|
||||
let count = unsafe { (&raw const iddcx::IddStructureCount).read() };
|
||||
if index >= count {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: read-only; `IddStructures` is the framework size table.
|
||||
let table = unsafe { (&raw const iddcx::IddStructures).read() };
|
||||
if table.is_null() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: `index` validated `< count`; `table` non-null.
|
||||
let size = unsafe { table.add(index as usize).read() };
|
||||
u32::try_from(size).ok()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user