[PATCH v3 8/23] rust: drm: kms: add connector detect() and mode_valid() hooks
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 optional connector detect() and mode_valid() callbacks to the safe KMS API. Map connector status and common mode-validation results through typed enums. Connectors which do not implement the callbacks retain the DRM defaults of connected and mode-valid. Assisted-by: Claude:claude-opus-5 Signed-off-by: Mike Lothian <[email protected]> --- rust/kernel/drm/kms/connector.rs | 141 ++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 3 deletions(-) diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 793a7bb5bfef..dd126469788f 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -5,7 +5,8 @@ //! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_connector.h) use super::{ - atomic::*, encoder::*, KmsDriver, ModeConfigGuard, ModeObject, ModeObjectVtable, Sealed, + atomic::*, encoder::*, modes::DisplayMode, KmsDriver, ModeConfigGuard, ModeObject, + ModeObjectVtable, Sealed, }; use crate::{ alloc::KBox, @@ -72,6 +73,43 @@ pub enum Type { USB as Usb } +/// The connection status of a [`Connector`], as returned by [`DriverConnector::detect`]. +/// +/// This is identical to [`enum drm_connector_status`]. +/// +/// [`enum drm_connector_status`]: srctree/include/drm/drm_connector.h +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(u32)] +pub enum Status { + /// The connector is connected to a display and a mode list can be retrieved. + Connected = bindings::drm_connector_status_connector_status_connected, + /// The connector has no display attached. + Disconnected = bindings::drm_connector_status_connector_status_disconnected, + /// The connection state could not be determined (treated as connected for probing). + Unknown = bindings::drm_connector_status_connector_status_unknown, +} + +/// The result of validating a display mode against a [`Connector`], as returned by +/// [`DriverConnector::mode_valid`]. +/// +/// This mirrors a small, commonly-used subset of [`enum drm_mode_status`]; use [`ModeStatus::Bad`] +/// for a generic rejection, or the clock-specific variants when a mode is out of the driver's +/// pixel-clock range. +/// +/// [`enum drm_mode_status`]: srctree/include/drm/drm_modes.h +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[repr(i32)] +pub enum ModeStatus { + /// The mode is usable. + Ok = bindings::drm_mode_status_MODE_OK, + /// The mode is rejected for an unspecified reason. + Bad = bindings::drm_mode_status_MODE_BAD, + /// The mode's pixel clock is above what the driver can drive. + ClockHigh = bindings::drm_mode_status_MODE_CLOCK_HIGH, + /// The mode's pixel clock is below what the driver can drive. + ClockLow = bindings::drm_mode_status_MODE_CLOCK_LOW, +} + /// The main trait for implementing the [`struct drm_connector`] API for [`Connector`]. /// /// Any KMS driver should have at least one implementation of this type, which allows them to create @@ -106,7 +144,11 @@ pub trait DriverConnector: Send + Sync + Sized { atomic_destroy_state: Some(atomic_destroy_state_callback::<Self::State>), destroy: Some(connector_destroy_callback::<Self>), force: None, - detect: None, + detect: if Self::HAS_DETECT { + Some(detect_callback::<Self>) + } else { + None + }, fill_modes: Some(bindings::drm_helper_probe_single_connector_modes), debugfs_init: None, oob_hotplug_event: None, @@ -114,7 +156,11 @@ pub trait DriverConnector: Send + Sync + Sized { color_format: None, }, helper_funcs: bindings::drm_connector_helper_funcs { - mode_valid: None, + mode_valid: if Self::HAS_MODE_VALID { + Some(mode_valid_callback::<Self>) + } else { + None + }, atomic_check: None, get_modes: Some(get_modes_callback::<Self>), detect_ctx: None, @@ -153,6 +199,31 @@ fn get_modes<'a>( connector: ConnectorGuard<'a, Self>, guard: &ModeConfigGuard<'a, Self::Driver>, ) -> i32; + + /// The optional [`drm_connector_funcs.detect`] hook for this connector. + /// + /// Drivers may implement this to report whether a display is currently attached. If not + /// implemented, the connector is always considered connected (DRM's default with no `detect` + /// hook). `force` is set when userspace explicitly requested a forced probe. + /// + /// [`drm_connector_funcs.detect`]: srctree/include/drm/drm_connector.h + fn detect(_connector: &Connector<Self>, _force: bool) -> Status { + build_error::build_error("This should not be reachable") + } + + /// The optional [`drm_connector_helper_funcs.mode_valid`] hook for this connector. + /// + /// Drivers may implement this to reject modes they cannot drive (for example, a mode whose + /// pixel clock exceeds the hardware's budget). Returning anything other than [`ModeStatus::Ok`] + /// prunes the mode from the probed list. If not implemented, every mode is accepted. + /// + /// [`drm_connector_helper_funcs.mode_valid`]: srctree/include/drm/drm_modeset_helper_vtables.h + fn mode_valid( + _connector: ConnectorModeValidation<'_, Self>, + _mode: &DisplayMode, + ) -> ModeStatus { + build_error::build_error("This should not be reachable") + } } /// The generated C vtable for a [`DriverConnector`]. @@ -477,6 +548,32 @@ impl<T: AsRawConnector> RawConnector for T {} T::get_modes(connector.guard(&guard), &guard) } +unsafe extern "C" fn detect_callback<T: DriverConnector>( + connector: *mut bindings::drm_connector, + force: bool, +) -> bindings::drm_connector_status { + // SAFETY: This is safe via `DriverConnector`s type invariants. + let connector = unsafe { Connector::<T>::from_raw(connector) }; + + T::detect(connector, force) as bindings::drm_connector_status +} + +unsafe extern "C" fn mode_valid_callback<T: DriverConnector>( + connector: *mut bindings::drm_connector, + mode: *const bindings::drm_display_mode, +) -> bindings::drm_mode_status { + // SAFETY: This is safe via `DriverConnector`s type invariants. + let connector = unsafe { Connector::<T>::from_raw(connector) }; + + // SAFETY: DRM guarantees `mode` points to a valid `drm_display_mode` for the duration of this + // callback, and only passes us shared access to it. + let mode = unsafe { DisplayMode::as_ref(mode) }; + + // DRM invokes the connector helper while the mode list is stable. Keep that guarantee in a + // capability type so drivers can safely compare this mode with the other probed modes. + T::mode_valid(ConnectorModeValidation(connector), mode) as bindings::drm_mode_status +} + /// A [`struct drm_connector`] without a known [`DriverConnector`] implementation. /// /// This is mainly for situations where our bindings can't infer the [`DriverConnector`] @@ -572,6 +669,44 @@ fn deref(&self) -> &Self::Target { } } +/// A connector being validated while its mode list is stable. +/// +/// This is only constructed by the DRM connector-helper callback. It permits read-only iteration +/// over the connector's modes without exposing list pointers or extending a mode reference beyond +/// the callback. +#[derive(Copy, Clone)] +pub struct ConnectorModeValidation<'a, T: DriverConnector>(&'a Connector<T>); + +impl<T: DriverConnector> Deref for ConnectorModeValidation<'_, T> { + type Target = Connector<T>; + + fn deref(&self) -> &Self::Target { + self.0 + } +} + +impl<T: DriverConnector> ConnectorModeValidation<'_, T> { + /// Return whether any mode currently on this connector satisfies `predicate`. + pub fn any_mode(&self, mut predicate: impl FnMut(&DisplayMode) -> bool) -> bool { + let raw = self.as_raw(); + // SAFETY: DRM only constructs this capability while `connector->modes` is stable. Each + // list entry is an initialized `drm_display_mode`, and the shared reference is confined to + // this callback invocation. + unsafe { + let head: *mut bindings::list_head = &raw mut (*raw).modes; + let mut node = (*head).next; + while node != head { + let mode = crate::container_of!(node, bindings::drm_display_mode, head); + if predicate(DisplayMode::as_ref(mode)) { + return true; + } + node = (*node).next; + } + } + false + } +} + impl<'a, T: DriverConnector> ConnectorGuard<'a, T> { /// Add modes for a [`ConnectorGuard`] without an EDID. ///