From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-wm1-f47.google.com (mail-wm1-f47.google.com [209.85.128.47]) (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 2A05A2DBF75 for ; Fri, 3 Jul 2026 03:01:50 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.128.47 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047712; cv=none; b=tzl0LxETksy70kZomzuOOqJLG2f0yjawEYjEdpNn2eIb8AWhQzeCE+dclQjt3V5W3EkKdbhHY8JI93WbmlCNLbcoXyhQk+xFXJt8A2aMRegkxdgMqozTVIWgpBFEj38fMPmhPfUM4K+dke9M/W3CNRnaUb8O5ngb5UvO2hzo5xQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783047712; c=relaxed/simple; bh=J/R2kUO9M0+bB41SxDDCdrF1wmpi/UNqhaoyNk+2RrE=; h=From:To:Cc:Subject:Date:Message-ID:In-Reply-To:References: MIME-Version; b=ecfSK0lwFEwi6OjBefiQTqQYUTnNSF26dZo4kX3R/USl94TFfqW9JAcR7xQGZ518g0pNxYlHHz0eqKjxuPWGBlUmzupYtYst2BeYCuDxh2fz+D73Deixr1zCkns7657aqxu21wFjacvJXXwhF45CWdox1vBWlRRla0l77y2/Bns= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk; spf=none smtp.mailfrom=fireburn.co.uk; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b=eVmniO20; arc=none smtp.client-ip=209.85.128.47 Authentication-Results: smtp.subspace.kernel.org; dmarc=none (p=none dis=none) header.from=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; spf=none smtp.mailfrom=fireburn.co.uk Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=fireburn-co-uk.20251104.gappssmtp.com header.i=@fireburn-co-uk.20251104.gappssmtp.com header.b="eVmniO20" Received: by mail-wm1-f47.google.com with SMTP id 5b1f17b1804b1-493c83474ddso414535e9.3 for ; Thu, 02 Jul 2026 20:01:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=fireburn-co-uk.20251104.gappssmtp.com; s=20251104; t=1783047708; x=1783652508; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to:content-type; bh=9gvG7drN2QAfRGCCu8odT8w4W0DWwcbaOn1tC3EwTHo=; b=eVmniO20skBCStigbIVgX1vwp3iQe1t1/KwCn8853R2eSV53KhCvH8rDJdEKINmnGl L18SEw9YlHbLEa5l4BYUhfGhdT6dyg1bws3Joxaap63uzIh0ukpc+CRm00qhEZzr1hLG Zkzho/hRPdUtnEQ5ner3yPkWnXwJrVUYOrpBmoJOyQZS+METd1b3qfl8QzRzAZ9AfmXf uVg186zMutkAgvW/i9IwdAK9cnu6d2OdJmQggaDbnaEEUHifSzv5bvkuWXt+jGlay+Qs 98TCukwG4/bsmv0qCwTX19EF4V+saSYdk23+N60QNlUJD1oyDExA/NbBkOe5gjk1dKGL Vr9g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783047709; x=1783652509; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to:content-type; bh=9gvG7drN2QAfRGCCu8odT8w4W0DWwcbaOn1tC3EwTHo=; b=S2zJu6YBZfxB0T8FVcRsSo8YkbbXqeoy5nsMB1pCP33b/W8E0qTb2qrVSYtNDAhHXx /at3/3kHz0GDcETIDCc4RRucWqtRY6tHWisWr2AuuYBJcYujIehL59cGgU7fzZdTCYMZ ZhblKPt8c8mfLyVV5wNutzdKyPSjwreDd5KZ1MsRf1Zwd05xnxdSw0aUsf7hrUlqXx+6 YuwA0x4D3nKfLY5Hp9nWXnb/W5904wQtBh2G3tOA7JxChecBOqyz4ernDT+10IDtqq4Q KtpFYlU7cqi0egbvehBricMv1tfJnlhZtE60XMMT1+qPUfoYhK/KFLfkh1i/QnOjrrH8 ZKTA== X-Forwarded-Encrypted: i=1; AFNElJ+OYIeXK7hrWUVnpl9vw86SLLEqtihGCzHPxth8QJGF4nf/j3hBwMBloOxQGs6gXTbWyDSmChHlyOYHLaQ=@vger.kernel.org X-Gm-Message-State: AOJu0Yz/fhbHRJSnv1uaDmHxTPoRfFPH5XoVNjxYUnypUdAx7x27JYM/ AqwPt01M+re735PGyKbYN51zeAVGz3grx29wb9fPVyxdNsOhnuw9vnCDpkfYj/Q8Iw== X-Gm-Gg: AfdE7clQ7ZKOF/9ufMszXioKR8ey1VObzqk2RqrKLP99jN5d8K5+C8jMFpFHzK3G6XA 9CCAaCWGcK/e1OEyjTfk4/9oj7phviSy+SKmeqhxA9TuIGflzLiUjFSE2Hp0QiYkLAQPsM0PkJB rCm4sVMYq88vfx3MKwgPtLs5MrWm+myPa4K1edRPoKjaMOxMSJKt9T394OqQh1zzFo/xRdIkUFR F8YCIi8z9IMJNfYG8p4vEXR8ZXrU3vMYdjynVsuVHsEjaFJExbHMFD8Z4lWFIgzIdTWsFvOsL2k opnknhMVMvC7bIdw7E5X8DB8+RruGZsxz7BCFp6A3WJUut3QJiz74DJ8XaibLVFdncVKhiOVWv1 daZ6BmB8BOVgecu46xt3XPQ0vPozuQATZJTIt35qehLwRv4td88i4yvL6WR0G0VxW6Gde8JEogs aNCeXEa5KRl/xIgvEejYoY38hC4PQhlAujWikc/cM4P4u/zdqj6gybiHbn X-Received: by 2002:a05:600c:34c8:b0:493:c2e3:b2c1 with SMTP id 5b1f17b1804b1-493c3df75ebmr115041365e9.33.1783047708439; Thu, 02 Jul 2026 20:01:48 -0700 (PDT) Received: from axion.fireburn.co.uk ([137.220.119.72]) by smtp.gmail.com with ESMTPSA id ffacd0b85a97d-477db3dbacbsm14466500f8f.4.2026.07.02.20.01.46 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 02 Jul 2026 20:01:47 -0700 (PDT) From: Mike Lothian To: rust-for-linux@vger.kernel.org Cc: dri-devel@lists.freedesktop.org, David Airlie , Simona Vetter , Danilo Krummrich , Lyude Paul , Miguel Ojeda , Boqun Feng , Gary Guo , =?UTF-8?q?Bj=C3=B6rn=20Roy=20Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , linux-kernel@vger.kernel.org, Mike Lothian Subject: [RFC PATCH v2 07/18] rust: drm: kms: add safe accessors for common state and connector modes Date: Fri, 3 Jul 2026 04:00:54 +0100 Message-ID: <20260703030123.2814-8-mike@fireburn.co.uk> X-Mailer: git-send-email 2.55.0 In-Reply-To: <20260703030123.2814-1-mike@fireburn.co.uk> References: <20260617150232.2210-1-mike@fireburn.co.uk> <20260703030123.2814-1-mike@fireburn.co.uk> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Add safe wrappers for the handful of raw drm_kms fields and helpers a real KMS driver reaches for, so drivers need no raw escape hatch: - drm::Device::hotplug_event() wraps drm_kms_helper_hotplug_event(). - RawCrtcState::mode() returns the state's programmed DisplayMode. - DisplayMode gains hdisplay()/hsync_start()/hsync_end()/htotal()/ vdisplay()/vsync_start()/vsync_end()/vtotal()/clock()/vrefresh(). - RawPlaneState::crtc_w()/crtc_h() return the destination rectangle. - ConnectorGuard::add_edid_modes() parses an EDID blob, updates the connector and adds its modes (the safe counterpart to the existing add_modes_noedid()). Signed-off-by: Mike Lothian Assisted-by: Claude:claude-sonnet-5 [Claude-Code] --- rust/kernel/drm/kms.rs | 12 ++++++ rust/kernel/drm/kms/connector.rs | 20 +++++++++ rust/kernel/drm/kms/crtc.rs | 13 +++++- rust/kernel/drm/kms/modes.rs | 70 ++++++++++++++++++++++++++++++++ rust/kernel/drm/kms/plane.rs | 10 +++++ 5 files changed, 123 insertions(+), 2 deletions(-) diff --git a/rust/kernel/drm/kms.rs b/rust/kernel/drm/kms.rs index 7c7e1d59c892..7207466094e7 100644 --- a/rust/kernel/drm/kms.rs +++ b/rust/kernel/drm/kms.rs @@ -114,6 +114,18 @@ fn deref(&self) -> &Self::Target { } } +impl Device { + /// Send a hotplug uevent to userspace, prompting it to re-probe connector state. + /// + /// A thin safe wrapper over `drm_kms_helper_hotplug_event()`, for drivers that detect a + /// connection change out of band (for example a dock delivering an EDID after bring-up). + pub fn hotplug_event(&self) { + // SAFETY: `self.as_raw()` is our live `drm_device`; a KMS driver has the KMS helper + // state this call requires. + unsafe { bindings::drm_kms_helper_hotplug_event(self.as_raw()) }; + } +} + /// 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 diff --git a/rust/kernel/drm/kms/connector.rs b/rust/kernel/drm/kms/connector.rs index 1c7c7b586340..591153de7ab4 100644 --- a/rust/kernel/drm/kms/connector.rs +++ b/rust/kernel/drm/kms/connector.rs @@ -574,6 +574,26 @@ pub fn set_preferred_mode(&self, (h_pref, w_pref): (u32, u32)) { // SAFETY: We hold the locks required to call this via our type invariants. unsafe { bindings::drm_set_preferred_mode(self.as_raw(), h_pref, w_pref) } } + + /// Parse a raw EDID blob, update the connector's display information from it, and add the + /// modes it advertises to the connector's mode list. + /// + /// Returns how many modes were added (0 if the blob is rejected). + pub fn add_edid_modes(&self, edid: &[u8]) -> i32 { + // SAFETY: `edid` is a valid byte buffer of `edid.len()` bytes; `drm_edid_alloc` copies it. + let drm_edid = unsafe { bindings::drm_edid_alloc(edid.as_ptr().cast(), edid.len()) }; + if drm_edid.is_null() { + return 0; + } + // SAFETY: We hold the locks required via our type invariants; `drm_edid` was just + // allocated above. + unsafe { bindings::drm_edid_connector_update(self.as_raw(), drm_edid) }; + // SAFETY: Same invariants; adds the EDID-derived modes and returns the count. + let n = unsafe { bindings::drm_edid_connector_add_modes(self.as_raw()) }; + // SAFETY: `drm_edid` was allocated by `drm_edid_alloc` above and is no longer needed. + unsafe { bindings::drm_edid_free(drm_edid) }; + n + } } /// A trait implemented by any type which can produce a reference to a diff --git a/rust/kernel/drm/kms/crtc.rs b/rust/kernel/drm/kms/crtc.rs index 9b43501bfb58..fbe113676e2f 100644 --- a/rust/kernel/drm/kms/crtc.rs +++ b/rust/kernel/drm/kms/crtc.rs @@ -5,8 +5,8 @@ //! C header: [`include/drm/drm_crtc.h`](srctree/include/drm/drm_crtc.h) use super::{ - atomic::*, plane::*, vblank::*, KmsDriver, ModeObject, ModeObjectVtable, StaticModeObject, - NewKmsDevice, Probing, Sealed, + atomic::*, modes::DisplayMode, plane::*, vblank::*, KmsDriver, ModeObject, ModeObjectVtable, + StaticModeObject, NewKmsDevice, Probing, Sealed, }; use crate::{ alloc::KBox, @@ -643,6 +643,7 @@ pub trait AsRawCrtcState: private::AsRawCrtcState { pub(crate) mod private { use super::*; + /// The raw-pointer half of [`AsRawCrtcState`](super::AsRawCrtcState). #[allow(unreachable_pub)] pub trait AsRawCrtcState { /// Return a raw pointer to the DRM CRTC state @@ -677,6 +678,14 @@ fn active(&self) -> bool { // this access is serialized unsafe { (*self.as_raw()).active } } + + /// Returns the display mode programmed into this CRTC state. + fn mode(&self) -> &DisplayMode { + // SAFETY: `mode` is embedded in `drm_crtc_state`, so it shares the state's lifetime, and + // is only mutated from the atomic check context - invariant beyond that point, so our + // interface can ensure this access is serialized. + unsafe { DisplayMode::as_ref(&(*self.as_raw()).mode) } + } } impl RawCrtcState for T {} diff --git a/rust/kernel/drm/kms/modes.rs b/rust/kernel/drm/kms/modes.rs index 88ea27729212..16024855c32f 100644 --- a/rust/kernel/drm/kms/modes.rs +++ b/rust/kernel/drm/kms/modes.rs @@ -78,4 +78,74 @@ pub fn crtc_vtotal(&self) -> u16 { // SAFETY: Reading these fields is safe via our type invariants unsafe { (*self.as_raw()).crtc_vtotal } } + + /// Horizontal active pixels (`hdisplay`). + #[inline] + pub fn hdisplay(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).hdisplay } + } + + /// Horizontal sync pulse start (`hsync_start`). + #[inline] + pub fn hsync_start(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).hsync_start } + } + + /// Horizontal sync pulse end (`hsync_end`). + #[inline] + pub fn hsync_end(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).hsync_end } + } + + /// Horizontal total pixels including blanking (`htotal`). + #[inline] + pub fn htotal(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).htotal } + } + + /// Vertical active scanlines (`vdisplay`). + #[inline] + pub fn vdisplay(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vdisplay } + } + + /// Vertical sync pulse start (`vsync_start`). + #[inline] + pub fn vsync_start(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vsync_start } + } + + /// Vertical sync pulse end (`vsync_end`). + #[inline] + pub fn vsync_end(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vsync_end } + } + + /// Vertical total scanlines including blanking (`vtotal`). + #[inline] + pub fn vtotal(&self) -> u16 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).vtotal } + } + + /// Pixel clock for this display mode in kHz (`clock`). + #[inline] + pub fn clock(&self) -> i32 { + // SAFETY: Reading these fields is safe via our type invariants + unsafe { (*self.as_raw()).clock } + } + + /// The refresh rate of this mode in Hz, as computed by `drm_mode_vrefresh()`. + #[inline] + pub fn vrefresh(&self) -> i32 { + // SAFETY: `drm_mode_vrefresh` only reads the mode, which is valid via our invariants. + unsafe { bindings::drm_mode_vrefresh(self.as_raw()) } + } } diff --git a/rust/kernel/drm/kms/plane.rs b/rust/kernel/drm/kms/plane.rs index 375f79bd3ceb..3863dfe1b84c 100644 --- a/rust/kernel/drm/kms/plane.rs +++ b/rust/kernel/drm/kms/plane.rs @@ -595,6 +595,16 @@ fn plane(&self) -> &Self::Plane { unsafe { Self::Plane::from_raw(self.as_raw().plane) } } + /// The width of this plane's destination rectangle within the CRTC, in pixels (`crtc_w`). + fn crtc_w(&self) -> u32 { + self.as_raw().crtc_w + } + + /// The height of this plane's destination rectangle within the CRTC, in pixels (`crtc_h`). + fn crtc_h(&self) -> u32 { + self.as_raw().crtc_h + } + /// Return the current [`OpaqueCrtc`] assigned to this plane, if there is one. fn crtc<'a, 'b: 'a, D>(&'a self) -> Option<&'b OpaqueCrtc> where -- 2.55.0