[RFC v3 02/33] rust: drm: Add traits for registering KMS devices

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This commit adds some traits for registering DRM devices with KMS support,
implemented through the kernel::drm::kms::KmsDriver trait. Devices which
don't have KMS support can simply use PhantomData<Self>.

Signed-off-by: Lyude Paul <lyude@xxxxxxxxxx>

---

V3:
* Get rid of Kms, long live KmsDriver
  After Daniel pointed out that we should just make KmsDriver a supertrait
  of Driver, it immediately occurred to me that there's no actual need for
  Kms to be a separate trait at all. So, drop Kms entirely and move its
  requirements over to KmsDriver.
* Drop fbdev module entirely and move fbdev related setup into AllocImpl
  (Daniel)
* Rebase to use drm_client_setup()

TODO:
* Generate feature flags automatically, these shouldn't need to be
  specified by the user

Signed-off-by: Lyude Paul <lyude@xxxxxxxxxx>
---
 rust/bindings/bindings_helper.h |   6 ++
 rust/kernel/drm/device.rs       |  10 +-
 rust/kernel/drm/drv.rs          |  56 ++++++++--
 rust/kernel/drm/gem/mod.rs      |   4 +
 rust/kernel/drm/gem/shmem.rs    |   4 +
 rust/kernel/drm/kms.rs          | 186 ++++++++++++++++++++++++++++++++
 rust/kernel/drm/mod.rs          |   1 +
 7 files changed, 258 insertions(+), 9 deletions(-)
 create mode 100644 rust/kernel/drm/kms.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index ca857fb00b1a5..e1ed4f40c8e89 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -6,10 +6,16 @@
  * Sorted alphabetically.
  */
 
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/clients/drm_client_setup.h>
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_fbdev_shmem.h>
 #include <drm/drm_gem.h>
+#include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_gem_shmem_helper.h>
 #include <drm/drm_ioctl.h>
 #include <kunit/test.h>
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
index 5b4db2dfe87f5..cf063de387329 100644
--- a/rust/kernel/drm/device.rs
+++ b/rust/kernel/drm/device.rs
@@ -5,8 +5,8 @@
 //! C header: [`include/linux/drm/drm_device.h`](srctree/include/linux/drm/drm_device.h)
 
 use crate::{
-    bindings, device, drm,
-    drm::drv::AllocImpl,
+    bindings, device,
+    drm::{self, drv::AllocImpl, kms::private::KmsImpl as KmsImplPrivate},
     error::code::*,
     error::from_err_ptr,
     error::Result,
@@ -73,7 +73,7 @@ impl<T: drm::drv::Driver> Device<T> {
         dumb_create: T::Object::ALLOC_OPS.dumb_create,
         dumb_map_offset: T::Object::ALLOC_OPS.dumb_map_offset,
         show_fdinfo: None,
-        fbdev_probe: None,
+        fbdev_probe: T::Object::ALLOC_OPS.fbdev_probe,
 
         major: T::INFO.major,
         minor: T::INFO.minor,
@@ -153,6 +153,10 @@ pub fn data(&self) -> <T::Data as ForeignOwnable>::Borrowed<'_> {
         // SAFETY: `Self::data` is always converted and set on device creation.
         unsafe { <T::Data as ForeignOwnable>::from_foreign(drm.raw_data()) };
     }
+
+    pub(crate) const fn has_kms() -> bool {
+        <T::Kms as KmsImplPrivate>::MODE_CONFIG_OPS.is_some()
+    }
 }
 
 // SAFETY: DRM device objects are always reference counted and the get/put functions
diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs
index e42e266bdd0da..3e09e130730f6 100644
--- a/rust/kernel/drm/drv.rs
+++ b/rust/kernel/drm/drv.rs
@@ -6,14 +6,15 @@
 
 use crate::{
     alloc::flags::*,
-    bindings,
+    bindings, device,
     devres::Devres,
-    drm,
+    drm::{self, kms::private::KmsImpl as KmsImplPrivate},
     error::{Error, Result},
     private::Sealed,
     str::CStr,
     types::{ARef, ForeignOwnable},
 };
+use core::ptr::null;
 use macros::vtable;
 
 /// Driver use the GEM memory manager. This should be set for all modern drivers.
@@ -115,6 +116,12 @@ pub struct AllocOps {
             offset: *mut u64,
         ) -> core::ffi::c_int,
     >,
