From nobody Mon Feb 9 00:01:00 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CE7991799F for ; Fri, 16 Jan 2026 20:48:43 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768596526; cv=none; b=c5d+Tcg+CyZEdP50ZFHBryDzaPxl2ghgQNpH2x/gBmZ3FFXr4a5m/kFG2xQSEbCBhSjtOIn1SFkVWhy0byRibXCz/kCt446h2PfR5JmG+8c7chu3J6eKHWYvuKL/IraXc2UVAQ8HqfKUQl9nrWoIQi/4pbCptfcp/o7POsdLzxY= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1768596526; c=relaxed/simple; bh=FAv0CtTJDxhhxp50EsrMuEvdyVU2QyAs1/6W6Kes+O0=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=u0JlIU/xew9d/AUnDDd8WQUvcrgqL9bQBHeDlz3xHQyhhqqxNPrqpES5B8tVuOzItu7ywU9mPT9KU5K9H1E+IALP2FGCV314VSAXQJ90zUXQS7ffINxmq2JZkzu816ZTWLCX7d6eNV2hK/yT2pD/reBFoGB/GV5X6c74RDpyQqE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com; spf=pass smtp.mailfrom=redhat.com; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b=MPH2i+S6; arc=none smtp.client-ip=170.10.129.124 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=redhat.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=redhat.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.b="MPH2i+S6" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1768596522; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=GgcxuedbKq3ykHuq5pS9+bDBt59oyyMAJFklmf10ejs=; b=MPH2i+S6YlX6kkzO+Qd2Nig+pYJC1XSaKUY9fPCNP1hmBszAatQDoG8A9/LziCFtyvgmBS L6GB0e01jZAgsxkaZS/kcc0tAs3U8E9hl5vJ/NZ3hoBlHyXjRYZuMbIu47QTbLfKpd/19h gg5/0kNN2hwfIlNnX0cO1bwpaYwHA7Y= Received: from mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-397-uyKkJJIDMcOQ2nau3WOljw-1; Fri, 16 Jan 2026 15:48:39 -0500 X-MC-Unique: uyKkJJIDMcOQ2nau3WOljw-1 X-Mimecast-MFC-AGG-ID: uyKkJJIDMcOQ2nau3WOljw_1768596517 Received: from mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.12]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mx-prod-mc-08.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 7956E1800372; Fri, 16 Jan 2026 20:48:37 +0000 (UTC) Received: from GoldenWind.redhat.com (unknown [10.22.88.63]) by mx-prod-int-03.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTP id 5917619560A7; Fri, 16 Jan 2026 20:48:35 +0000 (UTC) From: Lyude Paul To: dri-devel@lists.freedesktop.org, nouveau@lists.freedesktop.org, linux-kernel@vger.kernel.org Cc: rust-for-linux@vger.kernel.org, "Miguel Ojeda" , "Simona Vetter" , "Alice Ryhl" , "Shankari Anand" , "David Airlie" , "Benno Lossin" , "Danilo Krummrich" , "Asahi Lina" , "Atharv Dubey" , "Daniel Almeida" , "Lyude Paul" Subject: [PATCH v2 1/3] rust/drm: Introduce DeviceContext Date: Fri, 16 Jan 2026 15:41:41 -0500 Message-ID: <20260116204728.725579-2-lyude@redhat.com> In-Reply-To: <20260116204728.725579-1-lyude@redhat.com> References: <20260116204728.725579-1-lyude@redhat.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Scanned-By: MIMEDefang 3.0 on 10.30.177.12 Content-Type: text/plain; charset="utf-8" One of the tricky things about DRM bindings in Rust is the fact that initialization of a DRM device is a multi-step process. It's quite normal for a device driver to start making use of its DRM device for tasks like creating GEM objects before userspace registration happens. This is an issue in rust though, since prior to userspace registration the device is only partly initialized. This means there's a plethora of DRM device operations we can't yet expose without opening up the door to UB if the DRM device in question isn't yet registered. Additionally, this isn't something we can reliably check at runtime. And even if we could, performing an operation which requires the device be registered when the device isn't actually registered is a programmer bug, meaning there's no real way to gracefully handle such a mistake at runtime. And even if that wasn't the case, it would be horrendously annoying and noisy to have to check if a device is registered constantly throughout a driver. In order to solve this, we first take inspiration from `kernel::device::DeviceContext` and introduce `kernel::drm::DeviceContext`. This provides us with a ZST type that we can generalize over to represent contexts where a device is known to have been registered with userspace at some point in time (`Registered`), along with contexts where we can't make such a guarantee (`Uninit`). It's important to note we intentionally do not provide a `DeviceContext` which represents an unregistered device. This is because there's no reasonable way to guarantee that a device with long-living references to itself will not be registered eventually with userspace. Instead, we provide a new-type for this: `UnregisteredDevice` which can provide a guarantee that the `Device` has never been registered with userspace. To ensure this, we modify `Registration` so that creating a new `Registration` requires passing ownership of an `UnregisteredDevice`. Signed-off-by: Lyude Paul --- V2: * Make sure that `UnregisteredDevice` is not thread-safe (since DRM device initialization is also not thread-safe) * Rename from AnyCtx to Uninit, I think this name actually makes a bit more sense. * Change assume_registered() to assume_ctx() Since it looks like in some situations, we'll want to update the DeviceContext of a object to the latest DeviceContext we know the Device to be in. * Rename Init to Uninit When we eventually add KMS support, we're going to have 3 different DeviceContexts - Uninit, Init, Registered. Additionally, aside from not being registered there are a number of portions of the rest of the Device which also aren't usable before at least the Init context - so the naming of Uninit makes this a little clearer. * s/DeviceContext/DeviceContext/ For consistency with the rest of the kernel * Drop as_ref::>() for now since I don't actually think we need this quite yet drivers/gpu/drm/nova/driver.rs | 8 +- drivers/gpu/drm/tyr/driver.rs | 10 +- rust/kernel/drm/device.rs | 172 +++++++++++++++++++++++++++------ rust/kernel/drm/driver.rs | 35 +++++-- rust/kernel/drm/mod.rs | 4 + 5 files changed, 182 insertions(+), 47 deletions(-) diff --git a/drivers/gpu/drm/nova/driver.rs b/drivers/gpu/drm/nova/driver.rs index b1af0a099551d..99d6841b69cbc 100644 --- a/drivers/gpu/drm/nova/driver.rs +++ b/drivers/gpu/drm/nova/driver.rs @@ -21,7 +21,7 @@ pub(crate) struct NovaDriver { } =20 /// Convienence type alias for the DRM device type for this driver -pub(crate) type NovaDevice =3D drm::Device; +pub(crate) type NovaDevice =3D drm::Device; =20 #[pin_data] pub(crate) struct NovaData { @@ -56,10 +56,10 @@ impl auxiliary::Driver for NovaDriver { fn probe(adev: &auxiliary::Device, _info: &Self::IdInfo) -> impl= PinInit { let data =3D try_pin_init!(NovaData { adev: adev.into() }); =20 - let drm =3D drm::Device::::new(adev.as_ref(), data)?; - drm::Registration::new_foreign_owned(&drm, adev.as_ref(), 0)?; + let drm =3D drm::UnregisteredDevice::::new(adev.as_ref(), da= ta)?; + let drm =3D drm::Registration::new_foreign_owned(drm, adev.as_ref(= ), 0)?; =20 - Ok(Self { drm }) + Ok(Self { drm: drm.into() }) } } =20 diff --git a/drivers/gpu/drm/tyr/driver.rs b/drivers/gpu/drm/tyr/driver.rs index f0da589327027..09c3157531072 100644 --- a/drivers/gpu/drm/tyr/driver.rs +++ b/drivers/gpu/drm/tyr/driver.rs @@ -29,7 +29,7 @@ pub(crate) type IoMem =3D kernel::io::mem::IoMem; =20 /// Convenience type alias for the DRM device type for this driver. -pub(crate) type TyrDevice =3D drm::Device; +pub(crate) type TyrDevice =3D drm::Device; =20 #[pin_data(PinnedDrop)] pub(crate) struct TyrDriver { @@ -139,10 +139,12 @@ fn probe( gpu_info, }); =20 - let tdev: ARef =3D drm::Device::new(pdev.as_ref(), data= )?; - drm::driver::Registration::new_foreign_owned(&tdev, pdev.as_ref(),= 0)?; + let tdev =3D drm::UnregisteredDevice::::new(pdev.as_ref(), d= ata)?; + let tdev =3D drm::driver::Registration::new_foreign_owned(tdev, pd= ev.as_ref(), 0)?; =20 - let driver =3D TyrDriver { device: tdev }; + let driver =3D TyrDriver { + device: tdev.into(), + }; =20 // We need this to be dev_info!() because dev_dbg!() does not work= at // all in Rust for now, and we need to see whether probe succeeded. diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs index 3ce8f62a00569..53ca71daf2f86 100644 --- a/rust/kernel/drm/device.rs +++ b/rust/kernel/drm/device.rs @@ -7,14 +7,20 @@ use crate::{ alloc::allocator::Kmalloc, bindings, device, drm, - drm::driver::AllocImpl, + drm::{driver::AllocImpl, private::Sealed}, error::from_err_ptr, error::Result, prelude::*, sync::aref::{ARef, AlwaysRefCounted}, - types::Opaque, + types::{NotThreadSafe, Opaque}, +}; +use core::{ + alloc::Layout, + marker::PhantomData, + mem::{self}, + ops::Deref, + ptr::{self, NonNull}, }; -use core::{alloc::Layout, mem, ops::Deref, ptr, ptr::NonNull}; =20 #[cfg(CONFIG_DRM_LEGACY)] macro_rules! drm_legacy_fields { @@ -47,26 +53,88 @@ macro_rules! drm_legacy_fields { } } =20 -/// A typed DRM device with a specific `drm::Driver` implementation. +macro_rules! drm_dev_ctx { + ( + $( #[$attrs:meta] )* + $name:ident + ) =3D> { + $( #[$attrs] )* + pub struct $name; + + impl DeviceContext for $name {} + impl Sealed for $name {} + + // SAFETY: All registration states are free of side-effects (e.g. = no Drop) and are ZSTs, + // thus they are always thread-safe. + unsafe impl Send for $name {} + // SAFETY: All registration states are free of side-effects (e.g. = no Drop) and are ZSTs, + // thus they are always thread-safe. + unsafe impl Sync for $name {} + }; +} + +/// A trait implemented by all possible contexts a [`Device`] can be used = in. +pub trait DeviceContext: Sealed + Send + Sync {} + +drm_dev_ctx! { + /// The [`DeviceContext`] of a [`Device`] that was registered with use= rspace at some point. + /// + /// This represents a [`Device`] which is guaranteed to have been regi= stered with userspace at + /// some point in time. Such a DRM device is guaranteed to have been f= ully-initialized. + /// + /// Note: A device in this context is not guaranteed to remain registe= red with userspace for its + /// entire lifetime, as this is impossible to guarantee at compile-tim= e. However, any + /// userspace-dependent operations performed with an unregistered devi= ce in this [`DeviceContext`] + /// are guaranteed to be no-ops. + /// + /// # Invariants + /// + /// A [`Device`] in this [`DeviceContext`] is guaranteed to have calle= d `drm_dev_register` once. + Registered +} + +drm_dev_ctx! { + /// The [`DeviceContext`] of a [`Device`] that may be unregistered and= partly uninitialized. + /// + /// A [`Device`] in this context is only guaranteed to be partly initi= alized, and may or may not + /// be registered with userspace. Thus operations which depend on the = [`Device`] being fully + /// initialized, or which depend on the [`Device`] being registered wi= th userspace are not + /// available through this [`DeviceContext`]. + /// + /// A [`Device`] in this context can be used to create a + /// [`Registration`](drm::driver::Registration). + Uninit +} + +/// A [`Device`] which is known at compile-time to be unregistered with us= erspace. /// -/// The device is always reference-counted. +/// This type allows performing operations which are only safe to do befor= e userspace registration, +/// and can be used to create a [`Registration`](drm::driver::Registration= ) once the driver is ready +/// to register the device with userspace. +/// +/// Since DRM device initialization must be single-threaded, this object i= s not thread-safe. /// /// # Invariants /// -/// `self.dev` is a valid instance of a `struct device`. -#[repr(C)] -pub struct Device { - dev: Opaque, - data: T::Data, +/// The device in `self.0` is guaranteed to be a newly created [`Device`] = that has not yet been +/// registered with userspace until this type is dropped. +pub struct UnregisteredDevice(ARef>, Not= ThreadSafe); + +impl Deref for UnregisteredDevice { + type Target =3D Device; + + fn deref(&self) -> &Self::Target { + &self.0 + } } =20 -impl Device { +impl UnregisteredDevice { const VTABLE: bindings::drm_driver =3D drm_legacy_fields! { load: None, open: Some(drm::File::::open_callback), postclose: Some(drm::File::::postclose_callback), unload: None, - release: Some(Self::release), + release: Some(Device::::release), master_set: None, master_drop: None, debugfs_init: None, @@ -94,8 +162,10 @@ impl Device { =20 const GEM_FOPS: bindings::file_operations =3D drm::gem::create_fops(); =20 - /// Create a new `drm::Device` for a `drm::Driver`. - pub fn new(dev: &device::Device, data: impl PinInit) -= > Result> { + /// Create a new `UnregisteredDevice` for a `drm::Driver`. + /// + /// This can be used to create a [`Registration`](kernel::drm::Registr= ation). + pub fn new(dev: &device::Device, data: impl PinInit) -= > Result { // `__drm_dev_alloc` uses `kmalloc()` to allocate memory, hence en= sure a `kmalloc()` // compatible `Layout`. let layout =3D Kmalloc::aligned_layout(Layout::new::()); @@ -103,12 +173,12 @@ pub fn new(dev: &device::Device, data: impl PinInit) -> Result =3D unsafe { bindings::__drm_dev_alloc( dev.as_raw(), &Self::VTABLE, layout.size(), - mem::offset_of!(Self, dev), + mem::offset_of!(Device, dev), ) } .cast(); @@ -123,7 +193,7 @@ pub fn new(dev: &device::Device, data: impl PinInit) -> Result) -> Result { + dev: Opaque, + data: T::Data, + _ctx: PhantomData, +} + +impl Device { pub(crate) fn as_raw(&self) -> *mut bindings::drm_device { self.dev.get() } @@ -160,13 +261,13 @@ unsafe fn into_drm_device(ptr: NonNull) -> *mut= bindings::drm_device { /// /// # Safety /// - /// Callers must ensure that `ptr` is valid, non-null, and has a non-z= ero reference count, - /// i.e. it must be ensured that the reference count of the C `struct = drm_device` `ptr` points - /// to can't drop to zero, for the duration of this function call and = the entire duration when - /// the returned reference exists. - /// - /// Additionally, callers must ensure that the `struct device`, `ptr` = is pointing to, is - /// embedded in `Self`. + /// * Callers must ensure that `ptr` is valid, non-null, and has a non= -zero reference count, + /// i.e. it must be ensured that the reference count of the C `struc= t drm_device` `ptr` points + /// to can't drop to zero, for the duration of this function call an= d the entire duration when + /// the returned reference exists. + /// * Additionally, callers must ensure that the `struct device`, `ptr= ` is pointing to, is + /// embedded in `Self`. + /// * Callers promise that any type invariants of `C` will be upheld. #[doc(hidden)] pub unsafe fn from_raw<'a>(ptr: *const bindings::drm_device) -> &'a Se= lf { // SAFETY: By the safety requirements of this function `ptr` is a = valid pointer to a @@ -186,9 +287,20 @@ extern "C" fn release(ptr: *mut bindings::drm_device) { // - `this` is valid for dropping. unsafe { core::ptr::drop_in_place(this) }; } + + /// Change the [`DeviceContext`] for a [`Device`]. + /// + /// # Safety + /// + /// The caller promises that `self` fulfills all of the guarantees pro= vided by the given + /// [`DeviceContext`]. + pub(crate) unsafe fn assume_ctx(&self) -> &Devi= ce { + // SAFETY: The data layout is identical via our type invariants. + unsafe { mem::transmute(self) } + } } =20 -impl Deref for Device { +impl Deref for Device { type Target =3D T::Data; =20 fn deref(&self) -> &Self::Target { @@ -198,7 +310,7 @@ fn deref(&self) -> &Self::Target { =20 // SAFETY: DRM device objects are always reference counted and the get/put= functions // satisfy the requirements. -unsafe impl AlwaysRefCounted for Device { +unsafe impl AlwaysRefCounted for Device<= T, C> { fn inc_ref(&self) { // SAFETY: The existence of a shared reference guarantees that the= refcount is non-zero. unsafe { bindings::drm_dev_get(self.as_raw()) }; @@ -213,7 +325,7 @@ unsafe fn dec_ref(obj: NonNull) { } } =20 -impl AsRef for Device { +impl AsRef for Device { fn as_ref(&self) -> &device::Device { // SAFETY: `bindings::drm_device::dev` is valid as long as the DRM= device itself is valid, // which is guaranteed by the type invariant. @@ -222,8 +334,8 @@ fn as_ref(&self) -> &device::Device { } =20 // SAFETY: A `drm::Device` can be released from any thread. -unsafe impl Send for Device {} +unsafe impl Send for Device {} =20 // SAFETY: A `drm::Device` can be shared among threads because all immutab= le methods are protected // by the synchronization in `struct drm_device`. -unsafe impl Sync for Device {} +unsafe impl Sync for Device {} diff --git a/rust/kernel/drm/driver.rs b/rust/kernel/drm/driver.rs index f30ee4c6245cd..fb4f215320bd9 100644 --- a/rust/kernel/drm/driver.rs +++ b/rust/kernel/drm/driver.rs @@ -10,6 +10,7 @@ prelude::*, sync::aref::ARef, }; +use core::{mem, ptr::NonNull}; use macros::vtable; =20 /// Driver use the GEM memory manager. This should be set for all modern d= rivers. @@ -122,30 +123,46 @@ pub trait Driver { =20 impl Registration { /// Creates a new [`Registration`] and registers it. - fn new(drm: &drm::Device, flags: usize) -> Result { + fn new(drm: drm::UnregisteredDevice, flags: usize) -> Result { // SAFETY: `drm.as_raw()` is valid by the invariants of `drm::Devi= ce`. to_result(unsafe { bindings::drm_dev_register(drm.as_raw(), flags)= })?; =20 - Ok(Self(drm.into())) + // SAFETY: We just called `drm_dev_register` above + let new =3D NonNull::from(unsafe { drm.assume_ctx() }); + + // Leak the ARef from UnregisteredDevice in preparation for transf= erring its ownership. + mem::forget(drm); + + // SAFETY: `drm`'s `Drop` constructor was never called, ensuring t= hat there remains at least + // one reference to the device - which we take ownership over here. + let new =3D unsafe { ARef::from_raw(new) }; + + Ok(Self(new)) } =20 - /// Same as [`Registration::new`}, but transfers ownership of the [`Re= gistration`] to + /// Same as [`Registration::new`], but transfers ownership of the [`Re= gistration`] to /// [`devres::register`]. - pub fn new_foreign_owned( - drm: &drm::Device, - dev: &device::Device, + pub fn new_foreign_owned<'a>( + drm: drm::UnregisteredDevice, + dev: &'a device::Device, flags: usize, - ) -> Result + ) -> Result<&'a drm::Device> where T: 'static, { - if drm.as_ref().as_raw() !=3D dev.as_raw() { + let this_dev: &device::Device =3D drm.as_ref(); + if this_dev.as_raw() !=3D dev.as_raw() { return Err(EINVAL); } =20 let reg =3D Registration::::new(drm, flags)?; + let drm =3D NonNull::from(reg.device()); + + devres::register(dev, reg, GFP_KERNEL)?; =20 - devres::register(dev, reg, GFP_KERNEL) + // SAFETY: Since `reg` was passed to devres::register(), the devic= e now owns the lifetime + // of the DRM registration - ensuring that this references lives f= or at least as long as 'a. + Ok(unsafe { drm.as_ref() }) } =20 /// Returns a reference to the `Device` instance for this registration. diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 1b82b6945edf2..64a43cb0fe57c 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -9,6 +9,10 @@ pub mod ioctl; =20 pub use self::device::Device; +pub use self::device::DeviceContext; +pub use self::device::Registered; +pub use self::device::Uninit; +pub use self::device::UnregisteredDevice; pub use self::driver::Driver; pub use self::driver::DriverInfo; pub use self::driver::Registration; --=20 2.52.0