mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/9] drm/tyr: add VM and BO ioctl support
@ 2026-09-01 16:08 Ke Sun via B4 Relay
  2026-09-01 16:09 ` [PATCH 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
                   ` (10 more replies)
  0 siblings, 11 replies; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:08 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

Add the VM and BO ioctls to the Tyr driver, aligning its userspace
interface with panthor. The series is based on Danilo's `drm-file`
series on `drm-rust-next`, and uses the existing IdPool and xarray
abstractions for per-file VM management.

The panthor IGT tests pass on an RK3588S device.

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
Alvin Sun (9):
      rust: sizes: add SZ_4G constant
      rust: mm: add `task_size` helper
      rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
      drm/tyr: add per-file VM pool
      drm/tyr: add user and MCU VM specifications
      drm/tyr: add BO creation and lookup helpers
      drm/tyr: refactor new_dummy_object to use new_object
      drm/tyr: add VM-related ioctls
      drm/tyr: add BO-related ioctls

 drivers/gpu/drm/tyr/driver.rs   |  14 +-
 drivers/gpu/drm/tyr/file.rs     | 394 ++++++++++++++++++++++++++++++++++++++--
 drivers/gpu/drm/tyr/fw.rs       |   8 +-
 drivers/gpu/drm/tyr/gem.rs      |  41 ++++-
 drivers/gpu/drm/tyr/pool.rs     | 102 +++++++++++
 drivers/gpu/drm/tyr/tyr.rs      |   1 +
 drivers/gpu/drm/tyr/vm.rs       | 158 +++++++++++++++-
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/mm.rs               |   7 +
 rust/kernel/sizes.rs            |  12 ++
 rust/kernel/sync/arc.rs         |  12 +-
 11 files changed, 714 insertions(+), 36 deletions(-)
---
base-commit: 0b0aa9dcf17b6cffa9e325ff641e843b20d40c31
change-id: 20260901-tyr-ioctls-f10ef0dcbfa6

Best regards,
-- 
Ke Sun <sunke@kylinos.cn>



^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH 1/9] rust: sizes: add SZ_4G constant
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-02 12:57   ` Daniel Almeida
  2026-09-06 12:47   ` Gary Guo
  2026-09-01 16:09 ` [PATCH 2/9] rust: mm: add `task_size` helper Ke Sun via B4 Relay
                   ` (9 subsequent siblings)
  10 siblings, 2 replies; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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>
---
 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 1124785e210b3..3c37993991b1e 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -110,6 +110,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 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] 36+ messages in thread

* [PATCH 2/9] rust: mm: add `task_size` helper
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
  2026-09-01 16:09 ` [PATCH 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-03 13:09   ` Daniel Almeida
  2026-09-01 16:09 ` [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` Ke Sun via B4 Relay
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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>
---
 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] 36+ messages in thread

* [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
  2026-09-01 16:09 ` [PATCH 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
  2026-09-01 16:09 ` [PATCH 2/9] rust: mm: add `task_size` helper Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-03 13:12   ` Daniel Almeida
  2026-09-06 12:47   ` Gary Guo
  2026-09-01 16:09 ` [PATCH 4/9] drm/tyr: add per-file VM pool Ke Sun via B4 Relay
                   ` (7 subsequent siblings)
  10 siblings, 2 replies; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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>
---
 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 5ac4961b7cd20..cd6faba84ffcb 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] 36+ messages in thread

* [PATCH 4/9] drm/tyr: add per-file VM pool
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
                   ` (2 preceding siblings ...)
  2026-09-01 16:09 ` [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-03 17:51   ` Daniel Almeida
  2026-09-01 16:09 ` [PATCH 5/9] drm/tyr: add user and MCU VM specifications Ke Sun via B4 Relay
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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 a per-file VM pool using the IdPool for ID allocation
and an XArray for VM storage.

Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
 drivers/gpu/drm/tyr/pool.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/tyr/tyr.rs  |   1 +
 2 files changed, 103 insertions(+)

diff --git a/drivers/gpu/drm/tyr/pool.rs b/drivers/gpu/drm/tyr/pool.rs
new file mode 100644
index 0000000000000..1745116930c3e
--- /dev/null
+++ b/drivers/gpu/drm/tyr/pool.rs
@@ -0,0 +1,102 @@
+// SPDX-License-Identifier: GPL-2.0 or MIT
+
+//! Per-drm-file VM id pool.
+
+use kernel::{
+    id_pool::IdPool,
+    new_mutex,
+    prelude::*,
+    sync::{
+        Arc,
+        ArcBorrow,
+        Mutex, //
+    },
+    xarray::{
+        AllocKind,
+        XArray, //
+    }, //
+};
+
+use crate::vm::Vm;
+
+/// Maximum number of VMs per open file. Matches panthor's
+/// `PANTHOR_MAX_VMS_PER_FILE`.
+pub(crate) const PANTHOR_MAX_VMS_PER_FILE: u32 = 32;
+
+/// Per-open-file pool of VMs.
+#[pin_data]
+pub(crate) struct VmPool<'drm> {
+    #[pin]
+    ids: Mutex<IdPool>,
+    #[pin]
+    vms: XArray<Arc<Vm<'drm>>>,
+}
+
+impl<'drm> VmPool<'drm> {
+    /// Creates a new [`VmPool`] with capacity for [`PANTHOR_MAX_VMS_PER_FILE`] VMs.
+    pub(crate) fn new() -> Result<impl PinInit<Self>> {
+        let ids = IdPool::with_capacity(PANTHOR_MAX_VMS_PER_FILE as usize, GFP_KERNEL)?;
+        Ok(pin_init!(Self {
+            ids <- new_mutex!(ids),
+            vms <- XArray::new(AllocKind::Alloc),
+        }))
+    }
+
+    /// Inserts a VM into the pool, returning the allocated ID.
+    pub(crate) fn add(&self, vm: ArcBorrow<'_, Vm<'drm>>) -> Result<u32> {
+        let id = {
+            let mut ids = self.ids.lock();
+            ids.find_unused_id(1).ok_or(ENOSPC)?.acquire()
+        };
+
+        let vm: Arc<Vm<'drm>> = vm.into();
+        let mut vms = self.vms.lock();
+        match vms.store(id, vm, GFP_KERNEL) {
+            Ok(previous) => {
+                // Drop the previous entry (expected `None`).
+                drop(previous);
+                Ok(id as u32)
+            }
+            Err(err) => {
+                // Drop the XArray spinlock before acquiring the `ids` mutex.
+                drop(vms);
+                // Release the stored entry and the pooled id.
+                drop(err.value);
+                let mut ids = self.ids.lock();
+                ids.release_id(id);
+                Err(err.error)
+            }
+        }
+    }
+
+    /// Removes the VM with the given ID.
+    pub(crate) fn remove(&self, id: u32) -> Result<Arc<Vm<'drm>>> {
+        let mut vms = self.vms.lock();
+        match vms.remove(id as usize) {
+            Some(vm) => {
+                drop(vms);
+                let mut ids = self.ids.lock();
+                ids.release_id(id as usize);
+                Ok(vm)
+            }
+            None => Err(EINVAL),
+        }
+    }
+
+    /// Gets the VM with the given ID.
+    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))
+    }
+
+    /// Removes and returns the first VM in the pool.
+    pub(crate) fn pop_first(&self) -> Option<Arc<Vm<'drm>>> {
+        for id in 0..PANTHOR_MAX_VMS_PER_FILE {
+            if let Ok(vm) = self.remove(id) {
+                return Some(vm);
+            }
+        }
+        None
+    }
+}
diff --git a/drivers/gpu/drm/tyr/tyr.rs b/drivers/gpu/drm/tyr/tyr.rs
index e7ec450bdc9c0..8c0c18c1a970b 100644
--- a/drivers/gpu/drm/tyr/tyr.rs
+++ b/drivers/gpu/drm/tyr/tyr.rs
@@ -13,6 +13,7 @@
 mod gem;
 mod gpu;
 mod mmu;