+    pub(crate) fbdev_probe: Option<
+        unsafe extern "C" fn(
+            fbdev_helper: *mut bindings::drm_fb_helper,
+            sizes: *mut bindings::drm_fb_helper_surface_size,
+        ) -> core::ffi::c_int,
+    >,
 }
 
 /// Trait for memory manager implementations. Implemented internally.
@@ -142,6 +149,14 @@ pub trait Driver {
     /// The type used to represent a DRM File (client)
     type File: drm::file::DriverFile;
 
+    /// The KMS implementation for this driver.
+    ///
+    /// Drivers that wish to support KMS should pass their implementation of `drm::kms::KmsDriver`
+    /// here. Drivers which do not have KMS support can simply pass `drm::kms::NoKms` here.
+    type Kms: drm::kms::KmsImpl<Driver = Self>
+    where
+        Self: Sized;
+
     /// Driver metadata
     const INFO: DriverInfo;
 
@@ -159,21 +174,44 @@ pub trait Driver {
 
 impl<T: Driver> Registration<T> {
     /// Creates a new [`Registration`] and registers it.
-    pub fn new(drm: ARef<drm::device::Device<T>>, flags: usize) -> Result<Self> {
+    pub fn new(dev: &device::Device, data: T::Data, flags: usize) -> Result<Self> {
+        let drm = drm::device::Device::<T>::new(dev, data)?;
+        let has_kms = drm::device::Device::<T>::has_kms();
+
+        let mode_config_info = if has_kms {
+            // SAFETY: We have yet to register this device
+            Some(unsafe { T::Kms::setup_kms(&drm)? })
+        } else {
+            None
+        };
+
         // SAFETY: Safe by the invariants of `drm::device::Device`.
         let ret = unsafe { bindings::drm_dev_register(drm.as_raw(), flags) };
         if ret < 0 {
             return Err(Error::from_errno(ret));
         }
 
+        #[cfg(CONFIG_DRM_CLIENT = "y")]
+        if has_kms {
+            if let Some(ref info) = mode_config_info {
+                if let Some(fourcc) = info.preferred_fourcc {
+                    // SAFETY: We just registered `drm` above, fulfilling the C API requirements
+                    unsafe { bindings::drm_client_setup_with_fourcc(drm.as_raw(), fourcc) }
+                } else {
+                    // SAFETY: We just registered `drm` above, fulfilling the C API requirements
+                    unsafe { bindings::drm_client_setup(drm.as_raw(), null()) }
+                }
+            }
+        }
+
         Ok(Self(drm))
     }
 
     /// Same as [`Registration::new`}, but transfers ownership of the [`Registration`] to `Devres`.
-    pub fn new_foreign_owned(drm: ARef<drm::device::Device<T>>, flags: usize) -> Result {
-        let reg = Registration::<T>::new(drm.clone(), flags)?;
+    pub fn new_foreign_owned(dev: &device::Device, data: T::Data, flags: usize) -> Result {
+        let reg = Registration::<T>::new(dev, data, flags)?;
 
-        Devres::new_foreign_owned(drm.as_ref(), reg, GFP_KERNEL)
+        Devres::new_foreign_owned(dev, reg, GFP_KERNEL)
     }
 
     /// Returns a reference to the `Device` instance for this registration.
@@ -195,5 +233,11 @@ fn drop(&mut self) {
         // SAFETY: Safe by the invariant of `ARef<drm::device::Device<T>>`. The existance of this
         // `Registration` also guarantees the this `drm::device::Device` is actually registered.
         unsafe { bindings::drm_dev_unregister(self.0.as_raw()) };
+
+        if drm::device::Device::<T>::has_kms() {
+            // SAFETY: We just checked above that KMS was setup for this device, so this is safe to
+            // call
+            unsafe { bindings::drm_atomic_helper_shutdown(self.0.as_raw()) }
+        }
     }
 }
diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
index 3fcab497cc2a5..605b0a22ac08b 100644
--- a/rust/kernel/drm/gem/mod.rs
+++ b/rust/kernel/drm/gem/mod.rs
@@ -300,6 +300,10 @@ impl<T: DriverObject> drv::AllocImpl for Object<T> {
         gem_prime_import_sg_table: None,
         dumb_create: None,
         dumb_map_offset: None,
+        #[cfg(CONFIG_DRM_FBDEV_EMULATION = "y")]
+        fbdev_probe: Some(bindings::drm_fbdev_dma_driver_fbdev_probe),
+        #[cfg(CONFIG_DRM_FBDEV_EMULATION = "n")]
+        fbdev_probe: None,
     };
 }
 
diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs
index 92da0d7d59912..9c0162b268aa8 100644
--- a/rust/kernel/drm/gem/shmem.rs
+++ b/rust/kernel/drm/gem/shmem.rs
@@ -279,6 +279,10 @@ impl<T: DriverObject> drv::AllocImpl for Object<T> {
         gem_prime_import_sg_table: Some(bindings::drm_gem_shmem_prime_import_sg_table),
         dumb_create: Some(bindings::drm_gem_shmem_dumb_create),
         dumb_map_offset: None,
+        #[cfg(CONFIG_DRM_FBDEV_EMULATION = "y")]
+        fbdev_probe: Some(bindings::drm_fbdev_shmem_driver_fbdev_probe),
+        #[cfg(CONFIG_DRM_FBDEV_EMULATION = "n")]
+        fbdev_probe: None,
     };
 }
 
diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs
new file mode 100644
index 0000000000000..78970c69f4cda
--- /dev/null
+++ b/rust/kernel/drm/kms.rs
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! KMS driver abstractions for rust.
+
+use crate::{
+    device,
+    drm::{device::Device, drv::Driver},
+    error::to_result,
+    prelude::*,
+    types::*,
+};
+use bindings;
+use core::{marker::PhantomData, ops::Deref};
+
+/// The C vtable for a [`Device`].
+///
+/// This is created internally by DRM.
+pub struct ModeConfigOps {
+    pub(crate) kms_vtable: bindings::drm_mode_config_funcs,
+    pub(crate) kms_helper_vtable: bindings::drm_mode_config_helper_funcs,
+}
+
+/// A trait representing a type that can be used for setting up KMS, or a stub.
+///
+/// For drivers which don't have KMS support, the methods provided by this trait may be stubs. It is
+/// implemented internally by DRM.
+pub trait KmsImpl: private::KmsImpl {}
+
+pub(crate) mod private {
+    use super::*;
+
+    /// Private callback implemented internally by DRM for setting up KMS on a device, or stubbing
+    /// the KMS setup for devices which don't have KMS support.
+    #[allow(unreachable_pub)]
+    pub trait KmsImpl {
+        /// The parent driver for this KMS implementation
+        type Driver: Driver;
+
+        /// The optional KMS callback operations for this driver.
+        const MODE_CONFIG_OPS: Option<ModeConfigOps>;
+
+        /// The callback for setting up KMS on a device
+        ///
+        /// # Safety
+        ///
+        /// `drm` must be unregistered.
+        unsafe fn setup_kms(_drm: &Device<Self::Driver>) -> Result<ModeConfigInfo> {
+            build_error::build_error("This should never be reachable")
+        }
+    }
+}
+
+/// A [`Device`] with KMS initialized that has not been registered with userspace.
+///
+/// This type is identical to [`Device`], except that it is able to create new static KMS resources.
+/// It represents a KMS device that is not yet visible to userspace, and also contains miscellaneous
+/// state required during the initialization process of a [`Device`].
+pub struct UnregisteredKmsDevice<'a, T: Driver> {
+    drm: &'a Device<T>,
+}
+
+impl<'a, T: Driver> Deref for UnregisteredKmsDevice<'a, T> {
+    type Target = Device<T>;
+
+    fn deref(&self) -> &Self::Target {
+        self.drm
+    }
+}
+
+impl<'a, T: Driver> UnregisteredKmsDevice<'a, T> {
+    /// Construct a new [`UnregisteredKmsDevice`].
+    ///
+    /// # Safety
+    ///
+    /// The caller promises that `drm` is an unregistered [`Device`].
+    pub(crate) unsafe fn new(drm: &'a Device<T>) -> Self {
+        Self { drm }
+    }
+}
+
+/// A trait which must be implemented by drivers that wish to support KMS
+///
+/// It should be implemented for the same type that implements [`Driver`]. Drivers which don't
+/// support KMS should use [`PhantomData<Self>`].
+///
+/// [`PhantomData<Self>`]: PhantomData
+#[vtable]
+pub trait KmsDriver: Driver {
+    /// Return a [`ModeConfigInfo`] structure for this [`device::Device`].
+    fn mode_config_info(
+        dev: &device::Device,
+        drm_data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+    ) -> Result<ModeConfigInfo>;
+
+    /// Create mode objects like [`crtc::Crtc`], [`plane::Plane`], etc. for this device
+    fn create_objects(drm: &UnregisteredKmsDevice<'_, Self>) -> Result
+    where
+        Self: Sized;
+}
+
+impl<T: KmsDriver> private::KmsImpl for T {
+    type Driver = Self;
+
+    const MODE_CONFIG_OPS: Option<ModeConfigOps> = Some(ModeConfigOps {
+        kms_vtable: bindings::drm_mode_config_funcs {
+            atomic_check: Some(bindings::drm_atomic_helper_check),
+            fb_create: Some(bindings::drm_gem_fb_create),
+            mode_valid: None,
+            atomic_commit: Some(bindings::drm_atomic_helper_commit),
+            get_format_info: None,
+            atomic_state_free: None,
+            atomic_state_alloc: None,
+            atomic_state_clear: None,
+        },
+
+        kms_helper_vtable: bindings::drm_mode_config_helper_funcs {
+            atomic_commit_setup: None,
+            atomic_commit_tail: None,
+        },
+    });
+
+    unsafe fn setup_kms(drm: &Device<Self::Driver>) -> Result<ModeConfigInfo> {
+        let mode_config_info = T::mode_config_info(drm.as_ref(), drm.data())?;
+
+        // SAFETY: `MODE_CONFIG_OPS` is always Some() in this implementation
+        let ops = unsafe { T::MODE_CONFIG_OPS.as_ref().unwrap_unchecked() };
+
+        // SAFETY:
+        // - This function can only be called before registration via our safety contract.
+        // - Before registration, we are the only ones with access to this device.
+        unsafe {
+            (*drm.as_raw()).mode_config = bindings::drm_mode_config {
+                funcs: &ops.kms_vtable,
+                helper_private: &ops.kms_helper_vtable,
+                min_width: mode_config_info.min_resolution.0,
+                min_height: mode_config_info.min_resolution.1,
+                max_width: mode_config_info.max_resolution.0,
+                max_height: mode_config_info.max_resolution.1,
+                cursor_width: mode_config_info.max_cursor.0,
+                cursor_height: mode_config_info.max_cursor.1,
+                preferred_depth: mode_config_info.preferred_depth,
+                ..Default::default()
+            };
+        }
+
+        // SAFETY: We just setup all of the required info this function needs in `drm_device`
+        to_result(unsafe { bindings::drmm_mode_config_init(drm.as_raw()) })?;
+
+        // SAFETY: `drm` is guaranteed to be unregistered via our safety contract.
+        let drm = unsafe { UnregisteredKmsDevice::new(drm) };
+
+        T::create_objects(&drm)?;
+
+        // TODO: Eventually add a hook to customize how state readback happens, for now just reset
+        // SAFETY: Since all static modesetting objects were created in `T::create_objects()`, and
+        // that is the only place they can be created, this fulfills the C API requirements.
+        unsafe { bindings::drm_mode_config_reset(drm.as_raw()) };
+
+        Ok(mode_config_info)
+    }
+}
+
+impl<T: KmsDriver> KmsImpl for T {}
+
+impl<T: Driver> private::KmsImpl for PhantomData<T> {
+    type Driver = T;
+
+    const MODE_CONFIG_OPS: Option<ModeConfigOps> = None;
+}
+
+impl<T: Driver> KmsImpl for PhantomData<T> {}
+
+/// Various device-wide information for a [`Device`] that is provided during initialization.
+#[derive(Copy, Clone)]
+pub struct ModeConfigInfo {
+    /// The minimum (w, h) resolution this driver can support
+    pub min_resolution: (i32, i32),
+    /// The maximum (w, h) resolution this driver can support
+    pub max_resolution: (i32, i32),
+    /// The maximum (w, h) cursor size this driver can support
+    pub max_cursor: (u32, u32),
+    /// The preferred depth for dumb ioctls
+    pub preferred_depth: u32,
+    /// An optional default fourcc format code to be preferred for clients.
+    pub preferred_fourcc: Option<u32>,
+}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index 2c12dbd181997..049ae675cb9b1 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -8,3 +8,4 @@
 pub mod fourcc;
 pub mod gem;
 pub mod ioctl;
+pub mod kms;
-- 
2.48.1




[Index of Archives]     [Linux DRI Users]     [Linux Intel Graphics]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [XFree86]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [XFree86]
  Powered by Linux