[PATCH v3 10/23] rust: drm: framebuffer: add validated shmem scanout views
Mike Lothian <[email protected]>
| Newsgroups | org.kernel.vger.rust-for-linux,org.freedesktop.lists.dri-devel,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
Add framebuffer geometry and reference-counting operations for drivers that retain and inspect scanout buffers outside an atomic callback. Build borrowed and owned adapters on the existing shmem VMap implementation. The owned form retains the GEM object so a driver can prepare and reuse a bounded scanout pool. Constrain both adapters to the exact Rust shmem object type. Reject foreign-device, imported, multiplane, non-linear, block-layout, undersized, and invalid-pitch framebuffers. Apply the framebuffer offset to the returned SysMem view with checked size arithmetic, and add KUnit coverage for the validation rules. Assisted-by: Claude:claude-opus-5 Signed-off-by: Mike Lothian <[email protected]> --- rust/helpers/drm/drm.c | 1 + rust/helpers/drm/framebuffer.c | 13 ++ rust/kernel/drm/fourcc.rs | 28 ++- rust/kernel/drm/kms/framebuffer.rs | 345 ++++++++++++++++++++++++++++- 4 files changed, 382 insertions(+), 5 deletions(-) create mode 100644 rust/helpers/drm/framebuffer.c diff --git a/rust/helpers/drm/drm.c b/rust/helpers/drm/drm.c index 45890e9c3290..2144a67623bd 100644 --- a/rust/helpers/drm/drm.c +++ b/rust/helpers/drm/drm.c @@ -3,6 +3,7 @@ #ifdef CONFIG_DRM #ifdef CONFIG_DRM_KMS_HELPER #include "atomic.c" +#include "framebuffer.c" #include "vblank.c" #endif diff --git a/rust/helpers/drm/framebuffer.c b/rust/helpers/drm/framebuffer.c new file mode 100644 index 000000000000..672cee03463a --- /dev/null +++ b/rust/helpers/drm/framebuffer.c @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_framebuffer.h> + +__rust_helper void rust_helper_drm_framebuffer_get(struct drm_framebuffer *fb) +{ + drm_framebuffer_get(fb); +} + +__rust_helper void rust_helper_drm_framebuffer_put(struct drm_framebuffer *fb) +{ + drm_framebuffer_put(fb); +} diff --git a/rust/kernel/drm/fourcc.rs b/rust/kernel/drm/fourcc.rs index a30e40dbc037..010823c4c86c 100644 --- a/rust/kernel/drm/fourcc.rs +++ b/rust/kernel/drm/fourcc.rs @@ -12,9 +12,29 @@ const fn fourcc_code(a: u8, b: u8, c: u8, d: u8) -> u32 { // TODO: We manually import this because we don't have a reasonable way of getting constants from // function-like macros in bindgen yet. pub(crate) const FORMAT_MOD_INVALID: u64 = 0xffffffffffffff; +/// Linear framebuffer layout (`DRM_FORMAT_MOD_LINEAR`). +pub(crate) const FORMAT_MOD_LINEAR: u64 = 0; -// TODO: We need to automate importing all of these. For the time being, just add the single one -// that we need +/// 32 bpp RGB with unused alpha. +pub const XRGB8888: u32 = fourcc_code(b'X', b'R', b'2', b'4'); -/// 32 bpp RGB -pub const XRGB888: u32 = fourcc_code(b'X', b'R', b'2', b'4'); +/// 32 bpp RGB with alpha. +pub const ARGB8888: u32 = fourcc_code(b'A', b'R', b'2', b'4'); + +/// 32 bpp BGR with unused alpha. +pub const XBGR8888: u32 = fourcc_code(b'X', b'B', b'2', b'4'); + +/// 32 bpp BGR with alpha. +pub const ABGR8888: u32 = fourcc_code(b'A', b'B', b'2', b'4'); + +/// 30 bpp 10:10:10 RGB with unused alpha. +pub const XRGB2101010: u32 = fourcc_code(b'X', b'R', b'3', b'0'); + +/// 30 bpp 10:10:10 RGB with alpha. +pub const ARGB2101010: u32 = fourcc_code(b'A', b'R', b'3', b'0'); + +/// 30 bpp 10:10:10 BGR with unused alpha. +pub const XBGR2101010: u32 = fourcc_code(b'X', b'B', b'3', b'0'); + +/// 30 bpp 10:10:10 BGR with alpha. +pub const ABGR2101010: u32 = fourcc_code(b'A', b'B', b'3', b'0'); diff --git a/rust/kernel/drm/kms/framebuffer.rs b/rust/kernel/drm/kms/framebuffer.rs index 54d0391388a9..02e9e63cff30 100644 --- a/rust/kernel/drm/kms/framebuffer.rs +++ b/rust/kernel/drm/kms/framebuffer.rs @@ -5,8 +5,20 @@ //! C header: [`include/drm/drm_framebuffer.h`](srctree/include/drm/drm_framebuffer.h) use super::{KmsDriver, ModeObject, Sealed}; -use crate::{drm::device::Device, types::*}; +use crate::{ + drm::device::Device, + prelude::*, + sync::aref::{ARef, AlwaysRefCounted}, + types::*, +}; +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +use crate::{ + drm::gem::{self, shmem, BaseObject}, + io::{IoBase, SysMem}, +}; use bindings; +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +use core::ops::Deref; use core::{marker::*, ptr}; /// The main interface for [`struct drm_framebuffer`]. @@ -55,6 +67,145 @@ fn eq(&self, other: &Self) -> bool { } impl<T: KmsDriver> Eq for Framebuffer<T> {} +// SAFETY: DRM framebuffers use the refcount in their embedded mode object. The C get/put helpers +// operate on that refcount and release the object only after the last reference is dropped. +unsafe impl<T: KmsDriver> AlwaysRefCounted for Framebuffer<T> { + fn inc_ref(&self) { + // SAFETY: A shared reference proves the framebuffer and its refcount are live. + unsafe { bindings::drm_framebuffer_get(self.0.get()) }; + } + + unsafe fn dec_ref(obj: core::ptr::NonNull<Self>) { + // SAFETY: The caller transfers one live framebuffer reference to this method. + unsafe { bindings::drm_framebuffer_put(obj.as_ref().0.get()) }; + } +} + +/// A validated packed, linear framebuffer mapping backed by Lyude's shmem [`shmem::VMap`]. +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +pub struct FramebufferMapping<O, R> +where + O: gem::DriverObject, + R: Deref<Target = shmem::Object<O>>, +{ + map: shmem::VMap<O, R>, + offset: usize, + len: usize, + pitch: usize, + width: u32, + height: u32, + format: u32, +} + +/// A framebuffer mapping borrowed from its backing object. +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +pub type FramebufferVMap<'a, O> = FramebufferMapping<O, &'a shmem::Object<O>>; + +/// A framebuffer mapping which owns a reference to its backing object. +/// +/// This is suitable for a bounded scanout-registration cache: dropping it releases the mapping and +/// object reference, while retaining it keeps the validated CPU view stable across atomic commits. +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +pub type FramebufferVMapOwned<O> = FramebufferMapping<O, ARef<shmem::Object<O>>>; + +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +struct PackedLayout { + offset: usize, + len: usize, + pitch: usize, +} + +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +fn packed_layout(raw: &bindings::drm_framebuffer, object_size: usize) -> Result<PackedLayout> { + if raw.format.is_null() { + return Err(EINVAL); + } + + // SAFETY: The caller supplies a live framebuffer, whose format descriptor remains valid. + let format = unsafe { &*raw.format }; + if format.num_planes != 1 || raw.modifier != crate::drm::fourcc::FORMAT_MOD_LINEAR { + return Err(EINVAL); + } + + // Restrict this convenience adapter to ordinary packed scanlines. More complex block or tiled + // layouts need a layout-specific API instead of pretending to be a byte raster. + let block_width = unsafe { bindings::drm_format_info_block_width(raw.format, 0) }; + let block_height = unsafe { bindings::drm_format_info_block_height(raw.format, 0) }; + if block_width != 1 || block_height != 1 { + return Err(EINVAL); + } + + let min_pitch = + usize::try_from(unsafe { bindings::drm_format_info_min_pitch(raw.format, 0, raw.width) }) + .map_err(|_| EOVERFLOW)?; + let pitch = raw.pitches[0] as usize; + if pitch < min_pitch { + return Err(EINVAL); + } + + let offset = raw.offsets[0] as usize; + let len = pitch.checked_mul(raw.height as usize).ok_or(EOVERFLOW)?; + let end = offset.checked_add(len).ok_or(EOVERFLOW)?; + if end > object_size { + return Err(EINVAL); + } + + Ok(PackedLayout { offset, len, pitch }) +} + +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +fn validate_object( + raw: &bindings::drm_framebuffer, + object: *mut bindings::drm_gem_object, +) -> Result { + if object.is_null() { + return Err(EINVAL); + } + // SAFETY: The object is non-null and live while its framebuffer owns it. + let object = unsafe { &*object }; + if object.dev != raw.dev || !object.import_attach.is_null() { + return Err(EINVAL); + } + Ok(()) +} + +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +impl<O, R> FramebufferMapping<O, R> +where + O: gem::DriverObject, + R: Deref<Target = shmem::Object<O>>, +{ + /// Return the offset-adjusted pixel storage as a system-memory I/O view. + pub fn view(&self) -> SysMem<'_, [u8]> { + let base = (&self.map).as_view().as_ptr().cast::<u8>(); + // SAFETY: the mapping constructor checked `offset + len` against the object's size, and + // borrowing `self` keeps the owning VMap alive for the returned view. + let ptr = unsafe { core::ptr::slice_from_raw_parts_mut(base.add(self.offset), self.len) }; + // SAFETY: The range above is mapped, kernel-accessible system memory for this borrow. + unsafe { SysMem::new(ptr) } + } + + /// Return the validated line pitch in bytes. + pub fn pitch(&self) -> usize { + self.pitch + } + + /// Return the visible width in pixels. + pub fn width(&self) -> u32 { + self.width + } + + /// Return the visible height in pixels. + pub fn height(&self) -> u32 { + self.height + } + + /// Return the DRM fourcc pixel format. + pub fn format(&self) -> u32 { + self.format + } +} + impl<T: KmsDriver> Framebuffer<T> { /// Convert a raw pointer to a `struct drm_framebuffer` into a [`Framebuffer`] /// @@ -67,4 +218,196 @@ pub(super) unsafe fn from_raw<'a>(ptr: *const bindings::drm_framebuffer) -> &'a // SAFETY: Our data layout is identical to drm_framebuffer unsafe { &*ptr.cast() } } + + /// Return an owned reference to this framebuffer. + pub fn to_aref(&self) -> ARef<Self> { + self.into() + } + + /// Return the framebuffer width in pixels. + pub fn width(&self) -> u32 { + // SAFETY: The framebuffer is initialized via its type invariant. + unsafe { (*self.0.get()).width } + } + + /// Return the framebuffer height in pixels. + pub fn height(&self) -> u32 { + // SAFETY: The framebuffer is initialized via its type invariant. + unsafe { (*self.0.get()).height } + } + + /// Return the framebuffer's DRM fourcc pixel format. + pub fn format(&self) -> u32 { + // SAFETY: An initialized framebuffer has a valid format descriptor. + unsafe { (*(*self.0.get()).format).format } + } + + /// Return the pitch for `plane`, rejecting indices outside the format's actual plane count. + pub fn pitch(&self, plane: usize) -> Result<u32> { + // SAFETY: The framebuffer is initialized via its type invariant. + let raw = unsafe { &*self.0.get() }; + if raw.format.is_null() { + return Err(EINVAL); + } + // SAFETY: `format` is non-null and remains valid for the framebuffer's lifetime. + if plane >= unsafe { (*raw.format).num_planes as usize } || plane >= raw.pitches.len() { + return Err(EINVAL); + } + Ok(raw.pitches[plane]) + } + + /// Map a packed, single-plane, linear Rust shmem framebuffer. + /// + /// The returned view starts at the framebuffer plane's declared offset rather than the start + /// of the GEM object. Multi-plane, imported, non-linear, block-compressed, undersized and + /// cross-device objects are rejected. + #[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] + pub fn vmap<O>(&self) -> Result<FramebufferVMap<'_, O>> + where + O: gem::DriverObject<Driver = T>, + T: crate::drm::Driver<Object = shmem::Object<O>>, + { + // SAFETY: The framebuffer is initialized via its type invariant. + let raw = unsafe { &*self.0.get() }; + let object_raw = raw.obj[0]; + validate_object(raw, object_raw)?; + + // SAFETY: + // - `T::Object` is exactly `shmem::Object<O>` by the associated-type bound above. + // - `validate_object` checked that this is a local, non-imported object owned by this + // framebuffer's instance of `T`. + // - The framebuffer keeps its backing object alive for this borrow. + let object = unsafe { <shmem::Object<O> as gem::IntoGEMObject>::from_raw(object_raw) }; + let layout = packed_layout(raw, object.size())?; + + Ok(FramebufferMapping { + map: object.vmap()?, + offset: layout.offset, + len: layout.len, + pitch: layout.pitch, + width: raw.width, + height: raw.height, + // SAFETY: `packed_layout` rejected a null format pointer above. + format: unsafe { (*raw.format).format }, + }) + } + + /// Returns the GEM object backing plane 0 of this framebuffer. + /// + /// A driver needs this to hand the buffer to a client, which is done by minting a handle for it + /// in that client's file. The same type, ownership, import and device checks as [`Self::vmap`] + /// apply, so the returned reference is known to belong to this driver. + #[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] + pub fn object<O>(&self) -> Result<&shmem::Object<O>> + where + O: gem::DriverObject<Driver = T>, + T: crate::drm::Driver<Object = shmem::Object<O>>, + { + // SAFETY: The framebuffer is initialized via its type invariant. + let raw = unsafe { &*self.0.get() }; + let object_raw = raw.obj[0]; + validate_object(raw, object_raw)?; + + // SAFETY: `validate_object` established that `object_raw` is a live object of this + // driver's type, and it is owned by the framebuffer for at least this borrow. + Ok(unsafe { <shmem::Object<O> as gem::IntoGEMObject>::from_raw(object_raw) }) + } + + /// Map a packed, single-plane, linear Rust shmem framebuffer and retain its backing object. + /// + /// The validation is identical to [`Framebuffer::vmap`], but the returned mapping is not tied + /// to this framebuffer borrow. It can therefore be retained in a bounded prepared-scanout + /// cache and reused by later commits. The mapping itself keeps the GEM object alive. + #[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] + pub fn owned_vmap<O>(&self) -> Result<FramebufferVMapOwned<O>> + where + O: gem::DriverObject<Driver = T>, + T: crate::drm::Driver<Object = shmem::Object<O>>, + { + // SAFETY: The framebuffer is initialized via its type invariant. + let raw = unsafe { &*self.0.get() }; + let object_raw = raw.obj[0]; + validate_object(raw, object_raw)?; + + // SAFETY: The same type, ownership, import, and device checks as `vmap` hold here. The + // returned VMap takes its own object reference before this framebuffer borrow can end. + let object = unsafe { <shmem::Object<O> as gem::IntoGEMObject>::from_raw(object_raw) }; + let layout = packed_layout(raw, object.size())?; + + Ok(FramebufferMapping { + map: object.owned_vmap()?, + offset: layout.offset, + len: layout.len, + pitch: layout.pitch, + width: raw.width, + height: raw.height, + // SAFETY: `packed_layout` rejected a null format pointer above. + format: unsafe { (*raw.format).format }, + }) + } +} + +#[cfg(CONFIG_RUST_DRM_GEM_SHMEM_HELPER)] +#[kunit_tests(rust_drm_framebuffer)] +mod tests { + use super::*; + + fn linear_fb(width: u32, height: u32, pitch: u32, offset: u32) -> bindings::drm_framebuffer { + let mut fb = bindings::drm_framebuffer::default(); + // SAFETY: `XRGB8888` is a valid DRM fourcc and the returned descriptor has static lifetime. + fb.format = unsafe { bindings::drm_format_info(crate::drm::fourcc::XRGB8888) }; + fb.modifier = crate::drm::fourcc::FORMAT_MOD_LINEAR; + fb.width = width; + fb.height = height; + fb.pitches[0] = pitch; + fb.offsets[0] = offset; + fb + } + + #[test] + fn packed_layout_honours_nonzero_offset() -> Result { + let fb = linear_fb(4, 2, 16, 128); + let layout = packed_layout(&fb, 160)?; + assert_eq!(layout.offset, 128); + assert_eq!(layout.len, 32); + Ok(()) + } + + #[test] + fn packed_layout_rejects_too_small_object() { + let fb = linear_fb(4, 2, 16, 128); + assert!(packed_layout(&fb, 159).is_err()); + } + + #[test] + fn packed_layout_rejects_multiple_planes() { + let mut fb = linear_fb(4, 2, 16, 0); + // SAFETY: `linear_fb` stored a non-null static format descriptor. + let mut format = unsafe { *fb.format }; + format.num_planes = 2; + fb.format = &raw const format; + assert!(packed_layout(&fb, 32).is_err()); + } + + #[test] + fn imported_object_is_rejected() { + let mut fb = linear_fb(4, 2, 16, 0); + let dev = ptr::NonNull::<bindings::drm_device>::dangling().as_ptr(); + fb.dev = dev; + let mut object = bindings::drm_gem_object::default(); + object.dev = dev; + object.import_attach = ptr::NonNull::<bindings::dma_buf_attachment>::dangling().as_ptr(); + assert!(validate_object(&fb, &raw mut object).is_err()); + } + + #[test] + fn cross_device_object_is_rejected() { + let mut first = core::mem::MaybeUninit::<bindings::drm_device>::uninit(); + let mut second = core::mem::MaybeUninit::<bindings::drm_device>::uninit(); + let mut fb = linear_fb(4, 2, 16, 0); + fb.dev = first.as_mut_ptr(); + let mut object = bindings::drm_gem_object::default(); + object.dev = second.as_mut_ptr(); + assert!(validate_object(&fb, &raw mut object).is_err()); + } }