From nobody Sun Feb 8 09:10:27 2026 Received: from mail-wr1-f73.google.com (mail-wr1-f73.google.com [209.85.221.73]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 645F33EFD3B for ; Wed, 21 Jan 2026 11:32:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.221.73 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768995126; cv=none; b=LuHNToBRHv0+m6UJ8NSoSbc4C0nm0rG3LNem4THEhJ2Hd92WBu8wI+69zu0HomVozyVHGhCF5Bn/JDI8/4Xcs+sPSRvA1nQDCIcwZ3lTs+b8+f0nf2uao6qUdl0LrzDaOAyE6rh3L6zj/njRikS/Yamg5Ua81EIE5cGejt3W/gU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768995126; c=relaxed/simple; bh=WHjn7ojmZ651+H3xURlrDU/WE1FVfLNqkTEkLpEkBeQ=; h=Date:In-Reply-To:Mime-Version:References:Message-ID:Subject:From: To:Cc:Content-Type; b=TQYU74DOIAseCXEV5CjZXO7KlFjI69rmPu3rVQRY31T15wCwh1rikAo6NPwwa2qxZZcDbJT8OMUTrRdhWQPAQMyLiVHgaAYQ8mK2N5y1XWzcoKXSHwNX7+xOVeDE/34FK/X92buc8ywwEHeU0HmdTekHrPrVqK20C4bOFhBjSD0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com; spf=pass smtp.mailfrom=flex--aliceryhl.bounces.google.com; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b=CvaBd6WN; arc=none smtp.client-ip=209.85.221.73 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=reject dis=none) header.from=google.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=flex--aliceryhl.bounces.google.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=google.com header.i=@google.com header.b="CvaBd6WN" Received: by mail-wr1-f73.google.com with SMTP id ffacd0b85a97d-4325cc15176so4451481f8f.1 for ; Wed, 21 Jan 2026 03:32:04 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20230601; t=1768995123; x=1769599923; darn=vger.kernel.org; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:from:to:cc:subject:date:message-id:reply-to; bh=63bt8+7K/MSaVF9qHfILB+WRhvet6RgRj7D7Rrko1lw=; b=CvaBd6WNPrJu+AT4qo8D15nA13VoMZZebRkDR6KmIQrN56dYNHu7bcfLx3H4uEHnt1 Cg7LpR/Xc4aZdxFd9s/2c19GsWbG+/Xln/itvrkWd3fPqyvai3shvhdfw1PQ2FaEwvVj nCjJyX9+1zm7akpCSfXPmGakLTWH543mj1Ve1jbUy6e5RWUW9QWusuxLzg7dDTpRoYkJ 6vYOUCooh2H2sTxPzTwT0c0JDv/8YF0vw2OD3hz1nXdsIBtYHX1QERcOpeW9VvBTRSqQ 2i9h6GhTHHUZE8ZHZEmWcxsWPxBA18ERDcBoLFBUyHnR/kg/L++zgjl+s/i4xGlWjOh7 7cHA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1768995123; x=1769599923; h=cc:to:from:subject:message-id:references:mime-version:in-reply-to :date:x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=63bt8+7K/MSaVF9qHfILB+WRhvet6RgRj7D7Rrko1lw=; b=C1lcdx7OQHsws9SOvOgWJR/eWKCSt+lp4DeLgZ0K4FOA8FiGcnoQoWYvZgQ0mvHMve A6FI4Fy6eAfvtpiSxLVDHXXzduCGFKgBVZF0jR3dWIcoLgIwtEU+xtv7w39Nj/O3xjzC IM63BgVNBScqtf+/pvCGlKl8L2PdzFS0WQMSDlhgiMDY0t1whgTvL9UaKNdp7xmVCJ08 YVsIdn4boFkwGl2ugkPl6RLPXKBDTmYM2XoRkLtoSyznQl4RvaM/8pc0j6qfvNZvhnds EIDrVRyosPiCUdzF1Rki1EROpcMQepP7NK9pi/1vW+wTDsXkuEr837FgHHi5sevwyFXN U1ug== X-Forwarded-Encrypted: i=1; AJvYcCX4ocIuBS2rulP2TgAFxyqXJ9BUUZdnqf/NKrSbcUgpdPboRk4bjLIeNqQT1SIaTFpi6oUBEJEBNLH4eQg=@vger.kernel.org X-Gm-Message-State: AOJu0YzkoWVlvCfhq3tMDUKODuLYdGUZuvSofs1JsAZELu6SkV/W9+1L uG3RSN8rbTawbmQCKJCAt1WLfqq1OLk6pjrzNNBgf9OgFGTJcXWRt1AJ7mrCX4Q/q8ynyU6KMeV 1X5CKZWtrfq5VSXX5lA== X-Received: from wrbgx18.prod.google.com ([2002:a05:6000:4712:b0:435:9759:ea24]) (user=aliceryhl job=prod-delivery.src-stubby-dispatcher) by 2002:a05:6000:2007:b0:430:fd84:3175 with SMTP id ffacd0b85a97d-4358ff813bemr8336856f8f.38.1768995122676; Wed, 21 Jan 2026 03:32:02 -0800 (PST) Date: Wed, 21 Jan 2026 11:31:21 +0000 In-Reply-To: <20260121-gpuvm-rust-v3-0-dd95c04aec35@google.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: Mime-Version: 1.0 References: <20260121-gpuvm-rust-v3-0-dd95c04aec35@google.com> X-Developer-Key: i=aliceryhl@google.com; a=openpgp; fpr=49F6C1FAA74960F43A5B86A1EE7A392FDE96209F X-Developer-Signature: v=1; a=openpgp-sha256; l=13809; i=aliceryhl@google.com; h=from:subject:message-id; bh=WHjn7ojmZ651+H3xURlrDU/WE1FVfLNqkTEkLpEkBeQ=; b=owEBbQKS/ZANAwAKAQRYvu5YxjlGAcsmYgBpcLkeVPvVaozDdcE0o+0u8/qNsxrLd7OYtF7qS NnBfoyHXROJAjMEAAEKAB0WIQSDkqKUTWQHCvFIvbIEWL7uWMY5RgUCaXC5HgAKCRAEWL7uWMY5 RvzoD/9QH4Ch24HbF2aIYYteAsT+nLP8QizW1oxiu5oUznN8+jmcnmFQFNC4N2G1DCKVV4vRzfb +EXugWukA9mMk5q2RqnvL7mD57OXvFwmLAj91xPFAoAlC+hhgFO4b8QYnMgdw5++D0jAwO4rpWI x7jXc3nyEFVOdv0sH1YN9/vczUTyVDfk3sHbmvC1A76+hmHRvaPVI/FRIA5ZJT2l8l5MJkiQQAC 4dFzAoxuB42/LuBER0gTTckvyudbxVyO4JxFN6QeiEODEcNgweJmRzJWqXq/kZzKzhXFrquqt+c bRaeYN/fb65PayGtHSQr/naJcHBpZbU4+bbihp3zhUeZh9qxMxiRfbmfiu9hqgodruzzEWiQjNv LVnHHVanb0dhWcnFZn9h0xH2STUJv9/q/epG2ImBrit3PT1zIgvOyKM+iPBWi7M+CrF1k/sZlYG AF0sykLk9Qb4TrpVMRk8jIcov+2T/ih/99v0laTmue5RsZ1AeTJ3MdJMWLQ5Lr0Nr5jIE3ZrjxP 0GeQCf2IYOiEYuQID0Wx7LDOmd5uD1V2+AEOF3EZ2wwSTBNqylM5TCCC0ReOD103Ruft1X1MfPK qmpBf/0WXjDAkMMQQPrL1ocLJgghmSac95s5sYguw01Wt+PPWi2ZgHbaVZ3q71lPYzweftaAtiB jsEcsol+DYS3PbQ== X-Mailer: b4 0.14.2 Message-ID: <20260121-gpuvm-rust-v3-5-dd95c04aec35@google.com> Subject: [PATCH v3 5/6] rust: gpuvm: add GpuVmCore::sm_unmap() From: Alice Ryhl To: Danilo Krummrich , Daniel Almeida Cc: Boris Brezillon , Janne Grunau , Matthew Brost , "=?utf-8?q?Thomas_Hellstr=C3=B6m?=" , Lyude Paul , Asahi Lina , dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org, rust-for-linux@vger.kernel.org, Alice Ryhl Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Add the entrypoint for unmapping ranges in the GPUVM, and provide callbacks and VA types for the implementation. Co-developed-by: Asahi Lina Signed-off-by: Asahi Lina Signed-off-by: Alice Ryhl Reviewed-by: Daniel Almeida --- rust/kernel/drm/gpuvm/mod.rs | 30 ++++- rust/kernel/drm/gpuvm/sm_ops.rs | 264 ++++++++++++++++++++++++++++++++++++= ++++ rust/kernel/drm/gpuvm/va.rs | 1 - rust/kernel/drm/gpuvm/vm_bo.rs | 8 ++ 4 files changed, 298 insertions(+), 5 deletions(-) diff --git a/rust/kernel/drm/gpuvm/mod.rs b/rust/kernel/drm/gpuvm/mod.rs index 2179ddd717d8728bbe231bd94ea7b5d1e2652543..165a25666ccc3d62e59b73483d4= eedff044423e9 100644 --- a/rust/kernel/drm/gpuvm/mod.rs +++ b/rust/kernel/drm/gpuvm/mod.rs @@ -18,6 +18,7 @@ bindings, drm, drm::gem::IntoGEMObject, + error::to_result, prelude::*, sync::aref::{ ARef, @@ -28,6 +29,7 @@ =20 use core::{ cell::UnsafeCell, + marker::PhantomData, mem::{ ManuallyDrop, MaybeUninit, // @@ -43,12 +45,15 @@ }, // }; =20 -mod va; -pub use self::va::*; +mod sm_ops; +pub use self::sm_ops::*; =20 mod vm_bo; pub use self::vm_bo::*; =20 +mod va; +pub use self::va::*; + /// A DRM GPU VA manager. /// /// This object is refcounted, but the "core" is only accessible using a s= pecial unique handle. The @@ -89,8 +94,8 @@ const fn vtable() -> &'static bindings::drm_gpuvm_ops { vm_bo_free: GpuVmBo::::FREE_FN, vm_bo_validate: None, sm_step_map: None, - sm_step_unmap: None, - sm_step_remap: None, + sm_step_unmap: Some(Self::sm_step_unmap), + sm_step_remap: Some(Self::sm_step_remap), } } =20 @@ -239,6 +244,23 @@ pub trait DriverGpuVm: Sized { =20 /// Data stored with each `struct drm_gpuvm_bo`. type VmBoData; + + /// The private data passed to callbacks. + type SmContext<'ctx>; + + /// Indicates that an existing mapping should be removed. + fn sm_step_unmap<'op, 'ctx>( + &mut self, + op: OpUnmap<'op, Self>, + context: &mut Self::SmContext<'ctx>, + ) -> Result, Error>; + + /// Indicates that an existing mapping should be split up. + fn sm_step_remap<'op, 'ctx>( + &mut self, + op: OpRemap<'op, Self>, + context: &mut Self::SmContext<'ctx>, + ) -> Result, Error>; } =20 /// The core of the DRM GPU VA manager. diff --git a/rust/kernel/drm/gpuvm/sm_ops.rs b/rust/kernel/drm/gpuvm/sm_ops= .rs new file mode 100644 index 0000000000000000000000000000000000000000..3c29d10d63f0b0a1976c714a86d= 486948ba81a15 --- /dev/null +++ b/rust/kernel/drm/gpuvm/sm_ops.rs @@ -0,0 +1,264 @@ +// SPDX-License-Identifier: GPL-2.0 OR MIT + +use super::*; + +/// The actual data that gets threaded through the callbacks. +struct SmData<'a, 'ctx, T: DriverGpuVm> { + gpuvm: &'a mut GpuVmCore, + user_context: &'a mut T::SmContext<'ctx>, +} + +/// Represents an `sm_step_unmap` operation that has not yet been complete= d. +pub struct OpUnmap<'op, T: DriverGpuVm> { + op: &'op bindings::drm_gpuva_op_unmap, + _invariant: PhantomData<*mut &'op mut T>, +} + +impl<'op, T: DriverGpuVm> OpUnmap<'op, T> { + /// Indicates whether this `drm_gpuva` is physically contiguous with t= he + /// original mapping request. + /// + /// Optionally, if `keep` is set, drivers may keep the actual page tab= le + /// mappings for this `drm_gpuva`, adding the missing page table entri= es + /// only and update the `drm_gpuvm` accordingly. + pub fn keep(&self) -> bool { + self.op.keep + } + + /// The range being unmapped. + pub fn va(&self) -> &GpuVa { + // SAFETY: This is a valid va. + unsafe { GpuVa::::from_raw(self.op.va) } + } + + /// Remove the VA. + pub fn remove(self) -> (OpUnmapped<'op, T>, GpuVaRemoved) { + // SAFETY: The op references a valid drm_gpuva in the GPUVM. + unsafe { bindings::drm_gpuva_unmap(self.op) }; + // SAFETY: The va is no longer in the interval tree so we may unli= nk it. + unsafe { bindings::drm_gpuva_unlink_defer(self.op.va) }; + + // SAFETY: We just removed this va from the `GpuVm`. + let va =3D unsafe { GpuVaRemoved::from_raw(self.op.va) }; + + ( + OpUnmapped { + _invariant: self._invariant, + }, + va, + ) + } +} + +/// Represents a completed [`OpUnmap`] operation. +pub struct OpUnmapped<'op, T> { + _invariant: PhantomData<*mut &'op mut T>, +} + +/// Represents an `sm_step_remap` operation that has not yet been complete= d. +pub struct OpRemap<'op, T: DriverGpuVm> { + op: &'op bindings::drm_gpuva_op_remap, + _invariant: PhantomData<*mut &'op mut T>, +} + +impl<'op, T: DriverGpuVm> OpRemap<'op, T> { + /// The preceding part of a split mapping. + #[inline] + pub fn prev(&self) -> Option<&OpRemapMapData> { + // SAFETY: We checked for null, so the pointer must be valid. + NonNull::new(self.op.prev).map(|ptr| unsafe { OpRemapMapData::from= _raw(ptr) }) + } + + /// The subsequent part of a split mapping. + #[inline] + pub fn next(&self) -> Option<&OpRemapMapData> { + // SAFETY: We checked for null, so the pointer must be valid. + NonNull::new(self.op.next).map(|ptr| unsafe { OpRemapMapData::from= _raw(ptr) }) + } + + /// Indicates whether the `drm_gpuva` being removed is physically cont= iguous with the original + /// mapping request. + /// + /// Optionally, if `keep` is set, drivers may keep the actual page tab= le mappings for this + /// `drm_gpuva`, adding the missing page table entries only and update= the `drm_gpuvm` + /// accordingly. + #[inline] + pub fn keep(&self) -> bool { + // SAFETY: The unmap pointer is always valid. + unsafe { (*self.op.unmap).keep } + } + + /// The range being unmapped. + #[inline] + pub fn va_to_unmap(&self) -> &GpuVa { + // SAFETY: This is a valid va. + unsafe { GpuVa::::from_raw((*self.op.unmap).va) } + } + + /// The [`drm_gem_object`](crate::gem::Object) whose VA is being remap= ped. + #[inline] + pub fn obj(&self) -> &T::Object { + self.va_to_unmap().obj() + } + + /// The [`GpuVmBo`] that is being remapped. + #[inline] + pub fn vm_bo(&self) -> &GpuVmBo { + self.va_to_unmap().vm_bo() + } + + /// Update the GPUVM to perform the remapping. + pub fn remap( + self, + va_alloc: [GpuVaAlloc; 2], + prev_data: impl PinInit, + next_data: impl PinInit, + ) -> (OpRemapped<'op, T>, OpRemapRet) { + let [va1, va2] =3D va_alloc; + + let mut unused_va =3D None; + let mut prev_ptr =3D ptr::null_mut(); + let mut next_ptr =3D ptr::null_mut(); + if self.prev().is_some() { + prev_ptr =3D va1.prepare(prev_data); + } else { + unused_va =3D Some(va1); + } + if self.next().is_some() { + next_ptr =3D va2.prepare(next_data); + } else { + unused_va =3D Some(va2); + } + + // SAFETY: the pointers are non-null when required + unsafe { bindings::drm_gpuva_remap(prev_ptr, next_ptr, self.op) }; + + let gpuva_guard =3D self.vm_bo().lock_gpuva(); + if !prev_ptr.is_null() { + // SAFETY: The prev_ptr is a valid drm_gpuva prepared for inse= rtion. The vm_bo is still + // valid as the not-yet-unlinked gpuva holds a refcount on the= vm_bo. + unsafe { bindings::drm_gpuva_link(prev_ptr, self.vm_bo().as_ra= w()) }; + } + if !next_ptr.is_null() { + // SAFETY: The next_ptr is a valid drm_gpuva prepared for inse= rtion. The vm_bo is still + // valid as the not-yet-unlinked gpuva holds a refcount on the= vm_bo. + unsafe { bindings::drm_gpuva_link(next_ptr, self.vm_bo().as_ra= w()) }; + } + drop(gpuva_guard); + + // SAFETY: The va is no longer in the interval tree so we may unli= nk it. + unsafe { bindings::drm_gpuva_unlink_defer((*self.op.unmap).va) }; + + ( + OpRemapped { + _invariant: self._invariant, + }, + OpRemapRet { + // SAFETY: We just removed this va from the `GpuVm`. + unmapped_va: unsafe { GpuVaRemoved::from_raw((*self.op.unm= ap).va) }, + unused_va, + }, + ) + } +} + +/// Part of an [`OpRemap`] that represents a new mapping. +#[repr(transparent)] +pub struct OpRemapMapData(bindings::drm_gpuva_op_map); + +impl OpRemapMapData { + /// # Safety + /// Must reference a valid `drm_gpuva_op_map` for duration of `'a`. + unsafe fn from_raw<'a>(ptr: NonNull) -> &'= a Self { + // SAFETY: ok per safety requirements + unsafe { ptr.cast().as_ref() } + } + + /// The base address of the new mapping. + pub fn addr(&self) -> u64 { + self.0.va.addr + } + + /// The length of the new mapping. + pub fn length(&self) -> u64 { + self.0.va.range + } + + /// The offset within the [`drm_gem_object`](crate::gem::Object). + pub fn gem_offset(&self) -> u64 { + self.0.gem.offset + } +} + +/// Struct containing objects removed or not used by [`OpRemap::remap`]. +pub struct OpRemapRet { + /// The `drm_gpuva` that was removed. + pub unmapped_va: GpuVaRemoved, + /// If the remap did not split the region into two pieces, then the un= used `drm_gpuva` is + /// returned here. + pub unused_va: Option>, +} + +/// Represents a completed [`OpRemap`] operation. +pub struct OpRemapped<'op, T> { + _invariant: PhantomData<*mut &'op mut T>, +} + +impl GpuVmCore { + /// Remove any mappings in the given region. + /// + /// Internally calls [`DriverGpuVm::sm_step_unmap`] for ranges entirel= y contained within the + /// given range, and [`DriverGpuVm::sm_step_remap`] for ranges that ov= erlap with the range. + #[inline] + pub fn sm_unmap(&mut self, addr: u64, length: u64, context: &mut T::Sm= Context<'_>) -> Result { + let gpuvm =3D self.as_raw(); + let mut p =3D SmData { + gpuvm: self, + user_context: context, + }; + // SAFETY: + // * raw_request() creates a valid request. + // * The private data is valid to be interpreted as SmData. + to_result(unsafe { bindings::drm_gpuvm_sm_unmap(gpuvm, (&raw mut p= ).cast(), addr, length) }) + } +} + +impl GpuVm { + /// # Safety + /// Must be called from `sm_unmap` with a pointer to `SmData`. + pub(super) unsafe extern "C" fn sm_step_unmap( + op: *mut bindings::drm_gpuva_op, + p: *mut c_void, + ) -> c_int { + // SAFETY: The caller provides a pointer to `SmData`. + let p =3D unsafe { &mut *p.cast::>() }; + let op =3D OpUnmap { + // SAFETY: sm_step_unmap is called with an unmap operation. + op: unsafe { &(*op).__bindgen_anon_1.unmap }, + _invariant: PhantomData, + }; + match p.gpuvm.data().sm_step_unmap(op, p.user_context) { + Ok(OpUnmapped { .. }) =3D> 0, + Err(err) =3D> err.to_errno(), + } + } + + /// # Safety + /// Must be called from `sm_unmap` with a pointer to `SmData`. + pub(super) unsafe extern "C" fn sm_step_remap( + op: *mut bindings::drm_gpuva_op, + p: *mut c_void, + ) -> c_int { + // SAFETY: The caller provides a pointer to `SmData`. + let p =3D unsafe { &mut *p.cast::>() }; + let op =3D OpRemap { + // SAFETY: sm_step_remap is called with a remap operation. + op: unsafe { &(*op).__bindgen_anon_1.remap }, + _invariant: PhantomData, + }; + match p.gpuvm.data().sm_step_remap(op, p.user_context) { + Ok(OpRemapped { .. }) =3D> 0, + Err(err) =3D> err.to_errno(), + } + } +} diff --git a/rust/kernel/drm/gpuvm/va.rs b/rust/kernel/drm/gpuvm/va.rs index c96796a6b2c8c7c4b5475324562968ca0f07fd09..a31122ff22282186a1d76d4bb08= 5714f6465722b 100644 --- a/rust/kernel/drm/gpuvm/va.rs +++ b/rust/kernel/drm/gpuvm/va.rs @@ -1,6 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 OR MIT =20 -#![expect(dead_code)] use super::*; =20 /// Represents that a range of a GEM object is mapped in this [`GpuVm`] in= stance. diff --git a/rust/kernel/drm/gpuvm/vm_bo.rs b/rust/kernel/drm/gpuvm/vm_bo.rs index 310fa569b5bd43f0f872ff859b3624377b93d651..f600dfb15379233111b5893f6aa= 85a12e6c9e131 100644 --- a/rust/kernel/drm/gpuvm/vm_bo.rs +++ b/rust/kernel/drm/gpuvm/vm_bo.rs @@ -101,6 +101,14 @@ pub fn obj(&self) -> &T::Object { pub fn data(&self) -> &T::VmBoData { &self.data } + + pub(super) fn lock_gpuva(&self) -> crate::sync::MutexGuard<'_, ()> { + // SAFETY: The GEM object is valid. + let ptr =3D unsafe { &raw mut (*self.obj().as_raw()).gpuva.lock }; + // SAFETY: The GEM object is valid, so the mutex is properly initi= alized. + let mutex =3D unsafe { crate::sync::Mutex::from_raw(ptr) }; + mutex.lock() + } } =20 /// A pre-allocated [`GpuVmBo`] object. --=20 2.52.0.457.g6b5491de43-goog