* [PATCH v2 1/9] rust: sizes: add SZ_4G constant
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-08 19:47 ` Miguel Ojeda
2026-09-07 16:47 ` [PATCH v2 2/9] rust: mm: add `task_size` helper Ke Sun via B4 Relay
` (7 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
SZ_4G is used by the Tyr driver when splitting the GPU VA range into
user and kernel regions.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/sizes.rs | 12 ++++++++++++
2 files changed, 13 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 4b31aa7f432f5..9bbe1538d7c7c 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -115,6 +115,7 @@ const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN;
const size_t RUST_CONST_HELPER_ARCH_KMALLOC_MINALIGN = ARCH_KMALLOC_MINALIGN;
const size_t RUST_CONST_HELPER_PAGE_SIZE = PAGE_SIZE;
const size_t RUST_CONST_HELPER_GENLMSG_DEFAULT_SIZE = GENLMSG_DEFAULT_SIZE;
+const unsigned long long RUST_CONST_HELPER_SZ_4G = SZ_4G;
const gfp_t RUST_CONST_HELPER_GFP_ATOMIC = GFP_ATOMIC;
const gfp_t RUST_CONST_HELPER_GFP_KERNEL = GFP_KERNEL;
const gfp_t RUST_CONST_HELPER_GFP_KERNEL_ACCOUNT = GFP_KERNEL_ACCOUNT;
diff --git a/rust/kernel/sizes.rs b/rust/kernel/sizes.rs
index 521b2b38bfe77..b236573f0792e 100644
--- a/rust/kernel/sizes.rs
+++ b/rust/kernel/sizes.rs
@@ -132,3 +132,15 @@ impl SizeConstants for $first {
}
define_sizes!(u32, u64, usize);
+
+/// Large size constants (≥ 4 GiB).
+///
+/// Only implemented for `u64`.
+pub trait LargeSizeConstants {
+ /// `0x1_0000_0000`.
+ const SZ_4G: Self;
+}
+
+impl LargeSizeConstants for u64 {
+ const SZ_4G: Self = bindings::SZ_4G;
+}
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v2 1/9] rust: sizes: add SZ_4G constant
2026-09-07 16:47 ` [PATCH v2 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
@ 2026-09-08 19:47 ` Miguel Ojeda
0 siblings, 0 replies; 12+ messages in thread
From: Miguel Ojeda @ 2026-09-08 19:47 UTC (permalink / raw)
To: sunke
Cc: rust-for-linux, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Alvin Sun
On Mon, Sep 7, 2026 at 6:48 PM Ke Sun via B4 Relay
<devnull+sunke.kylinos.cn@kernel.org> wrote:
>
> SZ_4G is used by the Tyr driver when splitting the GPU VA range into
> user and kernel regions.
The message should explain what changes, e.g. a new paragraph after
this one like "Thus add a new trait that ... and implement it for
`u64` because ...".
> +const unsigned long long RUST_CONST_HELPER_SZ_4G = SZ_4G;
Is it added here to be nearby other sizes, or something else?
> +/// Only implemented for `u64`.
Intra-doc link: [`u64`]
With that:
Acked-by: Miguel Ojeda <ojeda@kernel.org>
Thanks!
Cheers,
Miguel
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 2/9] rust: mm: add `task_size` helper
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` Ke Sun via B4 Relay
` (6 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
Expose the task's address space size. It is used by the Tyr driver
for splitting a VM's GPU address space into user and kernel regions.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
---
rust/kernel/mm.rs | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/rust/kernel/mm.rs b/rust/kernel/mm.rs
index 4764d7b68f2a7..d2dfbb7d43972 100644
--- a/rust/kernel/mm.rs
+++ b/rust/kernel/mm.rs
@@ -149,6 +149,13 @@ pub fn mmget_not_zero(&self) -> Option<ARef<MmWithUser>> {
None
}
}
+
+ /// The size of the process virtual address space.
+ #[inline]
+ pub fn task_size(&self) -> usize {
+ // SAFETY: `self.as_raw()` is a valid pointer to an `mm_struct` per the type invariants.
+ unsafe { (*self.as_raw()).__bindgen_anon_1.task_size }
+ }
}
// These methods require `mm_users` to be non-zero.
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 2/9] rust: mm: add `task_size` helper Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 4/9] drm/tyr: add per-file VM pool Ke Sun via B4 Relay
` (5 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
Drop the `'static` bound so that refcounted values borrowing from a
driver registration scope can be foreign-owned by the XArray
abstraction.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
---
rust/kernel/sync/arc.rs | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/rust/kernel/sync/arc.rs b/rust/kernel/sync/arc.rs
index 8ae0fe6f19ec0..9b582a815f888 100644
--- a/rust/kernel/sync/arc.rs
+++ b/rust/kernel/sync/arc.rs
@@ -363,11 +363,17 @@ pub fn into_unique_or_drop(this: Self) -> Option<Pin<UniqueArc<T>>> {
// SAFETY: The pointer returned by `into_foreign` was originally allocated as an
// `KBox<ArcInner<T>>`, so that type is what determines the alignment.
-unsafe impl<T: 'static> ForeignOwnable for Arc<T> {
+unsafe impl<T> ForeignOwnable for Arc<T> {
const FOREIGN_ALIGN: usize = <KBox<ArcInner<T>> as ForeignOwnable>::FOREIGN_ALIGN;
- type Borrowed<'a> = ArcBorrow<'a, T>;
- type BorrowedMut<'a> = Self::Borrowed<'a>;
+ type Borrowed<'a>
+ = ArcBorrow<'a, T>
+ where
+ T: 'a;
+ type BorrowedMut<'a>
+ = Self::Borrowed<'a>
+ where
+ T: 'a;
fn into_foreign(self) -> *mut c_void {
ManuallyDrop::new(self).ptr.as_ptr().cast()
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 4/9] drm/tyr: add per-file VM pool
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
` (2 preceding siblings ...)
2026-09-07 16:47 ` [PATCH v2 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-14 23:35 ` Deborah Brouwer
2026-09-07 16:47 ` [PATCH v2 5/9] drm/tyr: add user and MCU VM specifications Ke Sun via B4 Relay
` (4 subsequent siblings)
8 siblings, 1 reply; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
Userspace needs multiple independent GPU address spaces per file,
addressed by ID through the VM ioctls as in panthor. Store them in an
IdPool (capped at 32 for panthor parity) plus an XArray. Each VM is
stored with its VmOwner, so it is killed exactly once - on destroy or
file close - regardless of remaining shared references.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
drivers/gpu/drm/tyr/vm.rs | 157 +++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 156 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
index c5e307b1e2416..ae58135eeffdc 100644
--- a/drivers/gpu/drm/tyr/vm.rs
+++ b/drivers/gpu/drm/tyr/vm.rs
@@ -8,6 +8,7 @@
//! mapped into hardware address space (AS) slots for GPU execution.
use core::marker::PhantomData;
+use core::mem::ManuallyDrop;
use core::ops::Range;
use kernel::{
@@ -33,6 +34,7 @@
}, //
},
fmt,
+ id_pool::IdPool,
impl_flags,
io::PhysAddr,
iommu::pgtable::{
@@ -53,7 +55,11 @@
ArcBorrow,
Mutex, //
},
- uapi, //
+ uapi,
+ xarray::{
+ AllocKind,
+ XArray, //
+ }, //
};
use crate::{
@@ -154,6 +160,57 @@ fn try_from(value: u32) -> Result<Self, Self::Error> {
}
}
+/// Owns a [`Vm`]'s destruction: the VM is killed exactly once, when this
+/// value is dropped, regardless of how many `Arc<Vm>` references remain.
+///
+/// Callers that only need to use the VM take an `Arc<Vm>` via
+/// [`VmOwner::get()`], which keeps it alive but does not kill it.
+pub(crate) struct VmOwner<'drm>(ManuallyDrop<Arc<Vm<'drm>>>);
+
+impl<'drm> VmOwner<'drm> {
+ /// A reference for callers that want to use the VM, not own it.
+ #[expect(dead_code)]
+ pub(crate) fn get(&self) -> Arc<Vm<'drm>> {
+ Arc::clone(&self.0)
+ }
+
+ /// Transfers the VM to the pool without killing it, leaving only the
+ /// shared reference. The pool reconstructs the owner with
+ /// [`VmOwner::from_shared()`] when the VM is removed.
+ fn into_shared(mut self) -> Arc<Vm<'drm>> {
+ // SAFETY: `self.0` is initialized, and `forget(self)` below prevents
+ // the outer wrapper from being dropped, so the taken `Arc` is moved
+ // out exactly once and nothing is leaked or double-dropped.
+ let vm = unsafe { ManuallyDrop::take(&mut self.0) };
+ core::mem::forget(self);
+ vm
+ }
+
+ /// Reconstructs an owner from a shared reference.
+ ///
+ /// The caller must currently own the VM's destruction.
+ fn from_shared(vm: Arc<Vm<'drm>>) -> Self {
+ Self(ManuallyDrop::new(vm))
+ }
+}
+
+impl<'drm> core::ops::Deref for VmOwner<'drm> {
+ type Target = Vm<'drm>;
+
+ fn deref(&self) -> &Vm<'drm> {
+ &self.0
+ }
+}
+
+impl Drop for VmOwner<'_> {
+ fn drop(&mut self) {
+ self.0.kill();
+ // SAFETY: `self.0` is initialized and we are in `drop`, so it is safe
+ // to drop the inner `Arc` now that the VM has been killed.
+ unsafe { ManuallyDrop::drop(&mut self.0) };
+ }
+}
+
/// Arguments for a virtual memory map operation.
struct VmMapArgs<'drm> {
/// Access permissions and caching behavior for the mapping.
@@ -948,3 +1005,101 @@ fn pt_unmap(dev: &Device, pt: &IoPageTable<'_, ARM64LPAES1>, range: Range<u64>)
Ok(())
}
+
+/// Maximum number of VMs a single file may hold, matching panthor's
+/// `PANTHOR_MAX_VMS_PER_FILE`.
+const MAX_VMS_PER_FILE: usize = 32;
+
+/// Per-open-file pool of VMs.
+#[pin_data(PinnedDrop)]
+pub(crate) struct VmPool<'drm> {
+ #[pin]
+ ids: Mutex<IdPool>,
+ #[pin]
+ vms: XArray<Arc<Vm<'drm>>>,
+}
+
+impl<'drm> VmPool<'drm> {
+ /// Creates a new [`VmPool`].
+ #[expect(dead_code)]
+ pub(crate) fn new() -> impl PinInit<Self> {
+ let ids = IdPool::new();
+ pin_init!(Self {
+ ids <- new_mutex!(ids),
+ vms <- XArray::new(AllocKind::Alloc),
+ })
+ }
+
+ /// Takes ownership of `vm` and stores it, returning the allocated ID.
+ ///
+ /// On failure - ID space exhausted or store failure - the VM is killed
+ /// here and only the error is returned.
+ // TODO: allocate IDs with the XArray directly (once it grows range
+ // allocation, the equivalent of C's `XA_LIMIT`) and drop the IdPool.
+ #[expect(dead_code)]
+ pub(crate) fn add(&self, vm: VmOwner<'drm>) -> Result<u32> {
+ let id = {
+ let mut ids = self.ids.lock();
+ let unused = ids.find_unused_id(1).ok_or(ENOSPC)?;
+ if unused.as_usize() > MAX_VMS_PER_FILE {
+ return Err(ENOSPC);
+ }
+ unused.acquire()
+ };
+
+ let vm = vm.into_shared();
+ let mut vms = self.vms.lock();
+ match vms.store(id, vm, GFP_KERNEL) {
+ Ok(prev_vm) => {
+ drop(prev_vm);
+ Ok(id as u32)
+ }
+ Err(err) => {
+ // Drop the XArray spinlock before acquiring the `ids` mutex.
+ drop(vms);
+ // Kill the VM and release the pooled id before returning.
+ drop(VmOwner::from_shared(err.value));
+ self.ids.lock().release_id(id);
+ Err(err.error)
+ }
+ }
+ }
+
+ /// Removes the VM with the given ID, handing back its owner.
+ ///
+ /// Dropping the returned [`VmOwner`] kills the VM immediately.
+ #[expect(dead_code)]
+ pub(crate) fn remove(&self, id: u32) -> Result<VmOwner<'drm>> {
+ let mut vms = self.vms.lock();
+ match vms.remove(id as usize) {
+ Some(vm) => {
+ drop(vms);
+ self.ids.lock().release_id(id as usize);
+ Ok(VmOwner::from_shared(vm))
+ }
+ None => Err(EINVAL),
+ }
+ }
+
+ /// Gets a shared reference to the VM with the given ID.
+ #[expect(dead_code)]
+ pub(crate) fn get(&self, id: u32) -> Option<Arc<Vm<'drm>>> {
+ let vms = self.vms.lock();
+ let borrow = vms.get(id as usize)?;
+ Some(Arc::from(borrow))
+ }
+}
+
+#[pinned_drop]
+impl PinnedDrop for VmPool<'_> {
+ fn drop(self: Pin<&mut Self>) {
+ let this = self.project();
+ // Kill every VM still owned by the pool. The ID range is bounded by
+ // `MAX_VMS_PER_FILE`, so this loop is cheap and runs at file close.
+ for id in 1..=MAX_VMS_PER_FILE {
+ // Release the XArray lock guard before killing: `kill()` may sleep.
+ let vm = this.vms.lock().remove(id);
+ drop(vm.map(VmOwner::from_shared));
+ }
+ }
+}
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* Re: [PATCH v2 4/9] drm/tyr: add per-file VM pool
2026-09-07 16:47 ` [PATCH v2 4/9] drm/tyr: add per-file VM pool Ke Sun via B4 Relay
@ 2026-09-14 23:35 ` Deborah Brouwer
0 siblings, 0 replies; 12+ messages in thread
From: Deborah Brouwer @ 2026-09-14 23:35 UTC (permalink / raw)
To: Ke Sun
Cc: rust-for-linux, Miguel Ojeda, Boqun Feng, Gary Guo,
Björn Roy Baron, Benno Lossin, Andreas Hindborg, Alice Ryhl,
Trevor Gross, Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Alvin Sun
On Tue, Sep 08, 2026 at 12:47:51AM +0800, Ke Sun wrote:
> From: Alvin Sun <alvin.sun@linux.dev>
>
> Userspace needs multiple independent GPU address spaces per file,
> addressed by ID through the VM ioctls as in panthor. Store them in an
> IdPool (capped at 32 for panthor parity) plus an XArray. Each VM is
> stored with its VmOwner, so it is killed exactly once - on destroy or
> file close - regardless of remaining shared references.
>
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> ---
> drivers/gpu/drm/tyr/vm.rs | 157 +++++++++++++++++++++++++++++++++++++++++++++-
> 1 file changed, 156 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
> index c5e307b1e2416..ae58135eeffdc 100644
> --- a/drivers/gpu/drm/tyr/vm.rs
> +++ b/drivers/gpu/drm/tyr/vm.rs
> @@ -8,6 +8,7 @@
> //! mapped into hardware address space (AS) slots for GPU execution.
>
> use core::marker::PhantomData;
> +use core::mem::ManuallyDrop;
> use core::ops::Range;
>
> use kernel::{
> @@ -33,6 +34,7 @@
> }, //
> },
> fmt,
> + id_pool::IdPool,
> impl_flags,
> io::PhysAddr,
> iommu::pgtable::{
> @@ -53,7 +55,11 @@
> ArcBorrow,
> Mutex, //
> },
> - uapi, //
> + uapi,
> + xarray::{
> + AllocKind,
> + XArray, //
> + }, //
> };
>
> use crate::{
> @@ -154,6 +160,57 @@ fn try_from(value: u32) -> Result<Self, Self::Error> {
> }
> }
>
> +/// Owns a [`Vm`]'s destruction: the VM is killed exactly once, when this
> +/// value is dropped, regardless of how many `Arc<Vm>` references remain.
> +///
> +/// Callers that only need to use the VM take an `Arc<Vm>` via
> +/// [`VmOwner::get()`], which keeps it alive but does not kill it.
> +pub(crate) struct VmOwner<'drm>(ManuallyDrop<Arc<Vm<'drm>>>);
> +
> +impl<'drm> VmOwner<'drm> {
> + /// A reference for callers that want to use the VM, not own it.
> + #[expect(dead_code)]
> + pub(crate) fn get(&self) -> Arc<Vm<'drm>> {
> + Arc::clone(&self.0)
> + }
> +
> + /// Transfers the VM to the pool without killing it, leaving only the
> + /// shared reference. The pool reconstructs the owner with
> + /// [`VmOwner::from_shared()`] when the VM is removed.
> + fn into_shared(mut self) -> Arc<Vm<'drm>> {
> + // SAFETY: `self.0` is initialized, and `forget(self)` below prevents
> + // the outer wrapper from being dropped, so the taken `Arc` is moved
> + // out exactly once and nothing is leaked or double-dropped.
> + let vm = unsafe { ManuallyDrop::take(&mut self.0) };
> + core::mem::forget(self);
This looks a bit more complicated than what Daniel suggested.
But also I am concerned that the ioctl series might not be the right place to
rework the whole VM ownership/lifetime model. I definitely think it's
worth looking at but I wonder if you could send the ioctl series first
without changing VM ownership, and then follow up with a series proposing
these kinds of changes. It is a significant change that deserves its own
scrutiny.
> + vm
> + }
> +
> + /// Reconstructs an owner from a shared reference.
> + ///
> + /// The caller must currently own the VM's destruction.
> + fn from_shared(vm: Arc<Vm<'drm>>) -> Self {
> + Self(ManuallyDrop::new(vm))
> + }
> +}
> +
> +impl<'drm> core::ops::Deref for VmOwner<'drm> {
> + type Target = Vm<'drm>;
> +
> + fn deref(&self) -> &Vm<'drm> {
> + &self.0
> + }
> +}
> +
> +impl Drop for VmOwner<'_> {
> + fn drop(&mut self) {
> + self.0.kill();
> + // SAFETY: `self.0` is initialized and we are in `drop`, so it is safe
> + // to drop the inner `Arc` now that the VM has been killed.
> + unsafe { ManuallyDrop::drop(&mut self.0) };
> + }
> +}
> +
> /// Arguments for a virtual memory map operation.
> struct VmMapArgs<'drm> {
> /// Access permissions and caching behavior for the mapping.
> @@ -948,3 +1005,101 @@ fn pt_unmap(dev: &Device, pt: &IoPageTable<'_, ARM64LPAES1>, range: Range<u64>)
>
> Ok(())
> }
> +
> +/// Maximum number of VMs a single file may hold, matching panthor's
> +/// `PANTHOR_MAX_VMS_PER_FILE`.
> +const MAX_VMS_PER_FILE: usize = 32;
> +
> +/// Per-open-file pool of VMs.
> +#[pin_data(PinnedDrop)]
> +pub(crate) struct VmPool<'drm> {
> + #[pin]
> + ids: Mutex<IdPool>,
> + #[pin]
> + vms: XArray<Arc<Vm<'drm>>>,
> +}
> +
> +impl<'drm> VmPool<'drm> {
> + /// Creates a new [`VmPool`].
> + #[expect(dead_code)]
> + pub(crate) fn new() -> impl PinInit<Self> {
> + let ids = IdPool::new();
> + pin_init!(Self {
> + ids <- new_mutex!(ids),
> + vms <- XArray::new(AllocKind::Alloc),
> + })
> + }
> +
> + /// Takes ownership of `vm` and stores it, returning the allocated ID.
> + ///
> + /// On failure - ID space exhausted or store failure - the VM is killed
> + /// here and only the error is returned.
> + // TODO: allocate IDs with the XArray directly (once it grows range
> + // allocation, the equivalent of C's `XA_LIMIT`) and drop the IdPool.
> + #[expect(dead_code)]
> + pub(crate) fn add(&self, vm: VmOwner<'drm>) -> Result<u32> {
> + let id = {
> + let mut ids = self.ids.lock();
> + let unused = ids.find_unused_id(1).ok_or(ENOSPC)?;
> + if unused.as_usize() > MAX_VMS_PER_FILE {
> + return Err(ENOSPC);
> + }
> + unused.acquire()
> + };
> +
> + let vm = vm.into_shared();
> + let mut vms = self.vms.lock();
> + match vms.store(id, vm, GFP_KERNEL) {
> + Ok(prev_vm) => {
> + drop(prev_vm);
> + Ok(id as u32)
> + }
> + Err(err) => {
> + // Drop the XArray spinlock before acquiring the `ids` mutex.
> + drop(vms);
> + // Kill the VM and release the pooled id before returning.
> + drop(VmOwner::from_shared(err.value));
> + self.ids.lock().release_id(id);
> + Err(err.error)
> + }
> + }
> + }
> +
> + /// Removes the VM with the given ID, handing back its owner.
> + ///
> + /// Dropping the returned [`VmOwner`] kills the VM immediately.
> + #[expect(dead_code)]
> + pub(crate) fn remove(&self, id: u32) -> Result<VmOwner<'drm>> {
> + let mut vms = self.vms.lock();
> + match vms.remove(id as usize) {
> + Some(vm) => {
> + drop(vms);
> + self.ids.lock().release_id(id as usize);
> + Ok(VmOwner::from_shared(vm))
> + }
> + None => Err(EINVAL),
> + }
> + }
> +
> + /// Gets a shared reference to the VM with the given ID.
> + #[expect(dead_code)]
> + pub(crate) fn get(&self, id: u32) -> Option<Arc<Vm<'drm>>> {
> + let vms = self.vms.lock();
> + let borrow = vms.get(id as usize)?;
> + Some(Arc::from(borrow))
> + }
> +}
> +
> +#[pinned_drop]
> +impl PinnedDrop for VmPool<'_> {
> + fn drop(self: Pin<&mut Self>) {
> + let this = self.project();
> + // Kill every VM still owned by the pool. The ID range is bounded by
> + // `MAX_VMS_PER_FILE`, so this loop is cheap and runs at file close.
> + for id in 1..=MAX_VMS_PER_FILE {
> + // Release the XArray lock guard before killing: `kill()` may sleep.
> + let vm = this.vms.lock().remove(id);
> + drop(vm.map(VmOwner::from_shared));
> + }
> + }
> +}
>
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH v2 5/9] drm/tyr: add user and MCU VM specifications
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
` (3 preceding siblings ...)
2026-09-07 16:47 ` [PATCH v2 4/9] drm/tyr: add per-file VM pool Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 6/9] drm/tyr: add BO creation and lookup helpers Ke Sun via B4 Relay
` (3 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
The MCU and user VMs need different VA layouts. Give each a dedicated
constructor: new_for_fw() builds the kernel-only 4G layout, while
new_for_user() splits the address space by task_size or a user-provided
size, rejecting oversized requests rather than clamping. The resulting
user range is what VM_CREATE reports back as user_va_range.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
drivers/gpu/drm/tyr/fw.rs | 37 +++++-----
drivers/gpu/drm/tyr/vm.rs | 179 +++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 178 insertions(+), 38 deletions(-)
diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs
index 47d25c901bd01..6aba9f6e6bc63 100644
--- a/drivers/gpu/drm/tyr/fw.rs
+++ b/drivers/gpu/drm/tyr/fw.rs
@@ -51,7 +51,6 @@
KernelBoVaAlloc, //
},
gpu::GpuInfo,
-
mmu::Mmu,
regs::{
gpu_control::{
@@ -66,7 +65,10 @@
JOB_IRQ_RAWSTAT, //
}, //
},
- vm::Vm, //
+ vm::{
+ Vm,
+ VmOwner, //
+ }, //
};
mod parser;
@@ -149,7 +151,10 @@ pub(crate) struct Firmware<'drm> {
iomem: Arc<IoMem<'drm>>,
/// MCU VM.
- vm: Arc<Vm<'drm>>,
+ ///
+ /// As the VM's owner, this field kills the firmware mappings when the
+ /// firmware is dropped.
+ vm: VmOwner<'drm>,
/// List of firmware sections.
#[expect(dead_code)]
@@ -160,9 +165,6 @@ impl<'drm> Drop for Firmware<'drm> {
fn drop(&mut self) {
// Stop the MCU before releasing its firmware mappings and memory.
let _ = self.stop();
-
- // AS slots retain a VM ref, we need to kill the circular ref manually.
- self.vm.kill();
}
}
@@ -220,10 +222,10 @@ pub(crate) fn new(
mmu: ArcBorrow<'_, Mmu<'drm>>,
gpu_info: &GpuInfo,
) -> Result<Firmware<'drm>> {
- let vm = Vm::new(dev, ddev, mmu, gpu_info)?;
+ let vm = Vm::new_for_fw(dev, ddev, mmu, gpu_info)?;
vm.activate()?;
- let result = (|| {
+ let sections = (|| -> Result<KVec<Section<'drm>>> {
let (fw, parsed_sections) = Self::load(dev, ddev, gpu_info)?;
let mut sections = KVec::new();
for parsed in parsed_sections {
@@ -233,7 +235,7 @@ pub(crate) fn new(
let mut mem = KernelBo::new(
ddev,
- vm.clone(),
+ vm.get(),
size,
KernelBoVaAlloc::Explicit(va),
parsed.vm_map_flags,
@@ -253,18 +255,15 @@ pub(crate) fn new(
sections.push(Section { data, mem }, GFP_KERNEL)?;
}
- Ok(Firmware {
- iomem,
- vm: vm.clone(),
- sections,
- })
+ Ok(sections)
})();
- if result.is_err() {
- vm.kill();
- }
-
- result
+ // On error, `vm` (the owner) is dropped and kills the MCU VM.
+ Ok(Firmware {
+ iomem,
+ vm,
+ sections: sections?,
+ })
}
pub(crate) fn boot(&self) -> Result {
diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
index ae58135eeffdc..db9e2ccc55056 100644
--- a/drivers/gpu/drm/tyr/vm.rs
+++ b/drivers/gpu/drm/tyr/vm.rs
@@ -9,6 +9,7 @@
use core::marker::PhantomData;
use core::mem::ManuallyDrop;
+use core::num::NonZeroU64;
use core::ops::Range;
use kernel::{
@@ -45,6 +46,8 @@
new_mutex,
prelude::*,
sizes::{
+ LargeSizeConstants,
+ SizeConstants,
SZ_1G,
SZ_2M,
SZ_4K, //
@@ -160,6 +163,25 @@ fn try_from(value: u32) -> Result<Self, Self::Error> {
}
}
+/// User VA size request for a user VM.
+pub(crate) enum UserVaRequest {
+ /// Split based on `task_size()` and the GPU VA range.
+ Auto,
+ /// Caller-specified size; construction guarantees `> 0`.
+ Fixed(NonZeroU64),
+}
+
+impl UserVaRequest {
+ /// UAPI boundary normalization: `0` -> [`Auto`](Self::Auto).
+ #[expect(dead_code)]
+ pub(crate) fn from_uapi(v: u64) -> Self {
+ match NonZeroU64::new(v) {
+ Some(size) => Self::Fixed(size),
+ None => Self::Auto,
+ }
+ }
+}
+
/// Owns a [`Vm`]'s destruction: the VM is killed exactly once, when this
/// value is dropped, regardless of how many `Arc<Vm>` references remain.
///
@@ -169,7 +191,6 @@ fn try_from(value: u32) -> Result<Self, Self::Error> {
impl<'drm> VmOwner<'drm> {
/// A reference for callers that want to use the VM, not own it.
- #[expect(dead_code)]
pub(crate) fn get(&self) -> Arc<Vm<'drm>> {
Arc::clone(&self.0)
}
@@ -211,6 +232,84 @@ fn drop(&mut self) {
}
}
+/// Final user/kernel VA layout for a VM.
+pub(crate) struct VmLayout {
+ /// Full GPU VA range covered by this VM.
+ pub(crate) full: Range<u64>,
+ /// User-accessible VA range. Empty for MCU VMs.
+ pub(crate) user: Range<u64>,
+}
+
+impl VmLayout {
+ /// Kernel VA range, reserved for future kernel object allocation.
+ #[expect(dead_code)]
+ pub(crate) fn kernel(&self) -> Range<u64> {
+ self.user.end..self.full.end
+ }
+
+ /// Compute a user/kernel split for a user VM from the full GPU VA range and
+ /// a user request.
+ pub(crate) fn compute(full: Range<u64>, req: UserVaRequest) -> Result<Self> {
+ // Minimum VA space reserved for kernel objects (heaps, ring buffers, ...).
+ const MIN_KERNEL_VA: u64 = u64::SZ_256M;
+
+ if full.end <= MIN_KERNEL_VA {
+ pr_err!(
+ "Invalid VA range {:#x}..{:#x}, kernel VA min required: >{:#x}\n",
+ full.start,
+ full.end,
+ MIN_KERNEL_VA
+ );
+ return Err(EINVAL);
+ }
+
+ let user_max = full.end - MIN_KERNEL_VA;
+
+ let user_end = match req {
+ UserVaRequest::Fixed(v) => {
+ let user_size = v.get();
+ if user_size > user_max {
+ pr_err!(
+ "Requested user VA range {:#x} exceeds maximum {:#x}\n",
+ user_size,
+ user_max
+ );
+ return Err(EINVAL);
+ }
+ user_size
+ }
+ UserVaRequest::Auto => {
+ let task_size = current!().mm().map(|mm| mm.task_size());
+ let candidate = match task_size {
+ // `task_size()` returns usize; widen to u64 for the comparison.
+ Some(t) if (t as u64) < full.end => t as u64,
+ None | Some(_) => {
+ // If the range exceeds 4G, split it in two so CPU and
+ // GPU share the same addresses (SVM).
+ if full.end > u64::SZ_4G {
+ full.end / 2
+ } else {
+ user_max
+ }
+ }
+ };
+ candidate.min(user_max)
+ }
+ };
+
+ let delta = full.end - user_end;
+ // Pick a kernel VA range that's a power of two, to have a clear split.
+ let kernel_va_range = 1u64 << delta.ilog2();
+ let kernel_va_start = full.end - kernel_va_range;
+ let full_start = full.start;
+
+ Ok(Self {
+ full,
+ user: full_start..kernel_va_start,
+ })
+ }
+}
+
/// Arguments for a virtual memory map operation.
struct VmMapArgs<'drm> {
/// Access permissions and caching behavior for the mapping.
@@ -386,26 +485,64 @@ pub(crate) struct Vm<'drm> {
/// Non-core part of the GPUVM. Can be used for stuff that doesn't modify the
/// internal mapping tree, like GpuVm::obtain()
gpuvm: ARef<GpuVm<GpuVmData<'drm>>>,
- /// VA range for this VM.
- va_range: Range<u64>,
+ /// VA layout for this VM.
+ pub(crate) layout: VmLayout,
}
impl<'drm> Vm<'drm> {
- /// Creates a new GPU virtual address space.
+ /// Creates the MCU/firmware VM.
///
- /// The VM is initialized with a page table configured according to the GPU's
- /// address translation capabilities and registered with the GPUVM framework.
- pub(crate) fn new(
+ /// The MCU VM is entirely kernel-managed: it has no user-accessible range.
+ pub(crate) fn new_for_fw(
+ dev: &'drm Device<Bound>,
+ ddev: &TyrDrmDevice,
+ mmu: ArcBorrow<'_, Mmu<'drm>>,
+ gpu_info: &GpuInfo,
+ ) -> Result<VmOwner<'drm>> {
+ // As in panthor: the CSF MCU is a Cortex-M7 and can only address 4G.
+ let layout = VmLayout {
+ full: 0..u64::SZ_4G,
+ user: 0..0u64,
+ };
+ Self::new_internal(dev, ddev, mmu, gpu_info, layout)
+ }
+
+ /// Creates a user VM, splitting the GPU VA range per `user_va`.
+ #[expect(dead_code)]
+ pub(crate) fn new_for_user(
dev: &'drm Device<Bound>,
ddev: &TyrDrmDevice,
mmu: ArcBorrow<'_, Mmu<'drm>>,
gpu_info: &GpuInfo,
- ) -> Result<Arc<Vm<'drm>>> {
+ user_va: UserVaRequest,
+ ) -> Result<VmOwner<'drm>> {
+ let mmu_features = MMU_FEATURES::from_raw(gpu_info.mmu_features);
+ let va_bits = mmu_features.va_bits().get();
+ let range = 0..(1u64 << va_bits);
+
+ let layout = VmLayout::compute(range.clone(), user_va).inspect_err(|_| {
+ dev_err!(
+ dev,
+ "Failed to split GPU VA range {:#x}..{:#x} into user and kernel regions\n",
+ range.start,
+ range.end
+ );
+ })?;
+ Self::new_internal(dev, ddev, mmu, gpu_info, layout)
+ }
+
+ /// Initializes a VM with the given layout and hands back its owner.
+ fn new_internal(
+ dev: &'drm Device<Bound>,
+ ddev: &TyrDrmDevice,
+ mmu: ArcBorrow<'_, Mmu<'drm>>,
+ gpu_info: &GpuInfo,
+ layout: VmLayout,
+ ) -> Result<VmOwner<'drm>> {
let mmu_features = MMU_FEATURES::from_raw(gpu_info.mmu_features);
let va_bits = mmu_features.va_bits().get();
let pa_bits = mmu_features.pa_bits().get();
- let range = 0..(1u64 << va_bits);
let reserve_range = 0..0u64;
// dummy_obj is used to initialize the GPUVM tree.
@@ -417,7 +554,7 @@ pub(crate) fn new(
c"Tyr::GpuVm",
ddev,
&*dummy_obj,
- range.clone(),
+ layout.full.clone(),
reserve_range,
GpuVmData::<'drm> {
_phantom: PhantomData::<&()>,
@@ -437,12 +574,12 @@ pub(crate) fn new(
mmu: mmu.into(),
gpuvm,
gpuvm_unique <- new_mutex!(gpuvm_unique),
- va_range: range,
+ layout,
}),
GFP_KERNEL,
)?;
- Ok(vm)
+ Ok(VmOwner(ManuallyDrop::new(vm)))
}
/// Returns the parent device used by this VM for DMA mapping and page-table operations.
@@ -467,11 +604,15 @@ fn deactivate(&self) -> Result {
}
/// Kills the VM by deactivating it and unmapping all regions.
- pub(crate) fn kill(&self) {
- // TODO: Turn the VM into a state where it can't be used.
+ ///
+ /// Only called from [`VmOwner`]'s `Drop`.
+ fn kill(&self) {
let _ = self.deactivate();
let _ = self
- .unmap_range(self.va_range.start, self.va_range.end - self.va_range.start)
+ .unmap_range(
+ self.layout.full.start,
+ self.layout.full.end - self.layout.full.start,
+ )
.inspect_err(|e| {
dev_err!(self.dev, "Failed to unmap range during deactivate: {:?}", e);
});
@@ -608,14 +749,14 @@ pub(crate) fn unmap_range(&self, va: u64, size: u64) -> Result {
let end = va.checked_add(size).ok_or(EINVAL)?;
- if va < self.va_range.start || end > self.va_range.end {
+ if va < self.layout.full.start || end > self.layout.full.end {
dev_err!(
self.dev,
"Unmap range {:#x}..{:#x} exceeds VM range {:#x}..{:#x}",
va,
end,
- self.va_range.start,
- self.va_range.end
+ self.layout.full.start,
+ self.layout.full.end
);
return Err(EINVAL);
}
@@ -625,7 +766,7 @@ pub(crate) fn unmap_range(&self, va: u64, size: u64) -> Result {
region: va..end,
};
- let full_vm = va == self.va_range.start && end == self.va_range.end;
+ let full_vm = va == self.layout.full.start && end == self.layout.full.end;
let mut resources = VmOpResources {
preallocated_gpuvas: if full_vm {
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 6/9] drm/tyr: add BO creation and lookup helpers
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
` (4 preceding siblings ...)
2026-09-07 16:47 ` [PATCH v2 5/9] drm/tyr: add user and MCU VM specifications Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 7/9] drm/tyr: refactor new_dummy_object to use new_object Ke Sun via B4 Relay
` (2 subsequent siblings)
8 siblings, 0 replies; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
Add helpers for the BO ioctls: new_object() creates a GEM object with
the size aligned up to PAGE_SIZE, and lookup_handle() resolves a handle
for a DRM file.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
drivers/gpu/drm/tyr/gem.rs | 31 ++++++++++++++++++++++++++++++-
1 file changed, 30 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
index 3bf3787f5c3fd..0e0989a48678f 100644
--- a/drivers/gpu/drm/tyr/gem.rs
+++ b/drivers/gpu/drm/tyr/gem.rs
@@ -9,8 +9,10 @@
use kernel::{
drm::gem::{
self,
- shmem, //
+ shmem,
+ BaseObject, //
},
+ page::PAGE_SIZE,
prelude::*,
sync::{
aref::ARef,
@@ -23,6 +25,7 @@
TyrDrmDevice,
TyrDrmDriver, //
},
+ file::TyrDrmFile,
vm::{
Vm,
VmMapFlags, //
@@ -53,6 +56,32 @@ fn new(_dev: &TyrDrmDevice, _size: usize, args: BoCreateArgs) -> impl PinInit<Se
/// Type alias for Tyr GEM buffer objects.
pub(crate) type Bo = gem::shmem::Object<BoData>;
+/// Create a new GEM buffer object.
+#[expect(dead_code)]
+pub(crate) fn new_object(ddev: &TyrDrmDevice, size: usize, flags: u32) -> Result<ARef<Bo>> {
+ if size == 0 {
+ return Err(EINVAL);
+ }
+
+ let aligned_size = size.checked_next_multiple_of(PAGE_SIZE).ok_or(EINVAL)?;
+
+ Bo::new(
+ ddev,
+ aligned_size,
+ shmem::ObjectConfig {
+ map_wc: true,
+ parent_resv_obj: None,
+ },
+ BoCreateArgs { flags },
+ )
+}
+
+/// Look up a GEM object by handle for a DRM file.
+#[expect(dead_code)]
+pub(crate) fn lookup_handle(file: &TyrDrmFile, handle: u32) -> Result<ARef<Bo>> {
+ Bo::lookup_handle(file, handle)
+}
+
/// Creates a dummy GEM object to serve as the root of a GPUVM.
pub(crate) fn new_dummy_object(ddev: &TyrDrmDevice) -> Result<ARef<Bo>> {
let bo = Bo::new(
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 7/9] drm/tyr: refactor new_dummy_object to use new_object
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
` (5 preceding siblings ...)
2026-09-07 16:47 ` [PATCH v2 6/9] drm/tyr: add BO creation and lookup helpers Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 8/9] drm/tyr: add VM-related ioctls Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 9/9] drm/tyr: add BO-related ioctls Ke Sun via B4 Relay
8 siblings, 0 replies; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
new_dummy_object() duplicated the BO creation code that new_object()
now provides; call new_object() instead.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
---
drivers/gpu/drm/tyr/gem.rs | 14 ++------------
1 file changed, 2 insertions(+), 12 deletions(-)
diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
index 0e0989a48678f..2523d05de5527 100644
--- a/drivers/gpu/drm/tyr/gem.rs
+++ b/drivers/gpu/drm/tyr/gem.rs
@@ -57,7 +57,6 @@ fn new(_dev: &TyrDrmDevice, _size: usize, args: BoCreateArgs) -> impl PinInit<Se
pub(crate) type Bo = gem::shmem::Object<BoData>;
/// Create a new GEM buffer object.
-#[expect(dead_code)]
pub(crate) fn new_object(ddev: &TyrDrmDevice, size: usize, flags: u32) -> Result<ARef<Bo>> {
if size == 0 {
return Err(EINVAL);
@@ -84,17 +83,8 @@ pub(crate) fn lookup_handle(file: &TyrDrmFile, handle: u32) -> Result<ARef<Bo>>
/// Creates a dummy GEM object to serve as the root of a GPUVM.
pub(crate) fn new_dummy_object(ddev: &TyrDrmDevice) -> Result<ARef<Bo>> {
- let bo = Bo::new(
- ddev,
- 4096,
- shmem::ObjectConfig {
- map_wc: true,
- parent_resv_obj: None,
- },
- BoCreateArgs { flags: 0 },
- )?;
-
- Ok(bo)
+ // FIXME: use a Rust resv-object abstraction once available, rather than a real BO.
+ new_object(ddev, PAGE_SIZE, 0)
}
/// Specifies how to choose a GPU virtual address for a [`KernelBo`].
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 8/9] drm/tyr: add VM-related ioctls
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
` (6 preceding siblings ...)
2026-09-07 16:47 ` [PATCH v2 7/9] drm/tyr: refactor new_dummy_object to use new_object Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
2026-09-07 16:47 ` [PATCH v2 9/9] drm/tyr: add BO-related ioctls Ke Sun via B4 Relay
8 siblings, 0 replies; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
Implement VM_CREATE, VM_DESTROY, VM_BIND and VM_GET_STATE. VM_CREATE
gives the new VM's owner to the per-file pool, so an aborted creation
never leaks a VM; VM_DESTROY takes it back and dropping it kills the
VM immediately even if in-flight jobs still hold references (matching
panthor: such jobs are expected to fault). VM_BIND runs synchronously
for now.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
drivers/gpu/drm/tyr/driver.rs | 12 +-
drivers/gpu/drm/tyr/file.rs | 308 ++++++++++++++++++++++++++++++++++++++++--
drivers/gpu/drm/tyr/gem.rs | 1 -
drivers/gpu/drm/tyr/vm.rs | 78 ++++++++---
4 files changed, 369 insertions(+), 30 deletions(-)
diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index 791c105ce626a..5757956470afd 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -33,7 +33,7 @@
Mutex, //
},
time,
- types::CovariantForLt, //
+ types::ForLt, //
};
use crate::{
@@ -72,6 +72,9 @@ pub(crate) struct TyrDrmRegistrationData<'drm> {
/// Firmware sections.
pub(crate) fw: Firmware<'drm>,
+ /// Memory management unit for address space slots.
+ pub(crate) mmu: Arc<Mmu<'drm>>,
+
#[pin]
clks: Mutex<Clocks>,
@@ -163,6 +166,7 @@ fn probe<'bound>(
let reg_data = pin_init!(TyrDrmRegistrationData {
pdev,
fw: firmware,
+ mmu,
clks <- new_mutex!(Clocks {
core: core_clk,
stacks: stacks_clk,
@@ -206,7 +210,7 @@ fn drop(self: Pin<&mut Self>) {}
impl drm::Driver for TyrDrmDriver {
type Data = ();
type RegistrationData<'drm> = TyrDrmRegistrationData<'drm>;
- type File = CovariantForLt!(TyrDrmFileData);
+ type File = ForLt!(TyrDrmFileData<'_>);
type Object = Bo;
type ParentDevice<Ctx: DeviceContext> = platform::Device<Ctx>;
@@ -215,6 +219,10 @@ impl drm::Driver for TyrDrmDriver {
kernel::declare_drm_ioctls! {
(PANTHOR_DEV_QUERY, drm_panthor_dev_query, ioctl::RENDER_ALLOW, TyrDrmFileData::dev_query),
+ (PANTHOR_VM_CREATE, drm_panthor_vm_create, ioctl::RENDER_ALLOW, TyrDrmFileData::vm_create),
+ (PANTHOR_VM_DESTROY, drm_panthor_vm_destroy, ioctl::RENDER_ALLOW, TyrDrmFileData::vm_destroy),
+ (PANTHOR_VM_BIND, drm_panthor_vm_bind, ioctl::RENDER_ALLOW, TyrDrmFileData::vm_bind),
+ (PANTHOR_VM_GET_STATE, drm_panthor_vm_get_state, ioctl::RENDER_ALLOW, TyrDrmFileData::vm_get_state),
}
}
diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs
index 933a365cb016e..dceaae6fb9717 100644
--- a/drivers/gpu/drm/tyr/file.rs
+++ b/drivers/gpu/drm/tyr/file.rs
@@ -3,37 +3,60 @@
use kernel::{
drm::{
self,
+ gem::BaseObject,
Registered, //
},
prelude::*,
- uaccess::UserSlice,
+ sizes::SizeConstants,
+ transmute::FromBytes,
+ uaccess::{
+ UserSlice,
+ UserSliceReader, //
+ },
uapi, //
};
-use crate::driver::{
- TyrDrmDevice,
- TyrDrmDriver,
- TyrDrmRegistrationData, //
+use crate::{
+ driver::{
+ TyrDrmDevice,
+ TyrDrmDriver,
+ TyrDrmRegistrationData, //
+ },
+ vm::{
+ UserVaRequest,
+ Vm,
+ VmBindOpType,
+ VmMapFlags,
+ VmPool, //
+ }, //
};
#[pin_data]
-pub(crate) struct TyrDrmFileData {}
+pub(crate) struct TyrDrmFileData<'a> {
+ reg: &'a TyrDrmRegistrationData<'a>,
+
+ #[pin]
+ vm_pool: VmPool<'a>,
+}
/// Convenience type alias for our DRM `File` type.
pub(crate) type TyrDrmFile = drm::file::File<TyrDrmDriver>;
-impl drm::file::DriverFile<'_> for TyrDrmFileData {
+impl<'a> drm::file::DriverFile<'a> for TyrDrmFileData<'a> {
type Driver = TyrDrmDriver;
fn open(
_device: &TyrDrmDevice<Registered>,
- _reg_data: &TyrDrmRegistrationData<'_>,
+ reg_data: &'a TyrDrmRegistrationData<'a>,
) -> impl PinInit<Self, Error> {
- Ok(Self {})
+ try_pin_init!(Self {
+ reg: reg_data,
+ vm_pool <- VmPool::new(),
+ })
}
}
-impl TyrDrmFileData {
+impl TyrDrmFileData<'_> {
pub(crate) fn dev_query(
_ddev: &TyrDrmDevice<Registered>,
reg_data: &TyrDrmRegistrationData<'_>,
@@ -65,4 +88,269 @@ pub(crate) fn dev_query(
}
}
}
+
+ pub(crate) fn vm_create(
+ ddev: &TyrDrmDevice<Registered>,
+ _reg_data: &TyrDrmRegistrationData<'_>,
+ vmcreate: &mut uapi::drm_panthor_vm_create,
+ file: &TyrDrmFile,
+ ) -> Result<u32> {
+ if vmcreate.flags != 0 {
+ dev_err!(
+ ddev.as_ref(),
+ "Invalid VM create flags: {:#x}\n",
+ vmcreate.flags
+ );
+ return Err(EINVAL);
+ }
+
+ let ret: Result<u32, Error> = file.inner_with(|pfile| {
+ let vm = Vm::new_for_user(
+ pfile.reg.pdev.as_ref(),
+ ddev,
+ pfile.reg.mmu.as_arc_borrow(),
+ &pfile.reg.gpu_info,
+ UserVaRequest::from_uapi(vmcreate.user_va_range),
+ )?;
+ let user_va_range = vm.layout.user.end;
+ let id = pfile.vm_pool.add(vm)?;
+ vmcreate.user_va_range = user_va_range;
+ vmcreate.id = id;
+
+ Ok(0)
+ });
+ ret
+ }
+
+ pub(crate) fn vm_destroy(
+ ddev: &TyrDrmDevice<Registered>,
+ _reg_data: &TyrDrmRegistrationData<'_>,
+ vmdestroy: &mut uapi::drm_panthor_vm_destroy,
+ file: &TyrDrmFile,
+ ) -> Result<u32> {
+ if vmdestroy.pad != 0 {
+ dev_err!(
+ ddev.as_ref(),
+ "Invalid VM destroy pad: {:#x}\n",
+ vmdestroy.pad
+ );
+ return Err(EINVAL);
+ }
+
+ let ret: Result<u32, Error> = file.inner_with(|pfile| {
+ pfile.vm_pool.remove(vmdestroy.id)?;
+ Ok(0)
+ });
+ ret
+ }
+
+ pub(crate) fn vm_bind(
+ ddev: &TyrDrmDevice<Registered>,
+ _reg_data: &TyrDrmRegistrationData<'_>,
+ vmbind: &mut uapi::drm_panthor_vm_bind,
+ file: &TyrDrmFile,
+ ) -> Result<u32> {
+ let async_flag = uapi::drm_panthor_vm_bind_flags_DRM_PANTHOR_VM_BIND_ASYNC;
+
+ if vmbind.flags & !async_flag != 0 {
+ dev_err!(
+ ddev.as_ref(),
+ "Invalid VM_BIND flags: {:#x}\n",
+ vmbind.flags
+ );
+ return Err(EINVAL);
+ }
+
+ if vmbind.flags & async_flag != 0 {
+ dev_err!(ddev.as_ref(), "Async VM_BIND not supported\n");
+ return Err(ENOTSUPP);
+ }
+
+ let count = vmbind.ops.count as usize;
+ if count == 0 {
+ return Ok(0);
+ }
+
+ let size_of_op = size_of::<VmBindOp>();
+ // Stride versions the UAPI struct: reject only undersized strides.
+ if size_of_op > vmbind.ops.stride as usize {
+ dev_err!(
+ ddev.as_ref(),
+ "Invalid VM_BIND op stride {} (expected at least {})\n",
+ vmbind.ops.stride,
+ size_of::<VmBindOp>()
+ );
+ return Err(EINVAL);
+ }
+ let stride = vmbind.ops.stride as usize;
+
+ let total_len = stride.checked_mul(count).ok_or_else(|| {
+ dev_err!(ddev.as_ref(), "VM_BIND ops length overflow\n");
+ EINVAL
+ })?;
+ let mut reader =
+ UserSlice::new(UserPtr::from_addr(vmbind.ops.array as usize), total_len).reader();
+ let mut ops = KVec::new();
+ for _ in 0..count {
+ ops.push(reader.read::<VmBindOp>()?, GFP_KERNEL)?;
+ read_padding_zero(&mut reader, stride - size_of_op)?;
+ }
+
+ let ret: Result<u32, Error> = file.inner_with(|pfile| {
+ let vm = pfile.vm_pool.get(vmbind.vm_id).ok_or_else(|| {
+ dev_err!(ddev.as_ref(), "Invalid VM_BIND vm_id: {}\n", vmbind.vm_id);
+ EINVAL
+ })?;
+
+ for (i, op) in ops.iter().enumerate() {
+ if let Err(e) = vm_bind_exec_op(&vm, file, op) {
+ dev_dbg!(ddev.as_ref(), "VM_BIND op {} failed: {:?}\n", i, e);
+ vmbind.ops.count = i as u32;
+ return Err(e);
+ }
+ }
+
+ Ok(0)
+ });
+ ret
+ }
+
+ pub(crate) fn vm_get_state(
+ ddev: &TyrDrmDevice<Registered>,
+ _reg_data: &TyrDrmRegistrationData<'_>,
+ vmgetstate: &mut uapi::drm_panthor_vm_get_state,
+ file: &TyrDrmFile,
+ ) -> Result<u32> {
+ file.inner_with(|pfile| {
+ let vm = pfile.vm_pool.get(vmgetstate.vm_id).ok_or_else(|| {
+ dev_err!(
+ ddev.as_ref(),
+ "Invalid VM_GET_STATE vm_id: {}\n",
+ vmgetstate.vm_id
+ );
+ EINVAL
+ })?;
+ vmgetstate.state = if vm.is_unusable() {
+ uapi::drm_panthor_vm_state_DRM_PANTHOR_VM_STATE_UNUSABLE
+ } else {
+ uapi::drm_panthor_vm_state_DRM_PANTHOR_VM_STATE_USABLE
+ };
+ Ok(0)
+ })
+ }
}
+
+fn vm_bind_exec_op(vm: &Vm<'_>, file: &TyrDrmFile, op: &VmBindOp) -> Result {
+ if op.size == 0 {
+ return Ok(());
+ }
+
+ if op.syncs.count != 0 {
+ dev_err!(vm.dev(), "VM_BIND op syncs not supported\n");
+ return Err(EINVAL);
+ }
+
+ let end = match op.va.checked_add(op.size) {
+ Some(end) => end,
+ None => {
+ dev_err!(vm.dev(), "VM_BIND op VA range overflow\n");
+ return Err(EINVAL);
+ }
+ };
+ if op.va < vm.layout.user.start || end > vm.layout.user.end {
+ dev_err!(
+ vm.dev(),
+ "VM_BIND op VA range {:#x}..{:#x} outside user range\n",
+ op.va,
+ end
+ );
+ return Err(EINVAL);
+ }
+
+ if (op.va | op.size | op.bo_offset) & (u64::SZ_4K - 1) != 0 {
+ dev_err!(vm.dev(), "VM_BIND op not GPU-page-aligned\n");
+ return Err(EINVAL);
+ }
+
+ match VmBindOpType::try_from(op.flags) {
+ Ok(VmBindOpType::Map) => {
+ // Once the VM is unusable only MAP ops are rejected; UNMAP
+ // stays available for cleanup (see the UAPI docs).
+ if vm.is_unusable() {
+ dev_err!(vm.dev(), "VM_BIND map op on unusable VM\n");
+ return Err(EINVAL);
+ }
+
+ let map_flags = match VmMapFlags::try_from(op.flags & !VmBindOpType::MASK) {
+ Ok(flags) => flags,
+ Err(_) => {
+ dev_err!(vm.dev(), "VM_BIND op invalid map flags {:#x}\n", op.flags);
+ return Err(EINVAL);
+ }
+ };
+ let bo = crate::gem::lookup_handle(file, op.bo_handle).map_err(|_| {
+ dev_err!(vm.dev(), "VM_BIND op invalid BO handle {}\n", op.bo_handle);
+ EINVAL
+ })?;
+ // Validate the BO window before mapping.
+ let bo_size = bo.size() as u64;
+ if op.size > bo_size || op.bo_offset > bo_size - op.size {
+ dev_err!(vm.dev(), "VM_BIND op BO range out of bounds\n");
+ return Err(EINVAL);
+ }
+ vm.map_bo_range(&bo, op.bo_offset, op.size, op.va, map_flags)
+ }
+ Ok(VmBindOpType::Unmap) => {
+ // Unmap must not carry map-specific flags or BO references.
+ if op.flags & !VmBindOpType::MASK != 0 || op.bo_handle != 0 || op.bo_offset != 0 {
+ dev_err!(
+ vm.dev(),
+ "VM_BIND UNMAP carries flags/BO refs: flags={:#x} bo_handle={} bo_offset={}\n",
+ op.flags,
+ op.bo_handle,
+ op.bo_offset
+ );
+ return Err(EINVAL);
+ }
+ vm.unmap_range(op.va, op.size)
+ }
+ Err(_) => {
+ dev_err!(
+ vm.dev(),
+ "VM_BIND op type {:#x} not supported\n",
+ op.flags & VmBindOpType::MASK
+ );
+ Err(EINVAL)
+ }
+ }
+}
+
+/// Reads `len` bytes of array padding, rejecting any nonzero byte with `E2BIG`.
+fn read_padding_zero(reader: &mut UserSliceReader, len: usize) -> Result {
+ let mut buf = [0u8; 64];
+ let mut remaining = len;
+ while remaining > 0 {
+ let chunk = remaining.min(buf.len());
+ reader.read_slice(&mut buf[..chunk])?;
+ if buf[..chunk].iter().any(|&b| b != 0) {
+ return Err(E2BIG);
+ }
+ remaining -= chunk;
+ }
+ Ok(())
+}
+
+#[repr(transparent)]
+struct VmBindOp(uapi::drm_panthor_vm_bind_op);
+
+impl core::ops::Deref for VmBindOp {
+ type Target = uapi::drm_panthor_vm_bind_op;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+// SAFETY: `VmBindOp` contains only integers, so any bit pattern is valid;
+// the `#[repr(transparent)]` wrapper has the same layout as the UAPI struct.
+unsafe impl FromBytes for VmBindOp {}
diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
index 2523d05de5527..e5030b645527d 100644
--- a/drivers/gpu/drm/tyr/gem.rs
+++ b/drivers/gpu/drm/tyr/gem.rs
@@ -76,7 +76,6 @@ pub(crate) fn new_object(ddev: &TyrDrmDevice, size: usize, flags: u32) -> Result
}
/// Look up a GEM object by handle for a DRM file.
-#[expect(dead_code)]
pub(crate) fn lookup_handle(file: &TyrDrmFile, handle: u32) -> Result<ARef<Bo>> {
Bo::lookup_handle(file, handle)
}
diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
index db9e2ccc55056..bd23f75a5bd8a 100644
--- a/drivers/gpu/drm/tyr/vm.rs
+++ b/drivers/gpu/drm/tyr/vm.rs
@@ -54,6 +54,11 @@
},
sync::{
aref::ARef,
+ atomic::{
+ Acquire,
+ Atomic,
+ Release, //
+ },
Arc,
ArcBorrow,
Mutex, //
@@ -173,7 +178,6 @@ pub(crate) enum UserVaRequest {
impl UserVaRequest {
/// UAPI boundary normalization: `0` -> [`Auto`](Self::Auto).
- #[expect(dead_code)]
pub(crate) fn from_uapi(v: u64) -> Self {
match NonZeroU64::new(v) {
Some(size) => Self::Fixed(size),
@@ -182,6 +186,38 @@ pub(crate) fn from_uapi(v: u64) -> Self {
}
}
+/// Operation type, packed into the top nibble of
+/// `drm_panthor_vm_bind_op::flags`.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) enum VmBindOpType {
+ /// Map a BO range into the VM.
+ Map,
+ /// Unmap a VA range.
+ Unmap,
+}
+
+impl VmBindOpType {
+ /// Bits occupied by the op type in `drm_panthor_vm_bind_op::flags`.
+ pub(crate) const MASK: u32 =
+ uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_MASK as u32;
+}
+
+impl TryFrom<u32> for VmBindOpType {
+ type Error = Error;
+
+ fn try_from(flags: u32) -> Result<Self, Self::Error> {
+ const MAP: u32 = uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_MAP as u32;
+ const UNMAP: u32 =
+ uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP as u32;
+
+ match flags & Self::MASK {
+ MAP => Ok(Self::Map),
+ UNMAP => Ok(Self::Unmap),
+ _ => Err(EINVAL),
+ }
+ }
+}
+
/// Owns a [`Vm`]'s destruction: the VM is killed exactly once, when this
/// value is dropped, regardless of how many `Arc<Vm>` references remain.
///
@@ -254,12 +290,6 @@ pub(crate) fn compute(full: Range<u64>, req: UserVaRequest) -> Result<Self> {
const MIN_KERNEL_VA: u64 = u64::SZ_256M;
if full.end <= MIN_KERNEL_VA {
- pr_err!(
- "Invalid VA range {:#x}..{:#x}, kernel VA min required: >{:#x}\n",
- full.start,
- full.end,
- MIN_KERNEL_VA
- );
return Err(EINVAL);
}
@@ -269,11 +299,6 @@ pub(crate) fn compute(full: Range<u64>, req: UserVaRequest) -> Result<Self> {
UserVaRequest::Fixed(v) => {
let user_size = v.get();
if user_size > user_max {
- pr_err!(
- "Requested user VA range {:#x} exceeds maximum {:#x}\n",
- user_size,
- user_max
- );
return Err(EINVAL);
}
user_size
@@ -487,6 +512,8 @@ pub(crate) struct Vm<'drm> {
gpuvm: ARef<GpuVm<GpuVmData<'drm>>>,
/// VA layout for this VM.
pub(crate) layout: VmLayout,
+ /// Whether the VM is unusable.
+ unusable: Atomic<bool>,
}
impl<'drm> Vm<'drm> {
@@ -508,7 +535,6 @@ pub(crate) fn new_for_fw(
}
/// Creates a user VM, splitting the GPU VA range per `user_va`.
- #[expect(dead_code)]
pub(crate) fn new_for_user(
dev: &'drm Device<Bound>,
ddev: &TyrDrmDevice,
@@ -575,6 +601,7 @@ fn new_internal(
gpuvm,
gpuvm_unique <- new_mutex!(gpuvm_unique),
layout,
+ unusable: Atomic::new(false),
}),
GFP_KERNEL,
)?;
@@ -607,6 +634,7 @@ fn deactivate(&self) -> Result {
///
/// Only called from [`VmOwner`]'s `Drop`.
fn kill(&self) {
+ self.mark_unusable();
let _ = self.deactivate();
let _ = self
.unmap_range(
@@ -618,6 +646,15 @@ fn kill(&self) {
});
}
+ /// Marks the VM unusable.
+ fn mark_unusable(&self) {
+ self.unusable.store(true, Release);
+ }
+
+ pub(crate) fn is_unusable(&self) -> bool {
+ self.unusable.load(Acquire)
+ }
+
/// Executes a virtual memory operation.
///
/// This handles both map and unmap operations by coordinating between the
@@ -729,6 +766,17 @@ pub(crate) fn map_bo_range(
};
let result = {
let mut gpuvm_unique = self.gpuvm_unique.lock();
+ // Check under the GPUVM lock so a concurrent kill cannot race
+ // with this operation.
+ if self.is_unusable() {
+ dev_err!(
+ self.dev,
+ "Failed to map VA {:#x}..{:#x}: VM is unusable\n",
+ req.region.start,
+ req.region.end
+ );
+ return Err(EINVAL);
+ }
self.exec_op(gpuvm_unique.as_mut().get_mut(), req, &mut resources)
};
// We flush the defer cleanup list now. Things will be different in
@@ -1162,7 +1210,6 @@ pub(crate) struct VmPool<'drm> {
impl<'drm> VmPool<'drm> {
/// Creates a new [`VmPool`].
- #[expect(dead_code)]
pub(crate) fn new() -> impl PinInit<Self> {
let ids = IdPool::new();
pin_init!(Self {
@@ -1177,7 +1224,6 @@ pub(crate) fn new() -> impl PinInit<Self> {
/// here and only the error is returned.
// TODO: allocate IDs with the XArray directly (once it grows range
// allocation, the equivalent of C's `XA_LIMIT`) and drop the IdPool.
- #[expect(dead_code)]
pub(crate) fn add(&self, vm: VmOwner<'drm>) -> Result<u32> {
let id = {
let mut ids = self.ids.lock();
@@ -1209,7 +1255,6 @@ pub(crate) fn add(&self, vm: VmOwner<'drm>) -> Result<u32> {
/// Removes the VM with the given ID, handing back its owner.
///
/// Dropping the returned [`VmOwner`] kills the VM immediately.
- #[expect(dead_code)]
pub(crate) fn remove(&self, id: u32) -> Result<VmOwner<'drm>> {
let mut vms = self.vms.lock();
match vms.remove(id as usize) {
@@ -1223,7 +1268,6 @@ pub(crate) fn remove(&self, id: u32) -> Result<VmOwner<'drm>> {
}
/// Gets a shared reference to the VM with the given ID.
- #[expect(dead_code)]
pub(crate) fn get(&self, id: u32) -> Option<Arc<Vm<'drm>>> {
let vms = self.vms.lock();
let borrow = vms.get(id as usize)?;
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread* [PATCH v2 9/9] drm/tyr: add BO-related ioctls
2026-09-07 16:47 [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
` (7 preceding siblings ...)
2026-09-07 16:47 ` [PATCH v2 8/9] drm/tyr: add VM-related ioctls Ke Sun via B4 Relay
@ 2026-09-07 16:47 ` Ke Sun via B4 Relay
8 siblings, 0 replies; 12+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-07 16:47 UTC (permalink / raw)
To: rust-for-linux
Cc: Miguel Ojeda, Boqun Feng, Gary Guo, Björn Roy Baron,
Benno Lossin, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Daniel Almeida, Tamir Duberstein,
Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
linux-kernel, linux-mm, dri-devel, Ke Sun, Alvin Sun
From: Alvin Sun <alvin.sun@linux.dev>
Implement BO_CREATE and BO_MMAP_OFFSET so userspace can allocate GPU
buffers and obtain a DRM mmap offset for the generic mmap path.
BO_CREATE page-aligns the requested size; BO_MMAP_OFFSET rejects
NO_MMAP objects, which must never be CPU-mapped.
Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>
---
drivers/gpu/drm/tyr/driver.rs | 2 ++
drivers/gpu/drm/tyr/file.rs | 70 +++++++++++++++++++++++++++++++++++++++++++
drivers/gpu/drm/tyr/gem.rs | 7 +++++
3 files changed, 79 insertions(+)
diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index 5757956470afd..06e0feae289c2 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -223,6 +223,8 @@ impl drm::Driver for TyrDrmDriver {
(PANTHOR_VM_DESTROY, drm_panthor_vm_destroy, ioctl::RENDER_ALLOW, TyrDrmFileData::vm_destroy),
(PANTHOR_VM_BIND, drm_panthor_vm_bind, ioctl::RENDER_ALLOW, TyrDrmFileData::vm_bind),
(PANTHOR_VM_GET_STATE, drm_panthor_vm_get_state, ioctl::RENDER_ALLOW, TyrDrmFileData::vm_get_state),
+ (PANTHOR_BO_CREATE, drm_panthor_bo_create, ioctl::RENDER_ALLOW, TyrDrmFileData::bo_create),
+ (PANTHOR_BO_MMAP_OFFSET, drm_panthor_bo_mmap_offset, ioctl::RENDER_ALLOW, TyrDrmFileData::bo_mmap_offset),
}
}
diff --git a/drivers/gpu/drm/tyr/file.rs b/drivers/gpu/drm/tyr/file.rs
index dceaae6fb9717..ec6b53c586d02 100644
--- a/drivers/gpu/drm/tyr/file.rs
+++ b/drivers/gpu/drm/tyr/file.rs
@@ -238,6 +238,76 @@ pub(crate) fn vm_get_state(
Ok(0)
})
}
+
+ pub(crate) fn bo_create(
+ ddev: &TyrDrmDevice<Registered>,
+ _reg_data: &TyrDrmRegistrationData<'_>,
+ bocreate: &mut uapi::drm_panthor_bo_create,
+ file: &TyrDrmFile,
+ ) -> Result<u32> {
+ if bocreate.size == 0
+ || bocreate.pad != 0
+ || bocreate.flags & !uapi::drm_panthor_bo_flags_DRM_PANTHOR_BO_NO_MMAP != 0
+ || bocreate.exclusive_vm_id != 0
+ {
+ dev_err!(
+ ddev.as_ref(),
+ "Invalid BO_CREATE params: size={}, pad={}, flags={:#x}, exclusive_vm_id={}\n",
+ bocreate.size,
+ bocreate.pad,
+ bocreate.flags,
+ bocreate.exclusive_vm_id
+ );
+ return Err(EINVAL);
+ }
+
+ let size = usize::try_from(bocreate.size).map_err(|_| {
+ dev_err!(
+ ddev.as_ref(),
+ "BO_CREATE size {:#x} too large\n",
+ bocreate.size
+ );
+ EINVAL
+ })?;
+ let bo = crate::gem::new_object(ddev, size, bocreate.flags)?;
+ bocreate.handle = bo.create_handle(file)?;
+ bocreate.size = bo.size() as u64;
+
+ Ok(0)
+ }
+
+ pub(crate) fn bo_mmap_offset(
+ ddev: &TyrDrmDevice<Registered>,
+ _reg_data: &TyrDrmRegistrationData<'_>,
+ bommap: &mut uapi::drm_panthor_bo_mmap_offset,
+ file: &TyrDrmFile,
+ ) -> Result<u32> {
+ if bommap.pad != 0 {
+ dev_err!(
+ ddev.as_ref(),
+ "BO mmap offset pad not zero: {}\n",
+ bommap.pad
+ );
+ return Err(EINVAL);
+ }
+
+ let bo = crate::gem::lookup_handle(file, bommap.handle).inspect_err(|_| {
+ dev_err!(ddev.as_ref(), "Invalid BO mmap handle: {}\n", bommap.handle);
+ })?;
+ if bo.flags() & uapi::drm_panthor_bo_flags_DRM_PANTHOR_BO_NO_MMAP != 0 {
+ dev_err!(ddev.as_ref(), "BO mmap offset on NO_MMAP object\n");
+ return Err(EPERM);
+ }
+ bommap.offset = bo.create_mmap_offset().inspect_err(|_| {
+ dev_err!(
+ ddev.as_ref(),
+ "Failed to create mmap offset for handle {}\n",
+ bommap.handle
+ );
+ })?;
+
+ Ok(0)
+ }
}
fn vm_bind_exec_op(vm: &Vm<'_>, file: &TyrDrmFile, op: &VmBindOp) -> Result {
diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
index e5030b645527d..27273636c09c9 100644
--- a/drivers/gpu/drm/tyr/gem.rs
+++ b/drivers/gpu/drm/tyr/gem.rs
@@ -38,6 +38,13 @@ pub(crate) struct BoData {
flags: u32,
}
+impl BoData {
+ /// Returns the flags the BO was created with.
+ pub(crate) fn flags(&self) -> u32 {
+ self.flags
+ }
+}
+
/// Provides a way to pass arguments when creating BoData
/// as required by the gem::DriverObject trait.
pub(crate) struct BoCreateArgs {
--
2.43.0
^ permalink raw reply [flat|nested] 12+ messages in thread