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:
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user