feat(windows-drivers): STEP 4 (2/n) — create_monitor + real mode DDIs + ADD/REMOVE
windows-drivers / probe-and-proto (push) Successful in 33s
windows-drivers / driver-build (push) Successful in 1m10s
android / android (push) Successful in 4m2s
ci / rust (push) Successful in 4m39s
ci / web (push) Successful in 44s
ci / docs-site (push) Successful in 52s
deb / build-publish (push) Successful in 2m17s
windows-host / package (push) Successful in 6m16s
decky / build-publish (push) Successful in 25s
ci / bench (push) Successful in 4m44s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 27s
apple / swift (push) Successful in 1m13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m51s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m51s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 47s
apple / screenshots (push) Successful in 5m45s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m49s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m47s
docker / deploy-docs (push) Successful in 21s
windows-drivers / probe-and-proto (push) Successful in 33s
windows-drivers / driver-build (push) Successful in 1m10s
android / android (push) Successful in 4m2s
ci / rust (push) Successful in 4m39s
ci / web (push) Successful in 44s
ci / docs-site (push) Successful in 52s
deb / build-publish (push) Successful in 2m17s
windows-host / package (push) Successful in 6m16s
decky / build-publish (push) Successful in 25s
ci / bench (push) Successful in 4m44s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 27s
apple / swift (push) Successful in 1m13s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m51s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m51s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 47s
apple / screenshots (push) Successful in 5m45s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m49s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m47s
docker / deploy-docs (push) Successful in 21s
The virtual-monitor lifecycle is now code-complete on the driver side (CI-green; deployed — no load/adapter-init regression, Status=OK): - new monitor.rs: the monitor/mode model (Mode/MonitorObject/MONITOR_MODES), ported from upstream virtual-display-rs with guid:u128 -> session_id:u64. create_monitor builds an EDID (serial=id) -> IddCxMonitorCreate -> IddCxMonitorArrival, stores the monitor, and returns the OS target id + adapter LUID for AddReply. remove_monitor / clear_all depart + drop. display_info/target_mode build the DISPLAYCONFIG timing (the union videoStandard u32 set directly — bindgen-API-agnostic, vs the oracle new_bitfield_1 transmute). - callbacks.rs: parse_monitor_description (EDID-serial lookup -> count-then-fill IDDCX_MONITOR_MODE) + monitor_query_modes (pointer-match -> IDDCX_TARGET_MODE) are real. - control.rs: IOCTL_ADD -> create_monitor + AddReply, REMOVE -> remove_monitor, CLEAR_ALL -> clear_all, via read_input/write_output_complete WDF buffer helpers. SET_RENDER_ADAPTER still stubbed (hybrid-GPU pin, next) + the watchdog thread (next). - DISPLAYCONFIG_* resolve at the wdk_sys root (pub use types::*), not iddcx. Warnings are the STEP-7 *2/HDR stubs + created_at (read by the watchdog, next). The on-glass "monitor appears at WxH@Hz" gate awaits the host switch to pf_vdisplay_proto. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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::<control::AddRequest>(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::<control::RemoveRequest>(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<T: Copy>(request: WDFREQUEST) -> Option<T> {
|
||||
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::<T>(),
|
||||
&mut buf,
|
||||
&mut len
|
||||
)
|
||||
};
|
||||
if !nt_success(st) || buf.is_null() || len < core::mem::size_of::<T>() {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: `buf` has >= size_of::<T>() bytes; T is a Pod control struct.
|
||||
Some(unsafe { buf.cast::<T>().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<T: Copy>(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::<control::InfoReply>(),
|
||||
core::mem::size_of::<T>(),
|
||||
&mut buf,
|
||||
&mut len
|
||||
)
|
||||
@@ -64,9 +134,9 @@ unsafe fn get_info(request: WDFREQUEST) {
|
||||
complete(request, st);
|
||||
return;
|
||||
}
|
||||
// SAFETY: `buf` has >= size_of::<InfoReply>() writable bytes (validated above); InfoReply is Pod.
|
||||
unsafe { buf.cast::<control::InfoReply>().write_unaligned(reply) };
|
||||
complete_info(request, STATUS_SUCCESS, core::mem::size_of::<control::InfoReply>());
|
||||
// SAFETY: `buf` has >= size_of::<T>() writable bytes; T is a Pod control struct.
|
||||
unsafe { buf.cast::<T>().write_unaligned(*value) };
|
||||
complete_info(request, STATUS_SUCCESS, core::mem::size_of::<T>());
|
||||
}
|
||||
|
||||
/// Complete a request with just a status (no output).
|
||||
|
||||
Reference in New Issue
Block a user