diff mbox series

[2/2] rust: drm: Add GPUVM abstraction

Message ID 20250324-gpuvm-v1-2-7f8213eebb56@collabora.com (mailing list archive)
State New, archived
Headers show
Series Add a Rust GPUVM abstraction | expand

Commit Message

Daniel Almeida March 24, 2025, 3:13 p.m. UTC
From: Asahi Lina <lina@asahilina.net>

Add a GPUVM abstraction to be used by Rust GPU drivers.

GPUVM keeps track of a GPU's virtual address (VA) space and manages the
corresponding virtual mappings represented by "GPU VA" objects. It also
keeps track of the mapping's backing GEM buffers.

This initial version only support synchronous operations. In other words,
we do not support the use case where the operations are pre-built and
handed over to the driver.

We also do not support using a driver-specific lock in order to serialize
the gpuva list for a given GEM object. This has to be the GEM object resv,
which is the default behavior in C.

Similarly to the C code, locking is left to the driver. This means that the
driver should make sure that the VM's interval tree is protected against
concurrent access. In Rust, we encode this requirement by requiring a
generic Guard type in GpuVm::lock(), which is a similar approach as the one
taken by CondVar.

It is up to drivers to make sure that the guard indeed provides the
required locking. Any operations that modifies the interval tree is only
available after the VM has been locked as per above.

Signed-off-by: Asahi Lina <lina@asahilina.net>
Co-developed-by: Daniel Almeida <daniel.almeida@collabora.com>
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/drm_gpuvm.c        |  29 ++
 rust/helpers/helpers.c          |   1 +
 rust/kernel/drm/gpuvm.rs        | 790 ++++++++++++++++++++++++++++++++++++++++
 rust/kernel/drm/mod.rs          |   2 +
 5 files changed, 823 insertions(+)

Comments

Miguel Ojeda March 24, 2025, 5:36 p.m. UTC | #1
Hi Daniel,

A few quick notes for future versions on style/docs to try to keep
things consistent upstream -- not an actual review.

On Mon, Mar 24, 2025 at 4:14 PM Daniel Almeida
<daniel.almeida@collabora.com> wrote:
>
> +#[allow(type_alias_bounds)]

The documentation says this is highly discouraged -- it may be good to
mention why it is OK in this instance in a comment or similar.

Also, could this be `expect`? (e.g. if it triggers in all compiler
versions we support)

> +// A convenience type for the driver's GEM object.

Should this be a `///` comment, i.e. docs?

> +/// Trait that must be implemented by DRM drivers to represent a DRM GpuVm (a GPU address space).

(Throughout the file) Markdown in documentation, e.g. `GpuVm`.

(Throughout the file) Intra-doc links where they work, e.g. [`GpuVm`]
(assuming it works this one).

> +        // - Ok(()) is always returned.

(Throughout the file) Markdown in normal comments too.

> +/// A transparent wrapper over `drm_gpuva_op_map`.

(Throughout the file) A link to C definitions is always nice if there
is a good one, e.g.

    [`drm_gpuva_op_map`]:
https://docs.kernel.org/gpu/drm-mm.html#c.drm_gpuva_op_map

Ideally we will eventually have a better way to link these
automatically, but for the time being, this helps (and later we can do
a replace easier).

> +/// `None`.
> +
> +/// Note: the reason for a dedicated remap operation, rather than arbitrary

Missing `///` (?).

> +#[repr(C)]
> +#[pin_data]
> +/// A GPU VA range.
> +///
> +/// Drivers can use `inner` to store additional data.

(Throughout the file) We typically place attributes go below the
documentation -- or is there a reason to do it like this?

We had cases with e.g. Clippy bugs regarding safety comments that
could be workarounded with "attribute movement", but it does not seem
to be the case here.

> +        if p.is_null() {
> +            Err(ENOMEM)

For error cases, we typically try to do early returns instead.

> +    /// Iterates the given range of the GPU VA space. It utilizes
> +    /// [`DriverGpuVm`] to call back into the driver providing the split and
> +    /// merge steps.

This title (and the next one) may be a bit too long (or not -- please
check in the rendered docs), i.e. the first paragraph is the "title",
which is used differently in the rendered docs. If there is a way to
have a shorter title that still differentiates between the two
methods, that would be nice.

> +    /// # Arguments
> +    ///
> +    /// - `ctx`: A driver-specific context.
> +    /// - `req_obj`: The GEM object to map.
> +    /// - `req_addr`: The start address of the new mapping.
> +    /// - `req_range`: The range of the mapping.
> +    /// - `req_offset`: The offset into the GEM object.

Normally we try to avoid this kind of sections and instead reference
the arguments from the text (e.g. "...the range of the mapping
(`req_range`)...") -- but if there is no good way to do it, then it is
OK.

> +// SAFETY: All our trait methods take locks

(Throughout the file) Period at the end.

Thanks!

Cheers,
Miguel
Charalampos Mitrodimas March 24, 2025, 6:29 p.m. UTC | #2
Daniel Almeida <daniel.almeida@collabora.com> writes:

> +/// The object returned after a call to [`GpuVm::lock`].
> +///
> +/// This object has access to operations that modify the VM's interval tree.
> +pub struct LockedGpuVm<'a, T: DriverGpuVm> {
> +    gpuvm: &'a GpuVm<T>,
> +}
> +
> +impl<T: DriverGpuVm> LockedGpuVm<'_, T> {
> +    /// Finds the [`GpuVmBo`] object that connects `obj` to this VM.
> +    ///
> +    /// If found, increases the reference count of the GpuVmBo object
> +    /// accordingly.
> +    pub fn find_bo(&mut self, obj: &DriverObject<T>) -> Option<ARef<GpuVmBo<T>>> {
> +        // SAFETY: LockedGpuVm implies the right locks are held.
> +        let p = unsafe {
> +            bindings::drm_gpuvm_bo_find(
> +                self.gpuvm.gpuvm() as *mut _,
> +                obj.gem_obj() as *const _ as *mut _,
> +            )
> +        };
> +        if p.is_null() {
> +            None
> +        } else {
> +            // SAFETY: All the drm_gpuvm_bo objects in this GpuVm are always allocated by us as GpuVmBo<T>.
> +            let p = unsafe { crate::container_of!(p, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
> +            // SAFETY: We checked for NULL above, and the types ensure that
> +            // this object was created by vm_bo_alloc_callback<T>.
> +            Some(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) })
> +        }

Hi Daniel,

This is mostly eye candy—maybe we can simplify it to just:
    if p.is_null() {
       return None;
    }

    // SAFETY: All the drm_gpuvm_bo objects in this GpuVm are always allocated by us as GpuVmBo<T>.
    let p = unsafe { crate::container_of!(p, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
    // SAFETY: We checked for NULL above, and the types ensure that
    // this object was created by vm_bo_alloc_callback<T>.
    Some(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) })


Same with `fn obtain_bo`?

--
C. Mitrodimas

> +    }
> +
> +    /// Obtains the [`GpuVmBo`] object that connects `obj` to this VM.
> +    ///
> +    /// This connection is unique, so an instane of [`GpuVmBo`] will be
> +    /// allocated for `obj` once, and that instance will be returned from that
> +    /// point forward.
> +    pub fn obtain_bo(&mut self, obj: &DriverObject<T>) -> Result<ARef<GpuVmBo<T>>> {
> +        // SAFETY: LockedGpuVm implies the right locks are held.
> +        let p = unsafe {
> +            bindings::drm_gpuvm_bo_obtain(
> +                self.gpuvm.gpuvm() as *mut _,
> +                obj.gem_obj() as *const _ as *mut _,
> +            )
> +        };
> +        if p.is_null() {
> +            Err(ENOMEM)
> +        } else {
> +            // SAFETY: Container invariant is guaranteed for GpuVmBo objects for this GpuVm.
> +            let p = unsafe { crate::container_of!(p, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
> +            // SAFETY: We checked for NULL above, and the types ensure that
> +            // this object was created by vm_bo_alloc_callback<T>.
> +            Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) })
> +        }
> +    }
Daniel Almeida March 24, 2025, 7:25 p.m. UTC | #3
Hi Miguel, thanks for having a look at this:

