feat(windows-drivers): host-gone watchdog, SET_RENDER_ADAPTER, log gate, mode bounds

Audit §4.1: implement the host-gone watchdog — it was dead code (WATCHDOG_PINGS bumped but never sampled, no thread). Every IOCTL now bumps a liveness counter; a watchdog thread reap_orphaned()s monitors (created_at grace) if no IOCTL arrives within WATCHDOG_TIMEOUT_S, so a crashed/TerminateProcess'd host no longer leaves its virtual monitor + swap-chain worker + pooled D3D device wedged until the next CLEAR_ALL. Removes the false 'watchdog thread' comments.

Audit §4.2: implement SET_RENDER_ADAPTER (was STATUS_NOT_IMPLEMENTED) via IddCxAdapterSetRenderAdapter, so the host can pin the IDD render to the NVENC GPU on a hybrid iGPU+dGPU box (else the OS-picked iGPU makes the host ring textures un-openable -> DRV_STATUS_TEX_FAIL).

Audit §4.4: gate the world-writable C:\Users\Public\pfvd-driver.log behind debug builds / PFVD_DEBUG_LOG (a release build never writes it).

Audit §4.5: bounds-check the requested mode in IOCTL_ADD; compute display_info clock_rate in u64 + saturate (the old u32 refresh*(h+4)^2 overflowed/aborted the mode DDI for large modes).

Verified: driver workspace builds clean on the RTX box (WDK 26100 + LLVM 21.1.2, MSVC). On-glass functional validation of the watchdog/render-pin is a follow-up (needs a driver reinstall + session).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-25 12:49:49 +00:00
parent 95dcef3515
commit 0a7ae5ef09
5 changed files with 177 additions and 18 deletions
@@ -142,3 +142,26 @@ pub fn set_adapter(adapter: iddcx::IDDCX_ADAPTER) {
pub(crate) fn adapter() -> Option<iddcx::IDDCX_ADAPTER> {
ADAPTER.get().map(|a| a.0)
}
/// Honor the host's `IOCTL_SET_RENDER_ADAPTER`: pin the GPU the IddCx swap-chain renders on. On a hybrid
/// iGPU+dGPU box the OS may otherwise pick the iGPU to render the virtual monitor, so the host's shared
/// ring textures (created on the NVENC dGPU) can't be opened → `DRV_STATUS_TEX_FAIL` → the host's 20 s
/// black bail. Pinning the render adapter to the encode GPU fixes that. Unconditional — NOT the
/// SudoVDA-parity default-off branch (`docs/windows-host-rewrite-audit.md` §4.2). Returns
/// `STATUS_NOT_FOUND` if called before the adapter exists.
pub fn set_render_adapter(luid_low: u32, luid_high: i32) -> NTSTATUS {
let Some(adapter) = adapter() else {
return crate::STATUS_NOT_FOUND;
};
// SAFETY: building a C POD — the all-zero bit pattern is a valid IDARG_IN_ADAPTERSETRENDERADAPTER;
// the one meaningful field is assigned below.
let mut in_args: iddcx::IDARG_IN_ADAPTERSETRENDERADAPTER = unsafe { core::mem::zeroed() };
in_args.PreferredRenderAdapter = wdk_sys::LUID {
LowPart: luid_low,
HighPart: luid_high,
};
dbglog!("[pf-vd] set_render_adapter -> {luid_high:08x}:{luid_low:08x}");
// SAFETY: `adapter` is the stashed IddCx adapter; `in_args` is valid local storage read synchronously.
unsafe { wdk_iddcx::IddCxAdapterSetRenderAdapter(adapter, &in_args) };
STATUS_SUCCESS
}