feat(windows): pf-vdisplay — all-Rust IddCx virtual display (replaces SudoVDA)

P1 done: a pure-Rust UMDF2 IddCx driver, drop-in compatible with the host's
existing vdisplay/sudovda.rs control plane (the {e5bcc234} interface + the
SudoVDA IOCTL ABI), so the host drives it unchanged. Validated streaming on
glass at 5120x1440@240 — steady 240 fps, ~2.4 ms encode, clean teardown, full
parity with SudoVDA.

- Vendored wdf-umdf-sys / wdf-umdf bindgen crates (MIT, from virtual-display-rs)
  + the SDK-version build.rs fix that resolves the IddCxStub lib path by the WDK
  version actually containing um\x64\iddcx, not the max base SDK.
- pf-vdisplay crate: entry/callbacks/context/control/monitor/edid/
  swap_chain_processor. Our OWN 128-byte EDID (manufacturer PNK, product
  punktfunk — no SudoVDA bytes), a real swap-chain drain (faithful vdd port,
  required so DWM keeps compositing), the SudoVDA-compatible IOCTL control plane
  (ADD/REMOVE/PING/GET_WATCHDOG/GET_VERSION/SET_RENDER_ADAPTER) + a watchdog that
  tears down orphaned monitors when the host stops pinging.
- deploy-dev.ps1: stage + sign + stampinf (date.time DriverVer) + Inf2Cat +
  install, codifying the "bump DriverVer or pnputil keeps the old binary" gotcha.