> On 24 Mar 2025, at 14:36, Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> wrote:
> 
> Hi Daniel,
> 
> A few quick notes for future versions on style/docs to try to keep
> things consistent upstream -- not an actual review.
> 
> On Mon, Mar 24, 2025 at 4:14 PM Daniel Almeida
> <daniel.almeida@collabora.com> wrote:
>> 
>> +#[allow(type_alias_bounds)]
> 
> The documentation says this is highly discouraged -- it may be good to
> mention why it is OK in this instance in a comment or similar.

Someone correct me here, but I see no issue with this warning. That’s
because we need the bound to make `<T::Driver as drv::Driver>` work in the
first place. Otherwise, we’d get a compiler error saying that there’s
no `Driver` associated type (assuming the case where a random T gets
passed in)

So, for this to be a problem, we would need to mix this up with something that
also has a `Driver` associated type, and this associated type would also need a
drv::Driver bound.

In other words, we would need a lot of things to align for this to actually
have a chance of being misused. When you consider that this is then only used
in a few places, the balance tips heavily in favor of the convenience of having
the type alias IMHO.

In fact, the docs point to the exact thing I am trying to do, i.e.:

>  these bounds may have secondary effects such as enabling the use of “shorthand” associated type paths

>  I.e., paths of the form T::Assoc where T is a type parameter bounded by trait Trait which defines an associated type called Assoc as opposed to a fully qualified path of the form <T as Trait>::Assoc. 

> 
> Also, could this be `expect`? (e.g. if it triggers in all compiler
> versions we support)
> 
>> +// A convenience type for the driver's GEM object.
> 
> Should this be a `///` comment, i.e. docs?
> 
>> +/// Trait that must be implemented by DRM drivers to represent a DRM GpuVm (a GPU address space).
> 
> (Throughout the file) Markdown in documentation, e.g. `GpuVm`.

By the way, maybe we should have a lint for CamelCase in docs? I tried my best to
cover all of these, but some slip through :/

i.e.: if you write something in CamelCase somewhere in the docs, there's a high
chance that you should actually use Markdown and link as appropriate.

