diff --git a/crates/wdk-sys/src/test_stubs.rs b/crates/wdk-sys/src/test_stubs.rs index ff301dc38..d9e92e5b7 100644 --- a/crates/wdk-sys/src/test_stubs.rs +++ b/crates/wdk-sys/src/test_stubs.rs @@ -16,6 +16,8 @@ pub use wdf::*; driver_model__driver_type = "UMDF" ))] use crate::{DRIVER_OBJECT, NTSTATUS, PCUNICODE_STRING}; +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +use crate::{ERESOURCE, EX_SPIN_LOCK, KIRQL, LOGICAL, ULONG, ULONG_PTR}; /// Stubbed version of `DriverEntry` Symbol so that test targets will compile /// @@ -37,6 +39,147 @@ pub const unsafe extern "system" fn driver_entry_stub( 0 } +/// Stubbed version of `ExInitializeResourceLite` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExInitializeResourceLite(_resource: *mut ERESOURCE) -> NTSTATUS { + crate::STATUS_SUCCESS +} + +/// Stubbed version of `ExAcquireResourceSharedLite` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquireResourceSharedLite( + _resource: *mut ERESOURCE, + _wait: crate::BOOLEAN, +) -> crate::BOOLEAN { + 1 +} + +/// Stubbed version of `ExAcquireResourceExclusiveLite` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquireResourceExclusiveLite( + _resource: *mut ERESOURCE, + _wait: crate::BOOLEAN, +) -> crate::BOOLEAN { + 1 +} + +/// Stubbed version of `ExReleaseResourceLite` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExReleaseResourceLite(_resource: *mut ERESOURCE) {} + +/// Stubbed version of `ExDeleteResourceLite` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExDeleteResourceLite(_resource: *mut ERESOURCE) -> NTSTATUS { + crate::STATUS_SUCCESS +} + +/// Stubbed version of `KeEnterCriticalRegion` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn KeEnterCriticalRegion() {} + +/// Stubbed version of `KeLeaveCriticalRegion` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn KeLeaveCriticalRegion() {} + +/// Stubbed version of `ExInitializePushLock` so test targets can link +/// +/// # Safety +/// +/// `push_lock` must point to valid writable push lock storage. +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub unsafe extern "system" fn ExInitializePushLock(push_lock: *mut ULONG_PTR) { + // SAFETY: Test callers pass a valid pointer to push lock storage. + unsafe { + push_lock.write(0); + } +} + +/// Stubbed version of `ExAcquirePushLockSharedEx` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquirePushLockSharedEx(_push_lock: *mut ULONG_PTR, _flags: ULONG) {} + +/// Stubbed version of `ExAcquirePushLockExclusiveEx` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquirePushLockExclusiveEx(_push_lock: *mut ULONG_PTR, _flags: ULONG) {} + +/// Stubbed version of `ExReleasePushLockSharedEx` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExReleasePushLockSharedEx(_push_lock: *mut ULONG_PTR, _flags: ULONG) {} + +/// Stubbed version of `ExReleasePushLockExclusiveEx` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExReleasePushLockExclusiveEx(_push_lock: *mut ULONG_PTR, _flags: ULONG) {} + +/// Stubbed version of `ExAcquireSpinLockShared` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquireSpinLockShared(_spin_lock: *mut EX_SPIN_LOCK) -> KIRQL { + 0 +} + +/// Stubbed version of `ExAcquireSpinLockSharedAtDpcLevel` so test targets can +/// link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquireSpinLockSharedAtDpcLevel(_spin_lock: *mut EX_SPIN_LOCK) {} + +/// Stubbed version of `ExReleaseSpinLockShared` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExReleaseSpinLockShared(_spin_lock: *mut EX_SPIN_LOCK, _old_irql: KIRQL) {} + +/// Stubbed version of `ExReleaseSpinLockSharedFromDpcLevel` so test targets can +/// link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExReleaseSpinLockSharedFromDpcLevel(_spin_lock: *mut EX_SPIN_LOCK) {} + +/// Stubbed version of `ExAcquireSpinLockExclusive` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquireSpinLockExclusive(_spin_lock: *mut EX_SPIN_LOCK) -> KIRQL { + 0 +} + +/// Stubbed version of `ExAcquireSpinLockExclusiveAtDpcLevel` so test targets +/// can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExAcquireSpinLockExclusiveAtDpcLevel(_spin_lock: *mut EX_SPIN_LOCK) {} + +/// Stubbed version of `ExReleaseSpinLockExclusive` so test targets can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExReleaseSpinLockExclusive(_spin_lock: *mut EX_SPIN_LOCK, _old_irql: KIRQL) {} + +/// Stubbed version of `ExReleaseSpinLockExclusiveFromDpcLevel` so test targets +/// can link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExReleaseSpinLockExclusiveFromDpcLevel(_spin_lock: *mut EX_SPIN_LOCK) {} + +/// Stubbed version of `ExTryConvertSharedSpinLockExclusive` so test targets can +/// link +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +#[unsafe(no_mangle)] +pub extern "system" fn ExTryConvertSharedSpinLockExclusive( + _spin_lock: *mut EX_SPIN_LOCK, +) -> LOGICAL { + 1 +} + #[cfg(any(driver_model__driver_type = "KMDF", driver_model__driver_type = "UMDF"))] mod wdf { use crate::ULONG; diff --git a/crates/wdk/src/lib.rs b/crates/wdk/src/lib.rs index 6f5db9c2f..88f097cab 100644 --- a/crates/wdk/src/lib.rs +++ b/crates/wdk/src/lib.rs @@ -10,6 +10,9 @@ no_std )] +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +extern crate alloc; + #[cfg(any( driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF", @@ -36,6 +39,9 @@ mod print; /// environments. pub mod fmt; +#[cfg(any(driver_model__driver_type = "WDM", driver_model__driver_type = "KMDF"))] +pub mod sync; + #[cfg(any(driver_model__driver_type = "KMDF", driver_model__driver_type = "UMDF"))] pub mod wdf; diff --git a/crates/wdk/src/sync/mod.rs b/crates/wdk/src/sync/mod.rs new file mode 100644 index 000000000..8b0cefdc4 --- /dev/null +++ b/crates/wdk/src/sync/mod.rs @@ -0,0 +1,28 @@ +//! Synchronization primitives backed by kernel-mode WDK objects. +//! +//! The wrappers in this module expose Rust guard-based access to common +//! shared/exclusive kernel locks: +//! +//! - [`RwLock`] uses `ERESOURCE` for waitable reader-writer locking at `IRQL <= +//! APC_LEVEL`. +//! - [`PushLock`] uses `EX_PUSH_LOCK` for compact waitable reader-writer +//! locking at `IRQL <= APC_LEVEL`. +//! - [`RwSpinLock`] uses `EX_SPIN_LOCK` for very short non-waiting sections +//! that can run up to `DISPATCH_LEVEL`. + +pub use push_lock::*; +pub use rw_lock::*; +pub use rw_spin_lock::*; + +mod push_lock; +mod rw_lock; +mod rw_spin_lock; + +// Stable Rust does not support negative `Send` impls for these guard types. +// This marker keeps guards from crossing threads, which ensures kernel lock +// release and IRQL restoration happen on the acquiring thread. +type NotSend = core::marker::PhantomData>; + +const fn not_send() -> NotSend { + core::marker::PhantomData +} diff --git a/crates/wdk/src/sync/push_lock.rs b/crates/wdk/src/sync/push_lock.rs new file mode 100644 index 000000000..22152e89d --- /dev/null +++ b/crates/wdk/src/sync/push_lock.rs @@ -0,0 +1,212 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +use core::{ + cell::UnsafeCell, + mem::MaybeUninit, + ops::{Deref, DerefMut}, +}; + +use wdk_sys::{ + ULONG, + ULONG_PTR, + ntddk::{ + ExAcquirePushLockExclusiveEx, + ExAcquirePushLockSharedEx, + ExInitializePushLock, + ExReleasePushLockExclusiveEx, + ExReleasePushLockSharedEx, + KeEnterCriticalRegion, + KeLeaveCriticalRegion, + }, +}; + +const EX_DEFAULT_PUSH_LOCK_FLAGS: ULONG = 0; + +/// Reader-writer lock backed by an executive push lock (`EX_PUSH_LOCK`). +/// +/// This lock is smaller than [`RwLock`](super::RwLock) and is useful for short +/// shared/exclusive critical sections at `IRQL <= APC_LEVEL`. Push locks can +/// wait while acquiring the lock, so they must not be used from DPC or ISR +/// contexts. Each acquisition enters a critical region before taking the push +/// lock and leaves it when the guard is dropped. Normal kernel APC delivery is +/// disabled while a guard is held. +/// +/// Push locks do not support recursive exclusive acquisition. Attempting to +/// acquire the lock exclusively while it is already held by the current thread +/// can hang the system. Each successful acquisition must be released by +/// dropping the returned guard. +/// +/// This wrapper uses the modern `ExAcquirePushLock*Ex` bindings generated from +/// the active WDK configuration. +pub struct PushLock { + push_lock: UnsafeCell, + value: UnsafeCell, +} + +// SAFETY: `PushLock` owns `T`, and moving the lock to another thread is safe +// when `T` can be moved between threads. +unsafe impl Send for PushLock {} + +// SAFETY: Shared access requires `T: Sync`, and mutable access is serialized by +// the underlying push lock. `T: Send` is required because a writer can move +// values out of the protected data. +unsafe impl Sync for PushLock {} + +impl PushLock { + /// Construct a new push lock protecting `value`. + pub fn new(value: T) -> Self { + let mut push_lock_storage = MaybeUninit::::uninit(); + let push_lock_ptr = push_lock_storage.as_mut_ptr(); + + // SAFETY: `push_lock_ptr` points to writable, uninitialized storage for + // exactly one `EX_PUSH_LOCK`, which `ExInitializePushLock` initializes. + unsafe { + ExInitializePushLock(push_lock_ptr); + } + + // SAFETY: `ExInitializePushLock` initialized the storage. + let push_lock = unsafe { push_lock_storage.assume_init() }; + + Self { + push_lock: UnsafeCell::new(push_lock), + value: UnsafeCell::new(value), + } + } +} + +impl PushLock { + /// Lock this `PushLock` with shared read access. + #[must_use] + pub fn read(&self) -> PushLockReadGuard<'_, T> { + self.enter_critical_region(); + + // SAFETY: `push_lock_ptr` returns the initialized push lock owned by + // this object. The caller is at `IRQL <= APC_LEVEL`, and the critical + // region has been entered for this acquisition. + unsafe { + ExAcquirePushLockSharedEx(self.push_lock_ptr(), EX_DEFAULT_PUSH_LOCK_FLAGS); + } + + PushLockReadGuard { + lock: self, + _not_send: super::not_send(), + } + } + + /// Lock this `PushLock` with exclusive write access. + #[must_use] + pub fn write(&self) -> PushLockWriteGuard<'_, T> { + self.enter_critical_region(); + + // SAFETY: `push_lock_ptr` returns the initialized push lock owned by + // this object. The caller is at `IRQL <= APC_LEVEL`, and the critical + // region has been entered for this acquisition. + unsafe { + ExAcquirePushLockExclusiveEx(self.push_lock_ptr(), EX_DEFAULT_PUSH_LOCK_FLAGS); + } + + PushLockWriteGuard { + lock: self, + _not_send: super::not_send(), + } + } + + /// Get mutable access to the protected value without locking. + /// + /// This is safe because the mutable borrow proves no other borrow of the + /// lock exists. + pub fn get_mut(&mut self) -> &mut T { + self.value.get_mut() + } + + fn push_lock_ptr(&self) -> *mut ULONG_PTR { + self.push_lock.get() + } + + fn release_shared(&self) { + // SAFETY: Each read guard is constructed only after a successful shared + // acquisition of this push lock. + unsafe { + ExReleasePushLockSharedEx(self.push_lock_ptr(), EX_DEFAULT_PUSH_LOCK_FLAGS); + } + self.leave_critical_region(); + } + + fn release_exclusive(&self) { + // SAFETY: Each write guard is constructed only after a successful + // exclusive acquisition of this push lock. + unsafe { + ExReleasePushLockExclusiveEx(self.push_lock_ptr(), EX_DEFAULT_PUSH_LOCK_FLAGS); + } + self.leave_critical_region(); + } + + fn enter_critical_region(&self) { + // SAFETY: Callers acquire `PushLock` only at `IRQL <= APC_LEVEL`, which + // is the WDK contract for entering a critical region. + unsafe { + KeEnterCriticalRegion(); + } + } + + fn leave_critical_region(&self) { + // SAFETY: This is paired with a prior successful `KeEnterCriticalRegion` + // call from the same thread. + unsafe { + KeLeaveCriticalRegion(); + } + } +} + +/// Shared read guard returned by [`PushLock::read`]. +pub struct PushLockReadGuard<'a, T: ?Sized> { + lock: &'a PushLock, + _not_send: super::NotSend, +} + +impl Deref for PushLockReadGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Shared guards are only created while the push lock is held in + // shared mode, and writers are excluded by the push lock. + unsafe { &*self.lock.value.get() } + } +} + +impl Drop for PushLockReadGuard<'_, T> { + fn drop(&mut self) { + self.lock.release_shared(); + } +} + +/// Exclusive write guard returned by [`PushLock::write`]. +pub struct PushLockWriteGuard<'a, T: ?Sized> { + lock: &'a PushLock, + _not_send: super::NotSend, +} + +impl Deref for PushLockWriteGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Write guards are only created while the push lock is held in + // exclusive mode, and all other access is excluded by the push lock. + unsafe { &*self.lock.value.get() } + } +} + +impl DerefMut for PushLockWriteGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: Write guards are only created while the push lock is held in + // exclusive mode, and all other access is excluded by the push lock. + unsafe { &mut *self.lock.value.get() } + } +} + +impl Drop for PushLockWriteGuard<'_, T> { + fn drop(&mut self) { + self.lock.release_exclusive(); + } +} diff --git a/crates/wdk/src/sync/rw_lock.rs b/crates/wdk/src/sync/rw_lock.rs new file mode 100644 index 000000000..cca783c31 --- /dev/null +++ b/crates/wdk/src/sync/rw_lock.rs @@ -0,0 +1,288 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +use alloc::alloc::{Layout, alloc, dealloc}; +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, + ptr::NonNull, +}; + +use wdk_sys::{ + ERESOURCE, + NTSTATUS, + STATUS_INSUFFICIENT_RESOURCES, + ntddk::{ + ExAcquireResourceExclusiveLite, + ExAcquireResourceSharedLite, + ExDeleteResourceLite, + ExInitializeResourceLite, + ExReleaseResourceLite, + KeEnterCriticalRegion, + KeLeaveCriticalRegion, + }, +}; + +use crate::nt_success; + +/// Reader-writer lock backed by an executive resource (`ERESOURCE`). +/// +/// The lock can be acquired at `IRQL <= APC_LEVEL`. The underlying executive +/// resource is initialized by [`RwLock::try_new`] and deleted when the lock is +/// dropped. Each successful acquisition enters a critical region before taking +/// the resource and leaves it when the guard is dropped. +/// +/// The backing `ERESOURCE` is allocated separately through the configured +/// global allocator so its kernel object address stays stable for the lifetime +/// of the lock. +pub struct RwLock { + resource: NonNull, + value: UnsafeCell, +} + +// SAFETY: `RwLock` owns `T`, and moving the lock to another thread is safe when +// `T` can be moved between threads. +unsafe impl Send for RwLock {} + +// SAFETY: Shared access requires `T: Sync`, and mutable access is serialized by +// the underlying executive resource. `T: Send` is required because a writer can +// move values out of the protected data. +unsafe impl Sync for RwLock {} + +impl RwLock { + /// Try to construct a new reader-writer lock. + /// + /// # Errors + /// + /// Returns the failing [`NTSTATUS`] if `ExInitializeResourceLite` cannot + /// initialize the underlying `ERESOURCE`, or + /// [`STATUS_INSUFFICIENT_RESOURCES`] if the backing resource allocation + /// fails. + pub fn try_new(value: T) -> Result { + let resource = allocate_resource()?; + + let status; + // SAFETY: `resource` points to writable, uninitialized storage for + // exactly one `ERESOURCE`, which `ExInitializeResourceLite` initializes. + unsafe { + status = ExInitializeResourceLite(resource.as_ptr()); + } + if !nt_success(status) { + // SAFETY: `resource` came from `allocate_resource`, and + // initialization failed, so no kernel teardown is required. + unsafe { + deallocate_resource(resource); + } + return Err(status); + } + + Ok(Self { + resource, + value: UnsafeCell::new(value), + }) + } +} + +impl RwLock { + /// Lock this `RwLock` with shared read access. + #[must_use] + pub fn read(&self) -> RwLockReadGuard<'_, T> { + let acquired = self.acquire_shared(true); + assert!(acquired); + + RwLockReadGuard { + lock: self, + _not_send: super::not_send(), + } + } + + /// Try to lock this `RwLock` with shared read access without waiting. + #[must_use] + pub fn try_read(&self) -> Option> { + self.acquire_shared(false).then_some(RwLockReadGuard { + lock: self, + _not_send: super::not_send(), + }) + } + + /// Lock this `RwLock` with exclusive write access. + #[must_use] + pub fn write(&self) -> RwLockWriteGuard<'_, T> { + let acquired = self.acquire_exclusive(true); + assert!(acquired); + + RwLockWriteGuard { + lock: self, + _not_send: super::not_send(), + } + } + + /// Try to lock this `RwLock` with exclusive write access without waiting. + #[must_use] + pub fn try_write(&self) -> Option> { + self.acquire_exclusive(false).then_some(RwLockWriteGuard { + lock: self, + _not_send: super::not_send(), + }) + } + + /// Get mutable access to the protected value without locking. + /// + /// This is safe because the mutable borrow proves no other borrow of the + /// lock exists. + pub fn get_mut(&mut self) -> &mut T { + self.value.get_mut() + } + + fn resource_ptr(&self) -> *mut ERESOURCE { + self.resource.as_ptr() + } + + fn acquire_shared(&self, wait: bool) -> bool { + self.enter_critical_region(); + + let acquired; + // SAFETY: `resource_ptr` returns the initialized `ERESOURCE` owned by + // this lock. The caller is at `IRQL <= APC_LEVEL`, and the critical + // region has been entered for this acquisition attempt. + unsafe { + acquired = ExAcquireResourceSharedLite(self.resource_ptr(), u8::from(wait)); + } + + if acquired == 0 { + self.leave_critical_region(); + } + + acquired != 0 + } + + fn acquire_exclusive(&self, wait: bool) -> bool { + self.enter_critical_region(); + + let acquired; + // SAFETY: `resource_ptr` returns the initialized `ERESOURCE` owned by + // this lock. The caller is at `IRQL <= APC_LEVEL`, and the critical + // region has been entered for this acquisition attempt. + unsafe { + acquired = ExAcquireResourceExclusiveLite(self.resource_ptr(), u8::from(wait)); + } + + if acquired == 0 { + self.leave_critical_region(); + } + + acquired != 0 + } + + fn release(&self) { + // SAFETY: Each guard is constructed only after a successful acquisition + // of this `ERESOURCE`, and this releases exactly that acquisition. + unsafe { + ExReleaseResourceLite(self.resource_ptr()); + } + self.leave_critical_region(); + } + + fn enter_critical_region(&self) { + // SAFETY: Callers acquire `RwLock` only at `IRQL <= APC_LEVEL`, which is + // the WDK contract for entering a critical region. + unsafe { + KeEnterCriticalRegion(); + } + } + + fn leave_critical_region(&self) { + // SAFETY: This is paired with a prior successful `KeEnterCriticalRegion` + // call from the same thread. + unsafe { + KeLeaveCriticalRegion(); + } + } +} + +impl Drop for RwLock { + fn drop(&mut self) { + // SAFETY: The resource was initialized by `try_new`, is owned by this + // `RwLock`, and no guards can outlive `self`. + unsafe { + let _ = ExDeleteResourceLite(self.resource.as_ptr()); + } + + // SAFETY: `self.resource` was allocated by `allocate_resource` and has + // not already been deallocated. + unsafe { + deallocate_resource(self.resource); + } + } +} + +fn allocate_resource() -> Result, NTSTATUS> { + let layout = Layout::new::(); + + // SAFETY: `layout` describes exactly one `ERESOURCE`. A null result is + // handled below. + let resource = unsafe { alloc(layout).cast::() }; + + NonNull::new(resource).ok_or(STATUS_INSUFFICIENT_RESOURCES) +} + +unsafe fn deallocate_resource(resource: NonNull) { + // SAFETY: Callers pass a pointer returned by `allocate_resource` that has + // not already been deallocated. + unsafe { + dealloc(resource.as_ptr().cast::(), Layout::new::()); + } +} + +/// Shared read guard returned by [`RwLock::read`] and [`RwLock::try_read`]. +pub struct RwLockReadGuard<'a, T: ?Sized> { + lock: &'a RwLock, + _not_send: super::NotSend, +} + +impl Deref for RwLockReadGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Shared guards are only created while the resource is held in + // shared mode, and writers are excluded by the `ERESOURCE`. + unsafe { &*self.lock.value.get() } + } +} + +impl Drop for RwLockReadGuard<'_, T> { + fn drop(&mut self) { + self.lock.release(); + } +} + +/// Exclusive write guard returned by [`RwLock::write`] and +/// [`RwLock::try_write`]. +pub struct RwLockWriteGuard<'a, T: ?Sized> { + lock: &'a RwLock, + _not_send: super::NotSend, +} + +impl Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Write guards are only created while the resource is held in + // exclusive mode, and all other access is excluded by the `ERESOURCE`. + unsafe { &*self.lock.value.get() } + } +} + +impl DerefMut for RwLockWriteGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: Write guards are only created while the resource is held in + // exclusive mode, and all other access is excluded by the `ERESOURCE`. + unsafe { &mut *self.lock.value.get() } + } +} + +impl Drop for RwLockWriteGuard<'_, T> { + fn drop(&mut self) { + self.lock.release(); + } +} diff --git a/crates/wdk/src/sync/rw_spin_lock.rs b/crates/wdk/src/sync/rw_spin_lock.rs new file mode 100644 index 000000000..07ec02a83 --- /dev/null +++ b/crates/wdk/src/sync/rw_spin_lock.rs @@ -0,0 +1,261 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +use core::{ + cell::UnsafeCell, + ops::{Deref, DerefMut}, +}; + +use wdk_sys::{ + EX_SPIN_LOCK, + KIRQL, + ntddk::{ + ExAcquireSpinLockExclusive, + ExAcquireSpinLockExclusiveAtDpcLevel, + ExAcquireSpinLockShared, + ExAcquireSpinLockSharedAtDpcLevel, + ExReleaseSpinLockExclusive, + ExReleaseSpinLockExclusiveFromDpcLevel, + ExReleaseSpinLockShared, + ExReleaseSpinLockSharedFromDpcLevel, + }, +}; + +/// Reader-writer spin lock backed by `EX_SPIN_LOCK`. +/// +/// This lock is intended for very short critical sections that may be accessed +/// from elevated-IRQL paths. [`RwSpinLock::read`] and [`RwSpinLock::write`] can +/// be called at `IRQL <= DISPATCH_LEVEL`; they raise to `DISPATCH_LEVEL` and +/// restore the previous IRQL when the guard is dropped. +/// +/// The `*_at_dpc_level` methods are for callers that are already running at +/// `DISPATCH_LEVEL`. They do not save or restore IRQL. Code while holding this +/// lock must not allocate, wait, call pageable code, or touch pageable data. +/// Because this lock can be held at `DISPATCH_LEVEL`, the lock object and the +/// protected value must be resident in nonpaged memory whenever they can be +/// accessed through this API. +/// +/// Guards are deliberately `!Send` so IRQL restoration happens on the same +/// thread that acquired the lock. +pub struct RwSpinLock { + spin_lock: UnsafeCell, + value: UnsafeCell, +} + +// SAFETY: `RwSpinLock` owns `T`, and moving the lock to another thread is safe +// when `T` can be moved between threads. +unsafe impl Send for RwSpinLock {} + +// SAFETY: Shared access requires `T: Sync`, and mutable access is serialized by +// the underlying spin lock. `T: Send` is required because a writer can move +// values out of the protected data. +unsafe impl Sync for RwSpinLock {} + +#[derive(Clone, Copy)] +enum ReleaseMode { + RestoreIrql(KIRQL), + AtDpcLevel, +} + +impl RwSpinLock { + /// Construct a new reader-writer spin lock protecting `value`. + #[must_use] + pub const fn new(value: T) -> Self { + Self { + spin_lock: UnsafeCell::new(0), + value: UnsafeCell::new(value), + } + } +} + +impl RwSpinLock { + /// Lock this `RwSpinLock` with shared read access. + /// + /// This raises the current IRQL to `DISPATCH_LEVEL` and restores the + /// previous IRQL when the returned guard is dropped. + #[must_use] + pub fn read(&self) -> RwSpinLockReadGuard<'_, T> { + let old_irql; + // SAFETY: `spin_lock_ptr` returns the initialized spin lock owned by + // this object. The caller is at `IRQL <= DISPATCH_LEVEL`. + unsafe { + old_irql = ExAcquireSpinLockShared(self.spin_lock_ptr()); + } + + RwSpinLockReadGuard { + lock: self, + release_mode: ReleaseMode::RestoreIrql(old_irql), + _not_send: super::not_send(), + } + } + + /// Lock this `RwSpinLock` with shared read access at `DISPATCH_LEVEL`. + /// + /// # Safety + /// + /// The caller must already be running at `DISPATCH_LEVEL`. The returned + /// guard will not restore IRQL when it is dropped. + #[must_use] + pub unsafe fn read_at_dpc_level(&self) -> RwSpinLockReadGuard<'_, T> { + // SAFETY: The caller guarantees current IRQL is `DISPATCH_LEVEL`, and + // `spin_lock_ptr` returns the initialized spin lock owned by this + // object. + unsafe { + ExAcquireSpinLockSharedAtDpcLevel(self.spin_lock_ptr()); + } + + RwSpinLockReadGuard { + lock: self, + release_mode: ReleaseMode::AtDpcLevel, + _not_send: super::not_send(), + } + } + + /// Lock this `RwSpinLock` with exclusive write access. + /// + /// This raises the current IRQL to `DISPATCH_LEVEL` and restores the + /// previous IRQL when the returned guard is dropped. + #[must_use] + pub fn write(&self) -> RwSpinLockWriteGuard<'_, T> { + let old_irql; + // SAFETY: `spin_lock_ptr` returns the initialized spin lock owned by + // this object. The caller is at `IRQL <= DISPATCH_LEVEL`. + unsafe { + old_irql = ExAcquireSpinLockExclusive(self.spin_lock_ptr()); + } + + RwSpinLockWriteGuard { + lock: self, + release_mode: ReleaseMode::RestoreIrql(old_irql), + _not_send: super::not_send(), + } + } + + /// Lock this `RwSpinLock` with exclusive write access at `DISPATCH_LEVEL`. + /// + /// # Safety + /// + /// The caller must already be running at `DISPATCH_LEVEL`. The returned + /// guard will not restore IRQL when it is dropped. + #[must_use] + pub unsafe fn write_at_dpc_level(&self) -> RwSpinLockWriteGuard<'_, T> { + // SAFETY: The caller guarantees current IRQL is `DISPATCH_LEVEL`, and + // `spin_lock_ptr` returns the initialized spin lock owned by this + // object. + unsafe { + ExAcquireSpinLockExclusiveAtDpcLevel(self.spin_lock_ptr()); + } + + RwSpinLockWriteGuard { + lock: self, + release_mode: ReleaseMode::AtDpcLevel, + _not_send: super::not_send(), + } + } + + /// Get mutable access to the protected value without locking. + /// + /// This is safe because the mutable borrow proves no other borrow of the + /// lock exists. + pub fn get_mut(&mut self) -> &mut T { + self.value.get_mut() + } + + fn spin_lock_ptr(&self) -> *mut EX_SPIN_LOCK { + self.spin_lock.get() + } + + fn release_shared(&self, release_mode: ReleaseMode) { + match release_mode { + ReleaseMode::RestoreIrql(old_irql) => { + // SAFETY: The guard was constructed by acquiring this spin lock + // with `ExAcquireSpinLockShared`, which returned `old_irql`. + unsafe { + ExReleaseSpinLockShared(self.spin_lock_ptr(), old_irql); + } + } + ReleaseMode::AtDpcLevel => { + // SAFETY: The guard was constructed by acquiring this spin lock + // with `ExAcquireSpinLockSharedAtDpcLevel`. + unsafe { + ExReleaseSpinLockSharedFromDpcLevel(self.spin_lock_ptr()); + } + } + } + } + + fn release_exclusive(&self, release_mode: ReleaseMode) { + match release_mode { + ReleaseMode::RestoreIrql(old_irql) => { + // SAFETY: The guard was constructed by acquiring this spin lock + // with `ExAcquireSpinLockExclusive`, which returned `old_irql`. + unsafe { + ExReleaseSpinLockExclusive(self.spin_lock_ptr(), old_irql); + } + } + ReleaseMode::AtDpcLevel => { + // SAFETY: The guard was constructed by acquiring this spin lock + // with `ExAcquireSpinLockExclusiveAtDpcLevel`. + unsafe { + ExReleaseSpinLockExclusiveFromDpcLevel(self.spin_lock_ptr()); + } + } + } + } +} + +/// Shared read guard returned by [`RwSpinLock::read`] and +/// [`RwSpinLock::read_at_dpc_level`]. +pub struct RwSpinLockReadGuard<'a, T: ?Sized> { + lock: &'a RwSpinLock, + release_mode: ReleaseMode, + _not_send: super::NotSend, +} + +impl Deref for RwSpinLockReadGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Shared guards are only created while the spin lock is held in + // shared mode, and writers are excluded by the spin lock. + unsafe { &*self.lock.value.get() } + } +} + +impl Drop for RwSpinLockReadGuard<'_, T> { + fn drop(&mut self) { + self.lock.release_shared(self.release_mode); + } +} + +/// Exclusive write guard returned by [`RwSpinLock::write`] and +/// [`RwSpinLock::write_at_dpc_level`]. +pub struct RwSpinLockWriteGuard<'a, T: ?Sized> { + lock: &'a RwSpinLock, + release_mode: ReleaseMode, + _not_send: super::NotSend, +} + +impl Deref for RwSpinLockWriteGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + // SAFETY: Write guards are only created while the spin lock is held in + // exclusive mode, and all other access is excluded by the spin lock. + unsafe { &*self.lock.value.get() } + } +} + +impl DerefMut for RwSpinLockWriteGuard<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + // SAFETY: Write guards are only created while the spin lock is held in + // exclusive mode, and all other access is excluded by the spin lock. + unsafe { &mut *self.lock.value.get() } + } +} + +impl Drop for RwSpinLockWriteGuard<'_, T> { + fn drop(&mut self) { + self.lock.release_exclusive(self.release_mode); + } +} diff --git a/examples/sample-wdm-driver/src/lib.rs b/examples/sample-wdm-driver/src/lib.rs index 5e690ac92..2556393c9 100644 --- a/examples/sample-wdm-driver/src/lib.rs +++ b/examples/sample-wdm-driver/src/lib.rs @@ -13,11 +13,15 @@ extern crate alloc; extern crate wdk_panic; use alloc::{ffi::CString, slice, string::String}; +use core::mem::size_of; -use wdk::println; +use wdk::{ + println, + sync::{PushLock, RwLock, RwSpinLock}, +}; #[cfg(not(test))] use wdk_alloc::WdkAllocator; -use wdk_sys::{ntddk::DbgPrint, DRIVER_OBJECT, NTSTATUS, PCUNICODE_STRING, STATUS_SUCCESS}; +use wdk_sys::{DRIVER_OBJECT, NTSTATUS, PCUNICODE_STRING, STATUS_SUCCESS, ntddk::DbgPrint}; #[cfg(not(test))] #[global_allocator] @@ -48,13 +52,46 @@ pub unsafe extern "system" fn driver_entry( driver.DriverUnload = Some(driver_exit); + let rw_lock = match RwLock::try_new(0_u32) { + Ok(rw_lock) => rw_lock, + Err(status) => return status, + }; + { + let mut sample_value = rw_lock.write(); + *sample_value = 42; + } + let sample_value = *rw_lock.read(); + println!("RwLock sample value: {sample_value}"); + + let push_lock = PushLock::new(0_u32); + { + let mut sample_value = push_lock.write(); + *sample_value = 7; + } + let sample_value = *push_lock.read(); + println!("PushLock sample value: {sample_value}"); + + let rw_spin_lock = RwSpinLock::new(0_u32); + { + let mut sample_value = rw_spin_lock.write(); + *sample_value = 3; + } + let sample_value = *rw_spin_lock.read(); + println!("RwSpinLock sample value: {sample_value}"); + // Translate UTF16 string to rust string - let registry_path = String::from_utf16_lossy(unsafe { - slice::from_raw_parts( - (*registry_path).Buffer, - (*registry_path).Length as usize / core::mem::size_of_val(&(*(*registry_path).Buffer)), - ) - }); + // SAFETY: WDM provides `registry_path` as a valid `UNICODE_STRING` pointer + // for the duration of `DriverEntry`. + let registry_path = unsafe { &*registry_path }; + let registry_path_len = registry_path.Length as usize / size_of::(); + let registry_path_buffer = if registry_path_len == 0 { + &[] + } else { + // SAFETY: `registry_path.Buffer` points to `Length` bytes of UTF-16 + // code units for the duration of `DriverEntry`. + unsafe { slice::from_raw_parts(registry_path.Buffer, registry_path_len) } + }; + let registry_path = String::from_utf16_lossy(registry_path_buffer); // It is much better to use the println macro that has an implementation in // wdk::print.rs to call DbgPrint. The println! implementation in diff --git a/tests/config-wdm/Cargo.lock b/tests/config-wdm/Cargo.lock index 141ae0f5d..0129ccc5b 100644 --- a/tests/config-wdm/Cargo.lock +++ b/tests/config-wdm/Cargo.lock @@ -1,7 +1,906 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[package]] +name = "camino" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce8d3bd5823c7504d3f579f13e7b2f3da252fcb938c594d5680ee508bf846f" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.2.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" +dependencies = [ + "find-msvc-tools", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "clap" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap-cargo" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d546f0e84ff2bfa4da1ce9b54be42285767ba39c688572ca32412a09a73851e5" +dependencies = [ + "anstyle", + "clap", +] + +[[package]] +name = "clap_builder" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "config-wdm" version = "0.1.0" +dependencies = [ + "wdk", + "wdk-sys", +] + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fs4" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8640e34b88f7652208ce9e88b1a37a2ae95227d84abec377ccd3c5cfeb141ed4" +dependencies = [ + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "scratch" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68f2ec51b097e4c1a75b681a8bec621909b5e91f15bb7b840c4f2f7b01148b2" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "wdk" +version = "0.4.1" +dependencies = [ + "cfg-if", + "tracing", + "tracing-subscriber", + "wdk-build", + "wdk-sys", +] + +[[package]] +name = "wdk-build" +version = "0.5.1" +dependencies = [ + "anyhow", + "bindgen", + "camino", + "cargo_metadata", + "cfg-if", + "clap", + "clap-cargo", + "paste", + "regex", + "rustversion", + "semver", + "serde", + "serde_json", + "thiserror", + "tracing", + "windows", +] + +[[package]] +name = "wdk-macros" +version = "0.5.1" +dependencies = [ + "cfg-if", + "fs4", + "itertools", + "proc-macro2", + "quote", + "scratch", + "serde", + "serde_json", + "syn", +] + +[[package]] +name = "wdk-sys" +version = "0.5.1" +dependencies = [ + "anyhow", + "bindgen", + "cargo_metadata", + "cc", + "cfg-if", + "rustversion", + "serde_json", + "thiserror", + "tracing", + "tracing-subscriber", + "wdk-build", + "wdk-macros", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/tests/config-wdm/Cargo.toml b/tests/config-wdm/Cargo.toml index 82d896f50..a886e8ffa 100644 --- a/tests/config-wdm/Cargo.toml +++ b/tests/config-wdm/Cargo.toml @@ -10,3 +10,10 @@ version = "0.1.0" driver-type = "WDM" [lib] + +[dev-dependencies] +wdk.path = "../../crates/wdk" +wdk-sys = { features = ["test-stubs"], path = "../../crates/wdk-sys" } + +[features] +nightly = ["wdk-sys/nightly", "wdk/nightly"] diff --git a/tests/config-wdm/tests/sync_tests.rs b/tests/config-wdm/tests/sync_tests.rs new file mode 100644 index 000000000..e31773a96 --- /dev/null +++ b/tests/config-wdm/tests/sync_tests.rs @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation +// License: MIT OR Apache-2.0 + +#[cfg(test)] +mod tests { + use wdk::sync::{PushLock, RwLock, RwSpinLock}; + #[allow(unused_imports)] + use wdk_sys::test_stubs as _; + + #[test] + fn rw_lock_read_and_write_guards_access_value() { + let lock = RwLock::try_new(1_u32).expect("ERESOURCE initialization should succeed"); + + assert_eq!(*lock.read(), 1); + + { + let mut value = lock.write(); + *value = 2; + } + + assert_eq!( + *lock.try_read().expect("shared acquisition should succeed"), + 2 + ); + + { + let mut value = lock + .try_write() + .expect("exclusive acquisition should succeed"); + *value += 1; + } + + assert_eq!(*lock.read(), 3); + } + + #[test] + fn rw_lock_get_mut_accesses_value_without_locking() { + let mut lock = RwLock::try_new(1_u32).expect("ERESOURCE initialization should succeed"); + + *lock.get_mut() = 7; + + assert_eq!(*lock.read(), 7); + } + + #[test] + fn push_lock_read_and_write_guards_access_value() { + let lock = PushLock::new(1_u32); + + assert_eq!(*lock.read(), 1); + + { + let mut value = lock.write(); + *value = 4; + } + + assert_eq!(*lock.read(), 4); + } + + #[test] + fn push_lock_get_mut_accesses_value_without_locking() { + let mut lock = PushLock::new(1_u32); + + *lock.get_mut() = 9; + + assert_eq!(*lock.read(), 9); + } + + #[test] + fn rw_spin_lock_read_and_write_guards_access_value() { + let lock = RwSpinLock::new(1_u32); + + assert_eq!(*lock.read(), 1); + + { + let mut value = lock.write(); + *value = 5; + } + + assert_eq!(*lock.read(), 5); + } + + #[test] + fn rw_spin_lock_get_mut_accesses_value_without_locking() { + let mut lock = RwSpinLock::new(1_u32); + + *lock.get_mut() = 11; + + assert_eq!(*lock.read(), 11); + } + + #[test] + fn rw_spin_lock_dpc_level_guards_access_value() { + let lock = RwSpinLock::new(1_u32); + + // SAFETY: The test stubs do not inspect or modify IRQL, so they model + // the caller already running at DISPATCH_LEVEL. + let value = unsafe { lock.read_at_dpc_level() }; + assert_eq!(*value, 1); + drop(value); + + // SAFETY: The test stubs do not inspect or modify IRQL, so they model + // the caller already running at DISPATCH_LEVEL. + let mut value = unsafe { lock.write_at_dpc_level() }; + *value = 13; + drop(value); + + assert_eq!(*lock.read(), 13); + } +}