diff --git a/packaging/windows/drivers/pf-vdisplay/src/adapter.rs b/packaging/windows/drivers/pf-vdisplay/src/adapter.rs index 852746e..65e46c3 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/adapter.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/adapter.rs @@ -120,3 +120,9 @@ pub fn init_adapter(device: WDFDEVICE) -> NTSTATUS { pub fn set_adapter(adapter: iddcx::IDDCX_ADAPTER) { let _ = ADAPTER.set(SendAdapter(adapter)); } + +/// The created adapter handle, once `EvtIddCxAdapterInitFinished` has fired — for `create_monitor` +/// (`IddCxMonitorCreate`) and SET_RENDER_ADAPTER. `None` before adapter init completes. +pub(crate) fn adapter() -> Option { + ADAPTER.get().map(|a| a.0) +} diff --git a/packaging/windows/drivers/pf-vdisplay/src/callbacks.rs b/packaging/windows/drivers/pf-vdisplay/src/callbacks.rs index 65bb23a..476609f 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/callbacks.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/callbacks.rs @@ -9,7 +9,10 @@ use wdk_sys::iddcx; use wdk_sys::{NTSTATUS, WDFDEVICE, WDFREQUEST}; -use crate::{STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS}; +use crate::{ + STATUS_BUFFER_TOO_SMALL, STATUS_INVALID_PARAMETER, STATUS_NOT_FOUND, STATUS_NOT_IMPLEMENTED, + STATUS_SUCCESS, +}; /// PnP `EvtDeviceD0Entry` (not an IddCx config callback). Adapter creation is deferred to the first D0 /// (the adapter object is only valid after D0), not driver_add. @@ -32,11 +35,48 @@ pub unsafe extern "C" fn adapter_init_finished( STATUS_SUCCESS } -/// SDR mode list for an EDID monitor. STEP 4: EDID-serial lookup + count-then-fill `IDDCX_MONITOR_MODE`. +/// SDR mode list for an EDID monitor: EDID-serial lookup → count-then-fill `IDDCX_MONITOR_MODE`. pub unsafe extern "C" fn parse_monitor_description( - _p_in: *const iddcx::IDARG_IN_PARSEMONITORDESCRIPTION, - _p_out: *mut iddcx::IDARG_OUT_PARSEMONITORDESCRIPTION, + p_in: *const iddcx::IDARG_IN_PARSEMONITORDESCRIPTION, + p_out: *mut iddcx::IDARG_OUT_PARSEMONITORDESCRIPTION, ) -> NTSTATUS { + // SAFETY: framework-provided in/out args, valid for the call. + let in_args = unsafe { &*p_in }; + let out_args = unsafe { &mut *p_out }; + // SAFETY: the framework supplies a valid EDID buffer of `DataSize` bytes. + let edid = unsafe { + core::slice::from_raw_parts( + in_args.MonitorDescription.pData.cast::(), + in_args.MonitorDescription.DataSize as usize, + ) + }; + let Ok(id) = crate::edid::Edid::get_serial(edid) else { + return STATUS_INVALID_PARAMETER; + }; + let Some(modes) = crate::monitor::modes_for_id(id) else { + return STATUS_NOT_FOUND; + }; + let count = crate::monitor::flatten(&modes).count() as u32; + out_args.MonitorModeBufferOutputCount = count; + if in_args.MonitorModeBufferInputCount < count { + // A zero input count is a count-only probe (success); a non-zero too-small buffer is an error. + return if in_args.MonitorModeBufferInputCount > 0 { + STATUS_BUFFER_TOO_SMALL + } else { + STATUS_SUCCESS + }; + } + // SAFETY: `pMonitorModes` points to >= `count` IDDCX_MONITOR_MODE entries (validated above). + let out = unsafe { core::slice::from_raw_parts_mut(in_args.pMonitorModes, count as usize) }; + for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) { + let mut mode: iddcx::IDDCX_MONITOR_MODE = unsafe { core::mem::zeroed() }; + mode.Size = core::mem::size_of::() as u32; + mode.Origin = iddcx::IDDCX_MONITOR_MODE_ORIGIN::IDDCX_MONITOR_MODE_ORIGIN_MONITORDESCRIPTOR; + mode.MonitorVideoSignalInfo = + crate::monitor::display_info(item.width, item.height, item.refresh_rate); + *slot = mode; + } + out_args.PreferredMonitorModeIdx = 0; STATUS_SUCCESS } @@ -57,12 +97,27 @@ pub unsafe extern "C" fn monitor_get_default_modes( STATUS_NOT_IMPLEMENTED } -/// SDR target (scan-out) modes. STEP 4: pointer-match the monitor + fill `IDDCX_TARGET_MODE`. +/// SDR target (scan-out) modes: pointer-match the monitor → fill `IDDCX_TARGET_MODE`. pub unsafe extern "C" fn monitor_query_modes( - _monitor: iddcx::IDDCX_MONITOR, - _p_in: *const iddcx::IDARG_IN_QUERYTARGETMODES, - _p_out: *mut iddcx::IDARG_OUT_QUERYTARGETMODES, + monitor: iddcx::IDDCX_MONITOR, + p_in: *const iddcx::IDARG_IN_QUERYTARGETMODES, + p_out: *mut iddcx::IDARG_OUT_QUERYTARGETMODES, ) -> NTSTATUS { + // SAFETY: framework-provided in/out args, valid for the call. + let in_args = unsafe { &*p_in }; + let out_args = unsafe { &mut *p_out }; + let Some(modes) = crate::monitor::modes_for_object(monitor) else { + return STATUS_NOT_FOUND; + }; + let count = crate::monitor::flatten(&modes).count() as u32; + out_args.TargetModeBufferOutputCount = count; + if in_args.TargetModeBufferInputCount >= count { + // SAFETY: `pTargetModes` points to >= `count` IDDCX_TARGET_MODE entries. + let out = unsafe { core::slice::from_raw_parts_mut(in_args.pTargetModes, count as usize) }; + for (item, slot) in crate::monitor::flatten(&modes).zip(out.iter_mut()) { + *slot = crate::monitor::target_mode(item.width, item.height, item.refresh_rate); + } + } STATUS_SUCCESS } diff --git a/packaging/windows/drivers/pf-vdisplay/src/control.rs b/packaging/windows/drivers/pf-vdisplay/src/control.rs index 49f5a1f..d713629 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/control.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/control.rs @@ -1,8 +1,7 @@ //! The `pf-vdisplay-proto` control plane (`EvtIddCxDeviceIoControl`). The host opens the device interface -//! (`PF_VDISPLAY_INTERFACE_GUID`) and drives the low-frequency IOCTLs: GET_INFO (version handshake), -//! PING (watchdog keepalive), and — STEP 4 (next) — ADD/REMOVE/SET_RENDER_ADAPTER/CLEAR_ALL for virtual -//! monitors. Every path completes the `WDFREQUEST` exactly once (the `EVT_IDD_CX_DEVICE_IO_CONTROL` shape -//! returns `()`). +//! (`PF_VDISPLAY_INTERFACE_GUID`) and drives the low-frequency IOCTLs: GET_INFO (version handshake), PING +//! (watchdog keepalive), ADD/REMOVE/CLEAR_ALL (virtual monitors), and SET_RENDER_ADAPTER (next). Every +//! path completes the `WDFREQUEST` exactly once (the `EVT_IDD_CX_DEVICE_IO_CONTROL` shape returns `()`). use core::sync::atomic::{AtomicU64, Ordering}; @@ -10,7 +9,7 @@ use pf_vdisplay_proto::control; use wdk_iddcx::nt_success; use wdk_sys::{call_unsafe_wdf_function_binding, NTSTATUS, WDFREQUEST}; -use crate::{STATUS_NOT_FOUND, STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS}; +use crate::{STATUS_INVALID_PARAMETER, STATUS_NOT_FOUND, STATUS_NOT_IMPLEMENTED, STATUS_SUCCESS}; /// The host must PING within this window or the watchdog reaps all monitors (STEP 4: the watchdog thread). const WATCHDOG_TIMEOUT_S: u32 = 10; @@ -24,38 +23,109 @@ static WATCHDOG_PINGS: AtomicU64 = AtomicU64::new(0); /// `request` is the framework-provided `WDFREQUEST` for an `EvtIddCxDeviceIoControl` call. pub unsafe fn dispatch(request: WDFREQUEST, ioctl_code: u32) { match ioctl_code { - control::IOCTL_GET_INFO => unsafe { get_info(request) }, + control::IOCTL_GET_INFO => { + let reply = control::InfoReply { + protocol_version: pf_vdisplay_proto::PROTOCOL_VERSION, + watchdog_timeout_s: WATCHDOG_TIMEOUT_S, + }; + // SAFETY: `request` is the framework WDFREQUEST. + unsafe { write_output_complete(request, &reply) }; + } control::IOCTL_PING => { WATCHDOG_PINGS.fetch_add(1, Ordering::Relaxed); complete(request, STATUS_SUCCESS); } - // STEP 4 (next): ADD -> create_monitor, REMOVE, SET_RENDER_ADAPTER, CLEAR_ALL. - control::IOCTL_ADD - | control::IOCTL_REMOVE - | control::IOCTL_SET_RENDER_ADAPTER - | control::IOCTL_CLEAR_ALL => complete(request, STATUS_NOT_IMPLEMENTED), + // SAFETY: `request` is the framework WDFREQUEST. + control::IOCTL_ADD => unsafe { add(request) }, + // SAFETY: `request` is the framework WDFREQUEST. + control::IOCTL_REMOVE => unsafe { remove(request) }, + control::IOCTL_CLEAR_ALL => { + crate::monitor::clear_all(); + complete(request, STATUS_SUCCESS); + } + // SET_RENDER_ADAPTER (hybrid-GPU render pin): STEP 4 (next). + control::IOCTL_SET_RENDER_ADAPTER => complete(request, STATUS_NOT_IMPLEMENTED), _ => complete(request, STATUS_NOT_FOUND), } } -/// `IOCTL_GET_INFO`: write [`control::InfoReply`] (protocol version + watchdog timeout). The host asserts -/// `protocol_version == PROTOCOL_VERSION` and fails loudly on a mismatch. +/// `IOCTL_ADD`: create a virtual monitor at the requested mode → reply with the OS target id + LUID. /// /// # Safety /// `request` is the framework `WDFREQUEST`. -unsafe fn get_info(request: WDFREQUEST) { - let reply = control::InfoReply { - protocol_version: pf_vdisplay_proto::PROTOCOL_VERSION, - watchdog_timeout_s: WATCHDOG_TIMEOUT_S, +unsafe fn add(request: WDFREQUEST) { + // SAFETY: `request` is the framework WDFREQUEST. + let Some(req) = (unsafe { read_input::(request) }) else { + complete(request, STATUS_INVALID_PARAMETER); + return; }; + let Some((target_id, luid_low, luid_high)) = + crate::monitor::create_monitor(req.session_id, req.width, req.height, req.refresh_hz) + else { + complete(request, STATUS_NOT_FOUND); + return; + }; + let reply = control::AddReply { + adapter_luid_low: luid_low, + adapter_luid_high: luid_high, + target_id, + _reserved: 0, + }; + // SAFETY: `request` is the framework WDFREQUEST. + unsafe { write_output_complete(request, &reply) }; +} + +/// `IOCTL_REMOVE`: depart + drop the monitor for the given session id. +/// +/// # Safety +/// `request` is the framework `WDFREQUEST`. +unsafe fn remove(request: WDFREQUEST) { + // SAFETY: `request` is the framework WDFREQUEST. + let Some(req) = (unsafe { read_input::(request) }) else { + complete(request, STATUS_INVALID_PARAMETER); + return; + }; + crate::monitor::remove_monitor(req.session_id); + complete(request, STATUS_SUCCESS); +} + +/// Read a `Copy`/`Pod` input struct from the request's input buffer (None if too small / unavailable). +/// +/// # Safety +/// `request` is the framework `WDFREQUEST`. +unsafe fn read_input(request: WDFREQUEST) -> Option { let mut buf: *mut core::ffi::c_void = core::ptr::null_mut(); let mut len: usize = 0; - // SAFETY: `request` is valid; `buf`/`len` are out-params written by the framework. + // SAFETY: `request` valid; `buf`/`len` are out-params written by the framework. + let st = unsafe { + call_unsafe_wdf_function_binding!( + WdfRequestRetrieveInputBuffer, + request, + core::mem::size_of::(), + &mut buf, + &mut len + ) + }; + if !nt_success(st) || buf.is_null() || len < core::mem::size_of::() { + return None; + } + // SAFETY: `buf` has >= size_of::() bytes; T is a Pod control struct. + Some(unsafe { buf.cast::().read_unaligned() }) +} + +/// Write a `Copy`/`Pod` reply to the request's output buffer + complete with its byte count. +/// +/// # Safety +/// `request` is the framework `WDFREQUEST`. +unsafe fn write_output_complete(request: WDFREQUEST, value: &T) { + let mut buf: *mut core::ffi::c_void = core::ptr::null_mut(); + let mut len: usize = 0; + // SAFETY: `request` valid; `buf`/`len` are out-params written by the framework. let st = unsafe { call_unsafe_wdf_function_binding!( WdfRequestRetrieveOutputBuffer, request, - core::mem::size_of::(), + core::mem::size_of::(), &mut buf, &mut len ) @@ -64,9 +134,9 @@ unsafe fn get_info(request: WDFREQUEST) { complete(request, st); return; } - // SAFETY: `buf` has >= size_of::() writable bytes (validated above); InfoReply is Pod. - unsafe { buf.cast::().write_unaligned(reply) }; - complete_info(request, STATUS_SUCCESS, core::mem::size_of::()); + // SAFETY: `buf` has >= size_of::() writable bytes; T is a Pod control struct. + unsafe { buf.cast::().write_unaligned(*value) }; + complete_info(request, STATUS_SUCCESS, core::mem::size_of::()); } /// Complete a request with just a status (no output). diff --git a/packaging/windows/drivers/pf-vdisplay/src/lib.rs b/packaging/windows/drivers/pf-vdisplay/src/lib.rs index d27e764..01916c8 100644 --- a/packaging/windows/drivers/pf-vdisplay/src/lib.rs +++ b/packaging/windows/drivers/pf-vdisplay/src/lib.rs @@ -15,9 +15,9 @@ mod log; mod adapter; mod callbacks; mod control; -#[allow(dead_code)] // salvaged verbatim; wired into the mode callbacks in STEP 4 mod edid; mod entry; +mod monitor; use wdk_sys::NTSTATUS; @@ -25,6 +25,8 @@ use wdk_sys::NTSTATUS; pub(crate) const STATUS_SUCCESS: NTSTATUS = 0; pub(crate) const STATUS_NOT_IMPLEMENTED: NTSTATUS = 0xC000_0002u32 as NTSTATUS; pub(crate) const STATUS_NOT_FOUND: NTSTATUS = 0xC000_0225u32 as NTSTATUS; +pub(crate) const STATUS_INVALID_PARAMETER: NTSTATUS = 0xC000_000Du32 as NTSTATUS; +pub(crate) const STATUS_BUFFER_TOO_SMALL: NTSTATUS = 0xC000_0023u32 as NTSTATUS; /// IddCx (stub mode) requires the driver to export the minimum IddCx framework version it needs — the /// `#ifndef IDD_STUB` branch of `IddCxFuncEnum.h` that normally emits it is compiled out under diff --git a/packaging/windows/drivers/pf-vdisplay/src/monitor.rs b/packaging/windows/drivers/pf-vdisplay/src/monitor.rs new file mode 100644 index 0000000..30acf87 --- /dev/null +++ b/packaging/windows/drivers/pf-vdisplay/src/monitor.rs @@ -0,0 +1,254 @@ +//! Virtual-monitor model + lifecycle (STEP 4). Monitors are created on demand by the control plane +//! ([`crate::control`], `IOCTL_ADD`): each carries the requested mode (advertised as preferred) plus the +//! `session_id` the host keys it by and the OS target id + render-adapter LUID captured at arrival. Ported +//! from the working upstream virtual-display-rs (`monitor.rs` + `context.rs::create_monitor`), with +//! `guid: u128` → `session_id: u64` for the owned `pf_vdisplay_proto` control plane. + +use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::Mutex; +use std::time::Instant; + +use wdk_sys::iddcx; + +/// One resolution with the refresh rates it supports. +#[derive(Clone)] +pub struct Mode { + pub width: u32, + pub height: u32, + pub refresh_rates: Vec, +} + +/// A single (width, height, refresh) tuple — modes flattened across their refresh rates. +#[derive(Copy, Clone)] +pub struct ModeItem { + pub width: u32, + pub height: u32, + pub refresh_rate: u32, +} + +/// Flatten a mode list into per-refresh-rate tuples (the order the mode DDIs emit). +pub fn flatten(modes: &[Mode]) -> impl Iterator + '_ { + modes.iter().flat_map(|m| { + m.refresh_rates.iter().map(|&rr| ModeItem { + width: m.width, + height: m.height, + refresh_rate: rr, + }) + }) +} + +/// A live (or pending) virtual monitor. +pub struct MonitorObject { + /// The IddCx monitor handle, set once `IddCxMonitorCreate` returns (None while pending). + pub object: Option, + /// EDID serial / connector index — the key the mode DDIs match on. + pub id: u32, + /// Advertised modes (requested mode first, then [`default_modes`]). + pub modes: Vec, + /// The host's monotonic key (ADD/REMOVE). + pub session_id: u64, + /// OS target id + render-adapter LUID from `IDARG_OUT_MONITORARRIVAL` (the ADD reply). + pub target_id: u32, + pub adapter_luid_low: u32, + pub adapter_luid_high: i32, + /// When the entry was created — the watchdog skips still-initializing monitors. + pub created_at: Instant, +} +// SAFETY: the raw IddCx monitor handle is framework-managed; access is serialized by MONITOR_MODES. +unsafe impl Send for MonitorObject {} + +pub static MONITOR_MODES: Mutex> = Mutex::new(Vec::new()); +/// Monitor id / EDID-serial counter (unique per created monitor). +static NEXT_ID: AtomicU32 = AtomicU32::new(1); + +/// Fallback modes appended after the requested mode, so a topology change still has options. +fn default_modes() -> Vec { + vec![ + Mode { width: 1920, height: 1080, refresh_rates: vec![60, 120] }, + Mode { width: 1280, height: 720, refresh_rates: vec![60] }, + ] +} + +/// `DISPLAYCONFIG_VIDEO_SIGNAL_INFO` for a monitor mode (vSyncFreqDivider = 0, per the DDI contract). +pub fn display_info(width: u32, height: u32, refresh_rate: u32) -> wdk_sys::DISPLAYCONFIG_VIDEO_SIGNAL_INFO { + let clock_rate = refresh_rate * (height + 4) * (height + 4) + 1000; + let mut si: wdk_sys::DISPLAYCONFIG_VIDEO_SIGNAL_INFO = unsafe { core::mem::zeroed() }; + si.pixelRate = u64::from(clock_rate); + si.hSyncFreq = wdk_sys::DISPLAYCONFIG_RATIONAL { Numerator: clock_rate, Denominator: height + 4 }; + si.vSyncFreq = + wdk_sys::DISPLAYCONFIG_RATIONAL { Numerator: clock_rate, Denominator: (height + 4) * (height + 4) }; + si.activeSize = wdk_sys::DISPLAYCONFIG_2DREGION { cx: width, cy: height }; + si.totalSize = wdk_sys::DISPLAYCONFIG_2DREGION { cx: width + 4, cy: height + 4 }; + // union { AdditionalSignalInfo bitfield | videoStandard:u32 }: videoStandard=255, vSyncFreqDivider=0. + si.__bindgen_anon_1.videoStandard = 255; + si.scanLineOrdering = + wdk_sys::DISPLAYCONFIG_SCANLINE_ORDERING::DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE; + si +} + +/// `IDDCX_TARGET_MODE` for a scan-out mode (vSyncFreqDivider = 1, per the DDI contract). +pub fn target_mode(width: u32, height: u32, refresh_rate: u32) -> iddcx::IDDCX_TARGET_MODE { + let region = wdk_sys::DISPLAYCONFIG_2DREGION { cx: width, cy: height }; + let mut si: wdk_sys::DISPLAYCONFIG_VIDEO_SIGNAL_INFO = unsafe { core::mem::zeroed() }; + si.pixelRate = u64::from(refresh_rate) * u64::from(width) * u64::from(height); + si.hSyncFreq = wdk_sys::DISPLAYCONFIG_RATIONAL { Numerator: refresh_rate * height, Denominator: 1 }; + si.vSyncFreq = wdk_sys::DISPLAYCONFIG_RATIONAL { Numerator: refresh_rate, Denominator: 1 }; + si.totalSize = region; + si.activeSize = region; + si.scanLineOrdering = + wdk_sys::DISPLAYCONFIG_SCANLINE_ORDERING::DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE; + // videoStandard=255, vSyncFreqDivider=1 (bits 16..21) => 255 | (1<<16). + si.__bindgen_anon_1.videoStandard = 255 | (1 << 16); + let mut tm: iddcx::IDDCX_TARGET_MODE = unsafe { core::mem::zeroed() }; + tm.Size = core::mem::size_of::() as u32; + tm.TargetVideoSignalInfo = wdk_sys::DISPLAYCONFIG_TARGET_MODE { targetVideoSignalInfo: si }; + tm +} + +/// A monitor's advertised modes (the looked-up entry returns a clone for lock-free mode-DDI fill). +pub fn modes_for_id(id: u32) -> Option> { + MONITOR_MODES.lock().ok()?.iter().find(|m| m.id == id).map(|m| m.modes.clone()) +} + +/// Modes for the monitor whose handle matches (used by `monitor_query_modes`). +pub fn modes_for_object(object: iddcx::IDDCX_MONITOR) -> Option> { + MONITOR_MODES + .lock() + .ok()? + .iter() + .find(|m| m.object == Some(object)) + .map(|m| m.modes.clone()) +} + +/// `IOCTL_ADD`: create + arrive a virtual monitor at `width`x`height`@`refresh`. Returns the OS +/// `(target_id, adapter_luid_low, adapter_luid_high)` for the [`AddReply`](pf_vdisplay_proto::control::AddReply), +/// or `None` on failure (no adapter yet / IddCx error). +pub fn create_monitor(session_id: u64, width: u32, height: u32, refresh: u32) -> Option<(u32, u32, i32)> { + let adapter = crate::adapter::adapter()?; + let id = NEXT_ID.fetch_add(1, Ordering::Relaxed); + + let mut modes = vec![Mode { width, height, refresh_rates: vec![refresh] }]; + modes.extend(default_modes()); + + // Register the (pending) monitor so the mode DDIs can find it by EDID-serial id before arrival. + if let Ok(mut lock) = MONITOR_MODES.lock() { + lock.push(MonitorObject { + object: None, + id, + modes, + session_id, + target_id: 0, + adapter_luid_low: 0, + adapter_luid_high: 0, + created_at: Instant::now(), + }); + } else { + return None; + } + + // EDID (serial = id) describes the monitor; the OS calls back into parse_monitor_description. + let mut edid = crate::edid::Edid::generate_with(id); + let mut desc: iddcx::IDDCX_MONITOR_DESCRIPTION = unsafe { core::mem::zeroed() }; + desc.Size = core::mem::size_of::() as u32; + desc.Type = iddcx::IDDCX_MONITOR_DESCRIPTION_TYPE::IDDCX_MONITOR_DESCRIPTION_TYPE_EDID; + desc.DataSize = edid.len() as u32; + desc.pData = edid.as_mut_ptr().cast(); + + let mut info: iddcx::IDDCX_MONITOR_INFO = unsafe { core::mem::zeroed() }; + info.Size = core::mem::size_of::() as u32; + info.MonitorContainerId = container_guid(id); + info.MonitorType = + wdk_sys::DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY::DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI; + info.ConnectorIndex = id; + info.MonitorDescription = desc; + + let mut attr: wdk_sys::WDF_OBJECT_ATTRIBUTES = unsafe { core::mem::zeroed() }; + attr.Size = core::mem::size_of::() as u32; + attr.ExecutionLevel = wdk_sys::_WDF_EXECUTION_LEVEL::WdfExecutionLevelInheritFromParent; + attr.SynchronizationScope = + wdk_sys::_WDF_SYNCHRONIZATION_SCOPE::WdfSynchronizationScopeInheritFromParent; + + let create_in = iddcx::IDARG_IN_MONITORCREATE { ObjectAttributes: &raw mut attr, pMonitorInfo: &raw mut info }; + let mut create_out: iddcx::IDARG_OUT_MONITORCREATE = unsafe { core::mem::zeroed() }; + // SAFETY: adapter is a valid IddCx adapter; create_in points to valid local storage read synchronously. + let st = unsafe { wdk_iddcx::IddCxMonitorCreate(adapter, &create_in, &mut create_out) }; + dbglog!("[pf-vd] IddCxMonitorCreate(id={id}) -> {st:#x}"); + if !wdk_iddcx::nt_success(st) { + remove_by_id(id); + return None; + } + let monitor = create_out.MonitorObject; + if let Ok(mut lock) = MONITOR_MODES.lock() { + if let Some(m) = lock.iter_mut().find(|m| m.id == id) { + m.object = Some(monitor); + } + } + + // Tell the OS the monitor is plugged in. + let mut arrival_out: iddcx::IDARG_OUT_MONITORARRIVAL = unsafe { core::mem::zeroed() }; + // SAFETY: `monitor` is the just-created IddCx monitor handle. + let st = unsafe { wdk_iddcx::IddCxMonitorArrival(monitor, &mut arrival_out) }; + dbglog!("[pf-vd] IddCxMonitorArrival(id={id}) -> {st:#x}"); + if !wdk_iddcx::nt_success(st) { + return None; + } + + let (target_id, luid_low, luid_high) = ( + arrival_out.OsTargetId, + arrival_out.OsAdapterLuid.LowPart, + arrival_out.OsAdapterLuid.HighPart, + ); + if let Ok(mut lock) = MONITOR_MODES.lock() { + if let Some(m) = lock.iter_mut().find(|m| m.id == id) { + m.target_id = target_id; + m.adapter_luid_low = luid_low; + m.adapter_luid_high = luid_high; + } + } + Some((target_id, luid_low, luid_high)) +} + +/// `IOCTL_REMOVE`: depart + drop the monitor for `session_id`. Returns true if one was removed. +pub fn remove_monitor(session_id: u64) -> bool { + let monitor = { + let Ok(mut lock) = MONITOR_MODES.lock() else { return false }; + let Some(pos) = lock.iter().position(|m| m.session_id == session_id) else { return false }; + let entry = lock.remove(pos); + entry.object + }; + if let Some(m) = monitor { + // SAFETY: `m` is a live IddCx monitor handle; departure tears it down. + unsafe { wdk_iddcx::IddCxMonitorDeparture(m) }; + } + true +} + +/// `IOCTL_CLEAR_ALL`: depart + drop every monitor (host-startup orphan reap). +pub fn clear_all() { + let monitors: Vec = { + let Ok(mut lock) = MONITOR_MODES.lock() else { return }; + lock.drain(..).filter_map(|m| m.object).collect() + }; + for m in monitors { + // SAFETY: `m` is a live IddCx monitor handle. + unsafe { wdk_iddcx::IddCxMonitorDeparture(m) }; + } +} + +/// Drop a pending entry by id (create failed before arrival). +fn remove_by_id(id: u32) { + if let Ok(mut lock) = MONITOR_MODES.lock() { + lock.retain(|m| m.id != id); + } +} + +/// A deterministic, monitor-unique container GUID (groups targets into a physical device). Derived from +/// `id` so it is stable + collision-free without a random source. +fn container_guid(id: u32) -> wdk_sys::GUID { + wdk_sys::GUID { + Data1: 0x7066_7664u32.wrapping_add(id), + Data2: 0x7044, + Data3: 0x5350, + Data4: [0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6, (id >> 8) as u8, id as u8], + } +}