I have no idea whether this would actually work in practice, to be honest. It’s just
a random suggestion (that I'd be willing to help with).


> 
> (Throughout the file) Intra-doc links where they work, e.g. [`GpuVm`]
> (assuming it works this one).
> 
>> +        // - Ok(()) is always returned.
> 
> (Throughout the file) Markdown in normal comments too.
> 
>> +/// A transparent wrapper over `drm_gpuva_op_map`.
> 
> (Throughout the file) A link to C definitions is always nice if there
> is a good one, e.g.
> 
>    [`drm_gpuva_op_map`]:
> https://docs.kernel.org/gpu/drm-mm.html#c.drm_gpuva_op_map
> 
> Ideally we will eventually have a better way to link these
> automatically, but for the time being, this helps (and later we can do
> a replace easier).
> 
>> +/// `None`.
>> +
>> +/// Note: the reason for a dedicated remap operation, rather than arbitrary
> 
> Missing `///` (?).
> 
>> +#[repr(C)]
>> +#[pin_data]
>> +/// A GPU VA range.
>> +///
>> +/// Drivers can use `inner` to store additional data.
> 
> (Throughout the file) We typically place attributes go below the
> documentation -- or is there a reason to do it like this?

I will be honest with you here: I never remember the right order for docs and attributes.

I’ll fix this.

> 
> We had cases with e.g. Clippy bugs regarding safety comments that
> could be workarounded with "attribute movement", but it does not seem
> to be the case here.
> 
>> +        if p.is_null() {
>> +            Err(ENOMEM)
> 
> For error cases, we typically try to do early returns instead.
> 
>> +    /// Iterates the given range of the GPU VA space. It utilizes
>> +    /// [`DriverGpuVm`] to call back into the driver providing the split and
>> +    /// merge steps.
> 
> This title (and the next one) may be a bit too long (or not -- please
> check in the rendered docs), i.e. the first paragraph is the "title",
> which is used differently in the rendered docs. If there is a way to
> have a shorter title that still differentiates between the two
> methods, that would be nice.
> 
>> +    /// # Arguments
>> +    ///
>> +    /// - `ctx`: A driver-specific context.
>> +    /// - `req_obj`: The GEM object to map.
>> +    /// - `req_addr`: The start address of the new mapping.
>> +    /// - `req_range`: The range of the mapping.
>> +    /// - `req_offset`: The offset into the GEM object.
> 
> Normally we try to avoid this kind of sections and instead reference
> the arguments from the text (e.g. "...the range of the mapping
> (`req_range`)...") -- but if there is no good way to do it, then it is
> OK.

Ack.

> 
>> +// SAFETY: All our trait methods take locks
> 
> (Throughout the file) Period at the end.
> 
> Thanks!
> 
> Cheers,
> Miguel

— Daniel
Miguel Ojeda March 24, 2025, 7:38 p.m. UTC | #4
On Mon, Mar 24, 2025 at 8:25 PM Daniel Almeida
<daniel.almeida@collabora.com> wrote:
>
> By the way, maybe we should have a lint for CamelCase in docs? I tried my best to
> cover all of these, but some slip through :/
>
> i.e.: if you write something in CamelCase somewhere in the docs, there's a high
> chance that you should actually use Markdown and link as appropriate.
>
> I have no idea whether this would actually work in practice, to be honest. It’s just
> a random suggestion (that I'd be willing to help with).

Yeah, I asked upstream if we could have something that at least
detects what could have been intra-doc links, because that should not
have too many false positives since it is a subset:

    https://github.com/rust-lang/rust/issues/131510

It is still tricky, because e.g. `Some` or `None` would need to be
excluded, even if you require the exact same case. So it probably
still requires extra heuristics.

Then there is:

    https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown

which is closer to your idea. It has a few false positives (e.g.
KUnit) from a quick try, so probably we cannot enable it for
everything, unless we commit to maintain a list of terms in the
config. It spots a handful of things we should fix, though -- I can
send a few patches or create good first issues.

Cheers,
Miguel
Benno Lossin March 24, 2025, 8:22 p.m. UTC | #5
On Mon Mar 24, 2025 at 8:25 PM CET, Daniel Almeida wrote:
>> On 24 Mar 2025, at 14:36, Miguel Ojeda <miguel.ojeda.sandonis@gmail.com> wrote:
>> 
>> Hi Daniel,
>> 
>> A few quick notes for future versions on style/docs to try to keep
>> things consistent upstream -- not an actual review.
>> 
>> On Mon, Mar 24, 2025 at 4:14 PM Daniel Almeida
>> <daniel.almeida@collabora.com> wrote:
>>> 
>>> +#[allow(type_alias_bounds)]
>> 
>> The documentation says this is highly discouraged -- it may be good to
>> mention why it is OK in this instance in a comment or similar.
>
> Someone correct me here, but I see no issue with this warning. That’s
> because we need the bound to make `<T::Driver as drv::Driver>` work in the
> first place. Otherwise, we’d get a compiler error saying that there’s
> no `Driver` associated type (assuming the case where a random T gets
> passed in)
>
> So, for this to be a problem, we would need to mix this up with something that
> also has a `Driver` associated type, and this associated type would also need a
> drv::Driver bound.
>
> In other words, we would need a lot of things to align for this to actually
> have a chance of being misused. When you consider that this is then only used
> in a few places, the balance tips heavily in favor of the convenience of having
> the type alias IMHO.
>
> In fact, the docs point to the exact thing I am trying to do, i.e.:
>
>>  these bounds may have secondary effects such as enabling the use of “shorthand” associated type paths
>
>>  I.e., paths of the form T::Assoc where T is a type parameter bounded by trait Trait which defines an associated type called Assoc as opposed to a fully qualified path of the form <T as Trait>::Assoc. 

You can avoid the allow by using:

    type DriverObject<T> = <<T as DriverGpuVm>::Driver as drv::Driver>::Object;

That is more wordy, but avoids the allow (it still errors when you put
in something that doesn't implement `DriverGpuVm`).

---
Cheers,
Benno
Miguel Ojeda March 24, 2025, 9:07 p.m. UTC | #6
On Mon, Mar 24, 2025 at 8:38 PM Miguel Ojeda
<miguel.ojeda.sandonis@gmail.com> wrote:
>
> Yeah, I asked upstream if we could have something that at least
> detects what could have been intra-doc links, because that should not
> have too many false positives since it is a subset:
>
>     https://github.com/rust-lang/rust/issues/131510
>
> It is still tricky, because e.g. `Some` or `None` would need to be
> excluded, even if you require the exact same case. So it probably
> still requires extra heuristics.
>
> Then there is:
>
>     https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown
>
> which is closer to your idea. It has a few false positives (e.g.
> KUnit) from a quick try, so probably we cannot enable it for
> everything, unless we commit to maintain a list of terms in the
> config. It spots a handful of things we should fix, though -- I can
> send a few patches or create good first issues.

I went ahead and sent a quick series for the latter:

    https://lore.kernel.org/rust-for-linux/20250324210359.1199574-1-ojeda@kernel.org/

Cheers,
Miguel
diff mbox series

Patch

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 53111b5b35588541eca05e574a8d24cdafbf1dd6..0152f87bfeaccfca4fd5d58c45fe4d9f81f05d7b 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -10,6 +10,7 @@ 
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
+#include <drm/drm_gpuvm.h>
 #include <drm/drm_ioctl.h>
 #include <kunit/test.h>
 #include <linux/auxiliary_bus.h>
diff --git a/rust/helpers/drm_gpuvm.c b/rust/helpers/drm_gpuvm.c
new file mode 100644
index 0000000000000000000000000000000000000000..7a074d2c2160ebc5f92909236c5aaecb1853e45d
--- /dev/null
+++ b/rust/helpers/drm_gpuvm.c
@@ -0,0 +1,29 @@ 
+// SPDX-License-Identifier: GPL-2.0 or MIT
+
+#include <drm/drm_gpuvm.h>
+
+#ifdef CONFIG_DRM
+#ifdef CONFIG_DRM_GPUVM
+
+struct drm_gpuvm *rust_helper_drm_gpuvm_get(struct drm_gpuvm *obj)
+{
+	return drm_gpuvm_get(obj);
+}
+
+void rust_helper_drm_gpuva_init_from_op(struct drm_gpuva *va, struct drm_gpuva_op_map *op)
+{
+	drm_gpuva_init_from_op(va, op);
+}
+
+struct drm_gpuvm_bo *rust_helper_drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
+{
+	return drm_gpuvm_bo_get(vm_bo);
+}
+
+bool rust_helper_drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj)
+{
+	return drm_gpuvm_is_extobj(gpuvm, obj);
+}
+
+#endif /* CONFIG_DRM_GPUVM */
+#endif /* CONFIG_DRM */
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index c5e536d688bc35c7b348daa61e868c91a7bdbd23..a013cc91020ae5094a04a2e4b93993cf0b149885 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -16,6 +16,7 @@ 
 #include "device.c"
 #include "dma-resv.c"
 #include "drm.c"
+#include "drm_gpuvm.c"
 #include "err.c"
 #include "fs.c"
 #include "io.c"
diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs
new file mode 100644
index 0000000000000000000000000000000000000000..98553aaea650c800d40bb483dbcf35c67e696114
--- /dev/null
+++ b/rust/kernel/drm/gpuvm.rs
@@ -0,0 +1,790 @@ 
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! GPUVM abstractions.
+//!
+//! Only synchronous operations are supported, and the GEM's `dma_resv` is used
+//! as the GEM's gpuva lock.
+//!
+//! C header: [`include/drm/drm_gpuvm.h`](srctree/include/drm/drm_gpuvm.h)
+
+use core::cell::UnsafeCell;
+use core::marker::{PhantomData, PhantomPinned};
+use core::ops::{Deref, DerefMut, Range};
+use core::ptr::NonNull;
+
+use crate::bindings;
+use crate::drm::device;
+use crate::drm::drv;
+use crate::drm::gem::IntoGEMObject;
+use crate::error::code::ENOMEM;
+use crate::error::from_result;
+use crate::error::to_result;
+use crate::error::Result;
+use crate::init;
+use crate::init::pin_init_from_closure;
+use crate::prelude::*;
+use crate::sync::lock::Backend;
+use crate::sync::lock::Guard;
+use crate::types::ARef;
+use crate::types::AlwaysRefCounted;
+use crate::types::Opaque;
+
+// SAFETY: This type is safe to zero-initialize.
+unsafe impl init::Zeroable for bindings::drm_gpuvm_bo {}
+
+#[allow(type_alias_bounds)]
+// A convenience type for the driver's GEM object.
+type DriverObject<T: DriverGpuVm> = <T::Driver as drv::Driver>::Object;
+
+/// Trait that must be implemented by DRM drivers to represent a DRM GpuVm (a GPU address space).
+pub trait DriverGpuVm: Sized {
+    /// The parent `Driver` implementation for this `DriverGpuVm`.
+    type Driver: drv::Driver;
+
+    /// The driver-specific GpuVa type.
+    type GpuVa: DriverGpuVa;
+
+    /// The driver-specific GpuVmBo type.
+    type GpuVmBo: DriverGpuVmBo;
+
+    /// The driver-specific context that is kept through the map, unmap and
+    /// remap steps.
+    type StepContext;
+
+    /// Implements the map step for the driver.
+    fn step_map(
+        self: &mut UpdatingGpuVm<'_, Self>,
+        op: &mut OpMap<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result;
+
+    /// Implements the unmap step for the driver.
+    fn step_unmap(
+        self: &mut UpdatingGpuVm<'_, Self>,
+        op: &mut OpUnMap<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result;
+
+    /// Implements the remap step for the driver.
+    fn step_remap(
+        self: &mut UpdatingGpuVm<'_, Self>,
+        op: &mut OpReMap<Self>,
+        vm_bo: &GpuVmBo<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result;
+}
+
+/// The driver-specific context that is passed through the map, unmap and remap
+/// steps.
+struct StepContext<'a, T: DriverGpuVm> {
+    gpuvm: &'a GpuVm<T>,
+    ctx: &'a mut T::StepContext,
+}
+
+/// Trait that must be implemented by DRM drivers to represent a DRM GpuVa (a mapping in GPU address space).
+pub trait DriverGpuVa: Sized {}
+
+/// Trait that must be implemented by DRM drivers to represent a DRM GpuVmBo (a connection between a BO and a VM).
+pub trait DriverGpuVmBo: Sized {
+    /// Initializes the GpuVmBo object.
+    fn new() -> impl PinInit<Self>;
+}
+
+/// Provide a default implementation for trivial types
+impl<T: Default> DriverGpuVmBo for T {
+    fn new() -> impl PinInit<Self> {
+        // SAFETY:
+        // - Ok(()) is always returned.
+        // - The slot is never moved.
+        unsafe {
+            pin_init_from_closure(|slot| {
+                *slot = Self::default();
+                Ok(())
+            })
+        }
+    }
+}
+
+#[repr(transparent)]
+/// A transparent wrapper over `drm_gpuva_op_map`.
+///
+/// This encodes the map operation to be carried out by the driver.
+pub struct OpMap<T: DriverGpuVm>(bindings::drm_gpuva_op_map, PhantomData<T>);
+
+#[repr(transparent)]
+/// Represents a single remap operation generated by [`GpuVm`].
+///
+/// A remap operation is generated when an existing GPU VA mapping is split by
+/// inserting a new one or by partially unmapping existing mappings. Hence, it
+/// consists of a maximum of two maps and one unmap operation,
+///
+/// The unmap operation takes care of removing the original existing mapping.
+/// The `prev` field is used to remap the preceding part and `next` is used to
+/// remap the subsequent part.
+///
+/// If the start address of the new mapping aligns with the start address of the
+/// old mapping, `prev` will be `None`. Similarly, if the end address of the new
+/// mapping aligns with the end address of the old mapping, `next` will be
+/// `None`.
+
+/// Note: the reason for a dedicated remap operation, rather than arbitrary
+/// unmap and map operations, is to give drivers the chance of extracting driver
+/// specific data for creating the new mappings from the unmap operations's
+/// [`GpuVa`] structure which typically is embedded in larger driver specific
+/// structures.
+pub struct OpReMap<T: DriverGpuVm>(bindings::drm_gpuva_op_remap, PhantomData<T>);
+
+impl<T: DriverGpuVm> OpMap<T> {
+    #[inline]
+    /// Returns the base address of the new mapping.
+    pub fn addr(&self) -> u64 {
+        self.0.va.addr
+    }
+
+    #[inline]
+    /// Returns the range of the new mapping.
+    pub fn range(&self) -> u64 {
+        self.0.va.range
+    }
+
+    #[inline]
+    /// Returns the offset within the GEM object.
+    pub fn offset(&self) -> u64 {
+        self.0.gem.offset
+    }
+
+    #[inline]
+    /// Returns the GEM object to map.
+    pub fn object(&self) -> &<T::Driver as drv::Driver>::Object {
+        let p = <<T::Driver as drv::Driver>::Object as IntoGEMObject>::from_gem_obj(self.0.gem.obj);
+        // SAFETY: The GEM object has an active reference for the lifetime of this op
+        unsafe { &*p }
+    }
+
+    /// A helper function to map and link a GpuVa to a GpuVmBo.
+    pub fn map_and_link_va(
+        &mut self,
+        gpuvm: &mut UpdatingGpuVm<'_, T>,
+        gpuva: Pin<KBox<GpuVa<T>>>,
+        gpuvmbo: &ARef<GpuVmBo<T>>,
+    ) -> Result<(), Pin<KBox<GpuVa<T>>>> {
+        // SAFETY: We are handing off the GpuVa ownership and it will not be moved.
+        let p = KBox::leak(unsafe { Pin::into_inner_unchecked(gpuva) });
+        // SAFETY: These C functions are called with the correct invariants
+        unsafe {
+            bindings::drm_gpuva_init_from_op(&mut p.gpuva, &mut self.0);
+            if bindings::drm_gpuva_insert(gpuvm.0.gpuvm() as *mut _, &mut p.gpuva) != 0 {
+                // EEXIST, return the GpuVa to the caller as an error
+                return Err(Pin::new_unchecked(KBox::from_raw(p)));
+            };
+            // SAFETY: This takes a new reference to the gpuvmbo.
+            bindings::drm_gpuva_link(&mut p.gpuva, &gpuvmbo.bo as *const _ as *mut _);
+        }
+        Ok(())
+    }
+}
+
+#[repr(transparent)]
+/// Represents a single unmap operation generated by [`GpuVm`].
+pub struct OpUnMap<T: DriverGpuVm>(bindings::drm_gpuva_op_unmap, PhantomData<T>);
+
+impl<T: DriverGpuVm> OpUnMap<T> {
+    #[inline]
+    /// Returns the GPU VA to unmap.
+    pub fn va(&self) -> Option<&GpuVa<T>> {
+        if self.0.va.is_null() {
+            return None;
+        }
+        // SAFETY: Container invariant is guaranteed for ops structs created for our types.
+        let p = unsafe { crate::container_of!(self.0.va, GpuVa<T>, gpuva) as *mut GpuVa<T> };
+        // SAFETY: The GpuVa object reference is valid per the op_unmap contract
+        Some(unsafe { &*p })
+    }
+
+    #[inline]
+    /// Indicates whether this GPU VA is physically contiguous with the original
+    /// mapping request.
+    ///
+    /// Optionally, if `keep` is set, drivers may keep the actual page table
+    /// mappings for this GPU VA, adding the missing page table entries only and
+    /// subsequently updating the VM accordingly.
+    pub fn keep(&self) -> bool {
+        self.0.keep
+    }
+
+    /// A helper to unmap and unlink a GpuVa from a GpuVmBo.
+    pub fn unmap_and_unlink_va(&mut self) -> Option<Pin<KBox<GpuVa<T>>>> {
+        if self.0.va.is_null() {
+            return None;
+        }
+        // SAFETY: Container invariant is guaranteed for ops structs created for our types.
+        let p = unsafe { crate::container_of!(self.0.va, GpuVa<T>, gpuva) as *mut GpuVa<T> };
+
+        // SAFETY: The GpuVa object reference is valid per the op_unmap contract
+        unsafe {
+            bindings::drm_gpuva_unmap(&mut self.0);
+            bindings::drm_gpuva_unlink(self.0.va);
+        }
+
+        // Unlinking/unmapping relinquishes ownership of the GpuVa object,
+        // so clear the pointer
+        self.0.va = core::ptr::null_mut();
+        // SAFETY: The GpuVa object reference is valid per the op_unmap contract
+        Some(unsafe { Pin::new_unchecked(KBox::from_raw(p)) })
+    }
+}
+
+impl<T: DriverGpuVm> OpReMap<T> {
+    #[inline]
+    /// Returns the preceding part of a split mapping, if any.
+    pub fn prev_map(&mut self) -> Option<&mut OpMap<T>> {
+        // SAFETY: The prev pointer must be valid if not-NULL per the op_remap contract
+        unsafe { (self.0.prev as *mut OpMap<T>).as_mut() }
+    }
+
+    #[inline]
+    /// Returns the subsequent part of a split mapping, if any.
+    pub fn next_map(&mut self) -> Option<&mut OpMap<T>> {
+        // SAFETY: The next pointer must be valid if not-NULL per the op_remap contract
+        unsafe { (self.0.next as *mut OpMap<T>).as_mut() }
+    }
+
+    #[inline]
+    /// Returns the unmap operation for the original existing mapping.
+    pub fn unmap(&mut self) -> &mut OpUnMap<T> {
+        // SAFETY: The unmap pointer is always valid per the op_remap contract
+        unsafe { (self.0.unmap as *mut OpUnMap<T>).as_mut().unwrap() }
+    }
+}
+
+#[repr(C)]
+#[pin_data]
+/// A GPU VA range.
+///
+/// Drivers can use `inner` to store additional data.
+pub struct GpuVa<T: DriverGpuVm> {
+    #[pin]
+    gpuva: bindings::drm_gpuva,
+    #[pin]
+    inner: T::GpuVa,
+    #[pin]
+    _p: PhantomPinned,
+}
+
+// SAFETY: This type is safe to zero-init (as far as C is concerned).
+unsafe impl init::Zeroable for bindings::drm_gpuva {}
+
+impl<T: DriverGpuVm> GpuVa<T> {
+    /// Creates a new GPU VA.
+    pub fn new<E>(inner: impl PinInit<T::GpuVa, E>) -> Result<Pin<KBox<GpuVa<T>>>>
+    where
+        Error: From<E>,
+    {
+        KBox::try_pin_init(
+            try_pin_init!(Self {
+                gpuva <- init::zeroed(),
+                inner <- inner,
+                _p: PhantomPinned
+            }),
+            GFP_KERNEL,
+        )
+    }
+
+    #[inline]
+    /// Returns the start address of the GPU VA range.
+    pub fn addr(&self) -> u64 {
+        self.gpuva.va.addr
+    }
+
+    #[inline]
+    /// Returns the range of the GPU VA.
+    pub fn range(&self) -> u64 {
+        self.gpuva.va.range
+    }
+
+    #[inline]
+    /// Returns the offset within the GEM object.
+    pub fn offset(&self) -> u64 {
+        self.gpuva.gem.offset
+    }
+}
+
+#[repr(C)]
+#[pin_data]
+/// The connection between a GEM object and a VM.
+pub struct GpuVmBo<T: DriverGpuVm> {
+    #[pin]
+    bo: bindings::drm_gpuvm_bo,
+    #[pin]
+    inner: T::GpuVmBo,
+    #[pin]
+    _p: PhantomPinned,
+}
+
+impl<T: DriverGpuVm> GpuVmBo<T> {
+    /// Return a reference to the inner driver data for this GpuVmBo
+    pub fn inner(&self) -> &T::GpuVmBo {
+        &self.inner
+    }
+}
+
+// SAFETY: DRM GpuVmBo objects are always reference counted and the get/put functions
+// satisfy the requirements.
+unsafe impl<T: DriverGpuVm> AlwaysRefCounted for GpuVmBo<T> {
+    fn inc_ref(&self) {
+        // SAFETY: The drm_gpuvm_get function satisfies the requirements for inc_ref().
+        unsafe { bindings::drm_gpuvm_bo_get(&self.bo as *const _ as *mut _) };
+    }
+
+    unsafe fn dec_ref(mut obj: NonNull<Self>) {
+        // SAFETY: drm_gpuvm_bo_put() requires holding the gpuva lock, which is the dma_resv lock by default.
+        // The drm_gpuvm_put function satisfies the requirements for dec_ref().
+        // (We do not support custom locks yet.)
+        unsafe {
+            let resv = (*obj.as_mut().bo.obj).resv;
+            bindings::dma_resv_lock(resv, core::ptr::null_mut());
+            bindings::drm_gpuvm_bo_put(&mut obj.as_mut().bo);
+            bindings::dma_resv_unlock(resv);
+        }
+    }
+}
+
+/// The DRM GPU VA Manager.
+///
+/// It keeps track of a GPU's virtual address space by using maple tree
+/// structures.
+///
+/// Typically, an instance of [`GpuVm`] is embedded bigger, driver-specific
+/// structures.
+///
+/// Drivers can pass addresses and ranges in arbitrary units, e.g.: bytes or
+/// pages.
+///
+/// There should be one manager instance per GPU virtual address space.
+#[repr(C)]
+#[pin_data]
+pub struct GpuVm<T: DriverGpuVm> {
+    #[pin]
+    gpuvm: Opaque<bindings::drm_gpuvm>,
+    #[pin]
+    inner: UnsafeCell<T>,
+    #[pin]
+    _p: PhantomPinned,
+}
+
+/// # Safety
+///
+/// This function is only safe to be called from the GPUVM C code.
+unsafe extern "C" fn vm_free_callback<T: DriverGpuVm>(raw_gpuvm: *mut bindings::drm_gpuvm) {
+    // SAFETY: Container invariant is guaranteed for objects using our callback.
+    let p = unsafe {
+        crate::container_of!(
+            raw_gpuvm as *mut Opaque<bindings::drm_gpuvm>,
+            GpuVm<T>,
+            gpuvm
+        ) as *mut GpuVm<T>
+    };
+
+    // SAFETY: p is guaranteed to be valid for drm_gpuvm objects using this callback.
+    unsafe { drop(KBox::from_raw(p)) };
+}
+
+/// # Safety
+///
+/// This function is only safe to be called from the GPUVM C code.
+unsafe extern "C" fn vm_bo_alloc_callback<T: DriverGpuVm>() -> *mut bindings::drm_gpuvm_bo {
+    let obj: Result<Pin<KBox<GpuVmBo<T>>>> = KBox::try_pin_init(
+        try_pin_init!(GpuVmBo::<T> {
+            bo <- init::zeroed(),
+            inner <- T::GpuVmBo::new(),
+            _p: PhantomPinned
+        }),
+        GFP_KERNEL,
+    );
+
+    match obj {
+        Ok(obj) =>
+        // SAFETY: The DRM core will keep this object pinned
+        unsafe {
+            let p = KBox::leak(Pin::into_inner_unchecked(obj));
+            &mut p.bo
+        },
+        Err(_) => core::ptr::null_mut(),
+    }
+}
+
+/// # Safety
+///
+/// This function is only safe to be called from the GPUVM C code.
+unsafe extern "C" fn vm_bo_free_callback<T: DriverGpuVm>(raw_vm_bo: *mut bindings::drm_gpuvm_bo) {
+    // SAFETY: Container invariant is guaranteed for objects using this callback.
+    let p = unsafe { crate::container_of!(raw_vm_bo, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
+
+    // SAFETY: p is guaranteed to be valid for drm_gpuvm_bo objects using this callback.
+    unsafe { drop(KBox::from_raw(p)) };
+}
+
+/// # Safety
+///
+/// This function is only safe to be called from the GPUVM C code.
+unsafe extern "C" fn step_map_callback<T: DriverGpuVm>(
+    op: *mut bindings::drm_gpuva_op,
+    _priv: *mut core::ffi::c_void,
+) -> core::ffi::c_int {
+    // SAFETY: We know this is a map op, and OpMap is a transparent wrapper.
+    let map = unsafe { &mut *((&mut (*op).__bindgen_anon_1.map) as *mut _ as *mut OpMap<T>) };
+    // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is
+    // guaranteed to outlive this function.
+    let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) };
+
+    from_result(|| {
+        UpdatingGpuVm(ctx.gpuvm).step_map(map, ctx.ctx)?;
+        Ok(0)
+    })
+}
+
+/// # Safety
+///
+/// This function is only safe to be called from the GPUVM C code.
+unsafe extern "C" fn step_remap_callback<T: DriverGpuVm>(
+    op: *mut bindings::drm_gpuva_op,
+    _priv: *mut core::ffi::c_void,
+) -> core::ffi::c_int {
+    // SAFETY: We know this is a map op, and OpReMap is a transparent wrapper.
+    let remap = unsafe { &mut *((&mut (*op).__bindgen_anon_1.remap) as *mut _ as *mut OpReMap<T>) };
+    // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is
+    // guaranteed to outlive this function.
+    let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) };
+
+    let p_vm_bo = remap.unmap().va().unwrap().gpuva.vm_bo;
+
+    let res = {
+        // SAFETY: vm_bo pointer must be valid and non-null by the step_remap invariants.
+        // Since we grab a ref, this reference's lifetime is until the decref.
+        let vm_bo_ref = unsafe {
+            bindings::drm_gpuvm_bo_get(p_vm_bo);
+            &*(crate::container_of!(p_vm_bo, GpuVmBo<T>, bo) as *mut GpuVmBo<T>)
+        };
+
+        from_result(|| {
+            UpdatingGpuVm(ctx.gpuvm).step_remap(remap, vm_bo_ref, ctx.ctx)?;
+            Ok(0)
+        })
+    };
+
+    // SAFETY: We incremented the refcount above, and the Rust reference we took is
+    // no longer in scope.
+    unsafe { bindings::drm_gpuvm_bo_put(p_vm_bo) };
+
+    res
+}
+
+/// # Safety
+///
+/// This function is only safe to be called from the GPUVM C code.
+unsafe extern "C" fn step_unmap_callback<T: DriverGpuVm>(
+    op: *mut bindings::drm_gpuva_op,
+    _priv: *mut core::ffi::c_void,
+) -> core::ffi::c_int {
+    // SAFETY: We know this is a map op, and OpUnMap is a transparent wrapper.
+    let unmap = unsafe { &mut *((&mut (*op).__bindgen_anon_1.unmap) as *mut _ as *mut OpUnMap<T>) };
+    // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is
+    // guaranteed to outlive this function.
+    let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) };
+
+    from_result(|| {
+        UpdatingGpuVm(ctx.gpuvm).step_unmap(unmap, ctx.ctx)?;
+        Ok(0)
+    })
+}
+
+impl<T: DriverGpuVm> GpuVm<T> {
+    const OPS: bindings::drm_gpuvm_ops = bindings::drm_gpuvm_ops {
+        vm_free: Some(vm_free_callback::<T>),
+        op_alloc: None,
+        op_free: None,
+        vm_bo_alloc: Some(vm_bo_alloc_callback::<T>),
+        vm_bo_free: Some(vm_bo_free_callback::<T>),
+        vm_bo_validate: None,
+        sm_step_map: Some(step_map_callback::<T>),
+        sm_step_remap: Some(step_remap_callback::<T>),
+        sm_step_unmap: Some(step_unmap_callback::<T>),
+    };
+
+    fn gpuvm(&self) -> *const bindings::drm_gpuvm {
+        self.gpuvm.get()
+    }
+
+    /// Creates a GPUVM instance.
+    pub fn new<E>(
+        name: &'static CStr,
+        dev: &device::Device<T::Driver>,
+        r_obj: &<T::Driver as drv::Driver>::Object,
+        range: Range<u64>,
+        reserve_range: Range<u64>,
+        inner: impl PinInit<T, E>,
+    ) -> Result<ARef<GpuVm<T>>>
+    where
+        Error: From<E>,
+    {
+        let obj: Pin<KBox<Self>> = KBox::try_pin_init(
+            try_pin_init!(Self {
+                // SAFETY: drm_gpuvm_init cannot fail and always initializes the member
+                gpuvm <- unsafe {
+                    init::pin_init_from_closure(move |slot: *mut Opaque<bindings::drm_gpuvm> | {
+                        // Zero-init required by drm_gpuvm_init
+                        *slot = core::mem::zeroed();
+                        bindings::drm_gpuvm_init(
+                            Opaque::raw_get(slot),
+                            name.as_char_ptr(),
+                            0,
+                            dev.as_raw(),
+                            r_obj.gem_obj() as *const _ as *mut _,
+                            range.start,
+                            range.end - range.start,
+                            reserve_range.start,
+                            reserve_range.end - reserve_range.start,
+                            &Self::OPS
+                        );
+                        Ok(())
+                    })
+                },
+                // SAFETY: Just passing through to the initializer argument
+                inner <- unsafe {
+                    init::pin_init_from_closure(move |slot: *mut UnsafeCell<T> | {
+                        inner.__pinned_init(slot as *mut _)
+                    })
+                },
+                _p: PhantomPinned
+            }),
+            GFP_KERNEL,
+        )?;
+
+        // SAFETY: We never move out of the object
+        let vm_ref = unsafe {
+            ARef::from_raw(NonNull::new_unchecked(KBox::leak(
+                Pin::into_inner_unchecked(obj),
+            )))
+        };
+
+        Ok(vm_ref)
+    }
+
+    /// Locks the VM, protecting its interval tree against concurrent accesses.
+    ///
+    /// Callers must prove that they have exclusive access to the VM by holding
+    /// some guard type. This encodes the driver-specific locking requirements.
+    ///
+    /// It is up to the caller to ensure that the guard indeed provides the
+    /// required locking.
+    pub fn lock<U: ?Sized, B: Backend>(&self, _guard: &mut Guard<'_, U, B>) -> LockedGpuVm<'_, T> {
+        LockedGpuVm { gpuvm: self }
+    }
+
+    /// Returns true if the given object is external to the GPUVM, i.e.: if it
+    /// does not share the DMA reservation object of the GPUVM.
+    pub fn is_extobj(&self, obj: &impl IntoGEMObject) -> bool {
+        let gem = obj.gem_obj() as *const _ as *mut _;
+        // SAFETY: This is safe to call as long as the arguments are valid pointers.
+        unsafe { bindings::drm_gpuvm_is_extobj(self.gpuvm() as *mut _, gem) }
+    }
+}
+
+// SAFETY: DRM GpuVm objects are always reference counted and the get/put functions
+// satisfy the requirements.
+unsafe impl<T: DriverGpuVm> AlwaysRefCounted for GpuVm<T> {
+    fn inc_ref(&self) {
+        // SAFETY: The drm_gpuvm_get function satisfies the requirements for inc_ref().
+        unsafe { bindings::drm_gpuvm_get(&self.gpuvm as *const _ as *mut _) };
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        // SAFETY: The drm_gpuvm_put function satisfies the requirements for dec_ref().
+        unsafe { bindings::drm_gpuvm_put(Opaque::raw_get(&(*obj.as_ptr()).gpuvm)) };
+    }
+}
+
+/// The object returned after a call to [`GpuVm::lock`].
+///
+/// This object has access to operations that modify the VM's interval tree.
+pub struct LockedGpuVm<'a, T: DriverGpuVm> {
+    gpuvm: &'a GpuVm<T>,
+}
+
+impl<T: DriverGpuVm> LockedGpuVm<'_, T> {
+    /// Finds the [`GpuVmBo`] object that connects `obj` to this VM.
+    ///
+    /// If found, increases the reference count of the GpuVmBo object
+    /// accordingly.
+    pub fn find_bo(&mut self, obj: &DriverObject<T>) -> Option<ARef<GpuVmBo<T>>> {
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        let p = unsafe {
+            bindings::drm_gpuvm_bo_find(
+                self.gpuvm.gpuvm() as *mut _,
+                obj.gem_obj() as *const _ as *mut _,
+            )
+        };
+        if p.is_null() {
+            None
+        } else {
+            // SAFETY: All the drm_gpuvm_bo objects in this GpuVm are always allocated by us as GpuVmBo<T>.
+            let p = unsafe { crate::container_of!(p, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
+            // SAFETY: We checked for NULL above, and the types ensure that
+            // this object was created by vm_bo_alloc_callback<T>.
+            Some(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) })
+        }
+    }
+
+    /// Obtains the [`GpuVmBo`] object that connects `obj` to this VM.
+    ///
+    /// This connection is unique, so an instane of [`GpuVmBo`] will be
+    /// allocated for `obj` once, and that instance will be returned from that
+    /// point forward.
+    pub fn obtain_bo(&mut self, obj: &DriverObject<T>) -> Result<ARef<GpuVmBo<T>>> {
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        let p = unsafe {
+            bindings::drm_gpuvm_bo_obtain(
+                self.gpuvm.gpuvm() as *mut _,
+                obj.gem_obj() as *const _ as *mut _,
+            )
+        };
+        if p.is_null() {
+            Err(ENOMEM)
+        } else {
+            // SAFETY: Container invariant is guaranteed for GpuVmBo objects for this GpuVm.
+            let p = unsafe { crate::container_of!(p, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
+            // SAFETY: We checked for NULL above, and the types ensure that
+            // this object was created by vm_bo_alloc_callback<T>.
+            Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) })
+        }
+    }
+
+    /// Iterates the given range of the GPU VA space. It utilizes
+    /// [`DriverGpuVm`] to call back into the driver providing the split and
+    /// merge steps.
+    ///
+    /// A sequence of callbacks can contain map, unmap and remap operations, but
+    /// the sequence of callbacks might also be empty if no operation is
+    /// required, e.g. if the requested mapping already exists in the exact same
+    /// way.
+    ///
+    /// There can be an arbitrary amount of unmap operations, a maximum of two
+    /// remap operations and a single map operation. The latter one represents
+    /// the original map operation requested by the caller.
+    ///
+    /// # Arguments
+    ///
+    /// - `ctx`: A driver-specific context.
+    /// - `req_obj`: The GEM object to map.
+    /// - `req_addr`: The start address of the new mapping.
+    /// - `req_range`: The range of the mapping.
+    /// - `req_offset`: The offset into the GEM object.
+    pub fn sm_map(
+        &mut self,
+        ctx: &mut T::StepContext,
+        req_obj: &DriverObject<T>,
+        req_addr: u64,
+        req_range: u64,
+        req_offset: u64,
+    ) -> Result {
+        let mut ctx = StepContext {
+            ctx,
+            gpuvm: self.gpuvm,
+        };
+
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        to_result(unsafe {
+            bindings::drm_gpuvm_sm_map(
+                self.gpuvm.gpuvm() as *mut _,
+                &mut ctx as *mut _ as *mut _,
+                req_addr,
+                req_range,
+                req_obj.gem_obj() as *const _ as *mut _,
+                req_offset,
+            )
+        })
+    }
+
+    /// Iterates the given range of the GPU VA space. It utilizes
+    /// [`DriverGpuVm`] to call back into the driver providing the operations to
+    /// unmap and, if required, split existent mappings.
+    ///
+    /// A sequence of callbacks can contain unmap and remap operations,
+    /// depending on whether there are actual overlapping mappings to split.
+    ///
+    /// There can be an arbitrary amount of unmap operations and a maximum of
+    /// two remap operations.
+    ///
+    /// # Arguments
+    ///
+    /// - `ctx`: A driver-specific context.
+    /// - `req_addr`: The start address of the range to unmap.
+    /// - `req_range`: The range of the mappings to unmap.
+    pub fn sm_unmap(&mut self, ctx: &mut T::StepContext, req_addr: u64, req_range: u64) -> Result {
+        let mut ctx = StepContext {
+            ctx,
+            gpuvm: self.gpuvm,
+        };
+
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        to_result(unsafe {
+            bindings::drm_gpuvm_sm_unmap(
+                self.gpuvm.gpuvm() as *mut _,
+                &mut ctx as *mut _ as *mut _,
+                req_addr,
+                req_range,
+            )
+        })
+    }
+}
+
+impl<T: DriverGpuVm> Deref for LockedGpuVm<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        // SAFETY: The existence of this LockedGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &*self.gpuvm.inner.get() }
+    }
+}
+
+impl<T: DriverGpuVm> DerefMut for LockedGpuVm<'_, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        // SAFETY: The existence of this UpdatingGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &mut *self.gpuvm.inner.get() }
+    }
+}
+
+/// A state representing a GPU VM that is being updated.
+pub struct UpdatingGpuVm<'a, T: DriverGpuVm>(&'a GpuVm<T>);
+
+impl<T: DriverGpuVm> UpdatingGpuVm<'_, T> {}
+
+impl<T: DriverGpuVm> Deref for UpdatingGpuVm<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        // SAFETY: The existence of this UpdatingGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &*self.0.inner.get() }
+    }
+}
+
+impl<T: DriverGpuVm> DerefMut for UpdatingGpuVm<'_, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        // SAFETY: The existence of this UpdatingGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &mut *self.0.inner.get() }
+    }
+}
+
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Sync for GpuVm<T> {}
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Send for GpuVm<T> {}
+
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Sync for GpuVmBo<T> {}
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Send for GpuVmBo<T> {}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index c44760a1332fa1ef875939b48e7af450f7372020..849dc1e577f15bfada11d6739dff48ac33813326 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -6,4 +6,6 @@ 
 pub mod drv;
 pub mod file;
 pub mod gem;
+#[cfg(CONFIG_DRM_GPUVM = "y")]
+pub mod gpuvm;
 pub mod ioctl;