+mod pool;
 mod regs;
 mod slot;
 mod vm;

-- 
2.43.0



^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH 5/9] drm/tyr: add user and MCU VM specifications
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
                   ` (3 preceding siblings ...)
  2026-09-01 16:09 ` [PATCH 4/9] drm/tyr: add per-file VM pool Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-03 18:10   ` Daniel Almeida
  2026-09-01 16:09 ` [PATCH 6/9] drm/tyr: add BO creation and lookup helpers Ke Sun via B4 Relay
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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>

Distinguish MCU VMs from user VMs, and compute the user/kernel
GPU VA split for user VMs.

Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
 drivers/gpu/drm/tyr/fw.rs |   8 +--
 drivers/gpu/drm/tyr/vm.rs | 134 +++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 131 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs
index 47d25c901bd01..9b4b488521b85 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,
+        VmSpec, //
+    }, //
 };
 
 mod parser;
@@ -220,7 +222,7 @@ 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(dev, ddev, mmu, gpu_info, VmSpec::Mcu)?;
         vm.activate()?;
 
         let result = (|| {
diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
index c5e307b1e2416..76c3d60bb2fe2 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::num::NonZeroU64;
 use core::ops::Range;
 
 use kernel::{
@@ -43,6 +44,8 @@
     new_mutex,
     prelude::*,
     sizes::{
+        LargeSizeConstants,
+        SizeConstants,
         SZ_1G,
         SZ_2M,
         SZ_4K, //
@@ -154,6 +157,109 @@ 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).
+    pub(crate) fn from_uapi(v: u64) -> Self {
+        match NonZeroU64::new(v) {
+            Some(size) => Self::Fixed(size),
+            None => Self::Auto,
+        }
+    }
+}
+
+pub(crate) enum VmSpec {
+    /// MCU/firmware VM, entirely kernel-managed.
+    Mcu,
+    /// User VM: full GPU VA range, split into user/kernel per `user_va`.
+    User { user_va: UserVaRequest },
+}
+
+/// 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.
@@ -329,8 +435,8 @@ 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> {
@@ -343,6 +449,7 @@ pub(crate) fn new(
         ddev: &TyrDrmDevice,
         mmu: ArcBorrow<'_, Mmu<'drm>>,
         gpu_info: &GpuInfo,
+        spec: VmSpec,
     ) -> Result<Arc<Vm<'drm>>> {
         let mmu_features = MMU_FEATURES::from_raw(gpu_info.mmu_features);
         let va_bits = mmu_features.va_bits().get();
@@ -351,6 +458,14 @@ pub(crate) fn new(
         let range = 0..(1u64 << va_bits);
         let reserve_range = 0..0u64;
 
+        let layout = match spec {
+            VmSpec::Mcu => VmLayout {
+                full: range.clone(),
+                user: 0..0u64,
+            },
+            VmSpec::User { user_va } => VmLayout::compute(range.clone(), user_va)?,
+        };
+
         // dummy_obj is used to initialize the GPUVM tree.
         let dummy_obj = gem::new_dummy_object(ddev).inspect_err(|e| {
             dev_err!(dev, "Failed to create dummy GEM object: {:?}", e);
@@ -380,7 +495,7 @@ pub(crate) fn new(
                 mmu: mmu.into(),
                 gpuvm,
                 gpuvm_unique <- new_mutex!(gpuvm_unique),
-                va_range: range,
+                layout,
             }),
             GFP_KERNEL,
         )?;
@@ -414,7 +529,10 @@ pub(crate) fn kill(&self) {
         // TODO: Turn the VM into a state where it can't be used.
         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);
             });
@@ -551,14 +669,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);
         }
@@ -568,7 +686,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] 36+ messages in thread

* [PATCH 6/9] drm/tyr: add BO creation and lookup helpers
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
                   ` (4 preceding siblings ...)
  2026-09-01 16:09 ` [PATCH 5/9] drm/tyr: add user and MCU VM specifications Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-03 22:06   ` Daniel Almeida
  2026-09-01 16:09 ` [PATCH 7/9] drm/tyr: refactor new_dummy_object to use new_object Ke Sun via B4 Relay
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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 the new_object() and lookup_handle() helpers.

Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
 drivers/gpu/drm/tyr/gem.rs | 29 ++++++++++++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