- docs/windows-virtual-display-rust-port.md: investigation, the on-glass
  validation, and the two traps that cost time (Session-0 measurement +
  accumulated device-state needing a reboot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 21:54:50 +02:00
parent 095540efc2
commit d39da4bc06
35 changed files with 7148 additions and 0 deletions
@@ -0,0 +1,319 @@
#![allow(non_snake_case)]
#![allow(clippy::missing_errors_doc)]
use std::sync::OnceLock;
use wdf_umdf_sys::{
IDARG_IN_ADAPTERSETRENDERADAPTER, IDARG_IN_ADAPTER_INIT, IDARG_IN_MONITORCREATE,
IDARG_IN_QUERY_HWCURSOR, IDARG_IN_SETUP_HWCURSOR, IDARG_IN_SWAPCHAINSETDEVICE,
IDARG_OUT_ADAPTER_INIT, IDARG_OUT_MONITORARRIVAL, IDARG_OUT_MONITORCREATE,
IDARG_OUT_QUERY_HWCURSOR, IDARG_OUT_RELEASEANDACQUIREBUFFER, IDDCX_ADAPTER, IDDCX_MONITOR,
IDDCX_SWAPCHAIN, IDD_CX_CLIENT_CONFIG, NTSTATUS, WDFDEVICE, WDFDEVICE_INIT,
};
#[derive(Copy, Clone, Debug, thiserror::Error)]
pub enum IddCxError {
#[error("{0}")]
IddCxFunctionNotAvailable(&'static str),
#[error("{0}")]
CallFailed(NTSTATUS),
#[error("{0}")]
NtStatus(NTSTATUS),
}
impl From<IddCxError> for NTSTATUS {
fn from(value: IddCxError) -> Self {
#[allow(clippy::enum_glob_use)]
use IddCxError::*;
match value {
IddCxFunctionNotAvailable(_) => Self::STATUS_NOT_FOUND,
CallFailed(status) => status,
NtStatus(n) => n,
}
}
}
impl From<NTSTATUS> for IddCxError {
fn from(value: NTSTATUS) -> Self {
IddCxError::CallFailed(value)
}
}
impl From<i32> for IddCxError {
fn from(val: i32) -> Self {
IddCxError::NtStatus(NTSTATUS(val))
}
}
// void IddCx functions return () on success; required by the call macro's error arm but never an error.
impl From<()> for IddCxError {
fn from(_: ()) -> Self {
IddCxError::NtStatus(NTSTATUS(0))
}
}
macro_rules! IddCxCall {
($name:ident ( $($args:expr),* )) => {
IddCxCall!(false, $name($($args),*))
};
($other_is_error:expr, $name:ident ( $($args:expr),* )) => {{
static CACHED_FN: OnceLock<
Result<
::paste::paste!(::wdf_umdf_sys::[<PFN_ $name:upper>]),
IddCxError
>
> = OnceLock::new();
let f = CACHED_FN.get_or_init(|| {
::paste::paste! {
const FN_INDEX: usize = ::wdf_umdf_sys::IDDFUNCENUM::[<$name TableIndex>].0 as usize;
// validate that wdf function can be used
let is_available = ::wdf_umdf_sys::IddCxIsFunctionAvailable!($name);
if is_available {
// SAFETY: Only immutable accesses are done to this
// The underlying array is Copy, so we call as_ptr() directly on it inside block
let fn_table = unsafe { ::wdf_umdf_sys::IddFunctions.as_ptr() };
// SAFETY: Ensured that this is present by if condition from `WdfIsFunctionAvailable!`
let f = unsafe {
fn_table.add(FN_INDEX)
.cast::<::wdf_umdf_sys::[<PFN_ $name:upper>]>()
};
// SAFETY: Ensured that this is present by if condition from `IddIsFunctionAvailable!`
let f = unsafe { f.read() };
Ok(f)
} else {
Err($crate::IddCxError::IddCxFunctionNotAvailable(concat!(stringify!($name), " is not available")))
}
}
}).clone()?;
// SAFETY: Above: If it's Ok, then it's guaranteed to be Some(fn)
let f = unsafe { f.unwrap_unchecked() };
// SAFETY: Pointer to globals is always immutable
let globals = unsafe { ::wdf_umdf_sys::IddDriverGlobals };
// SAFETY: None. User is responsible for safety and must use their own unsafe block
let result = unsafe { f(globals, $($args),*) };
if $crate::is_nt_error(&result, $other_is_error) {
Err(result.into())
} else {
Ok(result.into())
}
}};
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn IddCxDeviceInitConfig(
// in, out
DeviceInit: &mut WDFDEVICE_INIT,
// in
Config: &IDD_CX_CLIENT_CONFIG,
) -> Result<NTSTATUS, IddCxError> {
IddCxCall! {
IddCxDeviceInitConfig(
DeviceInit,
Config
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn IddCxDeviceInitialize(
// in
Device: WDFDEVICE,
) -> Result<NTSTATUS, IddCxError> {
IddCxCall! {
IddCxDeviceInitialize(
Device
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn IddCxAdapterInitAsync(
// in
pInArgs: &IDARG_IN_ADAPTER_INIT,
// out
pOutArgs: &mut IDARG_OUT_ADAPTER_INIT,
) -> Result<NTSTATUS, IddCxError> {
IddCxCall! {
IddCxAdapterInitAsync(
pInArgs,
pOutArgs
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxMonitorCreate(
// in
AdapterObject: IDDCX_ADAPTER,
// in
pInArgs: &IDARG_IN_MONITORCREATE,
// out
pOutArgs: &mut IDARG_OUT_MONITORCREATE,
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
IddCxMonitorCreate(
AdapterObject,
pInArgs,
pOutArgs
)
)
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxMonitorArrival(
// in
MonitorObject: IDDCX_MONITOR,
// out
pOutArgs: &mut IDARG_OUT_MONITORARRIVAL,
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
IddCxMonitorArrival(
MonitorObject,
pOutArgs
)
)
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxSwapChainSetDevice(
// in
SwapChainObject: IDDCX_SWAPCHAIN,
// in
pInArgs: &IDARG_IN_SWAPCHAINSETDEVICE
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
true,
IddCxSwapChainSetDevice(
SwapChainObject,
pInArgs
)
)
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxSwapChainReleaseAndAcquireBuffer(
// in
SwapChainObject: IDDCX_SWAPCHAIN,
// out
pOutArgs: &mut IDARG_OUT_RELEASEANDACQUIREBUFFER
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
true,
IddCxSwapChainReleaseAndAcquireBuffer(
SwapChainObject,
pOutArgs
)
)
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxSwapChainFinishedProcessingFrame(
// in
SwapChainObject: IDDCX_SWAPCHAIN
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
true,
IddCxSwapChainFinishedProcessingFrame(
SwapChainObject
)
)
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxMonitorDeparture(
// in
MonitorObject: IDDCX_MONITOR
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
IddCxMonitorDeparture(
MonitorObject
)
)
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn IddCxAdapterSetRenderAdapter(
// in
AdapterObject: IDDCX_ADAPTER,
// in
pInArgs: *const IDARG_IN_ADAPTERSETRENDERADAPTER,
) -> Result<(), IddCxError> {
IddCxCall!(IddCxAdapterSetRenderAdapter(AdapterObject, pInArgs))
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxMonitorSetupHardwareCursor(
// in
MonitorObject: IDDCX_MONITOR,
// in
pInArgs: &IDARG_IN_SETUP_HWCURSOR
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
IddCxMonitorSetupHardwareCursor(
MonitorObject,
pInArgs
)
)
}
/// # Safety
///
/// None. User is responsible for safety.
#[rustfmt::skip]
pub unsafe fn IddCxMonitorQueryHardwareCursor(
// in
MonitorObject: IDDCX_MONITOR,
// in
pInArgs: &IDARG_IN_QUERY_HWCURSOR,
// out
pOutArgs: &mut IDARG_OUT_QUERY_HWCURSOR
) -> Result<NTSTATUS, IddCxError> {
IddCxCall!(
IddCxMonitorQueryHardwareCursor(
MonitorObject,
pInArgs,
pOutArgs
)
)
}
@@ -0,0 +1,30 @@
mod iddcx;
mod wdf;
use std::any::Any;
pub use paste::paste;
pub use iddcx::*;
pub use wdf::*;
pub use wdf_umdf_sys;
use wdf_umdf_sys::NTSTATUS;
/// Used for the macros so they can correctly convert a functions result
fn is_nt_error(val: &dyn Any, other_is_error: bool) -> bool {
if let Some(status) = val.downcast_ref::<NTSTATUS>() {
return !status.is_success();
}
// other errors which may not be error codes, but may also be
// such as HRESULT == i32
if other_is_error {
if let Some(status) = val.downcast_ref::<i32>() {
let status = NTSTATUS(*status);
return !status.is_success();
}
}
false
}
@@ -0,0 +1,667 @@
#![allow(non_snake_case)]
#![allow(clippy::missing_errors_doc)]
use std::ffi::c_void;
use std::sync::OnceLock;
use wdf_umdf_sys::{
DEVPROPTYPE, GUID, NTSTATUS, PCUNICODE_STRING, PCWDF_OBJECT_CONTEXT_TYPE_INFO, PDRIVER_OBJECT,
POOL_TYPE, PVOID, PWDFDEVICE_INIT, PWDF_DRIVER_CONFIG, PWDF_OBJECT_ATTRIBUTES, ULONG_PTR,
WDFDEVICE, WDFDRIVER, WDFMEMORY, WDFOBJECT, WDFREQUEST, WDF_DEVICE_FAILED_ACTION, WDF_NO_HANDLE,
WDF_NO_OBJECT_ATTRIBUTES, WDF_OBJECT_ATTRIBUTES, _WDF_DEVICE_PROPERTY_DATA,
_WDF_PNPPOWER_EVENT_CALLBACKS,
};
#[derive(Copy, Clone, Debug, thiserror::Error)]
pub enum WdfError {
#[error("{0}")]
WdfFunctionNotAvailable(&'static str),
#[error("{0}")]
CallFailed(NTSTATUS),
#[error("Failed to upgrade Arc pointer")]
UpgradeFailed,
#[error("Failed to lock")]
LockFailed,
#[error("Unknown")]
Unknown,
// this is required for success status for ()
#[error("This is not an error, ignore it")]
_Success,
}
impl From<()> for WdfError {
fn from(_: ()) -> Self {
WdfError::_Success
}
}
impl From<*mut c_void> for WdfError {
fn from(_: *mut c_void) -> Self {
Self::Unknown
}
}
impl From<WdfError> for NTSTATUS {
fn from(value: WdfError) -> Self {
#[allow(clippy::enum_glob_use)]
use WdfError::*;
match value {
WdfFunctionNotAvailable(_) => Self::STATUS_NOT_FOUND,
CallFailed(status) => status,
UpgradeFailed => Self::STATUS_INVALID_HANDLE,
LockFailed => Self::STATUS_WAS_LOCKED,
Unknown => Self::STATUS_DRIVER_INTERNAL_ERROR,
_Success => 0.into(),
}
}
}
impl From<NTSTATUS> for WdfError {
fn from(value: NTSTATUS) -> Self {
WdfError::CallFailed(value)
}
}
macro_rules! WdfCall {
($name:ident ( $($args:expr),* )) => {
WdfCall!(false, $name($($args),*))
};
($other_is_error:expr, $name:ident ( $($args:expr),* )) => {{
static CACHED_FN: OnceLock<
Result<
::paste::paste!(::wdf_umdf_sys::[<PFN_ $name:upper>]),
WdfError
>
> = OnceLock::new();
let f = CACHED_FN.get_or_init(|| {
::paste::paste! {
const FN_INDEX: usize = ::wdf_umdf_sys::WDFFUNCENUM::[<$name TableIndex>].0 as usize;
// validate that wdf function can be used
let is_available = ::wdf_umdf_sys::WdfIsFunctionAvailable!($name);
if is_available {
// SAFETY: Only immutable accesses are done to this
let fn_table = unsafe { ::wdf_umdf_sys::WdfFunctions_02031 };
// SAFETY: Read-only, initialized by the time we use it, and checked to be in bounds
let f = unsafe {
fn_table
.add(FN_INDEX)
.cast::<::wdf_umdf_sys::[<PFN_ $name:upper>]>()
};
// SAFETY: Ensured that this is present by if condition from `WdfIsFunctionAvailable!`
let f = unsafe { f.read() };
Ok(f)
} else {
Err($crate::WdfError::WdfFunctionNotAvailable(concat!(stringify!($name), " is not available")))
}
}
}).clone()?;
// SAFETY: Above: If it's Ok, then it's guaranteed to be Some(fn)
let f = unsafe { f.unwrap_unchecked() };
// SAFETY: Pointer to globals is always immutable
let globals = unsafe { ::wdf_umdf_sys::WdfDriverGlobals };
// SAFETY: None. User is responsible for safety and must use their own unsafe block
let result = unsafe { f(globals, $($args),*) };
if $crate::is_nt_error(&result, $other_is_error) {
Err(result.into())
} else {
Ok(result.into())
}
}};
}
/// Unlike the official `WDF_DECLARE_CONTEXT_TYPE` macro, you only need to declare this on the actual data struct want to use
/// Safety is maintained through a `RwLock` of the underlying data
///
/// This generates associated fns `init`/`get`/`drop`/`get_type_info` on your `$context_type` with the same visibility
///
/// Example:
/// ```rust
/// pub struct IndirectDeviceContext {
/// device: WDFDEVICE,
/// }
///
/// impl IndirectDeviceContext {
/// pub fn new(device: WDFDEVICE) -> Self {
/// Self { device }
/// }
/// }
///
/// WDF_DECLARE_CONTEXT_TYPE!(pub IndirectDeviceContext);
///
/// // with a `device: WDFDEVICE`
/// let context = IndirectDeviceContext::new(device as WDFOBJECT);
/// IndirectDeviceContext::init(context);
/// // elsewhere
/// let mutable_access = IndirectDeviceContext::get_mut(device).unwrap();
/// ```
#[macro_export]
macro_rules! WDF_DECLARE_CONTEXT_TYPE {
($sv:vis $context_type:ident) => {
$crate::paste! {
// keep it in a mod block to disallow access to private types
#[allow(non_snake_case)]
mod [<WdfObject $context_type>] {
use super::$context_type;
// Require `T: Sync` for safety. User has to uphold the invariant themselves
#[repr(transparent)]
#[allow(non_camel_case_types)]
struct [<_WDF_ $context_type _STATIC_WRAPPER>]<T> {
cell: ::std::cell::UnsafeCell<$crate::wdf_umdf_sys::_WDF_OBJECT_CONTEXT_TYPE_INFO>,
_phantom: ::std::marker::PhantomData<T>
}
// SAFETY: `T` impls Sync too
unsafe impl<T: Sync> Sync for [<_WDF_ $context_type _STATIC_WRAPPER>]<T> {}
// Unsure if C mutates this data, but it's in an unsafecell just in case
#[allow(non_upper_case_globals)]
static [<_WDF_ $context_type _TYPE_INFO>]: [<_WDF_ $context_type _STATIC_WRAPPER>]<$context_type> =
[<_WDF_ $context_type _STATIC_WRAPPER>] {
cell: ::std::cell::UnsafeCell::new(
$crate::wdf_umdf_sys::_WDF_OBJECT_CONTEXT_TYPE_INFO {
Size: ::std::mem::size_of::<$crate::wdf_umdf_sys::_WDF_OBJECT_CONTEXT_TYPE_INFO>() as u32,
ContextName: concat!(stringify!($context_type), "\0")
.as_ptr().cast::<::std::ffi::c_char>(),
ContextSize: ::std::mem::size_of::<[<WdfObject $context_type>]>(),
// SAFETY:
// StaticWrapper and UnsafeCell are both repr(transparent), so cast to underlying _WDF_OBJECT_CONTEXT_TYPE_INFO is ok
UniqueType: &[<_WDF_ $context_type _TYPE_INFO>] as *const _ as *const _,
EvtDriverGetUniqueContextType: ::std::option::Option::None,
}
),
_phantom: ::std::marker::PhantomData
};
/// Allows us to keep ONE main Arc allocation while handing out weak pointers to the rest of the clones.
/// In this way, we can drop the allocation by dropping 1 arc, while letting others still access it
enum ArcPointer<T> {
Strong(::std::sync::Arc<T>),
Weak(::std::sync::Weak<T>)
}
#[repr(transparent)]
struct [<WdfObject $context_type>](ArcPointer<::std::sync::RwLock<$context_type>>);
impl $context_type {
/// Initialize and place context into internal WdfObject
///
/// SAFETY:
/// - handle must be a fresh unused object with no data in its context already
/// - context type must already have been set up for handle
/// - Must be set only once regardless of the object. For all other objects, use clone_into()
$sv unsafe fn init(
self,
handle: $crate::wdf_umdf_sys::WDFOBJECT,
) -> ::std::result::Result<(), $crate::WdfError> {
let context = unsafe {
$crate::WdfObjectGetTypedContextWorker(handle, [<_WDF_ $context_type _TYPE_INFO>].cell.get())?
} as *mut ::std::mem::MaybeUninit<[<WdfObject $context_type>]>;
let context = &mut *context;
// Write to the memory location, making the data in it init
context.write(
[<WdfObject $context_type>](
ArcPointer::Strong(::std::sync::Arc::new(::std::sync::RwLock::new(self)))
)
);
Ok(())
}
/// Initialize handle's context and clone a Weak pointer to self context into it.
/// Internally, these are Arc's, so they will always point to the same data.
/// When the main Arc drops, none of these may access memory any longer
///
/// SAFETY:
/// - handle must be a fresh unused object with no data in its context already
/// - to_handle must have set context_type for this type via WDF_OBJECT_ATTRIBUTES when it was created
/// - to_handle must be a valid T
$sv unsafe fn clone_into(
&self,
handle: $crate::wdf_umdf_sys::WDFOBJECT
) -> ::std::result::Result<(), $crate::WdfError> {
let context = unsafe {
$crate::WdfObjectGetTypedContextWorker(handle, [<_WDF_ $context_type _TYPE_INFO>].cell.get())?
} as *mut ::std::mem::MaybeUninit<[<WdfObject $context_type>]>;
let context = &mut *context;
let from_context = unsafe {
$crate::WdfObjectGetTypedContextWorker(self.device as *mut _, [<_WDF_ $context_type _TYPE_INFO>].cell.get())?
} as *mut [<WdfObject $context_type>];
let from_context = match &(*from_context).0 {
ArcPointer::Strong(a) => a.clone(),
ArcPointer::Weak(a) => a.upgrade().ok_or($crate::WdfError::UpgradeFailed)?.clone(),
};
// Write to the memory location, making the data in it init
// clones the arc into new handle
context.write(
[<WdfObject $context_type>](ArcPointer::Weak(::std::sync::Arc::downgrade(&from_context)))
);
Ok(())
}
/// NOTE: Dropping memory that was created via `clone_into` will never drop the main allocation.
/// To drop the main allocation, you need to drop the instance made via `init`.
/// That instance can be obtained through the original handle you created it through
///
/// SAFETY:
/// - Data in context is assumed to already be init and a valid T
/// - Therefore, init for the context must already have been done on this handle
/// - No other mutable/non-mutable refs can exist to data when this is called, or it will alias
///
/// This may overwrite data in the handle's context memory, it is UB to read it after drop (e.g. get*)
$sv unsafe fn drop(
handle: $crate::wdf_umdf_sys::WDFOBJECT,
) -> ::std::result::Result<(), $crate::WdfError> {
let context = $crate::WdfObjectGetTypedContextWorker(
handle,
[<_WDF_ $context_type _TYPE_INFO>].cell.get(),
)? as *mut [<WdfObject $context_type>];
// drop the memory
::std::ptr::drop_in_place(context);
Ok(())
}
/// Borrow the context immutably
/// Function returns with error and won't call cb if it failed to lock
///
/// SAFETY:
/// - Must have initialized WdfObject first
/// - Data must not have been dropped
/// - Object must not have been destroyed
$sv unsafe fn get<F>(
handle: *mut $crate::wdf_umdf_sys::WDFDEVICE__,
cb: F
) -> ::std::result::Result<(), $crate::WdfError>
where
F: ::std::ops::FnOnce(&$context_type)
{
let context = unsafe {
$crate::WdfObjectGetTypedContextWorker(handle as *mut _,
// SAFETY: Reading is always fine, since user cannot obtain mutable reference
(&*[<_WDF_ $context_type _TYPE_INFO>].cell.get()).UniqueType
)?
} as *mut [<WdfObject $context_type>];
let context = &*context;
let context = match &context.0 {
ArcPointer::Strong(a) => a.clone(),
ArcPointer::Weak(a) => a.upgrade().ok_or($crate::WdfError::UpgradeFailed)?.clone(),
};
let guard = context.read().map_err(|_| $crate::WdfError::LockFailed)?;
cb(&*guard);
Ok(())
}
/// Borrow the context mutably
/// Function returns with error and won't call cb if it failed to lock
///
/// SAFETY:
/// - Must have initialized WdfObject first
/// - Data must not have been dropped
/// - Object must not have been destroyed
$sv unsafe fn get_mut<F>(
handle: *mut $crate::wdf_umdf_sys::WDFDEVICE__,
cb: F
) -> ::std::result::Result<(), $crate::WdfError>
where
F: ::std::ops::FnOnce(&mut $context_type)
{
let context = unsafe {
$crate::WdfObjectGetTypedContextWorker(handle as *mut _,
// SAFETY: Reading is always fine, since user cannot obtain mutable reference
(&*[<_WDF_ $context_type _TYPE_INFO>].cell.get()).UniqueType
)?
} as *mut [<WdfObject $context_type>];
let context = &*context;
let context = match &context.0 {
ArcPointer::Strong(a) => a.clone(),
ArcPointer::Weak(a) => a.upgrade().ok_or($crate::WdfError::UpgradeFailed)?.clone(),
};
let mut guard = context.write().map_err(|_| $crate::WdfError::LockFailed)?;
cb(&mut *guard);
Ok(())
}
/// Borrow the context immutably
/// Function returns with error and won't call cb if it failed to lock
///
/// SAFETY:
/// - Must have initialized WdfObject first
/// - Data must not have been dropped
/// - Object must not have been destroyed
$sv unsafe fn try_get<F>(
handle: *mut $crate::wdf_umdf_sys::WDFDEVICE__,
cb: F
) -> ::std::result::Result<(), $crate::WdfError>
where
F: ::std::ops::FnOnce(&$context_type)
{
let context = unsafe {
$crate::WdfObjectGetTypedContextWorker(handle as *mut _,
// SAFETY: Reading is always fine, since user cannot obtain mutable reference
(&*[<_WDF_ $context_type _TYPE_INFO>].cell.get()).UniqueType
)?
} as *mut [<WdfObject $context_type>];
let context = &*context;
let context = match &context.0 {
ArcPointer::Strong(a) => a.clone(),
ArcPointer::Weak(a) => a.upgrade().ok_or($crate::WdfError::UpgradeFailed)?.clone(),
};
let guard = context.try_read().map_err(|_| $crate::WdfError::LockFailed)?;
cb(&*guard);
Ok(())
}
/// Try to borrow the context mutably. Immediately returns if it's locked
/// Function returns with error and won't call cb if it failed to lock
///
/// SAFETY:
/// - Must have initialized WdfObject first
/// - Data must not have been dropped
/// - Object must not have been destroyed
$sv unsafe fn try_get_mut<F>(
handle: *mut $crate::wdf_umdf_sys::WDFDEVICE__,
cb: F
) -> ::std::result::Result<(), $crate::WdfError>
where
F: ::std::ops::FnOnce(&mut $context_type)
{
let context = unsafe {
$crate::WdfObjectGetTypedContextWorker(handle as *mut _,
// SAFETY: Reading is always fine, since user cannot obtain mutable reference
(&*[<_WDF_ $context_type _TYPE_INFO>].cell.get()).UniqueType
)?
} as *mut [<WdfObject $context_type>];
let context = &*context;
let context = match &context.0 {
ArcPointer::Strong(a) => a.clone(),
ArcPointer::Weak(a) => a.upgrade().ok_or($crate::WdfError::UpgradeFailed)?.clone(),
};
let mut guard = context.try_write().map_err(|_| $crate::WdfError::LockFailed)?;
cb(&mut *guard);
Ok(())
}
// SAFETY:
// - No other mutable refs must exist to target type
// - Underlying memory must remain immutable and unchanged until reference is dropped
$sv unsafe fn get_type_info() -> &'static $crate::wdf_umdf_sys::_WDF_OBJECT_CONTEXT_TYPE_INFO {
unsafe { &*[<_WDF_ $context_type _TYPE_INFO>].cell.get() }
}
}
}
}
};
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfDriverCreate(
// in
DriverObject: PDRIVER_OBJECT,
// in
RegistryPath: PCUNICODE_STRING,
// in, optional
DriverAttributes: Option<PWDF_OBJECT_ATTRIBUTES>,
// in
DriverConfig: PWDF_DRIVER_CONFIG,
// out, optional
Driver: Option<&mut WDFDRIVER>,
) -> Result<NTSTATUS, WdfError> {
WdfCall! {
WdfDriverCreate(
DriverObject,
RegistryPath,
DriverAttributes.unwrap_or(WDF_NO_OBJECT_ATTRIBUTES!()),
DriverConfig,
Driver
.map(std::ptr::from_mut)
.unwrap_or(WDF_NO_HANDLE!())
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfDeviceCreate(
// in, out
DeviceInit: &mut PWDFDEVICE_INIT,
// in, optional
DeviceAttributes: Option<&mut WDF_OBJECT_ATTRIBUTES>,
// out
Device: &mut WDFDEVICE,
) -> Result<NTSTATUS, WdfError> {
WdfCall! {
WdfDeviceCreate(
DeviceInit,
DeviceAttributes.map_or(WDF_NO_OBJECT_ATTRIBUTES!(), std::ptr::from_mut),
Device
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfDeviceInitSetPnpPowerEventCallbacks(
// in
DeviceInit: PWDFDEVICE_INIT,
// in
PnpPowerEventCallbacks: *mut _WDF_PNPPOWER_EVENT_CALLBACKS,
) -> Result<(), WdfError> {
WdfCall! {
WdfDeviceInitSetPnpPowerEventCallbacks(
DeviceInit,
PnpPowerEventCallbacks
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfObjectGetTypedContextWorker(
// in
Handle: WDFOBJECT,
// in
TypeInfo: PCWDF_OBJECT_CONTEXT_TYPE_INFO,
) -> Result<*mut c_void, WdfError> {
WdfCall! {
WdfObjectGetTypedContextWorker(
Handle,
TypeInfo
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfObjectDelete(
// in
Object: WDFOBJECT,
) -> Result<(), WdfError> {
WdfCall! {
WdfObjectDelete(
Object
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfDeviceSetFailed(
// in
Device: WDFDEVICE,
// in
FailedAction: WDF_DEVICE_FAILED_ACTION,
) -> Result<(), WdfError> {
WdfCall! {
WdfDeviceSetFailed(
Device,
FailedAction
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfDeviceAllocAndQueryPropertyEx(
// in
Device: WDFDEVICE,
// in
DeviceProperty: &mut _WDF_DEVICE_PROPERTY_DATA,
// in
PoolType: POOL_TYPE,
// in, optional
PropertyMemoryAttributes: PWDF_OBJECT_ATTRIBUTES,
// out
PropertyMemory: &mut WDFMEMORY,
// out
Type: &mut DEVPROPTYPE,
) -> Result<NTSTATUS, WdfError> {
WdfCall! {
WdfDeviceAllocAndQueryPropertyEx(
Device,
DeviceProperty,
PoolType,
PropertyMemoryAttributes,
PropertyMemory,
Type
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfMemoryGetBuffer(
// in
Memory: WDFMEMORY,
// out, optional
BufferSize: Option<&mut usize>,
) -> Result<*mut c_void, WdfError> {
WdfCall! {
WdfMemoryGetBuffer(
Memory,
BufferSize.map_or(std::ptr::null_mut(), std::ptr::from_mut)
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfDeviceCreateDeviceInterface(
Device: WDFDEVICE,
InterfaceClassGUID: *const GUID,
ReferenceString: PCUNICODE_STRING,
) -> Result<NTSTATUS, WdfError> {
WdfCall! {
WdfDeviceCreateDeviceInterface(
Device,
InterfaceClassGUID,
ReferenceString
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfRequestRetrieveInputBuffer(
Request: WDFREQUEST,
MinimumRequiredLength: usize,
Buffer: *mut PVOID,
Length: *mut usize,
) -> Result<NTSTATUS, WdfError> {
WdfCall! {
WdfRequestRetrieveInputBuffer(
Request,
MinimumRequiredLength,
Buffer,
Length
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfRequestRetrieveOutputBuffer(
Request: WDFREQUEST,
MinimumRequiredSize: usize,
Buffer: *mut PVOID,
Length: *mut usize,
) -> Result<NTSTATUS, WdfError> {
WdfCall! {
WdfRequestRetrieveOutputBuffer(
Request,
MinimumRequiredSize,
Buffer,
Length
)
}
}
/// # Safety
///
/// None. User is responsible for safety.
pub unsafe fn WdfRequestCompleteWithInformation(
Request: WDFREQUEST,
Status: NTSTATUS,
Information: ULONG_PTR,
) -> Result<(), WdfError> {
WdfCall! {
WdfRequestCompleteWithInformation(
Request,
Status,
Information
)
}
}