summaryrefslogtreecommitdiff
path: root/rust/kernel
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-06-17 10:21:00 +0100
committerLinus Torvalds <torvalds@linux-foundation.org>2026-06-17 10:21:00 +0100
commit4b99990cdf9560e8a071640baf19f312e6ae02f4 (patch)
treeba3c58e860666130caf5ae3bf386b6dbfbe59b04 /rust/kernel
parent9c87e61e3c5797277407ba5eae4eac8a52be3fa3 (diff)
parent52d4ab1ca790a668cc8f2c27017138b1c467168c (diff)
Merge tag 'drm-next-2026-06-17' of https://gitlab.freedesktop.org/drm/kernel
Pull drm updates from Dave Airlie: "Highlights: - xe: add initial CRI platform support - amdgpu: initial HDMI 2.1 FRL support - rust: add some new type concepts for device lifetimes - scheduler: moves to a fair algorithm and lots of cleanups But it's mostly the usual mountain of changes across the board. core: - add docbook for DRM_IOCTL_SYNCOBJ_EVENTFD - change signature of drm_connector_attach_hdr_output_metadata_property - dedup counter and timestamp retrieval in vblank code - parse AMD VSDB v3 in CTA extension blocks - add P230, Y7, XYYY2101010, T430, XVUY210101010 formats - don't call drop master on file close if not master - use drm_printf_indent in atomic / bridge - fix 32b format descriptions - docs: fix toctree - hdmi: add common TMDS character rates - fix drm_syncobj_find_fence leak rust: - introduce Higher-Ranked lifetime types - replace drvdata with scoped registration data - add GPUVM immediate mode abstraction for rust GPU drivers - introduce DeviceContext type state for drm::Device bridge: - clarify drm_bridge_get/put - create drm_get_bridge_by_endpoint and use it - analogix_dp: add panel probing - ite-it6211 - use drm audio hdmi helpers buddy: - add lockdep annotations dp: - add PR and VRR updates - mst: fix buffer overflows - add Adaptive Sync SDP decoding support - fix OOB reads in dp-mst ttm: - bump fpfn/lpfn to 64-bit scheduler: - change default to fair scheduler - map runqueue 1:1 with scheduler dma-buf: - port selftests to kunit - convert dma-buf system/heap allocators to module - add separate DMABUF_HEAPS_SYSTEM_CC_SHARED Kconfig udmabuf: - revert hugetlb support - fix error with CONFIG_DMA_API_DEBUG dma-fence: - fix tracepoints lifetime - remove unused signal on any support ras: - add clear error counter netlink command to drm ras gpusvm: - reject VMAs with VM_IO or VM_PFNMAP when creating SVM ranges - use IOVA allocations pagemap: - use IOVA allocations panels: - update to use ref counts - add support for CSW PNB601LS1-2, LGD LP116WHA-SPB1 - add support for waveshare panels - CMN N116BCN-EA1, CMN N140HCA-EEK, IVO M140NWFQ R5, - IVO, R140NWFW R0, BOE NT140*, BOE NV133FHM-N4F, - AUO B140*, AUO B133HAN06.6 and AUO B116XTN02.3 eDP panels - Surface Pro 12 Panel xe: - add CRI PCI-IDs - debugfs add multi-lrc info - engine init cleanup - PF fair scheduling auto provisioning - system controller support for CRI/Xe3p - PXP state machine fixes - Reset/wedge/unload corner case fixes - Wedge path memory allocation fixes - PAT type cleanups - Reject unsafe PAT for CPU cached memory - OA improvements for CRI device memory - kernel doc syntax in xe headers - xe_drm.h documentation fixes - include guard cleanups - VF CCS memory pool - i915/xe step unification - Xe3p GT tuning fixes - forcewake cleanup in GT and GuC - admin-only PF mode - enable hwmon energy attributes for CRI - enable GT_MI_USER_INTERRUPT - refactor emit functions - oa workarounds - multi_queue: allow QUEUE_TIMESTAMP register - convert stolen memory to ttm range manager - use xe2 style blitter as a feature flag - make drm_driver const - add/use IRQ page to HW engine definition - fix oops when display disabled i915: - enable PIPEDMC_ERROR interrupt - more common display code refactoring - restructure DP/HDMI sink format handling - eliminate FB usage from lowlevel pinning code - panel replay bw optimization - integrate sharpness filter into the scaler - new fb_pin abstraction for xe/i915 fb transparent handling - skip inactive MST connectors on HDCP - start switching to display specific registers - use polling when irq unavailable - Adaptive-sync SDP prep amdgpu: - use drm_display_info for AMD VSDB data - Initial HDMI 2.1 FRL support - Initial DCN 4.2.1 support - GART fixes for non-4k pages - GC 11.5.6/SDMA 6.4.0/and other new IPs - GFX9/DCE6/Hawaii/SDMA4/GART/Userq fixes - Finish support for using multiple SDMA queues for TTM operations - SWSMU updates - GC 12.1 updates - SMU 15.0.8 updates - DCN 4.2 updates - DC type conversion fixes - Enable DC power module - Replay/PSR updates - SMU 13.x updates - Compute queue quantum MQD updates - ASPM fix - Align VKMS with common implementation - DC analog support fixes - UVD 3 fixes - TCC harvesting fixes for SI - GC 11 APU module reload fix - NBIO 6.3.2 support - IH 7.1 updates - DC cursor fixes - VCN/JPEG user fence fixes - DC support for connectors without DDC - Prefer ROM BAR for default VGA device - DC bandwidth fixes - Add PTL support for profiler - Introduce dc_plane_cm and migrate surface update color path - Add FRL registers for HDMI 2.1 - Restructure VM state machine - Auxless ALPM support - GEM_OP locking/warning fixes - switch to system_dfl_wq amdkfd: - GPUVM TLB flush fix - Hotplug fix - Boundary check fixes - SVM fixes - CRIU fixes - add profiler API - MES 12.1 updates msm: - core: - fix shrinker documentation - IFPC enabled for gen8 - PERFCNTR_CONFIG ioctl support - GPU: - reworked UBWC handling - a810 support - MDSS: - add support for Milos platform - reworked UBWC handling - DisplayPort: - reworked HPD handling as prep for MST - DPU: - Milos platform support - reworked UBWC handling - DSI: - Milos platform support nova: - Hopper/Blackwell enablement (GH100/GB100/GB202) - FSP support - 32-bit firmware support - HAL functions - refactor GSP boot/unload - GA100 support - VBIOS hardening/refactoring - Adopt higher order lifetime types tyr: - define register blocks - add shmem backed GEM objects - adopt higher order lifetime types - move clock cleanup into Drop radeon: - Hawaii SMU fixes - CS parser fix - use struct drm_edid instead of edid amdxdna: - export per-client BO memory via fdinfo - AIE4 device support - support medium/lower power modes - expandable device heap support - revert read-only user-pointer BO mappings ivpu: - support frequency limiting panthor: - enable GEM shrinker support - add eviction and reclaim info to fdinfo v3d: - enable runtime PM mgag200: - support XRGB1555 + C8 ast: - support XRGB1555 + C8 - use constants for lots of registers - fix register handling imagination: - fence handling refactoring nouveau: - fix sched double call - expose VBIOS on GSP-RM systems - add GA100 support virtio: - add VIRTIO_GPU_F_BLOB_ALIGNMENT flag - add deferred mapping support gud: - add RCade Display Adapter hibmc: - fix no connectors usage mediatek: - hdmi: convert error handling - simplify mtk_crtc allocation exynos: - move fbdev emulation to drm client buffers - use drm format helpers for geometry/size - adopt core DMA tracking - fix framebuffer offset handling renesas: - add RZ/T2H SOC support versilicon: - add cursor plane support tegra: - use drm client for framebuffer" * tag 'drm-next-2026-06-17' of https://gitlab.freedesktop.org/drm/kernel: (1731 commits) dma-buf: move system_cc_shared heap under separate Kconfig accel/amdxdna: Clear sva pointer after unbind agp/amd64: Fix broken error propagation in agp_amd64_probe() accel/amdxdna: Require carveout when PASID and force_iova are disabled drm/amdkfd: always resume_all after suspend_all drm/amdgpu/gfx: move fault and EOP IRQ get/put to hw_init/hw_fini drm/amd/display: Consult MCCS FreeSync cap only if requested & supported drm/amd/pm: Use strscpy in profile mode parsing drm/amdkfd: Fix infinite loop parsing CRAT with zero subtype length drm/amdkfd: fix sysfs topology prop length on buffer truncation drm/amdgpu: drop retry loop in amdgpu_hmm_range_get_pages drm/amd/pm: bound OD parameter parsing to stack array size drm/amd/pm: Stop pp_od_clk_voltage emit at PAGE_SIZE drm/amdkfd: Unwind debug trap enable on copy_to_user failure drm/amdgpu: validate the mes firmware version for gfx12.1 drm/amdgpu: validate the mes firmware version for gfx12 drm/amdgpu: compare MES firmware version ucode for gfx11 drm/amdkfd: Add bounds check for AMDKFD_IOC_WAIT_EVENTS drm/amdgpu: restart the CS if some parts of the VM are still invalidated drm/amd/display: use unsigned types for local pipe and REG_GET counters ...
Diffstat (limited to 'rust/kernel')
-rw-r--r--rust/kernel/drm/device.rs252
-rw-r--r--rust/kernel/drm/driver.rs49
-rw-r--r--rust/kernel/drm/gem/mod.rs72
-rw-r--r--rust/kernel/drm/gem/shmem.rs61
-rw-r--r--rust/kernel/drm/gpuvm/mod.rs328
-rw-r--r--rust/kernel/drm/gpuvm/sm_ops.rs429
-rw-r--r--rust/kernel/drm/gpuvm/va.rs168
-rw-r--r--rust/kernel/drm/gpuvm/vm_bo.rs249
-rw-r--r--rust/kernel/drm/mod.rs5
9 files changed, 1501 insertions, 112 deletions
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
index 403fc35353c7..477cf771fb10 100644
--- a/rust/kernel/drm/device.rs
+++ b/rust/kernel/drm/device.rs
@@ -6,10 +6,12 @@
use crate::{
alloc::allocator::Kmalloc,
- bindings, device,
+ bindings,
+ device,
drm::{
self,
- driver::AllocImpl, //
+ driver::AllocImpl,
+ private::Sealed, //
},
error::from_err_ptr,
prelude::*,
@@ -17,16 +19,20 @@ use crate::{
ARef,
AlwaysRefCounted, //
},
- types::Opaque,
+ types::{
+ NotThreadSafe,
+ Opaque, //
+ },
workqueue::{
HasDelayedWork,
HasWork,
Work,
WorkItem, //
- },
+ }, //
};
use core::{
alloc::Layout,
+ marker::PhantomData,
mem,
ops::Deref,
ptr::{
@@ -66,36 +72,122 @@ macro_rules! drm_legacy_fields {
}
}
-/// A typed DRM device with a specific `drm::Driver` implementation.
+/// A trait implemented by all possible contexts a [`Device`] can be used in.
+///
+/// Setting up a new [`Device`] is a multi-stage process. Each step of the process that a user
+/// interacts with in Rust has a respective [`DeviceContext`] typestate. For example,
+/// `Device<T, Registered>` would be a [`Device`] that reached the [`Registered`] [`DeviceContext`].
+///
+/// Each stage of this process is described below:
+///
+/// ```text
+/// 1 2 3
+/// +--------------+ +------------------+ +-----------------------+
+/// |Device created| → |Device initialized| → |Registered w/ userspace|
+/// +--------------+ +------------------+ +-----------------------+
+/// (Uninit) (Registered)
+/// ```
+///
+/// 1. The [`Device`] is in the [`Uninit`] context and is not guaranteed to be initialized or
+/// registered with userspace. Only a limited subset of DRM core functionality is available.
+/// 2. The [`Device`] is guaranteed to be fully initialized, but is not guaranteed to be registered
+/// with userspace. All DRM core functionality which doesn't interact with userspace is
+/// available. We currently don't have a context for representing this.
+/// 3. The [`Device`] is guaranteed to be fully initialized, and is guaranteed to have been
+/// registered with userspace at some point - thus putting it in the [`Registered`] context.
+///
+/// An important caveat of [`DeviceContext`] which must be kept in mind: when used as a typestate
+/// for a reference type, it can only guarantee that a [`Device`] reached a particular stage in the
+/// initialization process _at the time the reference was taken_. No guarantee is made in regards to
+/// what stage of the process the [`Device`] is currently in. This means for instance that a
+/// `&Device<T, Uninit>` may actually be registered with userspace, it just wasn't known to be
+/// registered at the time the reference was taken.
+pub trait DeviceContext: Sealed + Send + Sync {}
+
+/// The [`DeviceContext`] of a [`Device`] that was registered with userspace at some point.
///
-/// The device is always reference-counted.
+/// This represents a [`Device`] which is guaranteed to have been registered with userspace at
+/// some point in time. Such a DRM device is guaranteed to have been fully-initialized.
+///
+/// Note: A device in this context is not guaranteed to remain registered with userspace for its
+/// entire lifetime, as this is impossible to guarantee at compile-time.
///
/// # Invariants
///
-/// `self.dev` is a valid instance of a `struct device`.
-#[repr(C)]
-pub struct Device<T: drm::Driver> {
- dev: Opaque<bindings::drm_device>,
- data: T::Data,
+/// A [`Device`] in this [`DeviceContext`] is guaranteed to have been registered with userspace
+/// at some point in time.
+pub struct Registered;
+
+impl Sealed for Registered {}
+impl DeviceContext for Registered {}
+
+/// The [`DeviceContext`] of a [`Device`] that may be unregistered and partly uninitialized.
+///
+/// A [`Device`] in this context is only guaranteed to be partly initialized, and may or may not
+/// be registered with userspace. Thus operations which depend on the [`Device`] being fully
+/// initialized, or which depend on the [`Device`] being registered with userspace are not
+/// available through this [`DeviceContext`].
+///
+/// A [`Device`] in this context can be used to create a
+/// [`Registration`](drm::driver::Registration).
+pub struct Uninit;
+
+impl Sealed for Uninit {}
+impl DeviceContext for Uninit {}
+
+/// A [`Device`] which is known at compile-time to be unregistered with userspace.
+///
+/// This type allows performing operations which are only safe to do before userspace registration,
+/// and can be used to create a [`Registration`](drm::driver::Registration) once the driver is ready
+/// to register the device with userspace.
+///
+/// Since DRM device initialization must be single-threaded, this object is not thread-safe.
+///
+/// # Invariants
+///
+/// The device in `self.0` is guaranteed to be a newly created [`Device`] that has not yet been
+/// registered with userspace until this type is dropped.
+pub struct UnregisteredDevice<T: drm::Driver>(ARef<Device<T, Uninit>>, NotThreadSafe);
+
+impl<T: drm::Driver> Deref for UnregisteredDevice<T> {
+ type Target = Device<T, Uninit>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
}
-impl<T: drm::Driver> Device<T> {
+impl<T: drm::Driver> UnregisteredDevice<T> {
+ const fn compute_features() -> u32 {
+ let mut features = drm::driver::FEAT_GEM;
+
+ if T::FEAT_RENDER {
+ features |= drm::driver::FEAT_RENDER;
+ }
+
+ features
+ }
+
const VTABLE: bindings::drm_driver = drm_legacy_fields! {
load: None,
open: Some(drm::File::<T::File>::open_callback),
postclose: Some(drm::File::<T::File>::postclose_callback),
unload: None,
- release: Some(Self::release),
+ release: Some(Device::<T>::release),
master_set: None,
master_drop: None,
debugfs_init: None,
- gem_create_object: T::Object::ALLOC_OPS.gem_create_object,
- prime_handle_to_fd: T::Object::ALLOC_OPS.prime_handle_to_fd,
- prime_fd_to_handle: T::Object::ALLOC_OPS.prime_fd_to_handle,
- gem_prime_import: T::Object::ALLOC_OPS.gem_prime_import,
- gem_prime_import_sg_table: T::Object::ALLOC_OPS.gem_prime_import_sg_table,
- dumb_create: T::Object::ALLOC_OPS.dumb_create,
- dumb_map_offset: T::Object::ALLOC_OPS.dumb_map_offset,
+
+ // Ignore the Uninit DeviceContext below. It is only provided because it is required by the
+ // compiler, and it is not actually used by these functions.
+ gem_create_object: T::Object::<Uninit>::ALLOC_OPS.gem_create_object,
+ prime_handle_to_fd: T::Object::<Uninit>::ALLOC_OPS.prime_handle_to_fd,
+ prime_fd_to_handle: T::Object::<Uninit>::ALLOC_OPS.prime_fd_to_handle,
+ gem_prime_import: T::Object::<Uninit>::ALLOC_OPS.gem_prime_import,
+ gem_prime_import_sg_table: T::Object::<Uninit>::ALLOC_OPS.gem_prime_import_sg_table,
+ dumb_create: T::Object::<Uninit>::ALLOC_OPS.dumb_create,
+ dumb_map_offset: T::Object::<Uninit>::ALLOC_OPS.dumb_map_offset,
+
show_fdinfo: None,
fbdev_probe: None,
@@ -105,7 +197,7 @@ impl<T: drm::Driver> Device<T> {
name: crate::str::as_char_ptr_in_const_context(T::INFO.name).cast_mut(),
desc: crate::str::as_char_ptr_in_const_context(T::INFO.desc).cast_mut(),
- driver_features: drm::driver::FEAT_GEM,
+ driver_features: Self::compute_features(),
ioctls: T::IOCTLS.as_ptr(),
num_ioctls: T::IOCTLS.len() as i32,
fops: &Self::GEM_FOPS,
@@ -113,11 +205,13 @@ impl<T: drm::Driver> Device<T> {
const GEM_FOPS: bindings::file_operations = drm::gem::create_fops();
- /// Create a new `drm::Device` for a `drm::Driver`.
- pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<ARef<Self>> {
+ /// Create a new `UnregisteredDevice` for a `drm::Driver`.
+ ///
+ /// This can be used to create a [`Registration`](kernel::drm::Registration).
+ pub fn new(dev: &device::Device, data: impl PinInit<T::Data, Error>) -> Result<Self> {
// `__drm_dev_alloc` uses `kmalloc()` to allocate memory, hence ensure a `kmalloc()`
// compatible `Layout`.
- let layout = Kmalloc::aligned_layout(Layout::new::<Self>());
+ let layout = Kmalloc::aligned_layout(Layout::new::<Device<T, Uninit>>());
// Use a temporary vtable without a `release` callback until `data` is initialized, so
// init failure can release the DRM device without dropping uninitialized fields.
@@ -129,12 +223,12 @@ impl<T: drm::Driver> Device<T> {
// SAFETY:
// - `alloc_vtable` reference remains valid until no longer used,
// - `dev` is valid by its type invarants,
- let raw_drm: *mut Self = unsafe {
+ let raw_drm: *mut Device<T, Uninit> = unsafe {
bindings::__drm_dev_alloc(
dev.as_raw(),
&alloc_vtable,
layout.size(),
- mem::offset_of!(Self, dev),
+ mem::offset_of!(Device<T, Uninit>, dev),
)
}
.cast();
@@ -142,7 +236,7 @@ impl<T: drm::Driver> Device<T> {
// SAFETY: `raw_drm` is a valid pointer to `Self`, given that `__drm_dev_alloc` was
// successful.
- let drm_dev = unsafe { Self::into_drm_device(raw_drm) };
+ let drm_dev = unsafe { Device::into_drm_device(raw_drm) };
// SAFETY: `raw_drm` is a valid pointer to `Self`.
let raw_data = unsafe { ptr::addr_of_mut!((*raw_drm.as_ptr()).data) };
@@ -161,9 +255,39 @@ impl<T: drm::Driver> Device<T> {
// SAFETY: The reference count is one, and now we take ownership of that reference as a
// `drm::Device`.
- Ok(unsafe { ARef::from_raw(raw_drm) })
+ // INVARIANT: We just created the device above, but have yet to call `drm_dev_register`.
+ // `Self` cannot be copied or sent to another thread - ensuring that `drm_dev_register`
+ // won't be called during its lifetime and that the device is unregistered.
+ Ok(Self(unsafe { ARef::from_raw(raw_drm) }, NotThreadSafe))
}
+}
+/// A typed DRM device with a specific [`drm::Driver`] implementation and [`DeviceContext`].
+///
+/// Since DRM devices can be used before being fully initialized and registered with userspace, `C`
+/// represents the furthest [`DeviceContext`] we can guarantee that this [`Device`] has reached.
+///
+/// Keep in mind: this means that an unregistered device can still have the registration state
+/// [`Registered`] as long as it was registered with userspace once in the past, and that the
+/// behavior of such a device is still well-defined. Additionally, a device with the registration
+/// state [`Uninit`] simply does not have a guaranteed registration state at compile time, and could
+/// be either registered or unregistered. Since there is no way to guarantee a long-lived reference
+/// to an unregistered device would remain unregistered, we do not provide a [`DeviceContext`] for
+/// this.
+///
+/// # Invariants
+///
+/// * `self.dev` is a valid instance of a `struct device`.
+/// * The data layout of `Self` remains the same across all implementations of `C`.
+/// * Any invariants for `C` also apply.
+#[repr(C)]
+pub struct Device<T: drm::Driver, C: DeviceContext = Registered> {
+ dev: Opaque<bindings::drm_device>,
+ data: T::Data,
+ _ctx: PhantomData<C>,
+}
+
+impl<T: drm::Driver, C: DeviceContext> Device<T, C> {
pub(crate) fn as_raw(&self) -> *mut bindings::drm_device {
self.dev.get()
}
@@ -189,13 +313,13 @@ impl<T: drm::Driver> Device<T> {
///
/// # Safety
///
- /// Callers must ensure that `ptr` is valid, non-null, and has a non-zero reference count,
- /// i.e. it must be ensured that the reference count of the C `struct drm_device` `ptr` points
- /// to can't drop to zero, for the duration of this function call and the entire duration when
- /// the returned reference exists.
- ///
- /// Additionally, callers must ensure that the `struct device`, `ptr` is pointing to, is
- /// embedded in `Self`.
+ /// * Callers must ensure that `ptr` is valid, non-null, and has a non-zero reference count,
+ /// i.e. it must be ensured that the reference count of the C `struct drm_device` `ptr` points
+ /// to can't drop to zero, for the duration of this function call and the entire duration when
+ /// the returned reference exists.
+ /// * Additionally, callers must ensure that the `struct device`, `ptr` is pointing to, is
+ /// embedded in `Self`.
+ /// * Callers promise that any type invariants of `C` will be upheld.
#[doc(hidden)]
pub unsafe fn from_raw<'a>(ptr: *const bindings::drm_device) -> &'a Self {
// SAFETY: By the safety requirements of this function `ptr` is a valid pointer to a
@@ -215,9 +339,20 @@ impl<T: drm::Driver> Device<T> {
// - `this` is valid for dropping.
unsafe { core::ptr::drop_in_place(this) };
}
+
+ /// Change the [`DeviceContext`] for a [`Device`].
+ ///
+ /// # Safety
+ ///
+ /// The caller promises that `self` fulfills all of the guarantees provided by the given
+ /// [`DeviceContext`].
+ pub(crate) unsafe fn assume_ctx<NewCtx: DeviceContext>(&self) -> &Device<T, NewCtx> {
+ // SAFETY: The data layout is identical via our type invariants.
+ unsafe { mem::transmute(self) }
+ }
}
-impl<T: drm::Driver> Deref for Device<T> {
+impl<T: drm::Driver, C: DeviceContext> Deref for Device<T, C> {
type Target = T::Data;
fn deref(&self) -> &Self::Target {
@@ -227,7 +362,7 @@ impl<T: drm::Driver> Deref for Device<T> {
// SAFETY: DRM device objects are always reference counted and the get/put functions
// satisfy the requirements.
-unsafe impl<T: drm::Driver> AlwaysRefCounted for Device<T> {
+unsafe impl<T: drm::Driver, C: DeviceContext> AlwaysRefCounted for Device<T, C> {
fn inc_ref(&self) {
// SAFETY: The existence of a shared reference guarantees that the refcount is non-zero.
unsafe { bindings::drm_dev_get(self.as_raw()) };
@@ -242,7 +377,7 @@ unsafe impl<T: drm::Driver> AlwaysRefCounted for Device<T> {
}
}
-impl<T: drm::Driver> AsRef<device::Device> for Device<T> {
+impl<T: drm::Driver, C: DeviceContext> AsRef<device::Device> for Device<T, C> {
fn as_ref(&self) -> &device::Device {
// SAFETY: `bindings::drm_device::dev` is valid as long as the DRM device itself is valid,
// which is guaranteed by the type invariant.
@@ -251,21 +386,22 @@ impl<T: drm::Driver> AsRef<device::Device> for Device<T> {
}
// SAFETY: A `drm::Device` can be released from any thread.
-unsafe impl<T: drm::Driver> Send for Device<T> {}
+unsafe impl<T: drm::Driver, C: DeviceContext> Send for Device<T, C> {}
// SAFETY: A `drm::Device` can be shared among threads because all immutable methods are protected
// by the synchronization in `struct drm_device`.
-unsafe impl<T: drm::Driver> Sync for Device<T> {}
+unsafe impl<T: drm::Driver, C: DeviceContext> Sync for Device<T, C> {}
-impl<T, const ID: u64> WorkItem<ID> for Device<T>
+impl<T, C, const ID: u64> WorkItem<ID> for Device<T, C>
where
T: drm::Driver,
- T::Data: WorkItem<ID, Pointer = ARef<Device<T>>>,
- T::Data: HasWork<Device<T>, ID>,
+ T::Data: WorkItem<ID, Pointer = ARef<Self>>,
+ T::Data: HasWork<Self, ID>,
+ C: DeviceContext,
{
- type Pointer = ARef<Device<T>>;
+ type Pointer = ARef<Self>;
- fn run(ptr: ARef<Device<T>>) {
+ fn run(ptr: ARef<Self>) {
T::Data::run(ptr);
}
}
@@ -277,40 +413,42 @@ where
// stored inline in `drm::Device`, so the `container_of` call is valid.
//
// - The two methods are true inverses of each other: given `ptr: *mut
-// Device<T>`, `raw_get_work` will return a `*mut Work<Device<T>, ID>` through
-// `T::Data::raw_get_work` and given a `ptr: *mut Work<Device<T>, ID>`,
-// `work_container_of` will return a `*mut Device<T>` through `container_of`.
-unsafe impl<T, const ID: u64> HasWork<Device<T>, ID> for Device<T>
+// Device<T, C>`, `raw_get_work` will return a `*mut Work<Device<T, C>, ID>` through
+// `T::Data::raw_get_work` and given a `ptr: *mut Work<Device<T, C>, ID>`,
+// `work_container_of` will return a `*mut Device<T, C>` through `container_of`.
+unsafe impl<T, C, const ID: u64> HasWork<Self, ID> for Device<T, C>
where
T: drm::Driver,
- T::Data: HasWork<Device<T>, ID>,
+ T::Data: HasWork<Self, ID>,
+ C: DeviceContext,
{
- unsafe fn raw_get_work(ptr: *mut Self) -> *mut Work<Device<T>, ID> {
- // SAFETY: The caller promises that `ptr` points to a valid `Device<T>`.
+ unsafe fn raw_get_work(ptr: *mut Self) -> *mut Work<Self, ID> {
+ // SAFETY: The caller promises that `ptr` points to a valid `Device<T, C>`.
let data_ptr = unsafe { &raw mut (*ptr).data };
// SAFETY: `data_ptr` is a valid pointer to `T::Data`.
unsafe { T::Data::raw_get_work(data_ptr) }
}
- unsafe fn work_container_of(ptr: *mut Work<Device<T>, ID>) -> *mut Self {
+ unsafe fn work_container_of(ptr: *mut Work<Self, ID>) -> *mut Self {
// SAFETY: The caller promises that `ptr` points at a `Work` field in
// `T::Data`.
let data_ptr = unsafe { T::Data::work_container_of(ptr) };
- // SAFETY: `T::Data` is stored as the `data` field in `Device<T>`.
+ // SAFETY: `T::Data` is stored as the `data` field in `Device<T, C>`.
unsafe { crate::container_of!(data_ptr, Self, data) }
}
}
// SAFETY: Our `HasWork<T, ID>` implementation returns a `work_struct` that is
// stored in the `work` field of a `delayed_work` with the same access rules as
-// the `work_struct` owing to the bound on `T::Data: HasDelayedWork<Device<T>,
+// the `work_struct` owing to the bound on `T::Data: HasDelayedWork<Device<T, C>,
// ID>`, which requires that `T::Data::raw_get_work` return a `work_struct` that
// is inside a `delayed_work`.
-unsafe impl<T, const ID: u64> HasDelayedWork<Device<T>, ID> for Device<T>
+unsafe impl<T, C, const ID: u64> HasDelayedWork<Self, ID> for Device<T, C>
where
T: drm::Driver,
- T::Data: HasDelayedWork<Device<T>, ID>,
+ T::Data: HasDelayedWork<Self, ID>,
+ C: DeviceContext,
{
}
diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs
index 5233bdebc9fc..25f7e233884d 100644
--- a/rust/kernel/drm/driver.rs
+++ b/rust/kernel/drm/driver.rs
@@ -13,9 +13,15 @@ use crate::{
prelude::*,
sync::aref::ARef, //
};
+use core::{
+ mem,
+ ptr::NonNull, //
+};
/// Driver use the GEM memory manager. This should be set for all modern drivers.
pub(crate) const FEAT_GEM: u32 = bindings::drm_driver_feature_DRIVER_GEM;
+/// Driver supports render nodes, i.e.: /dev/dri/renderDXX devices.
+pub(crate) const FEAT_RENDER: u32 = bindings::drm_driver_feature_DRIVER_RENDER;
/// Information data for a DRM Driver.
pub struct DriverInfo {
@@ -105,7 +111,7 @@ pub trait Driver {
type Data: Sync + Send;
/// The type used to manage memory for this driver.
- type Object: AllocImpl;
+ type Object<Ctx: drm::DeviceContext>: AllocImpl;
/// The type used to represent a DRM File (client)
type File: drm::file::DriverFile;
@@ -115,6 +121,16 @@ pub trait Driver {
/// IOCTL list. See `kernel::drm::ioctl::declare_drm_ioctls!{}`.
const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor];
+
+ /// Sets the `DRIVER_RENDER` feature for this driver.
+ ///
+ /// When enabled, the driver exposes `/dev/dri/renderDXX` render nodes to
+ /// userspace. The render node is an alternate low-priviledge way to access
+ /// the driver, which is enforced on a per-ioctl level. Userspace processes
+ /// that open the render node can only invoke ioctls explicitly listed as
+ /// usable from the render node (i.e. marked DRM_RENDER_ALLOW), whereas
+ /// userspace processes using the master node can invoke any ioctl.
+ const FEAT_RENDER: bool = false;
}
/// The registration type of a `drm::Device`.
@@ -123,21 +139,31 @@ pub trait Driver {
pub struct Registration<T: Driver>(ARef<drm::Device<T>>);
impl<T: Driver> Registration<T> {
- fn new(drm: &drm::Device<T>, flags: usize) -> Result<Self> {
+ fn new(drm: drm::UnregisteredDevice<T>, flags: usize) -> Result<Self> {
// SAFETY: `drm.as_raw()` is valid by the invariants of `drm::Device`.
to_result(unsafe { bindings::drm_dev_register(drm.as_raw(), flags) })?;
- Ok(Self(drm.into()))
+ // SAFETY: We just called `drm_dev_register` above
+ let new = NonNull::from(unsafe { drm.assume_ctx() });
+
+ // Leak the ARef from UnregisteredDevice in preparation for transferring its ownership.
+ mem::forget(drm);
+
+ // SAFETY: `drm`'s `Drop` constructor was never called, ensuring that there remains at least
+ // one reference to the device - which we take ownership over here.
+ let new = unsafe { ARef::from_raw(new) };
+
+ Ok(Self(new))
}
- /// Registers a new [`Device`](drm::Device) with userspace.
+ /// Registers a new [`UnregisteredDevice`](drm::UnregisteredDevice) with userspace.
///
/// Ownership of the [`Registration`] object is passed to [`devres::register`].
- pub fn new_foreign_owned(
- drm: &drm::Device<T>,
- dev: &device::Device<device::Bound>,
+ pub fn new_foreign_owned<'a>(
+ drm: drm::UnregisteredDevice<T>,
+ dev: &'a device::Device<device::Bound>,
flags: usize,
- ) -> Result
+ ) -> Result<&'a drm::Device<T>>
where
T: 'static,
{
@@ -146,8 +172,13 @@ impl<T: Driver> Registration<T> {
}
let reg = Registration::<T>::new(drm, flags)?;
+ let drm = NonNull::from(reg.device());
+
+ devres::register(dev, reg, GFP_KERNEL)?;
- devres::register(dev, reg, GFP_KERNEL)
+ // SAFETY: Since `reg` was passed to devres::register(), the device now owns the lifetime
+ // of the DRM registration - ensuring that this references lives for at least as long as 'a.
+ Ok(unsafe { drm.as_ref() })
}
/// Returns a reference to the `Device` instance for this registration.
diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
index 01b5bd47a333..c8b66d816871 100644
--- a/rust/kernel/drm/gem/mod.rs
+++ b/rust/kernel/drm/gem/mod.rs
@@ -8,6 +8,10 @@ use crate::{
bindings,
drm::{
self,
+ device::{
+ DeviceContext,
+ Registered, //
+ },
driver::{
AllocImpl,
AllocOps, //
@@ -22,6 +26,7 @@ use crate::{
types::Opaque,
};
use core::{
+ marker::PhantomData,
ops::Deref,
ptr::NonNull, //
};
@@ -73,6 +78,12 @@ pub(crate) use impl_aref_for_gem_obj;
/// [`DriverFile`]: drm::file::DriverFile
pub type DriverFile<T> = drm::File<<<T as DriverObject>::Driver as drm::Driver>::File>;
+/// A type alias for retrieving the current [`AllocImpl`] for a given [`DriverObject`].
+///
+/// [`Driver`]: drm::Driver
+pub type DriverAllocImpl<T, Ctx = Registered> =
+ <<T as DriverObject>::Driver as drm::Driver>::Object<Ctx>;
+
/// GEM object functions, which must be implemented by drivers.
pub trait DriverObject: Sync + Send + Sized {
/// Parent `Driver` for this object.
@@ -82,19 +93,19 @@ pub trait DriverObject: Sync + Send + Sized {
type Args;
/// Create a new driver data object for a GEM object of a given size.
- fn new(
- dev: &drm::Device<Self::Driver>,
+ fn new<Ctx: DeviceContext>(
+ dev: &drm::Device<Self::Driver, Ctx>,
size: usize,
args: Self::Args,
) -> impl PinInit<Self, Error>;
/// Open a new handle to an existing object, associated with a File.
- fn open(_obj: &<Self::Driver as drm::Driver>::Object, _file: &DriverFile<Self>) -> Result {
+ fn open(_obj: &DriverAllocImpl<Self>, _file: &DriverFile<Self>) -> Result {
Ok(())
}
/// Close a handle to an existing object, associated with a File.
- fn close(_obj: &<Self::Driver as drm::Driver>::Object, _file: &DriverFile<Self>) {}
+ fn close(_obj: &DriverAllocImpl<Self>, _file: &DriverFile<Self>) {}
}
/// Trait that represents a GEM object subtype
@@ -120,9 +131,12 @@ extern "C" fn open_callback<T: DriverObject>(
// SAFETY: `open_callback` is only ever called with a valid pointer to a `struct drm_file`.
let file = unsafe { DriverFile::<T>::from_raw(raw_file) };
- // SAFETY: `open_callback` is specified in the AllocOps structure for `DriverObject<T>`,
- // ensuring that `raw_obj` is contained within a `DriverObject<T>`
- let obj = unsafe { <<T::Driver as drm::Driver>::Object as IntoGEMObject>::from_raw(raw_obj) };
+ // SAFETY:
+ // * `open_callback` is specified in the AllocOps structure for `DriverObject`, ensuring that
+ // `raw_obj` is contained within a `DriverAllocImpl<T>`
+ // * It is only possible for `open_callback` to be called after device registration, ensuring
+ // that the object's device is in the `Registered` state.
+ let obj: &DriverAllocImpl<T> = unsafe { IntoGEMObject::from_raw(raw_obj) };
match T::open(obj, file) {
Err(e) => e.to_errno(),
@@ -139,12 +153,12 @@ extern "C" fn close_callback<T: DriverObject>(
// SAFETY: `close_callback` is specified in the AllocOps structure for `Object<T>`, ensuring
// that `raw_obj` is indeed contained within a `Object<T>`.
- let obj = unsafe { <<T::Driver as drm::Driver>::Object as IntoGEMObject>::from_raw(raw_obj) };
+ let obj: &DriverAllocImpl<T> = unsafe { IntoGEMObject::from_raw(raw_obj) };
T::close(obj, file);
}
-impl<T: DriverObject> IntoGEMObject for Object<T> {
+impl<T: DriverObject, Ctx: DeviceContext> IntoGEMObject for Object<T, Ctx> {
fn as_raw(&self) -> *mut bindings::drm_gem_object {
self.obj.get()
}
@@ -152,7 +166,7 @@ impl<T: DriverObject> IntoGEMObject for Object<T> {
unsafe fn from_raw<'a>(self_ptr: *mut bindings::drm_gem_object) -> &'a Self {
// SAFETY: `obj` is guaranteed to be in an `Object<T>` via the safety contract of this
// function
- unsafe { &*crate::container_of!(Opaque::cast_from(self_ptr), Object<T>, obj) }
+ unsafe { &*crate::container_of!(Opaque::cast_from(self_ptr), Object<T, Ctx>, obj) }
}
}
@@ -169,7 +183,7 @@ pub trait BaseObject: IntoGEMObject {
fn create_handle<D, F>(&self, file: &drm::File<F>) -> Result<u32>
where
Self: AllocImpl<Driver = D>,
- D: drm::Driver<Object = Self, File = F>,
+ D: drm::Driver<Object<Registered> = Self, File = F>,
F: drm::file::DriverFile<Driver = D>,
{
let mut handle: u32 = 0;
@@ -184,7 +198,7 @@ pub trait BaseObject: IntoGEMObject {
fn lookup_handle<D, F>(file: &drm::File<F>, handle: u32) -> Result<ARef<Self>>
where
Self: AllocImpl<Driver = D>,
- D: drm::Driver<Object = Self, File = F>,
+ D: drm::Driver<Object<Registered> = Self, File = F>,
F: drm::file::DriverFile<Driver = D>,
{
// SAFETY: The arguments are all valid per the type invariants.
@@ -236,16 +250,18 @@ impl<T: IntoGEMObject> BaseObjectPrivate for T {}
///
/// # Invariants
///
-/// - `self.obj` is a valid instance of a `struct drm_gem_object`.
+/// * `self.obj` is a valid instance of a `struct drm_gem_object`.
+/// * Any type invariants of `Ctx` apply to the parent DRM device for this GEM object.
#[repr(C)]
#[pin_data]
-pub struct Object<T: DriverObject + Send + Sync> {
+pub struct Object<T: DriverObject + Send + Sync, Ctx: DeviceContext = Registered> {
obj: Opaque<bindings::drm_gem_object>,
#[pin]
data: T,
+ _ctx: PhantomData<Ctx>,
}
-impl<T: DriverObject> Object<T> {
+impl<T: DriverObject, Ctx: DeviceContext> Object<T, Ctx> {
const OBJECT_FUNCS: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs {
free: Some(Self::free_callback),
open: Some(open_callback::<T>),
@@ -265,11 +281,16 @@ impl<T: DriverObject> Object<T> {
};
/// Create a new GEM object.
- pub fn new(dev: &drm::Device<T::Driver>, size: usize, args: T::Args) -> Result<ARef<Self>> {
+ pub fn new(
+ dev: &drm::Device<T::Driver, Ctx>,
+ size: usize,
+ args: T::Args,
+ ) -> Result<ARef<Self>> {
let obj: Pin<KBox<Self>> = KBox::pin_init(
try_pin_init!(Self {
obj: Opaque::new(bindings::drm_gem_object::default()),
data <- T::new(dev, size, args),
+ _ctx: PhantomData,
}),
GFP_KERNEL,
)?;
@@ -277,6 +298,8 @@ impl<T: DriverObject> Object<T> {
// SAFETY: `obj.as_raw()` is guaranteed to be valid by the initialization above.
unsafe { (*obj.as_raw()).funcs = &Self::OBJECT_FUNCS };
+ // INVARIANT: `dev` and the GEM object are in the same state at the moment, and upgrading
+ // the typestate in `dev` will not carry over to the GEM object.
if let Err(err) =
// SAFETY: The arguments are all valid per the type invariants.
to_result(unsafe {
@@ -300,13 +323,15 @@ impl<T: DriverObject> Object<T> {
}
/// Returns the `Device` that owns this GEM object.
- pub fn dev(&self) -> &drm::Device<T::Driver> {
+ pub fn dev(&self) -> &drm::Device<T::Driver, Ctx> {
// SAFETY:
// - `struct drm_gem_object.dev` is initialized and valid for as long as the GEM
// object lives.
// - The device we used for creating the gem object is passed as &drm::Device<T::Driver> to
// Object::<T>::new(), so we know that `T::Driver` is the right generic parameter to use
// here.
+ // - Any type invariants of `Ctx` are upheld by using the same `Ctx` for the `Device` we
+ // return.
unsafe { drm::Device::from_raw((*self.as_raw()).dev) }
}
@@ -331,11 +356,16 @@ impl<T: DriverObject> Object<T> {
}
}
-impl_aref_for_gem_obj!(impl<T> for Object<T> where T: DriverObject);
+impl_aref_for_gem_obj! {
+ impl<T, C> for Object<T, C>
+ where
+ T: DriverObject,
+ C: DeviceContext
+}
-impl<T: DriverObject> super::private::Sealed for Object<T> {}
+impl<T: DriverObject, Ctx: DeviceContext> super::private::Sealed for Object<T, Ctx> {}
-impl<T: DriverObject> Deref for Object<T> {
+impl<T: DriverObject, Ctx: DeviceContext> Deref for Object<T, Ctx> {
type Target = T;
fn deref(&self) -> &Self::Target {
@@ -343,7 +373,7 @@ impl<T: DriverObject> Deref for Object<T> {
}
}
-impl<T: DriverObject> AllocImpl for Object<T> {
+impl<T: DriverObject, Ctx: DeviceContext> AllocImpl for Object<T, Ctx> {
type Driver = T::Driver;
const ALLOC_OPS: AllocOps = AllocOps {
diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs
index e1b648920d2f..34af402899a0 100644
--- a/rust/kernel/drm/gem/shmem.rs
+++ b/rust/kernel/drm/gem/shmem.rs
@@ -12,10 +12,12 @@
use crate::{
container_of,
drm::{
- device,
driver,
gem,
- private::Sealed, //
+ private::Sealed,
+ Device,
+ DeviceContext,
+ Registered, //
},
error::to_result,
prelude::*,
@@ -23,11 +25,12 @@ use crate::{
types::Opaque, //
};
use core::{
+ marker::PhantomData,
ops::{
Deref,
DerefMut, //
},
- ptr::NonNull,
+ ptr::NonNull, //
};
use gem::{
BaseObjectPrivate,
@@ -40,42 +43,49 @@ use gem::{
/// This is used with [`Object::new()`] to control various properties that can only be set when
/// initially creating a shmem-backed GEM object.
#[derive(Default)]
-pub struct ObjectConfig<'a, T: DriverObject> {
+pub struct ObjectConfig<'a, T: DriverObject, C: DeviceContext = Registered> {
/// Whether to set the write-combine map flag.
pub map_wc: bool,
/// Reuse the DMA reservation from another GEM object.
///
/// The newly created [`Object`] will hold an owned refcount to `parent_resv_obj` if specified.
- pub parent_resv_obj: Option<&'a Object<T>>,
+ pub parent_resv_obj: Option<&'a Object<T, C>>,
}
/// A shmem-backed GEM object.
///
/// # Invariants
///
-/// `obj` contains a valid initialized `struct drm_gem_shmem_object` for the lifetime of this
-/// object.
+/// - `obj` contains a valid initialized `struct drm_gem_shmem_object` for the lifetime of this
+/// object.
+/// - Any type invariants of `C` apply to the parent DRM device for this GEM object.
#[repr(C)]
#[pin_data]
-pub struct Object<T: DriverObject> {
+pub struct Object<T: DriverObject, C: DeviceContext = Registered> {
#[pin]
obj: Opaque<bindings::drm_gem_shmem_object>,
/// Parent object that owns this object's DMA reservation object.
- parent_resv_obj: Option<ARef<Object<T>>>,
+ parent_resv_obj: Option<ARef<Object<T, C>>>,
#[pin]
inner: T,
+ _ctx: PhantomData<C>,
}
-super::impl_aref_for_gem_obj!(impl<T> for Object<T> where T: DriverObject);
+super::impl_aref_for_gem_obj! {
+ impl<T, C> for Object<T, C>
+ where
+ T: DriverObject,
+ C: DeviceContext
+}
// SAFETY: All GEM objects are thread-safe.
-unsafe impl<T: DriverObject> Send for Object<T> {}
+unsafe impl<T: DriverObject, C: DeviceContext> Send for Object<T, C> {}
// SAFETY: All GEM objects are thread-safe.
-unsafe impl<T: DriverObject> Sync for Object<T> {}
+unsafe impl<T: DriverObject, C: DeviceContext> Sync for Object<T, C> {}
-impl<T: DriverObject> Object<T> {
+impl<T: DriverObject, C: DeviceContext> Object<T, C> {
/// `drm_gem_object_funcs` vtable suitable for GEM shmem objects.
const VTABLE: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs {
free: Some(Self::free_callback),
@@ -106,9 +116,9 @@ impl<T: DriverObject> Object<T> {
///
/// Additional config options can be specified using `config`.
pub fn new(
- dev: &device::Device<T::Driver>,
+ dev: &Device<T::Driver, C>,
size: usize,
- config: ObjectConfig<'_, T>,
+ config: ObjectConfig<'_, T, C>,
args: T::Args,
) -> Result<ARef<Self>> {
let new: Pin<KBox<Self>> = KBox::try_pin_init(
@@ -116,6 +126,7 @@ impl<T: DriverObject> Object<T> {
obj <- Opaque::init_zeroed(),
parent_resv_obj: config.parent_resv_obj.map(|p| p.into()),
inner <- T::new(dev, size, args),
+ _ctx: PhantomData::<C>,
}),
GFP_KERNEL,
)?;
@@ -148,9 +159,9 @@ impl<T: DriverObject> Object<T> {
}
/// Returns the `Device` that owns this GEM object.
- pub fn dev(&self) -> &device::Device<T::Driver> {
+ pub fn dev(&self) -> &Device<T::Driver, C> {
// SAFETY: `dev` will have been initialized in `Self::new()` by `drm_gem_shmem_init()`.
- unsafe { device::Device::from_raw((*self.as_raw()).dev) }
+ unsafe { Device::from_raw((*self.as_raw()).dev) }
}
extern "C" fn free_callback(obj: *mut bindings::drm_gem_object) {
@@ -168,7 +179,7 @@ impl<T: DriverObject> Object<T> {
// SAFETY:
// - We verified above that `obj` is valid, which makes `this` valid
// - This function is set in AllocOps, so we know that `this` is contained within a
- // `Object<T>`
+ // `Object<T, C>`
let this = unsafe { container_of!(Opaque::cast_from(this), Self, obj) }.cast_mut();
// SAFETY: We're recovering the Kbox<> we created in gem_create_object()
@@ -176,7 +187,7 @@ impl<T: DriverObject> Object<T> {
}
}
-impl<T: DriverObject> Deref for Object<T> {
+impl<T: DriverObject, C: DeviceContext> Deref for Object<T, C> {
type Target = T;
fn deref(&self) -> &Self::Target {
@@ -184,15 +195,15 @@ impl<T: DriverObject> Deref for Object<T> {
}
}
-impl<T: DriverObject> DerefMut for Object<T> {
+impl<T: DriverObject, C: DeviceContext> DerefMut for Object<T, C> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
-impl<T: DriverObject> Sealed for Object<T> {}
+impl<T: DriverObject, C: DeviceContext> Sealed for Object<T, C> {}
-impl<T: DriverObject> gem::IntoGEMObject for Object<T> {
+impl<T: DriverObject, C: DeviceContext> gem::IntoGEMObject for Object<T, C> {
fn as_raw(&self) -> *mut bindings::drm_gem_object {
// SAFETY:
// - Our immutable reference is proof that this is safe to dereference.
@@ -200,18 +211,18 @@ impl<T: DriverObject> gem::IntoGEMObject for Object<T> {
unsafe { &raw mut (*self.obj.get()).base }
}
- unsafe fn from_raw<'a>(obj: *mut bindings::drm_gem_object) -> &'a Object<T> {
+ unsafe fn from_raw<'a>(obj: *mut bindings::drm_gem_object) -> &'a Self {
// SAFETY: The safety contract of from_gem_obj() guarantees that `obj` is contained within
// `Self`
unsafe {
let obj = Opaque::cast_from(container_of!(obj, bindings::drm_gem_shmem_object, base));
- &*container_of!(obj, Object<T>, obj)
+ &*container_of!(obj, Self, obj)
}
}
}
-impl<T: DriverObject> driver::AllocImpl for Object<T> {
+impl<T: DriverObject, C: DeviceContext> driver::AllocImpl for Object<T, C> {
type Driver = T::Driver;
const ALLOC_OPS: driver::AllocOps = driver::AllocOps {
diff --git a/rust/kernel/drm/gpuvm/mod.rs b/rust/kernel/drm/gpuvm/mod.rs
new file mode 100644
index 000000000000..ae58f6f667c1
--- /dev/null
+++ b/rust/kernel/drm/gpuvm/mod.rs
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+#![cfg(CONFIG_RUST_DRM_GPUVM)]
+
+//! DRM GPUVM in immediate mode
+//!
+//! Rust abstractions for using GPUVM in immediate mode. This is when the GPUVM state is updated
+//! during `run_job()`, i.e., in the DMA fence signalling critical path, to ensure that the GPUVM
+//! and the GPU's virtual address space has the same state at all times.
+//!
+//! C header: [`include/drm/drm_gpuvm.h`](srctree/include/drm/drm_gpuvm.h)
+
+use kernel::{
+ alloc::{
+ AllocError,
+ Flags as AllocFlags, //
+ },
+ bindings,
+ drm,
+ drm::gem::IntoGEMObject,
+ error::to_result,
+ prelude::*,
+ sync::aref::{
+ ARef,
+ AlwaysRefCounted, //
+ },
+ types::Opaque, //
+};
+
+use core::{
+ cell::UnsafeCell,
+ marker::PhantomData,
+ mem::{
+ ManuallyDrop,
+ MaybeUninit, //
+ },
+ ops::{
+ Deref,
+ DerefMut,
+ Range, //
+ },
+ ptr::{
+ self,
+ NonNull, //
+ }, //
+};
+
+mod sm_ops;
+pub use self::sm_ops::*;
+
+mod vm_bo;
+pub use self::vm_bo::*;
+
+mod va;
+pub use self::va::*;
+
+/// A DRM GPU VA manager.
+///
+/// This object is refcounted, but the locations of mapped ranges may only be accessed or changed
+/// via the special unique handle [`UniqueRefGpuVm`].
+///
+/// # Invariants
+///
+/// * Stored in an allocation managed by the refcount in `self.vm`.
+/// * Access to `data` and the gpuvm interval tree is controlled via the [`UniqueRefGpuVm`] type.
+/// * Does not contain any sparse [`GpuVa<T>`] instances.
+#[pin_data]
+pub struct GpuVm<T: DriverGpuVm> {
+ #[pin]
+ vm: Opaque<bindings::drm_gpuvm>,
+ /// Accessed only through the [`UniqueRefGpuVm`] reference.
+ data: UnsafeCell<T>,
+}
+
+// SAFETY: The GPUVM api does not assume that it is tied to a specific thread. The destructor will
+// drop the `data` field, which is okay because it is guaranteed `Send` by the `DriverGpuVm` trait.
+unsafe impl<T: DriverGpuVm> Send for GpuVm<T> {}
+// SAFETY: The GPUVM api is designed to allow &self methods to be called in parallel.
+unsafe impl<T: DriverGpuVm> Sync for GpuVm<T> {}
+
+// SAFETY: By type invariants, the allocation is managed by the refcount in `self.vm`.
+unsafe impl<T: DriverGpuVm> AlwaysRefCounted for GpuVm<T> {
+ fn inc_ref(&self) {
+ // SAFETY: By type invariants, the allocation is managed by the refcount in `self.vm`.
+ unsafe { bindings::drm_gpuvm_get(self.vm.get()) };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // SAFETY: By type invariants, the allocation is managed by the refcount in `self.vm`.
+ unsafe { bindings::drm_gpuvm_put((*obj.as_ptr()).vm.get()) };
+ }
+}
+
+impl<T: DriverGpuVm> PartialEq for GpuVm<T> {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ core::ptr::eq(self.as_raw(), other.as_raw())
+ }
+}
+impl<T: DriverGpuVm> Eq for GpuVm<T> {}
+
+impl<T: DriverGpuVm> GpuVm<T> {
+ const fn vtable() -> &'static bindings::drm_gpuvm_ops {
+ &bindings::drm_gpuvm_ops {
+ vm_free: Some(Self::vm_free),
+ op_alloc: None,
+ op_free: None,
+ vm_bo_alloc: GpuVmBo::<T>::ALLOC_FN,
+ vm_bo_free: GpuVmBo::<T>::FREE_FN,
+ vm_bo_validate: None,
+ sm_step_map: Some(Self::sm_step_map),
+ sm_step_unmap: Some(Self::sm_step_unmap),
+ sm_step_remap: Some(Self::sm_step_remap),
+ }
+ }
+
+ /// Creates a GPUVM instance.
+ #[expect(clippy::new_ret_no_self)]
+ pub fn new<E>(
+ name: &'static CStr,
+ dev: &drm::Device<T::Driver>,
+ r_obj: &T::Object,
+ range: Range<u64>,
+ reserve_range: Range<u64>,
+ data: T,
+ ) -> Result<UniqueRefGpuVm<T>, E>
+ where
+ E: From<AllocError>,
+ E: From<core::convert::Infallible>,
+ {
+ let obj = KBox::try_pin_init::<E>(
+ try_pin_init!(Self {
+ data: UnsafeCell::new(data),
+ vm <- Opaque::ffi_init(|vm| {
+ // SAFETY: These arguments are valid. `vm` is valid until refcount drops to
+ // zero. The `vm` is zeroed before calling this method by `__GFP_ZERO` flag
+ // below.
+ unsafe {
+ bindings::drm_gpuvm_init(
+ vm,
+ name.as_char_ptr(),
+ bindings::drm_gpuvm_flags_DRM_GPUVM_IMMEDIATE_MODE
+ | bindings::drm_gpuvm_flags_DRM_GPUVM_RESV_PROTECTED,
+ dev.as_raw(),
+ r_obj.as_raw(),
+ range.start,
+ range.end - range.start,
+ reserve_range.start,
+ reserve_range.end - reserve_range.start,
+ const { Self::vtable() },
+ )
+ }
+ }),
+ }? E),
+ GFP_KERNEL | __GFP_ZERO,
+ )?;
+ // SAFETY: This transfers the initial refcount to the ARef.
+ let aref = unsafe {
+ ARef::from_raw(NonNull::new_unchecked(KBox::into_raw(
+ Pin::into_inner_unchecked(obj),
+ )))
+ };
+ // INVARIANT: This reference is unique.
+ Ok(UniqueRefGpuVm(aref))
+ }
+
+ /// Access this [`GpuVm`] from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// The pointer must reference the `struct drm_gpuvm` in a valid [`GpuVm<T>`] that remains
+ /// valid for at least `'a`.
+ #[inline]
+ pub unsafe fn from_raw<'a>(ptr: *mut bindings::drm_gpuvm) -> &'a Self {
+ // SAFETY: Caller passes a pointer to the `drm_gpuvm` in a `GpuVm<T>`. Caller ensures the
+ // pointer is valid for 'a.
+ unsafe { &*kernel::container_of!(Opaque::cast_from(ptr), Self, vm) }
+ }
+
+ /// Returns a raw pointer to the embedded `struct drm_gpuvm`.
+ #[inline]
+ pub fn as_raw(&self) -> *mut bindings::drm_gpuvm {
+ self.vm.get()
+ }
+
+ /// The start of the VA space.
+ #[inline]
+ pub fn va_start(&self) -> u64 {
+ // SAFETY: The `mm_start` field is immutable.
+ unsafe { (*self.as_raw()).mm_start }
+ }
+
+ /// The length of the GPU's virtual address space.
+ #[inline]
+ pub fn va_length(&self) -> u64 {
+ // SAFETY: The `mm_range` field is immutable.
+ unsafe { (*self.as_raw()).mm_range }
+ }
+
+ /// Returns the range of the GPU virtual address space.
+ #[inline]
+ pub fn va_range(&self) -> Range<u64> {
+ let start = self.va_start();
+ // OVERFLOW: This reconstructs the Range<u64> passed to the constructor, so it won't fail.
+ let end = start + self.va_length();
+ Range { start, end }
+ }
+
+ /// Get or create the [`GpuVmBo`] for this gem object.
+ #[inline]
+ pub fn obtain(
+ &self,
+ obj: &T::Object,
+ data: impl PinInit<T::VmBoData>,
+ ) -> Result<ARef<GpuVmBo<T>>, AllocError> {
+ Ok(GpuVmBoAlloc::new(self, obj, data)?.obtain())
+ }
+
+ /// Clean up buffer objects that are no longer used.
+ #[inline]
+ pub fn deferred_cleanup(&self) {
+ // SAFETY: This GPUVM uses immediate mode.
+ unsafe { bindings::drm_gpuvm_bo_deferred_cleanup(self.as_raw()) }
+ }
+
+ /// Check if this GEM object is an external object for this GPUVM.
+ #[inline]
+ pub fn is_extobj(&self, obj: &T::Object) -> bool {
+ // SAFETY: We may call this with any GPUVM and GEM object.
+ unsafe { bindings::drm_gpuvm_is_extobj(self.as_raw(), obj.as_raw()) }
+ }
+
+ /// Free this GPUVM.
+ ///
+ /// # Safety
+ ///
+ /// Called when refcount hits zero.
+ unsafe extern "C" fn vm_free(me: *mut bindings::drm_gpuvm) {
+ // SAFETY: Caller passes a pointer to the `drm_gpuvm` in a `GpuVm<T>`.
+ let me = unsafe { kernel::container_of!(Opaque::cast_from(me), Self, vm).cast_mut() };
+ // SAFETY: By type invariants we can free it when refcount hits zero.
+ drop(unsafe { KBox::from_raw(me) })
+ }
+
+ #[inline]
+ fn raw_resv(&self) -> *mut bindings::dma_resv {
+ // SAFETY: `r_obj` is immutable and valid for duration of GPUVM.
+ unsafe { (*(*self.as_raw()).r_obj).resv }
+ }
+}
+
+/// The manager for a GPUVM.
+pub trait DriverGpuVm: Sized + Send {
+ /// Parent `Driver` for this object.
+ type Driver: drm::Driver<Object = Self::Object>;
+
+ /// The kind of GEM object stored in this GPUVM.
+ type Object: IntoGEMObject;
+
+ /// Data stored with each [`struct drm_gpuva`](struct@GpuVa).
+ type VaData;
+
+ /// Data stored with each [`struct drm_gpuvm_bo`](struct@GpuVmBo).
+ type VmBoData;
+
+ /// The private data passed to callbacks.
+ type SmContext<'ctx>;
+
+ /// Indicates that a new mapping should be created.
+ fn sm_step_map<'op, 'ctx>(
+ &mut self,
+ op: OpMap<'op, Self>,
+ context: &mut Self::SmContext<'ctx>,
+ ) -> Result<OpMapped<'op, Self>, Error>;
+
+ /// Indicates that an existing mapping should be removed.
+ fn sm_step_unmap<'op, 'ctx>(
+ &mut self,
+ op: OpUnmap<'op, Self>,
+ context: &mut Self::SmContext<'ctx>,
+ ) -> Result<OpUnmapped<'op, Self>, Error>;
+
+ /// Indicates that an existing mapping should be split up.
+ fn sm_step_remap<'op, 'ctx>(
+ &mut self,
+ op: OpRemap<'op, Self>,
+ context: &mut Self::SmContext<'ctx>,
+ ) -> Result<OpRemapped<'op, Self>, Error>;
+}
+
+/// The core of the DRM GPU VA manager.
+///
+/// This object is a unique reference to the VM that can access the interval tree and the Rust
+/// `data` field.
+///
+/// # Invariants
+///
+/// Each `GpuVm` instance has at most one `UniqueRefGpuVm` reference.
+pub struct UniqueRefGpuVm<T: DriverGpuVm>(ARef<GpuVm<T>>);
+
+// SAFETY: The GPUVM api is designed to allow &self methods to be called in parallel, and
+// concurrent access to `data` is safe due to the `T: Sync` requirement.
+unsafe impl<T: DriverGpuVm + Sync> Sync for UniqueRefGpuVm<T> {}
+
+impl<T: DriverGpuVm> UniqueRefGpuVm<T> {
+ /// Access the data owned by this `UniqueRefGpuVm` immutably.
+ #[inline]
+ pub fn data_ref(&self) -> &T {
+ // SAFETY: By the type invariants we may access `data`.
+ unsafe { &*self.0.data.get() }
+ }
+
+ /// Access the data owned by this `UniqueRefGpuVm` mutably.
+ #[inline]
+ pub fn data(&mut self) -> &mut T {
+ // SAFETY: By the type invariants we may access `data`.
+ unsafe { &mut *self.0.data.get() }
+ }
+}
+
+impl<T: DriverGpuVm> Deref for UniqueRefGpuVm<T> {
+ type Target = GpuVm<T>;
+
+ #[inline]
+ fn deref(&self) -> &GpuVm<T> {
+ &self.0
+ }
+}
diff --git a/rust/kernel/drm/gpuvm/sm_ops.rs b/rust/kernel/drm/gpuvm/sm_ops.rs
new file mode 100644
index 000000000000..69a8e5ab2821
--- /dev/null
+++ b/rust/kernel/drm/gpuvm/sm_ops.rs
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+use super::*;
+
+/// The actual data that gets threaded through the callbacks.
+struct SmData<'a, 'ctx, T: DriverGpuVm> {
+ gpuvm: &'a mut UniqueRefGpuVm<T>,
+ user_context: &'a mut T::SmContext<'ctx>,
+}
+
+/// Adds an extra field to `SmData` for `sm_map()` callbacks.
+///
+/// # Invariants
+///
+/// `self.vm_bo.gpuvm() == self.sm_data.gpuvm`.
+#[repr(C)]
+struct SmMapData<'a, 'ctx, T: DriverGpuVm> {
+ sm_data: SmData<'a, 'ctx, T>,
+ vm_bo: &'a GpuVmBo<T>,
+}
+
+/// The argument for [`UniqueRefGpuVm::sm_map`].
+pub struct OpMapRequest<'a, 'ctx, T: DriverGpuVm> {
+ /// Address in GPU virtual address space.
+ pub addr: u64,
+ /// Length of mapping to create.
+ pub range: u64,
+ /// Offset in GEM object.
+ pub gem_offset: u64,
+ /// The GEM object to map.
+ pub vm_bo: &'a GpuVmBo<T>,
+ /// The user-provided context type.
+ pub context: &'a mut T::SmContext<'ctx>,
+}
+
+impl<'a, 'ctx, T: DriverGpuVm> OpMapRequest<'a, 'ctx, T> {
+ fn raw_request(&self) -> bindings::drm_gpuvm_map_req {
+ bindings::drm_gpuvm_map_req {
+ map: bindings::drm_gpuva_op_map {
+ va: bindings::drm_gpuva_op_map__bindgen_ty_1 {
+ addr: self.addr,
+ range: self.range,
+ },
+ gem: bindings::drm_gpuva_op_map__bindgen_ty_2 {
+ offset: self.gem_offset,
+ obj: self.vm_bo.obj().as_raw(),
+ },
+ },
+ }
+ }
+}
+
+/// Represents an `sm_step_map` operation that has not yet been completed.
+pub struct OpMap<'op, T: DriverGpuVm> {
+ op: &'op bindings::drm_gpuva_op_map,
+ // Since these abstractions are designed for immediate mode, the VM BO needs to be
+ // pre-allocated, so we always have it available when we reach this point.
+ vm_bo: &'op GpuVmBo<T>,
+ // This ensures that 'op is invariant, so that `OpMap<'long, T>` does not
+ // coerce to `OpMap<'short, T>`. This ensures that the user can't return
+ // the wrong `OpMapped` value.
+ _invariant: PhantomData<*mut &'op mut T>,
+}
+
+impl<'op, T: DriverGpuVm> OpMap<'op, T> {
+ /// The base address of the new mapping.
+ pub fn addr(&self) -> u64 {
+ self.op.va.addr
+ }
+
+ /// The length of the new mapping.
+ pub fn length(&self) -> u64 {
+ self.op.va.range
+ }
+
+ /// The offset within the [`drm_gem_object`](DriverGpuVm::Object).
+ pub fn gem_offset(&self) -> u64 {
+ self.op.gem.offset
+ }
+
+ /// The [`drm_gem_object`](DriverGpuVm::Object) to map.
+ pub fn obj(&self) -> &T::Object {
+ // SAFETY: The `obj` pointer is guaranteed to be valid.
+ unsafe { <T::Object as IntoGEMObject>::from_raw(self.op.gem.obj) }
+ }
+
+ /// The [`GpuVmBo`] that the new VA will be associated with.
+ pub fn vm_bo(&self) -> &GpuVmBo<T> {
+ self.vm_bo
+ }
+
+ /// Use the pre-allocated VA to carry out this map operation.
+ pub fn insert(self, va: GpuVaAlloc<T>, va_data: impl PinInit<T::VaData>) -> OpMapped<'op, T> {
+ let va = va.prepare(va_data);
+ // SAFETY: By the type invariants we may access the interval tree.
+ unsafe { bindings::drm_gpuva_map(self.vm_bo.gpuvm().as_raw(), va, self.op) };
+
+ let _gpuva_guard = self.vm_bo().lock_gpuva();
+ // SAFETY: The va is prepared for insertion, and we hold the GEM lock.
+ unsafe { bindings::drm_gpuva_link(va, self.vm_bo.as_raw()) };
+
+ OpMapped {
+ _invariant: self._invariant,
+ }
+ }
+}
+
+/// Represents a completed [`OpMap`] operation.
+pub struct OpMapped<'op, T> {
+ _invariant: PhantomData<*mut &'op mut T>,
+}
+
+/// Represents an `sm_step_unmap` operation that has not yet been completed.
+pub struct OpUnmap<'op, T: DriverGpuVm> {
+ op: &'op bindings::drm_gpuva_op_unmap,
+ // This ensures that 'op is invariant, so that `OpUnmap<'long, T>` does not
+ // coerce to `OpUnmap<'short, T>`. This ensures that the user can't return the
+ // wrong`OpUnmapped` value.
+ _invariant: PhantomData<*mut &'op mut T>,
+}
+
+impl<'op, T: DriverGpuVm> OpUnmap<'op, T> {
+ /// Indicates whether this [`GpuVa`] is physically contiguous with the
+ /// original mapping request.
+ ///
+ /// Optionally, if `keep` is set, drivers may keep the actual page table
+ /// mappings for this `drm_gpuva`, adding the missing page table entries
+ /// only and update the `drm_gpuvm` accordingly.
+ pub fn keep(&self) -> bool {
+ self.op.keep
+ }
+
+ /// The range being unmapped.
+ pub fn va(&self) -> &GpuVa<T> {
+ // SAFETY: This is a valid va. It's not the `kernel_alloc_node` because you can't unmap it,
+ // and it's not sparse by the `GpuVm<T>` type invariants.
+ unsafe { GpuVa::<T>::from_raw(self.op.va) }
+ }
+
+ /// Remove the VA.
+ pub fn remove(self) -> (OpUnmapped<'op, T>, GpuVaRemoved<T>) {
+ // SAFETY: The op references a valid drm_gpuva in the GPUVM.
+ unsafe { bindings::drm_gpuva_unmap(self.op) };
+ // SAFETY: The va is no longer in the interval tree so we may unlink it.
+ unsafe { bindings::drm_gpuva_unlink_defer(self.op.va) };
+
+ // SAFETY: We just removed this va from the `GpuVm<T>`.
+ let va = unsafe { GpuVaRemoved::from_raw(self.op.va) };
+
+ (
+ OpUnmapped {
+ _invariant: self._invariant,
+ },
+ va,
+ )
+ }
+}
+
+/// Represents a completed [`OpUnmap`] operation.
+pub struct OpUnmapped<'op, T> {
+ _invariant: PhantomData<*mut &'op mut T>,
+}
+
+/// Represents an `sm_step_remap` operation that has not yet been completed.
+pub struct OpRemap<'op, T: DriverGpuVm> {
+ op: &'op bindings::drm_gpuva_op_remap,
+ // This ensures that 'op is invariant, so that `OpRemap<'long, T>` does not
+ // coerce to `OpRemap<'short, T>`. This ensures that the user can't return the
+ // wrong`OpRemapped` value.
+ _invariant: PhantomData<*mut &'op mut T>,
+}
+
+impl<'op, T: DriverGpuVm> OpRemap<'op, T> {
+ /// The preceding part of a split mapping.
+ #[inline]
+ pub fn prev(&self) -> Option<&OpRemapMapData> {
+ // SAFETY: We checked for null, so the pointer must be valid.
+ NonNull::new(self.op.prev).map(|ptr| unsafe { OpRemapMapData::from_raw(ptr) })
+ }
+
+ /// The subsequent part of a split mapping.
+ #[inline]
+ pub fn next(&self) -> Option<&OpRemapMapData> {
+ // SAFETY: We checked for null, so the pointer must be valid.
+ NonNull::new(self.op.next).map(|ptr| unsafe { OpRemapMapData::from_raw(ptr) })
+ }
+
+ /// Indicates whether the `drm_gpuva` being removed is physically contiguous with the original
+ /// mapping request.
+ ///
+ /// Optionally, if `keep` is set, drivers may keep the actual page table mappings for this
+ /// `drm_gpuva`, adding the missing page table entries only and update the `drm_gpuvm`
+ /// accordingly.
+ #[inline]
+ pub fn keep(&self) -> bool {
+ // SAFETY: The unmap pointer is always valid.
+ unsafe { (*self.op.unmap).keep }
+ }
+
+ /// The range being unmapped.
+ #[inline]
+ pub fn va_to_unmap(&self) -> &GpuVa<T> {
+ // SAFETY: This is a valid va. It's not the `kernel_alloc_node` because you can't unmap it,
+ // and it's not sparse by the `GpuVm<T>` type invariants.
+ unsafe { GpuVa::<T>::from_raw((*self.op.unmap).va) }
+ }
+
+ /// The [`drm_gem_object`](DriverGpuVm::Object) whose VA is being remapped.
+ #[inline]
+ pub fn obj(&self) -> &T::Object {
+ self.va_to_unmap().obj()
+ }
+
+ /// The [`GpuVmBo`] that is being remapped.
+ #[inline]
+ pub fn vm_bo(&self) -> &GpuVmBo<T> {
+ self.va_to_unmap().vm_bo()
+ }
+
+ /// Update the GPUVM to perform the remapping.
+ pub fn remap(
+ self,
+ va_alloc: [GpuVaAlloc<T>; 2],
+ prev_data: impl PinInit<T::VaData>,
+ next_data: impl PinInit<T::VaData>,
+ ) -> (OpRemapped<'op, T>, OpRemapRet<T>) {
+ let [va1, va2] = va_alloc;
+
+ let mut unused_va = None;
+ let mut prev_ptr = ptr::null_mut();
+ let mut next_ptr = ptr::null_mut();
+ if self.prev().is_some() {
+ prev_ptr = va1.prepare(prev_data);
+ } else {
+ unused_va = Some(va1);
+ }
+ if self.next().is_some() {
+ next_ptr = va2.prepare(next_data);
+ } else {
+ unused_va = Some(va2);
+ }
+
+ // SAFETY: the pointers are non-null when required
+ unsafe { bindings::drm_gpuva_remap(prev_ptr, next_ptr, self.op) };
+
+ let gpuva_guard = self.vm_bo().lock_gpuva();
+ if !prev_ptr.is_null() {
+ // SAFETY: The prev_ptr is a valid drm_gpuva prepared for insertion. The vm_bo is still
+ // valid as the not-yet-unlinked gpuva holds a refcount on the vm_bo.
+ unsafe { bindings::drm_gpuva_link(prev_ptr, self.vm_bo().as_raw()) };
+ }
+ if !next_ptr.is_null() {
+ // SAFETY: The next_ptr is a valid drm_gpuva prepared for insertion. The vm_bo is still
+ // valid as the not-yet-unlinked gpuva holds a refcount on the vm_bo.
+ unsafe { bindings::drm_gpuva_link(next_ptr, self.vm_bo().as_raw()) };
+ }
+ drop(gpuva_guard);
+
+ // SAFETY: The va is no longer in the interval tree so we may unlink it.
+ unsafe { bindings::drm_gpuva_unlink_defer((*self.op.unmap).va) };
+
+ (
+ OpRemapped {
+ _invariant: self._invariant,
+ },
+ OpRemapRet {
+ // SAFETY: We just removed this va from the `GpuVm<T>`.
+ unmapped_va: unsafe { GpuVaRemoved::from_raw((*self.op.unmap).va) },
+ unused_va,
+ },
+ )
+ }
+}
+
+/// Part of an [`OpRemap`] that represents a new mapping.
+#[repr(transparent)]
+pub struct OpRemapMapData(bindings::drm_gpuva_op_map);
+
+impl OpRemapMapData {
+ /// # Safety
+ /// Must reference a valid `drm_gpuva_op_map` for duration of `'a`.
+ unsafe fn from_raw<'a>(ptr: NonNull<bindings::drm_gpuva_op_map>) -> &'a Self {
+ // SAFETY: ok per safety requirements
+ unsafe { ptr.cast().as_ref() }
+ }
+
+ /// The base address of the new mapping.
+ pub fn addr(&self) -> u64 {
+ self.0.va.addr
+ }
+
+ /// The length of the new mapping.
+ pub fn length(&self) -> u64 {
+ self.0.va.range
+ }
+
+ /// The offset within the [`drm_gem_object`](DriverGpuVm::Object).
+ pub fn gem_offset(&self) -> u64 {
+ self.0.gem.offset
+ }
+}
+
+/// Struct containing objects removed or not used by [`OpRemap::remap`].
+pub struct OpRemapRet<T: DriverGpuVm> {
+ /// The `drm_gpuva` that was removed.
+ pub unmapped_va: GpuVaRemoved<T>,
+ /// If the remap did not split the region into two pieces, then the unused `drm_gpuva` is
+ /// returned here.
+ pub unused_va: Option<GpuVaAlloc<T>>,
+}
+
+/// Represents a completed [`OpRemap`] operation.
+pub struct OpRemapped<'op, T> {
+ _invariant: PhantomData<*mut &'op mut T>,
+}
+
+impl<T: DriverGpuVm> UniqueRefGpuVm<T> {
+ /// Create a mapping, removing or remapping anything that overlaps.
+ ///
+ /// Internally calls the [`DriverGpuVm`] callbacks similar to [`Self::sm_unmap`], except that
+ /// the [`DriverGpuVm::sm_step_map`] is called once to create the requested mapping.
+ #[inline]
+ pub fn sm_map(&mut self, req: OpMapRequest<'_, '_, T>) -> Result {
+ if req.vm_bo.gpuvm() != &**self {
+ return Err(EINVAL);
+ }
+
+ let gpuvm = self.as_raw();
+ let raw_req = req.raw_request();
+ // INVARIANT: Checked above that `vm_bo.gpuvm() == self`.
+ let mut p = SmMapData {
+ sm_data: SmData {
+ gpuvm: self,
+ user_context: req.context,
+ },
+ vm_bo: req.vm_bo,
+ };
+ // SAFETY:
+ // * raw_request() creates a valid request.
+ // * The private data is valid to be interpreted as both SmData and SmMapData since the
+ // first field of SmMapData is SmData.
+ to_result(unsafe {
+ bindings::drm_gpuvm_sm_map(gpuvm, (&raw mut p).cast(), &raw const raw_req)
+ })
+ }
+
+ /// Remove any mappings in the given region.
+ ///
+ /// Internally calls [`DriverGpuVm::sm_step_unmap`] for ranges entirely contained within the
+ /// given range, and [`DriverGpuVm::sm_step_remap`] for ranges that overlap with the range.
+ #[inline]
+ pub fn sm_unmap(&mut self, addr: u64, length: u64, context: &mut T::SmContext<'_>) -> Result {
+ let gpuvm = self.as_raw();
+ let mut p = SmData {
+ gpuvm: self,
+ user_context: context,
+ };
+ // SAFETY:
+ // * raw_request() creates a valid request.
+ // * The private data is a valid SmData.
+ to_result(unsafe { bindings::drm_gpuvm_sm_unmap(gpuvm, (&raw mut p).cast(), addr, length) })
+ }
+}
+
+impl<T: DriverGpuVm> GpuVm<T> {
+ /// # Safety
+ /// Must be called from `sm_map` with a pointer to `SmMapData`.
+ pub(super) unsafe extern "C" fn sm_step_map(
+ op: *mut bindings::drm_gpuva_op,
+ p: *mut c_void,
+ ) -> c_int {
+ // SAFETY: If we reach `sm_step_map` then we were called from `sm_map` which always passes
+ // an `SmMapData` as private data.
+ let p = unsafe { &mut *p.cast::<SmMapData<'_, '_, T>>() };
+ let op = OpMap {
+ // SAFETY: sm_step_map is called with a map operation.
+ op: unsafe { &(*op).__bindgen_anon_1.map },
+ vm_bo: p.vm_bo,
+ _invariant: PhantomData,
+ };
+ match p
+ .sm_data
+ .gpuvm
+ .data()
+ .sm_step_map(op, p.sm_data.user_context)
+ {
+ Ok(OpMapped { .. }) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ /// Must be called from `sm_map` or `sm_unmap` with a pointer to `SmMapData` or `SmData`.
+ pub(super) unsafe extern "C" fn sm_step_unmap(
+ op: *mut bindings::drm_gpuva_op,
+ p: *mut c_void,
+ ) -> c_int {
+ // SAFETY: The caller provides a pointer that can be treated as `SmData`.
+ let p = unsafe { &mut *p.cast::<SmData<'_, '_, T>>() };
+ let op = OpUnmap {
+ // SAFETY: sm_step_unmap is called with an unmap operation.
+ op: unsafe { &(*op).__bindgen_anon_1.unmap },
+ _invariant: PhantomData,
+ };
+ match p.gpuvm.data().sm_step_unmap(op, p.user_context) {
+ Ok(OpUnmapped { .. }) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+
+ /// # Safety
+ /// Must be called from `sm_map` or `sm_unmap` with a pointer to `SmMapData` or `SmData`.
+ pub(super) unsafe extern "C" fn sm_step_remap(
+ op: *mut bindings::drm_gpuva_op,
+ p: *mut c_void,
+ ) -> c_int {
+ // SAFETY: The caller provides a pointer that can be treated as `SmData`.
+ let p = unsafe { &mut *p.cast::<SmData<'_, '_, T>>() };
+ let op = OpRemap {
+ // SAFETY: sm_step_remap is called with a remap operation.
+ op: unsafe { &(*op).__bindgen_anon_1.remap },
+ _invariant: PhantomData,
+ };
+ match p.gpuvm.data().sm_step_remap(op, p.user_context) {
+ Ok(OpRemapped { .. }) => 0,
+ Err(err) => err.to_errno(),
+ }
+ }
+}
diff --git a/rust/kernel/drm/gpuvm/va.rs b/rust/kernel/drm/gpuvm/va.rs
new file mode 100644
index 000000000000..0b09fe44ab39
--- /dev/null
+++ b/rust/kernel/drm/gpuvm/va.rs
@@ -0,0 +1,168 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+use super::*;
+
+/// Represents that a range of a GEM object is mapped in this [`GpuVm`] instance.
+///
+/// Does not assume that GEM lock is held.
+///
+/// # Invariants
+///
+/// * This is a valid `drm_gpuva` object that is resident in a [`GpuVm<T>`] instance.
+/// * It is associated with a [`GpuVmBo<T>`]. Or in other words, it's not an
+/// `gpuvm->kernel_alloc_node` and `DRM_GPUVA_SPARSE` is not set.
+/// * The associated [`GpuVmBo<T>`] is part of the GEM list.
+#[repr(C)]
+#[pin_data]
+pub struct GpuVa<T: DriverGpuVm> {
+ #[pin]
+ inner: Opaque<bindings::drm_gpuva>,
+ #[pin]
+ data: T::VaData,
+}
+
+impl<T: DriverGpuVm> PartialEq for GpuVa<T> {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ core::ptr::eq(self.as_raw(), other.as_raw())
+ }
+}
+impl<T: DriverGpuVm> Eq for GpuVa<T> {}
+
+impl<T: DriverGpuVm> GpuVa<T> {
+ /// Access this [`GpuVa`] from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// * For the duration of `'a`, the pointer must reference a valid `drm_gpuva` associated with
+ /// a [`GpuVm<T>`].
+ /// * It must be associated with a [`GpuVmBo<T>`].
+ /// * The associated [`GpuVmBo<T>`] is part of the GEM list.
+ #[inline]
+ pub unsafe fn from_raw<'a>(ptr: *mut bindings::drm_gpuva) -> &'a Self {
+ // CAST: `drm_gpuva` is first field and `repr(C)`.
+ // SAFETY: The safety requirements match the invariants of `GpuVa`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns a raw pointer to underlying C value.
+ #[inline]
+ pub fn as_raw(&self) -> *mut bindings::drm_gpuva {
+ self.inner.get()
+ }
+
+ /// Returns the address of this mapping in the GPU virtual address space.
+ #[inline]
+ pub fn addr(&self) -> u64 {
+ // SAFETY: The `va.addr` field of `drm_gpuva` is immutable.
+ unsafe { (*self.as_raw()).va.addr }
+ }
+
+ /// Returns the length of this mapping.
+ #[inline]
+ pub fn length(&self) -> u64 {
+ // SAFETY: The `va.range` field of `drm_gpuva` is immutable.
+ unsafe { (*self.as_raw()).va.range }
+ }
+
+ /// Returns `addr..addr+length`.
+ #[inline]
+ pub fn range(&self) -> Range<u64> {
+ let addr = self.addr();
+ addr..addr + self.length()
+ }
+
+ /// Returns the offset within the GEM object.
+ #[inline]
+ pub fn gem_offset(&self) -> u64 {
+ // SAFETY: The `gem.offset` field of `drm_gpuva` is immutable.
+ unsafe { (*self.as_raw()).gem.offset }
+ }
+
+ /// Returns the GEM object.
+ #[inline]
+ pub fn obj(&self) -> &T::Object {
+ // SAFETY: The `gem.obj` field of `drm_gpuva` is immutable. We know that it's not null
+ // because this VA is associated with a `GpuVmBo<T>`.
+ unsafe { <T::Object as IntoGEMObject>::from_raw((*self.as_raw()).gem.obj) }
+ }
+
+ /// Returns the underlying [`GpuVmBo`] object that backs this [`GpuVa`].
+ #[inline]
+ pub fn vm_bo(&self) -> &GpuVmBo<T> {
+ // SAFETY: The `vm_bo` field of `drm_gpuva` is immutable. We know that it's not null
+ // because this VA is associated with a `GpuVmBo<T>`. The BO is in the GEM list by the type
+ // invariants.
+ unsafe { GpuVmBo::from_raw((*self.as_raw()).vm_bo) }
+ }
+}
+
+/// A pre-allocated [`GpuVa`] object.
+///
+/// # Invariants
+///
+/// The memory is zeroed.
+pub struct GpuVaAlloc<T: DriverGpuVm>(KBox<MaybeUninit<GpuVa<T>>>);
+
+impl<T: DriverGpuVm> GpuVaAlloc<T> {
+ /// Pre-allocate a [`GpuVa`] object.
+ pub fn new(flags: AllocFlags) -> Result<GpuVaAlloc<T>, AllocError> {
+ // INVARIANTS: Memory allocated with __GFP_ZERO.
+ Ok(GpuVaAlloc(KBox::new_uninit(flags | __GFP_ZERO)?))
+ }
+
+ /// Prepare this `drm_gpuva` for insertion into the GPUVM.
+ #[must_use]
+ pub(super) fn prepare(mut self, va_data: impl PinInit<T::VaData>) -> *mut bindings::drm_gpuva {
+ let va_ptr = MaybeUninit::as_mut_ptr(&mut self.0);
+ // SAFETY: The `data` field is pinned.
+ let Ok(()) = unsafe { va_data.__pinned_init(&raw mut (*va_ptr).data) };
+ KBox::into_raw(self.0).cast()
+ }
+}
+
+/// A [`GpuVa`] object that has been removed.
+///
+/// # Invariants
+///
+/// The `drm_gpuva` is not resident in the [`GpuVm`].
+pub struct GpuVaRemoved<T: DriverGpuVm>(KBox<GpuVa<T>>);
+
+impl<T: DriverGpuVm> GpuVaRemoved<T> {
+ /// Convert a raw pointer into a [`GpuVaRemoved`].
+ ///
+ /// # Safety
+ ///
+ /// * Must have been removed from a [`GpuVm<T>`].
+ /// * It must not be a `gpuvm->kernel_alloc_node` va.
+ pub(super) unsafe fn from_raw(ptr: *mut bindings::drm_gpuva) -> Self {
+ // SAFETY: Since it used to be a VA in a `GpuVm<T>` and it's not a kernel_alloc_node, this
+ // pointer references a `GpuVa<T>` with a valid `T::VaData`. Since it has been removed, we
+ // can take ownership of the allocation.
+ GpuVaRemoved(unsafe { KBox::from_raw(ptr.cast()) })
+ }
+
+ /// Take ownership of the VA data.
+ pub fn into_inner(self) -> T::VaData
+ where
+ T::VaData: Unpin,
+ {
+ KBox::into_inner(self.0).data
+ }
+}
+
+impl<T: DriverGpuVm> Deref for GpuVaRemoved<T> {
+ type Target = T::VaData;
+ fn deref(&self) -> &T::VaData {
+ &self.0.data
+ }
+}
+
+impl<T: DriverGpuVm> DerefMut for GpuVaRemoved<T>
+where
+ T::VaData: Unpin,
+{
+ fn deref_mut(&mut self) -> &mut T::VaData {
+ &mut self.0.data
+ }
+}
diff --git a/rust/kernel/drm/gpuvm/vm_bo.rs b/rust/kernel/drm/gpuvm/vm_bo.rs
new file mode 100644
index 000000000000..c064ac63897b
--- /dev/null
+++ b/rust/kernel/drm/gpuvm/vm_bo.rs
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+use super::*;
+
+/// Represents that a given GEM object has at least one mapping on this [`GpuVm`] instance.
+///
+/// Does not assume that GEM lock is held.
+///
+/// # Invariants
+///
+/// * Allocated with `kmalloc` and refcounted via `inner`.
+/// * Is present in the gem list.
+#[repr(C)]
+#[pin_data]
+pub struct GpuVmBo<T: DriverGpuVm> {
+ #[pin]
+ inner: Opaque<bindings::drm_gpuvm_bo>,
+ #[pin]
+ data: T::VmBoData,
+}
+
+// SAFETY: By type invariants, the allocation is managed by the refcount in `self.inner`.
+unsafe impl<T: DriverGpuVm> AlwaysRefCounted for GpuVmBo<T> {
+ fn inc_ref(&self) {
+ // SAFETY: By type invariants, the allocation is managed by the refcount in `self.inner`.
+ unsafe { bindings::drm_gpuvm_bo_get(self.inner.get()) };
+ }
+
+ unsafe fn dec_ref(obj: NonNull<Self>) {
+ // CAST: `drm_gpuvm_bo` is first field of repr(C) struct.
+ // SAFETY: By type invariants, the allocation is managed by the refcount in `self.inner`.
+ // This GPUVM instance uses immediate mode, so we may put the refcount using the deferred
+ // mechanism.
+ unsafe { bindings::drm_gpuvm_bo_put_deferred(obj.as_ptr().cast()) };
+ }
+}
+
+impl<T: DriverGpuVm> PartialEq for GpuVmBo<T> {
+ #[inline]
+ fn eq(&self, other: &Self) -> bool {
+ core::ptr::eq(self.as_raw(), other.as_raw())
+ }
+}
+impl<T: DriverGpuVm> Eq for GpuVmBo<T> {}
+
+impl<T: DriverGpuVm> GpuVmBo<T> {
+ /// The function pointer for allocating a GpuVmBo stored in the gpuvm vtable.
+ ///
+ /// Allocation is always implemented according to [`Self::vm_bo_alloc`], but it is set to
+ /// `None` if the default gpuvm behavior is the same as `vm_bo_alloc`.
+ ///
+ /// This may be `Some` even if `FREE_FN` is `None`, or vice-versa.
+ pub(super) const ALLOC_FN: Option<unsafe extern "C" fn() -> *mut bindings::drm_gpuvm_bo> = {
+ use core::alloc::Layout;
+ let base = Layout::new::<bindings::drm_gpuvm_bo>();
+ let rust = Layout::new::<Self>();
+ assert!(base.size() <= rust.size());
+ if base.size() != rust.size() || base.align() != rust.align() {
+ Some(Self::vm_bo_alloc)
+ } else {
+ // This causes GPUVM to allocate a `GpuVmBo<T>` with `kzalloc(sizeof(drm_gpuvm_bo))`.
+ None
+ }
+ };
+
+ /// The function pointer for freeing a GpuVmBo stored in the gpuvm vtable.
+ ///
+ /// Freeing is always implemented according to [`Self::vm_bo_free`], but it is set to `None` if
+ /// the default gpuvm behavior is the same as `vm_bo_free`.
+ ///
+ /// This may be `Some` even if `ALLOC_FN` is `None`, or vice-versa.
+ pub(super) const FREE_FN: Option<unsafe extern "C" fn(*mut bindings::drm_gpuvm_bo)> = {
+ if core::mem::needs_drop::<Self>() {
+ Some(Self::vm_bo_free)
+ } else {
+ // This causes GPUVM to free a `GpuVmBo<T>` with `kfree`.
+ None
+ }
+ };
+
+ /// Custom function for allocating a `drm_gpuvm_bo`.
+ ///
+ /// # Safety
+ ///
+ /// Always safe to call.
+ unsafe extern "C" fn vm_bo_alloc() -> *mut bindings::drm_gpuvm_bo {
+ let raw_ptr = KBox::<Self>::new_uninit(GFP_KERNEL | __GFP_ZERO)
+ .map(KBox::into_raw)
+ .unwrap_or(ptr::null_mut());
+
+ // CAST: `drm_gpuvm_bo` is first field of `Self`.
+ raw_ptr.cast()
+ }
+
+ /// Custom function for freeing a `drm_gpuvm_bo`.
+ ///
+ /// # Safety
+ ///
+ /// The pointer must have been allocated with [`GpuVmBo::ALLOC_FN`], and must not be used after
+ /// this call.
+ unsafe extern "C" fn vm_bo_free(ptr: *mut bindings::drm_gpuvm_bo) {
+ // CAST: `drm_gpuvm_bo` is first field of `Self`.
+ // SAFETY:
+ // * The ptr was allocated from kmalloc with the layout of `GpuVmBo<T>`.
+ // * `ptr->inner` has no destructor.
+ // * `ptr->data` contains a valid `T::VmBoData` that we can drop.
+ drop(unsafe { KBox::<Self>::from_raw(ptr.cast()) });
+ }
+
+ /// Access this [`GpuVmBo`] from a raw pointer.
+ ///
+ /// # Safety
+ ///
+ /// For the duration of `'a`, the pointer must reference a valid `drm_gpuvm_bo` associated with
+ /// a [`GpuVm<T>`]. The BO must also be present in the GEM list.
+ #[inline]
+ pub(crate) unsafe fn from_raw<'a>(ptr: *mut bindings::drm_gpuvm_bo) -> &'a Self {
+ // SAFETY: `drm_gpuvm_bo` is first field and `repr(C)`.
+ unsafe { &*ptr.cast() }
+ }
+
+ /// Returns a raw pointer to underlying C value.
+ #[inline]
+ pub fn as_raw(&self) -> *mut bindings::drm_gpuvm_bo {
+ self.inner.get()
+ }
+
+ /// The [`GpuVm`] that this GEM object is mapped in.
+ #[inline]
+ pub fn gpuvm(&self) -> &GpuVm<T> {
+ // SAFETY: The `obj` pointer is guaranteed to be valid.
+ unsafe { GpuVm::<T>::from_raw((*self.inner.get()).vm) }
+ }
+
+ /// The [`drm_gem_object`](DriverGpuVm::Object) for these mappings.
+ #[inline]
+ pub fn obj(&self) -> &T::Object {
+ // SAFETY: The `obj` pointer is guaranteed to be valid.
+ unsafe { <T::Object as IntoGEMObject>::from_raw((*self.inner.get()).obj) }
+ }
+
+ /// The driver data with this buffer object.
+ #[inline]
+ pub fn data(&self) -> &T::VmBoData {
+ &self.data
+ }
+
+ pub(super) fn lock_gpuva(&self) -> crate::sync::MutexGuard<'_, ()> {
+ // SAFETY: The GEM object is valid.
+ let ptr = unsafe { &raw mut (*self.obj().as_raw()).gpuva.lock };
+ // SAFETY: The GEM object is valid, so the mutex is properly initialized.
+ let mutex = unsafe { crate::sync::Mutex::from_raw(ptr) };
+ mutex.lock()
+ }
+}
+
+/// A pre-allocated [`GpuVmBo`] object.
+///
+/// # Invariants
+///
+/// Points at a `drm_gpuvm_bo` that contains a valid `T::VmBoData`, has a refcount of one, and is
+/// absent from any gem, extobj, or evict lists.
+pub(super) struct GpuVmBoAlloc<T: DriverGpuVm>(NonNull<GpuVmBo<T>>);
+
+impl<T: DriverGpuVm> GpuVmBoAlloc<T> {
+ /// Create a new pre-allocated [`GpuVmBo`].
+ ///
+ /// It's intentional that the initializer is infallible because `drm_gpuvm_bo_put` will call
+ /// drop on the data, so we don't have a way to free it when the data is missing.
+ #[inline]
+ pub(super) fn new(
+ gpuvm: &GpuVm<T>,
+ gem: &T::Object,
+ value: impl PinInit<T::VmBoData>,
+ ) -> Result<GpuVmBoAlloc<T>, AllocError> {
+ // CAST: `GpuVmBoAlloc::vm_bo_alloc` ensures that this memory was allocated with the layout
+ // of `GpuVmBo<T>`. The type is repr(C), so `container_of` is not required.
+ // SAFETY: The provided gpuvm and gem ptrs are valid for the duration of this call.
+ let raw_ptr = unsafe {
+ bindings::drm_gpuvm_bo_create(gpuvm.as_raw(), gem.as_raw()).cast::<GpuVmBo<T>>()
+ };
+ let ptr = NonNull::new(raw_ptr).ok_or(AllocError)?;
+ // SAFETY: `ptr->data` is a valid pinned location.
+ let Ok(()) = unsafe { value.__pinned_init(&raw mut (*raw_ptr).data) };
+ // INVARIANTS: We just created the vm_bo so it's absent from lists, and the data is valid
+ // as we just initialized it.
+ Ok(GpuVmBoAlloc(ptr))
+ }
+
+ /// Returns a raw pointer to underlying C value.
+ #[inline]
+ pub(super) fn as_raw(&self) -> *mut bindings::drm_gpuvm_bo {
+ // SAFETY: The pointer references a valid `drm_gpuvm_bo`.
+ unsafe { (*self.0.as_ptr()).inner.get() }
+ }
+
+ /// Look up whether there is an existing [`GpuVmBo`] for this gem object.
+ ///
+ /// The caller should not hold the GEM mutex or DMA resv lock.
+ #[inline]
+ pub(super) fn obtain(self) -> ARef<GpuVmBo<T>> {
+ let me = ManuallyDrop::new(self);
+ // SAFETY: Valid `drm_gpuvm_bo` not already in the lists. We do not access `me` after this
+ // call.
+ let ptr = unsafe { bindings::drm_gpuvm_bo_obtain_prealloc(me.as_raw()) };
+
+ // SAFETY: `drm_gpuvm_bo_obtain_prealloc` always returns a non-null ptr
+ let nonnull = unsafe { NonNull::new_unchecked(ptr.cast()) };
+
+ // INVARIANTS: `drm_gpuvm_bo_obtain_prealloc` ensures that the bo is in the GEM list.
+ // SAFETY: We received one refcount from `drm_gpuvm_bo_obtain_prealloc`.
+ let ret = unsafe { ARef::<GpuVmBo<T>>::from_raw(nonnull) };
+
+ // Ensure that external objects are in the extobj list.
+ //
+ // Note that we must call `extobj_add` even if `ptr != me` to avoid a race condition where
+ // we could end up using the extobj before the thread with `ptr == me` calls extobj_add.
+ if ret.gpuvm().is_extobj(ret.obj()) {
+ let resv_lock = ret.gpuvm().raw_resv();
+ // TODO: Use a proper lock guard here once a dma_resv lock abstraction exists.
+ // SAFETY: The GPUVM is still alive, so its resv lock is too.
+ unsafe { bindings::dma_resv_lock(resv_lock, ptr::null_mut()) };
+ // SAFETY: We hold the GPUVMs resv lock.
+ unsafe { bindings::drm_gpuvm_bo_extobj_add(ptr) };
+ // SAFETY: We took the lock, so we can unlock it.
+ unsafe { bindings::dma_resv_unlock(resv_lock) };
+ }
+
+ ret
+ }
+}
+
+impl<T: DriverGpuVm> Deref for GpuVmBoAlloc<T> {
+ type Target = GpuVmBo<T>;
+ #[inline]
+ fn deref(&self) -> &GpuVmBo<T> {
+ // SAFETY: By the type invariants we may deref while `Self` exists.
+ unsafe { self.0.as_ref() }
+ }
+}
+
+impl<T: DriverGpuVm> Drop for GpuVmBoAlloc<T> {
+ #[inline]
+ fn drop(&mut self) {
+ // TODO: Call drm_gpuvm_bo_destroy_not_in_lists() directly.
+ // SAFETY: It's safe to perform a deferred put in any context.
+ unsafe { bindings::drm_gpuvm_bo_put_deferred(self.as_raw()) };
+ }
+}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index 1b82b6945edf..a66e7166f66b 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -6,9 +6,14 @@ pub mod device;
pub mod driver;
pub mod file;
pub mod gem;
+pub mod gpuvm;
pub mod ioctl;
pub use self::device::Device;
+pub use self::device::DeviceContext;
+pub use self::device::Registered;
+pub use self::device::Uninit;
+pub use self::device::UnregisteredDevice;
pub use self::driver::Driver;
pub use self::driver::DriverInfo;
pub use self::driver::Registration;