index 3bf3787f5c3fd..be1affe80db1a 100644
--- a/drivers/gpu/drm/tyr/gem.rs
+++ b/drivers/gpu/drm/tyr/gem.rs
@@ -9,9 +9,11 @@
 use kernel::{
     drm::gem::{
         self,
-        shmem, //
+        shmem,
+        BaseObject, //
     },
     prelude::*,
+    sizes::SZ_4K,
     sync::{
         aref::ARef,
         Arc, //
@@ -23,6 +25,7 @@
         TyrDrmDevice,
         TyrDrmDriver, //
     },
+    file::TyrDrmFile,
     vm::{
         Vm,
         VmMapFlags, //
@@ -53,6 +56,30 @@ 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.
+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(SZ_4K).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.
+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] 36+ messages in thread

* [PATCH 7/9] drm/tyr: refactor new_dummy_object to use new_object
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
                   ` (5 preceding siblings ...)
  2026-09-01 16:09 ` [PATCH 6/9] drm/tyr: add BO creation and lookup helpers Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-03 22:16   ` Daniel Almeida
  2026-09-01 16:09 ` [PATCH 8/9] drm/tyr: add VM-related ioctls Ke Sun via B4 Relay
                   ` (3 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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>
---
 drivers/gpu/drm/tyr/gem.rs | 13 ++-----------
 1 file changed, 2 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
index be1affe80db1a..d9ddcb287f52b 100644
--- a/drivers/gpu/drm/tyr/gem.rs
+++ b/drivers/gpu/drm/tyr/gem.rs
@@ -82,17 +82,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, 4096, 0)
 }
 
 /// Specifies how to choose a GPU virtual address for a [`KernelBo`].

-- 
2.43.0



^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH 8/9] drm/tyr: add VM-related ioctls
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
                   ` (6 preceding siblings ...)
  2026-09-01 16:09 ` [PATCH 7/9] drm/tyr: refactor new_dummy_object to use new_object Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-04 18:44   ` Daniel Almeida
  2026-09-01 16:09 ` [PATCH 9/9] drm/tyr: add BO-related ioctls Ke Sun via B4 Relay
                   ` (2 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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>

Manage per-file user VMs.

- VM_CREATE creates a user VM and returns its ID.
- VM_DESTROY destroys the VM identified by the given ID.
- VM_BIND maps or unmaps BO ranges in the VM's user VA space.
- VM_GET_STATE reports whether the VM is usable or unusable.

Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
 drivers/gpu/drm/tyr/driver.rs |  12 +-
 drivers/gpu/drm/tyr/file.rs   | 324 ++++++++++++++++++++++++++++++++++++++++--
 drivers/gpu/drm/tyr/vm.rs     |  24 +++-
 3 files changed, 346 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
index 94bc85635725e..b3145526ada06 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>,
 
@@ -164,6 +167,7 @@ fn probe<'bound>(
         let reg_data = pin_init!(TyrDrmRegistrationData {
                 pdev,
                 fw: firmware,
+                mmu,
                 clks <- new_mutex!(Clocks {
                     core: core_clk,
                     stacks: stacks_clk,
@@ -207,7 +211,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>;
 
@@ -216,6 +220,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..157bc40e1cac4 100644
--- a/drivers/gpu/drm/tyr/file.rs
+++ b/drivers/gpu/drm/tyr/file.rs
@@ -3,37 +3,70 @@
 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, //
+    },
+    pool::VmPool,
+    vm::{
+        UserVaRequest,
+        Vm,
+        VmMapFlags,
+        VmSpec, //
+    }, //
 };
 
-#[pin_data]
-pub(crate) struct TyrDrmFileData {}
+#[pin_data(PinnedDrop)]
+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 {
+#[pinned_drop]
+impl PinnedDrop for TyrDrmFileData<'_> {
+    fn drop(self: Pin<&mut Self>) {
+        let proj = self.project();
+        while let Some(vm) = proj.vm_pool.pop_first() {
+            vm.kill();
+        }
+    }
+}
+
+impl TyrDrmFileData<'_> {
     pub(crate) fn dev_query(
         _ddev: &TyrDrmDevice<Registered>,
         reg_data: &TyrDrmRegistrationData<'_>,
@@ -65,4 +98,273 @@ 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(|fd| {
+            let vm = Vm::new(
+                fd.reg.pdev.as_ref(),
+                ddev,
+                fd.reg.mmu.as_arc_borrow(),
+                &fd.reg.gpu_info,
+                VmSpec::User {
+                    user_va: UserVaRequest::from_uapi(vmcreate.user_va_range),
+                },
+            )?;
+            vmcreate.user_va_range = vm.layout.user.end;
+
+            let id = fd.vm_pool.add(vm.as_arc_borrow()).inspect_err(|_| {
+                vm.kill();
+            })?;
+            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(|fd| {
+            let vm = fd.vm_pool.remove(vmdestroy.id)?;
+            vm.kill();
+            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 {}\n",
+                vmbind.ops.stride
+            );
+            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(|fd| {
+            let vm = fd.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(|fd| {
+            let vm = fd.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 vm.is_unusable() {
+        dev_err!(vm.dev(), "VM_BIND on destroyed VM\n");
+        return Err(EINVAL);
+    }
+
+    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);
+    }
+
+    const TYPE_MASK: u32 =
+        uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_MASK as u32;
+    const TYPE_MAP: u32 = uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_MAP as u32;
+    const TYPE_UNMAP: u32 =
+        uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP as u32;
+
+    match op.flags & TYPE_MASK {
+        TYPE_MAP => {
+            let map_flags = match VmMapFlags::try_from(op.flags & !TYPE_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)
+        }
+        TYPE_UNMAP => {
+            // Unmap must not carry map-specific flags or BO references.
+            if op.flags & !TYPE_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)
+        }
+        _ => {
+            dev_err!(vm.dev(), "VM_BIND op type {:#x} not supported\n", op.flags);
+            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/vm.rs b/drivers/gpu/drm/tyr/vm.rs
index 76c3d60bb2fe2..610bab69c1a55 100644
--- a/drivers/gpu/drm/tyr/vm.rs
+++ b/drivers/gpu/drm/tyr/vm.rs
@@ -10,6 +10,10 @@
 use core::marker::PhantomData;
 use core::num::NonZeroU64;
 use core::ops::Range;
+use core::sync::atomic::{
+    AtomicBool,
+    Ordering, //
+};
 
 use kernel::{
     device::{
@@ -437,6 +441,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: AtomicBool,
 }
 
 impl<'drm> Vm<'drm> {
@@ -496,6 +502,7 @@ pub(crate) fn new(
                 gpuvm,
                 gpuvm_unique <- new_mutex!(gpuvm_unique),
                 layout,
+                unusable: AtomicBool::new(false),
             }),
             GFP_KERNEL,
         )?;
@@ -526,7 +533,7 @@ 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.
+        self.mark_unusable();
         let _ = self.deactivate();
         let _ = self
             .unmap_range(
@@ -538,6 +545,15 @@ pub(crate) fn kill(&self) {
             });
     }
 
+    /// Marks the VM unusable.
+    pub(crate) fn mark_unusable(&self) {
+        self.unusable.store(true, Ordering::Release);
+    }
+
+    pub(crate) fn is_unusable(&self) -> bool {
+        self.unusable.load(Ordering::Acquire)
+    }
+
     /// Executes a virtual memory operation.
     ///
     /// This handles both map and unmap operations by coordinating between the
@@ -649,6 +665,12 @@ pub(crate) fn map_bo_range(
         };
         let result = {
             let mut gpuvm_unique = self.gpuvm_unique.lock();
+            // Check under the GPUVM lock so a concurrent `mark_unusable()`
+            // teardown cannot race with this operation.
+            if self.is_unusable() {
+                dev_err!(self.dev, "Cannot map on unusable VM\n");
+                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

-- 
2.43.0



^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH 9/9] drm/tyr: add BO-related ioctls
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
                   ` (7 preceding siblings ...)
  2026-09-01 16:09 ` [PATCH 8/9] drm/tyr: add VM-related ioctls Ke Sun via B4 Relay
@ 2026-09-01 16:09 ` Ke Sun via B4 Relay
  2026-09-04 20:35   ` Daniel Almeida
  2026-09-02  0:14 ` [PATCH 0/9] drm/tyr: add VM and BO ioctl support Deborah Brouwer
  2026-09-07 16:47 ` [PATCH v2 " Ke Sun via B4 Relay
  10 siblings, 1 reply; 36+ messages in thread
From: Ke Sun via B4 Relay @ 2026-09-01 16:09 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 buffer creation and BO mmap offset retrieval.

- BO_CREATE page-aligns the size and returns a handle with
  write-combined mapping.
- BO_MMAP_OFFSET provides the offset for the DRM generic mmap
  path, rejecting NO_MMAP objects and non-zero pad.

Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
---
 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 b3145526ada06..fc8d11c9ba1ca 100644
--- a/drivers/gpu/drm/tyr/driver.rs
+++ b/drivers/gpu/drm/tyr/driver.rs
@@ -224,6 +224,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 157bc40e1cac4..16abf2968299c 100644
--- a/drivers/gpu/drm/tyr/file.rs
+++ b/drivers/gpu/drm/tyr/file.rs
@@ -252,6 +252,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.create_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 d9ddcb287f52b..f404139f54f32 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 create_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] 36+ messages in thread

* Re: [PATCH 0/9] drm/tyr: add VM and BO ioctl support
  2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
                   ` (8 preceding siblings ...)
  2026-09-01 16:09 ` [PATCH 9/9] drm/tyr: add BO-related ioctls Ke Sun via B4 Relay
@ 2026-09-02  0:14 ` Deborah Brouwer
  2026-09-07 16:47 ` [PATCH v2 " Ke Sun via B4 Relay
  10 siblings, 0 replies; 36+ messages in thread
From: Deborah Brouwer @ 2026-09-02  0:14 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 Wed, Sep 02, 2026 at 12:08:59AM +0800, Ke Sun wrote:
> Add the VM and BO ioctls to the Tyr driver, aligning its userspace
> interface with panthor. The series is based on Danilo's `drm-file`
> series on `drm-rust-next`, and uses the existing IdPool and xarray
> abstractions for per-file VM management.
> 
> The panthor IGT tests pass on an RK3588S device.

Hi Alvin,

Thanks for sending this series, I am still reviewing it but this is what I
noticed today:

1. Do we still need a separate pool module, since this is just for VmPool,
could we put it in vm.rs?

2. We might have a problem matching panthor’s 32 VMs per file, since the
minimum capacity of IdPool is MAX_INLINE_LEN. But maybe it will be ok to
extend this to 64 VMs per file. But then when we drop the vm pool we
better not rely on that PANTHOR_MAX_VMS_PER_FILE constant.

3. Also, I am not sure, but do we need to call vm.kill() when we drop the
vm_pool? Does it depend on the vms being activated? And if they have been
activated, should this go in Vm::drop() instead? 

4. Could you use dev_err! instead of pr_err! please, we tried to convert
over to that in the booting series.

5. We should be able to compile each patch in your series separately (to
help with future bisecting) so use the annotation #[expect(dead_code)] for
patches early in the series and then remove it in patches later in the
series when you actually use the code.

6. It would be nice if you could give a link to a repo where you have
applied all the prerequisite series and fixed conflicts. Could you still
update the branch from:
https://gitlab.freedesktop.org/panfrost/linux/-/merge_requests/64

Thanks,
Deborah

> 
> Signed-off-by: Ke Sun <sunke@kylinos.cn>
> ---
> Alvin Sun (9):
>       rust: sizes: add SZ_4G constant
>       rust: mm: add `task_size` helper
>       rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
>       drm/tyr: add per-file VM pool
>       drm/tyr: add user and MCU VM specifications
>       drm/tyr: add BO creation and lookup helpers
>       drm/tyr: refactor new_dummy_object to use new_object
>       drm/tyr: add VM-related ioctls
>       drm/tyr: add BO-related ioctls
> 
>  drivers/gpu/drm/tyr/driver.rs   |  14 +-
>  drivers/gpu/drm/tyr/file.rs     | 394 ++++++++++++++++++++++++++++++++++++++--
>  drivers/gpu/drm/tyr/fw.rs       |   8 +-
>  drivers/gpu/drm/tyr/gem.rs      |  41 ++++-
>  drivers/gpu/drm/tyr/pool.rs     | 102 +++++++++++
>  drivers/gpu/drm/tyr/tyr.rs      |   1 +
>  drivers/gpu/drm/tyr/vm.rs       | 158 +++++++++++++++-
>  rust/bindings/bindings_helper.h |   1 +
>  rust/kernel/mm.rs               |   7 +
>  rust/kernel/sizes.rs            |  12 ++
>  rust/kernel/sync/arc.rs         |  12 +-
>  11 files changed, 714 insertions(+), 36 deletions(-)
> ---
> base-commit: 0b0aa9dcf17b6cffa9e325ff641e843b20d40c31
> change-id: 20260901-tyr-ioctls-f10ef0dcbfa6
> 
> Best regards,
> -- 
> Ke Sun <sunke@kylinos.cn>
> 

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 1/9] rust: sizes: add SZ_4G constant
  2026-09-01 16:09 ` [PATCH 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
@ 2026-09-02 12:57   ` Daniel Almeida
  2026-09-06 12:47   ` Gary Guo
  1 sibling, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-02 12:57 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> 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>
> ---
> 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 1124785e210b3..3c37993991b1e 100644
> --- a/rust/bindings/bindings_helper.h
> +++ b/rust/bindings/bindings_helper.h
> @@ -110,6 +110,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 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
> 
> 

Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 2/9] rust: mm: add `task_size` helper
  2026-09-01 16:09 ` [PATCH 2/9] rust: mm: add `task_size` helper Ke Sun via B4 Relay
@ 2026-09-03 13:09   ` Daniel Almeida
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-03 13:09 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> 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>
> ---
> 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
> 
> 


Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
  2026-09-01 16:09 ` [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` Ke Sun via B4 Relay
@ 2026-09-03 13:12   ` Daniel Almeida
  2026-09-06 12:47   ` Gary Guo
  1 sibling, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-03 13:12 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> 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>
> ---
> 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 5ac4961b7cd20..cd6faba84ffcb 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
> 
> 

Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 4/9] drm/tyr: add per-file VM pool
  2026-09-01 16:09 ` [PATCH 4/9] drm/tyr: add per-file VM pool Ke Sun via B4 Relay
@ 2026-09-03 17:51   ` Daniel Almeida
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-03 17:51 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> From: Alvin Sun <alvin.sun@linux.dev>
> 
> Add a per-file VM pool using the IdPool for ID allocation
> and an XArray for VM storage.

Can you say a few more words here? You don’t need to over-explain, but I
don’t think this alone says _why_ your change is needed for the community
at large.

> 
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> ---
> drivers/gpu/drm/tyr/pool.rs | 102 ++++++++++++++++++++++++++++++++++++++++++++
> drivers/gpu/drm/tyr/tyr.rs  |   1 +
> 2 files changed, 103 insertions(+)
> 
> diff --git a/drivers/gpu/drm/tyr/pool.rs b/drivers/gpu/drm/tyr/pool.rs
> new file mode 100644
> index 0000000000000..1745116930c3e
> --- /dev/null
> +++ b/drivers/gpu/drm/tyr/pool.rs
> @@ -0,0 +1,102 @@
> +// SPDX-License-Identifier: GPL-2.0 or MIT
> +
> +//! Per-drm-file VM id pool.
> +
> +use kernel::{
> +    id_pool::IdPool,
> +    new_mutex,
> +    prelude::*,
> +    sync::{
> +        Arc,
> +        ArcBorrow,
> +        Mutex, //
> +    },
> +    xarray::{
> +        AllocKind,
> +        XArray, //
> +    }, //
> +};
> +
> +use crate::vm::Vm;
> +
> +/// Maximum number of VMs per open file. Matches panthor's
> +/// `PANTHOR_MAX_VMS_PER_FILE`.
> +pub(crate) const PANTHOR_MAX_VMS_PER_FILE: u32 = 32;
> +
> +/// Per-open-file pool of VMs.
> +#[pin_data]
> +pub(crate) struct VmPool<'drm> {

I agree with Deborah that this should be in vm.rs itself.

> +    #[pin]
> +    ids: Mutex<IdPool>,

Can you add a todo here so we use the XArray directly in the future?

> +    #[pin]
> +    vms: XArray<Arc<Vm<'drm>>>,
> +}
> +
> +impl<'drm> VmPool<'drm> {
> +    /// Creates a new [`VmPool`] with capacity for [`PANTHOR_MAX_VMS_PER_FILE`] VMs.
> +    pub(crate) fn new() -> Result<impl PinInit<Self>> {
> +        let ids = IdPool::with_capacity(PANTHOR_MAX_VMS_PER_FILE as usize, GFP_KERNEL)?;
> +        Ok(pin_init!(Self {
> +            ids <- new_mutex!(ids),
> +            vms <- XArray::new(AllocKind::Alloc),
> +        }))
> +    }
> +
> +    /// Inserts a VM into the pool, returning the allocated ID.
> +    pub(crate) fn add(&self, vm: ArcBorrow<'_, Vm<'drm>>) -> Result<u32> {
> +        let id = {
> +            let mut ids = self.ids.lock();
> +            ids.find_unused_id(1).ok_or(ENOSPC)?.acquire()
> +        };
> +
> +        let vm: Arc<Vm<'drm>> = vm.into();
> +        let mut vms = self.vms.lock();
> +        match vms.store(id, vm, GFP_KERNEL) {
> +            Ok(previous) => {
> +                // Drop the previous entry (expected `None`).
> +                drop(previous);
> +                Ok(id as u32)
> +            }
> +            Err(err) => {
> +                // Drop the XArray spinlock before acquiring the `ids` mutex.
> +                drop(vms);
> +                // Release the stored entry and the pooled id.
> +                drop(err.value);
> +                let mut ids = self.ids.lock();
> +                ids.release_id(id);
> +                Err(err.error)
> +            }
> +        }
> +    }
> +
> +    /// Removes the VM with the given ID.
> +    pub(crate) fn remove(&self, id: u32) -> Result<Arc<Vm<'drm>>> {
> +        let mut vms = self.vms.lock();
> +        match vms.remove(id as usize) {
> +            Some(vm) => {
> +                drop(vms);
> +                let mut ids = self.ids.lock();
> +                ids.release_id(id as usize);
> +                Ok(vm)
> +            }
> +            None => Err(EINVAL),
> +        }
> +    }
> +
> +    /// Gets the VM with the given ID.
> +    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))
> +    }
> +
> +    /// Removes and returns the first VM in the pool.
> +    pub(crate) fn pop_first(&self) -> Option<Arc<Vm<'drm>>> {
> +        for id in 0..PANTHOR_MAX_VMS_PER_FILE {

This should be inclusive, i.e. “..=“ instead of “..”. But a
better solution is to do:

for id in 0..self.ids.lock().capacity() {
  ..
}


> +            if let Ok(vm) = self.remove(id) {
> +                return Some(vm);
> +            }
> +        }
> +        None
> +    }
> +}
> diff --git a/drivers/gpu/drm/tyr/tyr.rs b/drivers/gpu/drm/tyr/tyr.rs
> index e7ec450bdc9c0..8c0c18c1a970b 100644
> --- a/drivers/gpu/drm/tyr/tyr.rs
> +++ b/drivers/gpu/drm/tyr/tyr.rs
> @@ -13,6 +13,7 @@
> mod gem;
> mod gpu;
> mod mmu;
> +mod pool;
> mod regs;
> mod slot;
> mod vm;
> 
> -- 
> 2.43.0
> 
> 


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 5/9] drm/tyr: add user and MCU VM specifications
  2026-09-01 16:09 ` [PATCH 5/9] drm/tyr: add user and MCU VM specifications Ke Sun via B4 Relay
@ 2026-09-03 18:10   ` Daniel Almeida
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-03 18:10 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> From: Alvin Sun <alvin.sun@linux.dev>
> 
> Distinguish MCU VMs from user VMs, and compute the user/kernel
> GPU VA split for user VMs.

Same comment as the previous commit: please write a few
more words here if possible :)

> 
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> ---
> drivers/gpu/drm/tyr/fw.rs |   8 +--
> drivers/gpu/drm/tyr/vm.rs | 134 +++++++++++++++++++++++++++++++++++++++++++---
> 2 files changed, 131 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/gpu/drm/tyr/fw.rs b/drivers/gpu/drm/tyr/fw.rs
> index 47d25c901bd01..9b4b488521b85 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,
> +        VmSpec, //
> +    }, //
> };
> 
> mod parser;
> @@ -220,7 +222,7 @@ 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(dev, ddev, mmu, gpu_info, VmSpec::Mcu)?;
>         vm.activate()?;
> 
>         let result = (|| {
> diff --git a/drivers/gpu/drm/tyr/vm.rs b/drivers/gpu/drm/tyr/vm.rs
> index c5e307b1e2416..76c3d60bb2fe2 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::num::NonZeroU64;
> use core::ops::Range;
> 
> use kernel::{
> @@ -43,6 +44,8 @@
>     new_mutex,
>     prelude::*,
>     sizes::{
> +        LargeSizeConstants,
> +        SizeConstants,
>         SZ_1G,
>         SZ_2M,
>         SZ_4K, //
> @@ -154,6 +157,109 @@ 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).
> +    pub(crate) fn from_uapi(v: u64) -> Self {
> +        match NonZeroU64::new(v) {
> +            Some(size) => Self::Fixed(size),
> +            None => Self::Auto,
> +        }
> +    }
> +}
> +
> +pub(crate) enum VmSpec {

Instead of having an enum, I think we could go with the current
tyr-dev design, i.e.:

- new_fw() (or, perhaps even better, new_for_fw())
- new_for_user()

> +    /// MCU/firmware VM, entirely kernel-managed.
> +    Mcu,
> +    /// User VM: full GPU VA range, split into user/kernel per `user_va`.
> +    User { user_va: UserVaRequest },
> +}
> +
> +/// 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.
> @@ -329,8 +435,8 @@ 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> {
> @@ -343,6 +449,7 @@ pub(crate) fn new(
>         ddev: &TyrDrmDevice,
>         mmu: ArcBorrow<'_, Mmu<'drm>>,
>         gpu_info: &GpuInfo,
> +        spec: VmSpec,
>     ) -> Result<Arc<Vm<'drm>>> {
>         let mmu_features = MMU_FEATURES::from_raw(gpu_info.mmu_features);
>         let va_bits = mmu_features.va_bits().get();
> @@ -351,6 +458,14 @@ pub(crate) fn new(
>         let range = 0..(1u64 << va_bits);
>         let reserve_range = 0..0u64;
> 
> +        let layout = match spec {
> +            VmSpec::Mcu => VmLayout {
> +                full: range.clone(),
> +                user: 0..0u64,
> +            },
> +            VmSpec::User { user_va } => VmLayout::compute(range.clone(), user_va)?,
> +        };
> +
>         // dummy_obj is used to initialize the GPUVM tree.
>         let dummy_obj = gem::new_dummy_object(ddev).inspect_err(|e| {
>             dev_err!(dev, "Failed to create dummy GEM object: {:?}", e);
> @@ -380,7 +495,7 @@ pub(crate) fn new(
>                 mmu: mmu.into(),
>                 gpuvm,
>                 gpuvm_unique <- new_mutex!(gpuvm_unique),
> -                va_range: range,
> +                layout,
>             }),
>             GFP_KERNEL,
>         )?;
> @@ -414,7 +529,10 @@ pub(crate) fn kill(&self) {
>         // TODO: Turn the VM into a state where it can't be used.
>         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);
>             });
> @@ -551,14 +669,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);
>         }
> @@ -568,7 +686,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] 36+ messages in thread

* Re: [PATCH 6/9] drm/tyr: add BO creation and lookup helpers
  2026-09-01 16:09 ` [PATCH 6/9] drm/tyr: add BO creation and lookup helpers Ke Sun via B4 Relay
@ 2026-09-03 22:06   ` Daniel Almeida
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-03 22:06 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> From: Alvin Sun <alvin.sun@linux.dev>
> 
> Add the new_object() and lookup_handle() helpers.
> 
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> ---
> drivers/gpu/drm/tyr/gem.rs | 29 ++++++++++++++++++++++++++++-
> 1 file changed, 28 insertions(+), 1 deletion(-)
> 
> diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
> index 3bf3787f5c3fd..be1affe80db1a 100644
> --- a/drivers/gpu/drm/tyr/gem.rs
> +++ b/drivers/gpu/drm/tyr/gem.rs
> @@ -9,9 +9,11 @@
> use kernel::{
>     drm::gem::{
>         self,
> -        shmem, //
> +        shmem,
> +        BaseObject, //
>     },
>     prelude::*,
> +    sizes::SZ_4K,
>     sync::{
>         aref::ARef,
>         Arc, //
> @@ -23,6 +25,7 @@
>         TyrDrmDevice,
>         TyrDrmDriver, //
>     },
> +    file::TyrDrmFile,
>     vm::{
>         Vm,
>         VmMapFlags, //
> @@ -53,6 +56,30 @@ 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.
> +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(SZ_4K).ok_or(EINVAL)?;

I think this needs to be PAGE_SIZE instead of SZ_4K.

> +
> +    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.
> +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] 36+ messages in thread

* Re: [PATCH 7/9] drm/tyr: refactor new_dummy_object to use new_object
  2026-09-01 16:09 ` [PATCH 7/9] drm/tyr: refactor new_dummy_object to use new_object Ke Sun via B4 Relay
@ 2026-09-03 22:16   ` Daniel Almeida
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-03 22:16 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> 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>
> ---
> drivers/gpu/drm/tyr/gem.rs | 13 ++-----------
> 1 file changed, 2 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/gpu/drm/tyr/gem.rs b/drivers/gpu/drm/tyr/gem.rs
> index be1affe80db1a..d9ddcb287f52b 100644
> --- a/drivers/gpu/drm/tyr/gem.rs
> +++ b/drivers/gpu/drm/tyr/gem.rs
> @@ -82,17 +82,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, 4096, 0)

For the same reason as the previous patch, I think this needs to be PAGE_SIZE too.

Feel free to work on the FIXME itself separately, if you want.

> }
> 
> /// Specifies how to choose a GPU virtual address for a [`KernelBo`].
> 
> -- 
> 2.43.0
> 
> 
> 

With the change above,

Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 8/9] drm/tyr: add VM-related ioctls
  2026-09-01 16:09 ` [PATCH 8/9] drm/tyr: add VM-related ioctls Ke Sun via B4 Relay
@ 2026-09-04 18:44   ` Daniel Almeida
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel Almeida @ 2026-09-04 18:44 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> From: Alvin Sun <alvin.sun@linux.dev>
> 
> Manage per-file user VMs.
> 
> - VM_CREATE creates a user VM and returns its ID.
> - VM_DESTROY destroys the VM identified by the given ID.
> - VM_BIND maps or unmaps BO ranges in the VM's user VA space.
> - VM_GET_STATE reports whether the VM is usable or unusable.

Same comment as the previous patches

> 
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> ---
> drivers/gpu/drm/tyr/driver.rs |  12 +-
> drivers/gpu/drm/tyr/file.rs   | 324 ++++++++++++++++++++++++++++++++++++++++--
> drivers/gpu/drm/tyr/vm.rs     |  24 +++-
> 3 files changed, 346 insertions(+), 14 deletions(-)
> 
> diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs
> index 94bc85635725e..b3145526ada06 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>,
> 
> @@ -164,6 +167,7 @@ fn probe<'bound>(
>         let reg_data = pin_init!(TyrDrmRegistrationData {
>                 pdev,
>                 fw: firmware,
> +                mmu,
>                 clks <- new_mutex!(Clocks {
>                     core: core_clk,
>                     stacks: stacks_clk,
> @@ -207,7 +211,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>;
> 
> @@ -216,6 +220,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..157bc40e1cac4 100644
> --- a/drivers/gpu/drm/tyr/file.rs
> +++ b/drivers/gpu/drm/tyr/file.rs
> @@ -3,37 +3,70 @@
> 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, //
> +    },
> +    pool::VmPool,
> +    vm::{
> +        UserVaRequest,
> +        Vm,
> +        VmMapFlags,
> +        VmSpec, //
> +    }, //
> };
> 
> -#[pin_data]
> -pub(crate) struct TyrDrmFileData {}
> +#[pin_data(PinnedDrop)]
> +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 {
> +#[pinned_drop]
> +impl PinnedDrop for TyrDrmFileData<'_> {
> +    fn drop(self: Pin<&mut Self>) {
> +        let proj = self.project();
> +        while let Some(vm) = proj.vm_pool.pop_first() {
> +            vm.kill();
> +        }
> +    }
> +}
> +
> +impl TyrDrmFileData<'_> {
>     pub(crate) fn dev_query(
>         _ddev: &TyrDrmDevice<Registered>,
>         reg_data: &TyrDrmRegistrationData<'_>,
> @@ -65,4 +98,273 @@ 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(|fd| {
> +            let vm = Vm::new(
> +                fd.reg.pdev.as_ref(),
> +                ddev,
> +                fd.reg.mmu.as_arc_borrow(),
> +                &fd.reg.gpu_info,
> +                VmSpec::User {
> +                    user_va: UserVaRequest::from_uapi(vmcreate.user_va_range),
> +                },
> +            )?;
> +            vmcreate.user_va_range = vm.layout.user.end;
> +
> +            let id = fd.vm_pool.add(vm.as_arc_borrow()).inspect_err(|_| {
> +                vm.kill();
> +            })?;

I think we can improve this, because currently we depend on kill() to not leak
resources. I propose the following:

/// The unique right to tear a VM down.
///
/// `Vm::new()` hands one of these back, so a VM is owned from the moment it
/// exists and any early return tears it down. Whoever ends up holding it (the
/// per-file pool) owns the teardown; everyone else takes an `Arc<Vm>` via
/// `VmOwner::get()`, which keeps the object alive but carries no such duty.
pub(crate) struct VmOwner<'drm>(Arc<Vm<'drm>>);

impl<'drm> VmOwner<'drm> {
    /// A reference for callers that want to use the VM, not own it.
    pub(crate) fn get(&self) -> Arc<Vm<'drm>> {
        self.0.clone()
    }
}

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();
    }
}

Where Vm::new() would be adapted to return VmOwner, instead of Arc<Vm>, so this
API cannot be circumvented. Also, kill() would be changed to private, such that
only VmOwner would be able to call it (since it will live in vm.rs).



> +            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(|fd| {
> +            let vm = fd.vm_pool.remove(vmdestroy.id)?;
> +            vm.kill();
> +            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 {}\n",
> +                vmbind.ops.stride
> +            );
> +            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(|fd| {
> +            let vm = fd.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(|fd| {
> +            let vm = fd.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 vm.is_unusable() {
> +        dev_err!(vm.dev(), "VM_BIND on destroyed VM\n");
> +        return Err(EINVAL);
> +    }
> +
> +    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);
> +    }
> +
> +    const TYPE_MASK: u32 =
> +        uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_MASK as u32;
> +    const TYPE_MAP: u32 = uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_MAP as u32;
> +    const TYPE_UNMAP: u32 =
> +        uapi::drm_panthor_vm_bind_op_flags_DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP as u32;

How about:

/// 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 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),
        }
    }
}

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;
}

> +
> +    match op.flags & TYPE_MASK {
> +        TYPE_MAP => {
> +            let map_flags = match VmMapFlags::try_from(op.flags & !TYPE_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)
> +        }
> +        TYPE_UNMAP => {
> +            // Unmap must not carry map-specific flags or BO references.
> +            if op.flags & !TYPE_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)
> +        }
> +        _ => {
> +            dev_err!(vm.dev(), "VM_BIND op type {:#x} not supported\n", op.flags);
> +            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/vm.rs b/drivers/gpu/drm/tyr/vm.rs
> index 76c3d60bb2fe2..610bab69c1a55 100644
> --- a/drivers/gpu/drm/tyr/vm.rs
> +++ b/drivers/gpu/drm/tyr/vm.rs
> @@ -10,6 +10,10 @@
> use core::marker::PhantomData;
> use core::num::NonZeroU64;
> use core::ops::Range;
> +use core::sync::atomic::{
> +    AtomicBool,
> +    Ordering, //
> +};
> 
> use kernel::{
>     device::{
> @@ -437,6 +441,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: AtomicBool,

Atomic<bool>

> }
> 
> impl<'drm> Vm<'drm> {
> @@ -496,6 +502,7 @@ pub(crate) fn new(
>                 gpuvm,
>                 gpuvm_unique <- new_mutex!(gpuvm_unique),
>                 layout,
> +                unusable: AtomicBool::new(false),
>             }),
>             GFP_KERNEL,
>         )?;
> @@ -526,7 +533,7 @@ 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.
> +        self.mark_unusable();
>         let _ = self.deactivate();
>         let _ = self
>             .unmap_range(
> @@ -538,6 +545,15 @@ pub(crate) fn kill(&self) {
>             });
>     }
> 
> +    /// Marks the VM unusable.
> +    pub(crate) fn mark_unusable(&self) {
> +        self.unusable.store(true, Ordering::Release);
> +    }
> +
> +    pub(crate) fn is_unusable(&self) -> bool {
> +        self.unusable.load(Ordering::Acquire)
> +    }
> +
>     /// Executes a virtual memory operation.
>     ///
>     /// This handles both map and unmap operations by coordinating between the
> @@ -649,6 +665,12 @@ pub(crate) fn map_bo_range(
>         };
>         let result = {
>             let mut gpuvm_unique = self.gpuvm_unique.lock();
> +            // Check under the GPUVM lock so a concurrent `mark_unusable()`
> +            // teardown cannot race with this operation.
> +            if self.is_unusable() {
> +                dev_err!(self.dev, "Cannot map on unusable VM\n");

Can you improve the wording a bit on these messages?

> +                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
> 
> -- 
> 2.43.0
> 
> 


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 9/9] drm/tyr: add BO-related ioctls
  2026-09-01 16:09 ` [PATCH 9/9] drm/tyr: add BO-related ioctls Ke Sun via B4 Relay
@ 2026-09-04 20:35   ` Daniel Almeida
  2026-09-08 14:24     ` Alvin Sun
  0 siblings, 1 reply; 36+ messages in thread
From: Daniel Almeida @ 2026-09-04 20:35 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, 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 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
> 
> From: Alvin Sun <alvin.sun@linux.dev>
> 
> Expose buffer creation and BO mmap offset retrieval.
> 
> - BO_CREATE page-aligns the size and returns a handle with
>  write-combined mapping.
> - BO_MMAP_OFFSET provides the offset for the DRM generic mmap
>  path, rejecting NO_MMAP objects and non-zero pad.
> 
> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>

Same comment as the previous patches, please write a few
more things here about why the changes are needed.

With that addressed, 

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 b3145526ada06..fc8d11c9ba1ca 100644
> --- a/drivers/gpu/drm/tyr/driver.rs
> +++ b/drivers/gpu/drm/tyr/driver.rs
> @@ -224,6 +224,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 157bc40e1cac4..16abf2968299c 100644
> --- a/drivers/gpu/drm/tyr/file.rs
> +++ b/drivers/gpu/drm/tyr/file.rs
> @@ -252,6 +252,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.create_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 d9ddcb287f52b..f404139f54f32 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 create_flags(&self) -> u32 {

I would prefer flags() instead of create_flags(), which reads a bit more like a constructor.

> +        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] 36+ messages in thread

* Re: [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
  2026-09-01 16:09 ` [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` Ke Sun via B4 Relay
  2026-09-03 13:12   ` Daniel Almeida
@ 2026-09-06 12:47   ` Gary Guo
  1 sibling, 0 replies; 36+ messages in thread
From: Gary Guo @ 2026-09-06 12:47 UTC (permalink / raw)
  To: sunke, 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, Alvin Sun

On Tue Sep 1, 2026 at 5:09 PM BST, Ke Sun via B4 Relay wrote:
> 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: Gary Guo <gary@garyguo.net>

> ---
>  rust/kernel/sync/arc.rs | 12 +++++++++---
>  1 file changed, 9 insertions(+), 3 deletions(-)


^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [PATCH 1/9] rust: sizes: add SZ_4G constant
  2026-09-01 16:09 ` [PATCH 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
  2026-09-02 12:57   ` Daniel Almeida
@ 2026-09-06 12:47   ` Gary Guo
  1 sibling, 0 replies; 36+ messages in thread
From: Gary Guo @ 2026-09-06 12:47 UTC (permalink / raw)
  To: sunke, 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, Alvin Sun

On Tue Sep 1, 2026 at 5:09 PM BST, Ke Sun via B4 Relay wrote:
> 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: Gary Guo <gary@garyguo.net>

> ---
>  rust/bindings/bindings_helper.h |  1 +
>  rust/kernel/sizes.rs            | 12 ++++++++++++
>  2 files changed, 13 insertions(+)


^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v2 0/9] drm/tyr: add VM and BO ioctl support
@ 2026-09-07 16:47 ` 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
                     ` (8 more replies)
  0 siblings, 9 replies; 36+ 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

Add the VM and BO ioctls to the Tyr driver, aligning its userspace
interface with panthor. The series is based on Danilo's `drm-file`
series on `drm-rust-next`, and uses the existing IdPool and xarray
abstractions for per-file VM management.

The panthor IGT tests pass on an RK3588S device.

A branch with the full series and all prerequisites applied is
available at:
https://gitlab.freedesktop.org/alvinsun039/linux.git tyr-ioctls

Signed-off-by: Ke Sun <sunke@kylinos.cn>
Link: https://gitlab.freedesktop.org/panfrost/linux/-/merge_requests/64
---
Changes in v2:
- Fold the VM pool into vm.rs and cap it at 32 VMs per file (Deborah).
- Add VmOwner so a VM is killed exactly once on drop, regardless of
  remaining references (Daniel).
- Replace the VmSpec enum with new_for_fw()/new_for_user()
  constructors (Daniel).
- Parse VM_BIND op types via VmBindOpType and use the kernel
  Atomic<bool> for the unusable flag (Daniel).
- Use PAGE_SIZE for the dummy object size (Daniel).
- Rename BoData::create_flags() to flags() (Daniel).
- Report VA-split failures with dev_err! at the caller (Deborah).
- Link to v1: https://lore.kernel.org/r/20260902-tyr-ioctls-v1-0-e0fdbf8bd108@kylinos.cn

---
Alvin Sun (9):
      rust: sizes: add SZ_4G constant
      rust: mm: add `task_size` helper
      rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
      drm/tyr: add per-file VM pool
      drm/tyr: add user and MCU VM specifications
      drm/tyr: add BO creation and lookup helpers
      drm/tyr: refactor new_dummy_object to use new_object
      drm/tyr: add VM-related ioctls
      drm/tyr: add BO-related ioctls

 drivers/gpu/drm/tyr/driver.rs   |  14 +-
 drivers/gpu/drm/tyr/file.rs     | 378 ++++++++++++++++++++++++++++++++++++++--
 drivers/gpu/drm/tyr/fw.rs       |  37 ++--
 drivers/gpu/drm/tyr/gem.rs      |  41 ++++-
 drivers/gpu/drm/tyr/vm.rs       | 378 ++++++++++++++++++++++++++++++++++++++--
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/mm.rs               |   7 +
 rust/kernel/sizes.rs            |  12 ++
 rust/kernel/sync/arc.rs         |  12 +-
 9 files changed, 819 insertions(+), 61 deletions(-)
---
base-commit: 3ac51829f95368876dcfbfbb42823ca0cd6e6f56
change-id: 20260901-tyr-ioctls-f10ef0dcbfa6

Best regards,
-- 
Ke Sun <sunke@kylinos.cn>



^ permalink raw reply	[flat|nested] 36+ messages in thread

* [PATCH v2 1/9] rust: sizes: add SZ_4G constant
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 2/9] rust: mm: add `task_size` helper
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>`
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 4/9] drm/tyr: add per-file VM pool
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 5/9] drm/tyr: add user and MCU VM specifications
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 6/9] drm/tyr: add BO creation and lookup helpers
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 7/9] drm/tyr: refactor new_dummy_object to use new_object
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 8/9] drm/tyr: add VM-related ioctls
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* [PATCH v2 9/9] drm/tyr: add BO-related ioctls
  2026-09-07 16:47 ` [PATCH v2 " 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; 36+ 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] 36+ messages in thread

* Re: [PATCH 9/9] drm/tyr: add BO-related ioctls
  2026-09-04 20:35   ` Daniel Almeida
@ 2026-09-08 14:24     ` Alvin Sun
  0 siblings, 0 replies; 36+ messages in thread
From: Alvin Sun @ 2026-09-08 14:24 UTC (permalink / raw)
  To: Daniel Almeida, 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, Tamir Duberstein,
	Alexandre Courbot, Onur Özkan, Lorenzo Stoakes,
	Liam R. Howlett, Lyude Paul, David Airlie, Simona Vetter,
	linux-kernel, linux-mm, dri-devel


On 9/5/26 04:35, Daniel Almeida wrote:
>
>> On 1 Sep 2026, at 13:09, Ke Sun via B4 Relay <devnull+sunke.kylinos.cn@kernel.org> wrote:
>>
>> From: Alvin Sun <alvin.sun@linux.dev>
>>
>> Expose buffer creation and BO mmap offset retrieval.
>>
>> - BO_CREATE page-aligns the size and returns a handle with
>>   write-combined mapping.
>> - BO_MMAP_OFFSET provides the offset for the DRM generic mmap
>>   path, rejecting NO_MMAP objects and non-zero pad.
>>
>> Signed-off-by: Alvin Sun <alvin.sun@linux.dev>
> Same comment as the previous patches, please write a few
> more things here about why the changes are needed.
>
> With that addressed,
>
> Reviewed-by: Daniel Almeida <daniel.almeida@collabora.com>

Hi Daniel,

Thanks for the review — your comments on patches 7 and 9 (the 
conditional Reviewed-bys) are addressed in v2:

https://lore.kernel.org/all/20260908-tyr-ioctls-v2-0-88bea777df67@kylinos.cn

Best regards,
Alvin


>
>> ---
>> 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 b3145526ada06..fc8d11c9ba1ca 100644
>> --- a/drivers/gpu/drm/tyr/driver.rs
>> +++ b/drivers/gpu/drm/tyr/driver.rs
>> @@ -224,6 +224,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 157bc40e1cac4..16abf2968299c 100644
>> --- a/drivers/gpu/drm/tyr/file.rs
>> +++ b/drivers/gpu/drm/tyr/file.rs
>> @@ -252,6 +252,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.create_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 d9ddcb287f52b..f404139f54f32 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 create_flags(&self) -> u32 {
> I would prefer flags() instead of create_flags(), which reads a bit more like a constructor.
>
>> +        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] 36+ 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
  2026-09-28 10:30       ` Alvin Sun
  0 siblings, 1 reply; 36+ 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] 36+ 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; 36+ 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] 36+ messages in thread

* Re: [PATCH v2 1/9] rust: sizes: add SZ_4G constant
  2026-09-08 19:47     ` Miguel Ojeda
@ 2026-09-28 10:30       ` Alvin Sun
  0 siblings, 0 replies; 36+ messages in thread
From: Alvin Sun @ 2026-09-28 10:30 UTC (permalink / raw)
  To: Miguel Ojeda
  Cc: rust-for-linux, 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

Hi Miguel,

Thank you for the review.

On Mon, Sep 8, 2026 at 9:47 PM Miguel Ojeda
<miguel.ojeda.sandonis@gmail.com> 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 ...".

Done in v3 -- the commit message now includes the explanation.

> +const unsigned long long RUST_CONST_HELPER_SZ_4G = SZ_4G;
>
> Is it added here to be nearby other sizes, or something else?

Yes -- to keep the size-related helpers grouped together.

> +/// Only implemented for `u64`.
>
> Intra-doc link: [`u64`]

Addressed.

The Acked-by is already carried in the commit.

Best regards,
Alvin

^ permalink raw reply	[flat|nested] 36+ messages in thread

end of thread, other threads:[~2026-09-29  3:49 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-01 16:08 [PATCH 0/9] drm/tyr: add VM and BO ioctl support Ke Sun via B4 Relay
2026-09-01 16:09 ` [PATCH 1/9] rust: sizes: add SZ_4G constant Ke Sun via B4 Relay
2026-09-02 12:57   ` Daniel Almeida
2026-09-06 12:47   ` Gary Guo
2026-09-01 16:09 ` [PATCH 2/9] rust: mm: add `task_size` helper Ke Sun via B4 Relay
2026-09-03 13:09   ` Daniel Almeida
2026-09-01 16:09 ` [PATCH 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` Ke Sun via B4 Relay
2026-09-03 13:12   ` Daniel Almeida
2026-09-06 12:47   ` Gary Guo
2026-09-01 16:09 ` [PATCH 4/9] drm/tyr: add per-file VM pool Ke Sun via B4 Relay
2026-09-03 17:51   ` Daniel Almeida
2026-09-01 16:09 ` [PATCH 5/9] drm/tyr: add user and MCU VM specifications Ke Sun via B4 Relay
2026-09-03 18:10   ` Daniel Almeida
2026-09-01 16:09 ` [PATCH 6/9] drm/tyr: add BO creation and lookup helpers Ke Sun via B4 Relay
2026-09-03 22:06   ` Daniel Almeida
2026-09-01 16:09 ` [PATCH 7/9] drm/tyr: refactor new_dummy_object to use new_object Ke Sun via B4 Relay
2026-09-03 22:16   ` Daniel Almeida
2026-09-01 16:09 ` [PATCH 8/9] drm/tyr: add VM-related ioctls Ke Sun via B4 Relay
2026-09-04 18:44   ` Daniel Almeida
2026-09-01 16:09 ` [PATCH 9/9] drm/tyr: add BO-related ioctls Ke Sun via B4 Relay
2026-09-04 20:35   ` Daniel Almeida
2026-09-08 14:24     ` Alvin Sun
2026-09-02  0:14 ` [PATCH 0/9] drm/tyr: add VM and BO ioctl support Deborah Brouwer
2026-09-07 16:47 ` [PATCH v2 " 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-08 19:47     ` Miguel Ojeda
2026-09-28 10:30       ` Alvin Sun
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   ` [PATCH v2 3/9] rust: sync: arc: relax `ForeignOwnable` for `Arc<T>` 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
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
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   ` [PATCH v2 7/9] drm/tyr: refactor new_dummy_object to use new_object 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

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®