c8423e42f3
DECISIVE: installed the pre-built UPSTREAM virtual-display-rs (Rust wdf-umdf IddCx) driver on the SAME box -> Status=OK. So a Rust IddCx driver inits an adapter here, self-signed, right now. My wdk-sys driver still fails ONLY at IddCxAdapterInitAsync (0xc000000d) despite matching virtual-display-rs on EVERY inspectable dimension: - same iddcx 1.10 headers+stub - IDDCX_ADAPTER_CAPS + IDD_CX_CLIENT_CONFIG byte-perfect (offsets match C header) - runtime pointers all valid/non-null (names .rdata, version stack, dev handle) - identical IddFunctions[idx]+IddDriverGlobals dispatch; indices 0/1/2 - matched the minimal link (tested vendored wdk-build WITHOUT OneCoreUAP/ NODEFAULTLIB/OPT/INTEGRITYCHECK -> still fails; export pollution ruled out) - device context, no device interface (control via EvtIddCxDeviceIoControl), init order The IddCx ClassExtension ETW provider emits no decodable reason (WPP/kernel-debugger only). The remaining difference is the wdk-sys IddCx binding itself, invisible to inspection. This commit keeps the upstream-matching structure (device context, no interface) + the on-glass instrumentation; vendored wdk-build reverted to pristine. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
180 lines
9.6 KiB
Rust
180 lines
9.6 KiB
Rust
//! IddCx adapter bring-up. Adapter creation is DEFERRED to the first `EvtDeviceD0Entry` (the adapter
|
|
//! object is only valid after D0), and is ASYNC: `init_adapter` builds the caps and calls
|
|
//! `IddCxAdapterInitAsync`; the adapter object arrives later via `EvtIddCxAdapterInitFinished`
|
|
//! (`adapter_init_finished` → [`set_adapter`]). FP16 caps + the obligated `*2`/gamma/hdr callbacks (in
|
|
//! `callbacks.rs`) together enable HDR. STEP 3.
|
|
|
|
use std::sync::OnceLock;
|
|
|
|
use wdk_sys::{iddcx, NTSTATUS, WDFDEVICE};
|
|
|
|
use crate::STATUS_SUCCESS;
|
|
|
|
/// A static, null-terminated UTF-16 string pointer (ASCII only) — wdk-sys has no `windows` `w!`.
|
|
macro_rules! wstr {
|
|
($s:literal) => {{
|
|
const N: usize = $s.len() + 1;
|
|
// `static` (NOT `const`) — a const's `.as_ptr()` points to a temporary dropped at the end of the
|
|
// statement (a dangling pointer); IddCx then reads garbage for the endpoint name strings and
|
|
// IddCxAdapterInitAsync fails INVALID_PARAMETER. A `static` has a stable 'static address.
|
|
static W: [u16; N] = {
|
|
let b = $s.as_bytes();
|
|
let mut w = [0u16; N];
|
|
let mut i = 0;
|
|
while i < b.len() {
|
|
w[i] = b[i] as u16;
|
|
i += 1;
|
|
}
|
|
w
|
|
};
|
|
W.as_ptr()
|
|
}};
|
|
}
|
|
|
|
/// The IddCx adapter handle, stashed for later DDIs (e.g. `SET_RENDER_ADAPTER`, STEP 4).
|
|
struct SendAdapter(iddcx::IDDCX_ADAPTER);
|
|
// SAFETY: an opaque IddCx handle, used only as an argument to IddCx DDIs (themselves the synchronisation
|
|
// point) — never dereferenced in Rust. Storing it across threads in a OnceLock is sound.
|
|
unsafe impl Send for SendAdapter {}
|
|
unsafe impl Sync for SendAdapter {}
|
|
|
|
static ADAPTER: OnceLock<SendAdapter> = OnceLock::new();
|
|
|
|
/// A WDF context type for the adapter object. SudoVDA/the oracle create the adapter via
|
|
/// `WDF_OBJECT_ATTRIBUTES::init_context_type(..)`; passing attributes with NO `ContextTypeInfo` is the one
|
|
/// structural difference left vs the working SudoVDA driver. `WDF_OBJECT_CONTEXT_TYPE_INFO` holds raw
|
|
/// pointers (so a Sync wrapper to allow a `static`); `UniqueType` self-references per `WDF_DECLARE_CONTEXT_TYPE`.
|
|
#[repr(transparent)]
|
|
struct CtxTypeInfo(wdk_sys::WDF_OBJECT_CONTEXT_TYPE_INFO);
|
|
// SAFETY: immutable 'static type metadata; the inner raw pointers are 'static and never written.
|
|
unsafe impl Sync for CtxTypeInfo {}
|
|
static ADAPTER_CTX: CtxTypeInfo = CtxTypeInfo(wdk_sys::WDF_OBJECT_CONTEXT_TYPE_INFO {
|
|
Size: core::mem::size_of::<wdk_sys::WDF_OBJECT_CONTEXT_TYPE_INFO>() as u32,
|
|
ContextName: c"PfVdAdapterCtx".as_ptr().cast(),
|
|
ContextSize: core::mem::size_of::<iddcx::IDDCX_ADAPTER>(),
|
|
UniqueType: &ADAPTER_CTX.0,
|
|
EvtDriverGetUniqueContextType: None,
|
|
});
|
|
|
|
/// Build the adapter caps (FP16/HDR-capable) and kick off the async adapter creation. Called from
|
|
/// `EvtDeviceD0Entry`; idempotent across re-entrant D0 transitions.
|
|
pub fn init_adapter(device: WDFDEVICE) -> NTSTATUS {
|
|
if ADAPTER.get().is_some() {
|
|
return STATUS_SUCCESS;
|
|
}
|
|
dbglog!("[pf-vd] init_adapter");
|
|
|
|
// 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}");
|
|
// Field-offset audit vs the expected C layout (x64): caps Flags=4 MaxRate=8 MaxMon=16 Diag=24
|
|
// Static=80; diag Trans=4 Friendly=8 Model=16 Manuf=24 HwVer=32 FwVer=40 Gamma=48. A mismatch =
|
|
// bindgen mis-laid the struct (would make IddCxAdapterInitAsync read garbage -> INVALID_PARAMETER).
|
|
dbglog!(
|
|
"[pf-vd] caps off: Flags={} MaxRate={} MaxMon={} Diag={} Static={}",
|
|
core::mem::offset_of!(iddcx::IDDCX_ADAPTER_CAPS, Flags),
|
|
core::mem::offset_of!(iddcx::IDDCX_ADAPTER_CAPS, MaxDisplayPipelineRate),
|
|
core::mem::offset_of!(iddcx::IDDCX_ADAPTER_CAPS, MaxMonitorsSupported),
|
|
core::mem::offset_of!(iddcx::IDDCX_ADAPTER_CAPS, EndPointDiagnostics),
|
|
core::mem::offset_of!(iddcx::IDDCX_ADAPTER_CAPS, StaticDesktopReencodeFrameCount),
|
|
);
|
|
dbglog!(
|
|
"[pf-vd] diag off: Trans={} Friendly={} Model={} Manuf={} HwVer={} FwVer={} Gamma={}",
|
|
core::mem::offset_of!(iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO, TransmissionType),
|
|
core::mem::offset_of!(iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO, pEndPointFriendlyName),
|
|
core::mem::offset_of!(iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO, pEndPointModelName),
|
|
core::mem::offset_of!(iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO, pEndPointManufacturerName),
|
|
core::mem::offset_of!(iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO, pHardwareVersion),
|
|
core::mem::offset_of!(iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO, pFirmwareVersion),
|
|
core::mem::offset_of!(iddcx::IDDCX_ENDPOINT_DIAGNOSTIC_INFO, GammaSupport),
|
|
);
|
|
|
|
// 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 = 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 = caps_size;
|
|
// FP16 (HDR) — consistent with the config's *2/gamma/hdr callbacks. NOTE: a minimal SDR adapter
|
|
// (Flags=NONE + only the required callbacks) ALSO fails IddCxAdapterInitAsync identically, so the
|
|
// FP16/*2/HDR set is NOT the blocker (see windows-pfvd-onglass-load memory).
|
|
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;
|
|
attr.ContextTypeInfo = &ADAPTER_CTX.0;
|
|
dbglog!(
|
|
"[pf-vd] rt: dev={:#x} pCaps={:#x} model={:#x} mfg={:#x} fwVer={:#x} hwVer={:#x} verSizeOf={} verSet={} diagSet={}",
|
|
device as usize,
|
|
(&raw const caps) as usize,
|
|
caps.EndPointDiagnostics.pEndPointModelName as usize,
|
|
caps.EndPointDiagnostics.pEndPointManufacturerName as usize,
|
|
caps.EndPointDiagnostics.pFirmwareVersion as usize,
|
|
caps.EndPointDiagnostics.pHardwareVersion as usize,
|
|
core::mem::size_of::<iddcx::IDDCX_ENDPOINT_VERSION>(),
|
|
version.Size,
|
|
caps.EndPointDiagnostics.Size,
|
|
);
|
|
let init = iddcx::IDARG_IN_ADAPTER_INIT {
|
|
WdfDevice: device,
|
|
pCaps: &raw mut caps,
|
|
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.
|
|
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).
|
|
pub fn set_adapter(adapter: iddcx::IDDCX_ADAPTER) {
|
|
let _ = ADAPTER.set(SendAdapter(adapter));
|
|
}
|