fix(windows-drivers): pf-vdisplay robustness — AdapterInitStatus gate, pooled-device TDR check, MMCSS-optional worker
Batch B of the audit's medium tier (M4+M5+M6): - M4: adapter_init_finished now reads AdapterInitStatus (was ignored) and only stashes the adapter on NT_SUCCESS, per the MS sample. A failed async init previously produced a HUSK adapter: monitors created on it arrive but the OS never assigns a swap-chain — every session black-screens with no visible cause (the exact signature live fault-injection produced after a WUDFHost kill). Unset adapter → ADD fails cleanly (host-retryable) and a re-entrant D0 retries the init; the status is now in the debug log. - M5: pooled_device checks GetDeviceRemovedReason on a cache hit — a TDR'd device was returned for its LUID forever (SetDevice fail-loop, black virtual display until device teardown); now it falls through to a fresh create. - M6: an AvSetMmThreadCharacteristicsW failure no longer aborts the worker before draining (which stalled the monitor and leaked the WDF swap-chain object) — continue unprioritized like the MS sample; revert only if MMCSS actually engaged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,13 +25,23 @@ pub unsafe extern "C" fn device_d0_entry(
|
|||||||
crate::adapter::init_adapter(device)
|
crate::adapter::init_adapter(device)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Async completion of `IddCxAdapterInitAsync`: stash the adapter for later DDIs. STEP 4 also starts the
|
/// Async completion of `IddCxAdapterInitAsync`: stash the adapter for later DDIs — IFF the init
|
||||||
/// watchdog here.
|
/// actually SUCCEEDED. STEP 4 also starts the watchdog here.
|
||||||
pub unsafe extern "C" fn adapter_init_finished(
|
pub unsafe extern "C" fn adapter_init_finished(
|
||||||
adapter: iddcx::IDDCX_ADAPTER,
|
adapter: iddcx::IDDCX_ADAPTER,
|
||||||
_p_in: *const iddcx::IDARG_IN_ADAPTER_INIT_FINISHED,
|
p_in: *const iddcx::IDARG_IN_ADAPTER_INIT_FINISHED,
|
||||||
) -> NTSTATUS {
|
) -> NTSTATUS {
|
||||||
dbglog!("[pf-vd] adapter_init_finished");
|
// SAFETY: the framework supplies a valid, live input-args pointer for the call.
|
||||||
|
let status = unsafe { (*p_in).AdapterInitStatus };
|
||||||
|
dbglog!("[pf-vd] adapter_init_finished (AdapterInitStatus={status:#010x})");
|
||||||
|
// The MS sample gates on NT_SUCCESS(AdapterInitStatus). An adapter whose async init FAILED is a
|
||||||
|
// husk the contract forbids using: monitors created on it arrive but are never activated (no
|
||||||
|
// swap-chain ever assigned) — every session then black-screens with no visible cause. Leaving
|
||||||
|
// the ADAPTER unset makes `create_monitor` fail the ADD cleanly (host-visible + retryable), and
|
||||||
|
// a re-entrant D0 retries the init (`init_adapter` only short-circuits once the stash is set).
|
||||||
|
if status < 0 {
|
||||||
|
return STATUS_SUCCESS; // the callback itself succeeded; the failure is in NOT adopting
|
||||||
|
}
|
||||||
crate::adapter::set_adapter(adapter);
|
crate::adapter::set_adapter(adapter);
|
||||||
crate::control::start_watchdog();
|
crate::control::start_watchdog();
|
||||||
STATUS_SUCCESS
|
STATUS_SUCCESS
|
||||||
|
|||||||
@@ -150,7 +150,16 @@ pub fn pooled_device(luid: LUID) -> Option<Arc<Direct3DDevice>> {
|
|||||||
if let Some((k, dev)) = pool.as_ref()
|
if let Some((k, dev)) = pool.as_ref()
|
||||||
&& *k == key
|
&& *k == key
|
||||||
{
|
{
|
||||||
return Some(dev.clone());
|
// A TDR / driver reset REMOVES the pooled device permanently; handing it out again gives
|
||||||
|
// every future swap-chain a dead device (SetDevice fail-loop → black virtual display until
|
||||||
|
// device teardown). Detect and fall through to a fresh create instead.
|
||||||
|
// SAFETY: plain status query on the live pooled device.
|
||||||
|
match unsafe { dev.device.GetDeviceRemovedReason() } {
|
||||||
|
Ok(()) => return Some(dev.clone()),
|
||||||
|
Err(e) => {
|
||||||
|
dbglog!("[pf-vd] pooled D3D device was REMOVED ({e:?}) — recreating on {key:#x}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
match Direct3DDevice::init(luid) {
|
match Direct3DDevice::init(luid) {
|
||||||
Ok(d) => {
|
Ok(d) => {
|
||||||
|
|||||||
@@ -117,9 +117,18 @@ impl SwapChainProcessor {
|
|||||||
// SAFETY: `w!("Distribution")` is a 'static null-terminated UTF-16 task name; `av_task` is a
|
// SAFETY: `w!("Distribution")` is a 'static null-terminated UTF-16 task name; `av_task` is a
|
||||||
// valid local out-param. The returned handle is reverted with AvRevertMmThreadCharacteristics.
|
// valid local out-param. The returned handle is reverted with AvRevertMmThreadCharacteristics.
|
||||||
let res = unsafe { AvSetMmThreadCharacteristicsW(w!("Distribution"), &mut av_task) };
|
let res = unsafe { AvSetMmThreadCharacteristicsW(w!("Distribution"), &mut av_task) };
|
||||||
let Ok(av_handle) = res else {
|
// MMCSS can fail under the restricted WUDFHost token ('Distribution' task unregistered /
|
||||||
dbglog!("[pf-vd] swap-chain: failed to prioritize thread: {res:?}");
|
// service unavailable). The MS sample CONTINUES unprioritized — never abort: returning
|
||||||
return;
|
// here would leave the assigned swap-chain undrained (the monitor stalls, DWM blocks on
|
||||||
|
// it) and leak the WDF swap-chain object until device teardown.
|
||||||
|
let av_handle = match res {
|
||||||
|
Ok(h) => Some(h),
|
||||||
|
Err(e) => {
|
||||||
|
dbglog!(
|
||||||
|
"[pf-vd] swap-chain: MMCSS prioritization failed ({e:?}) — continuing unprioritized"
|
||||||
|
);
|
||||||
|
None
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Self::run_core(
|
Self::run_core(
|
||||||
@@ -144,13 +153,15 @@ impl SwapChainProcessor {
|
|||||||
call_unsafe_wdf_function_binding!(WdfObjectDelete, swap_chain.0 as WDFOBJECT);
|
call_unsafe_wdf_function_binding!(WdfObjectDelete, swap_chain.0 as WDFOBJECT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Revert the thread to normal once it's done.
|
// Revert the thread to normal once it's done (only if MMCSS was actually engaged).
|
||||||
// SAFETY: `av_handle` is the live characteristics handle returned by AvSetMmThreadCharacteristicsW
|
if let Some(h) = av_handle {
|
||||||
// above, reverted exactly once here at thread exit.
|
// SAFETY: `h` is the live characteristics handle returned by
|
||||||
let res = unsafe { AvRevertMmThreadCharacteristics(av_handle) };
|
// AvSetMmThreadCharacteristicsW above, reverted exactly once here at thread exit.
|
||||||
|
let res = unsafe { AvRevertMmThreadCharacteristics(h) };
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
dbglog!("[pf-vd] swap-chain: failed to revert prioritized thread: {e:?}");
|
dbglog!("[pf-vd] swap-chain: failed to revert prioritized thread: {e:?}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
self.thread = Some(join_handle);
|
self.thread = Some(join_handle);
|
||||||
|
|||||||
Reference in New Issue
Block a user