From nobody Mon Sep 28 21:05:36 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 4916940BCB3 for ; Mon, 17 Aug 2026 11:41:07 +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=1786966870; cv=none; b=FSqNQ/zY/ftyRL81jyBhMrrsWQxcoIWTStwQ1ldwrRTZ0KgzPsQNU5aPKXpITRLt2+d+/zm7R/cHH5ghtkh4BOmhLk6gegC8QnPcaRCQCvlKzWPJyNHrJdGoEVTiruvn0lgOeFgoGN6Nh3Vy9FXB13YtZTF5ISdu0roVvDYLzHs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786966870; c=relaxed/simple; bh=ZRV3ir9Mip4kV3HKkGcK9iC14+NTymLEZwyObo6R3pk=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=cRWuFUu+IPlHjDdNgb+fVDx93nX20pZYhGFfrXcG+0S4xSMkBwuyzoxltlpYmy3cnegDOrj6Zl64bCq02ekSDTgeFkkvZk5fvxZMxfzmSQDUFE1u4sdv6fHDVo6vmF2pPSIrEUB3AfMmH312wjepKdH5/o4khSBncrV/0cyc9qU= 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=WA5w4tuE; 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="WA5w4tuE" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1786966867; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=zcBfFGBouFqsx+ABHHYb+Wnv6e6DiBO6Gs1BOe4gNuo=; b=WA5w4tuErq2ulY4IJLx9Lh4q3k0h4OB7stlFIOEwtkU3IlhXCw6J6cz/WE/ny21CbjBUEV bO+6I3ZWaEOXjZ2Ougdhr5r8ifA4k7kHDx/yERC8MuVWQzo6ir6Q23yeEU3F4gO4Yv4ObL dOGIjkHPo1/bfwWrLHf/AcTsOj35hJ4= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-619-So7fCxQpN4eMF9XmS5UGKA-1; Mon, 17 Aug 2026 07:41:03 -0400 X-MC-Unique: So7fCxQpN4eMF9XmS5UGKA-1 X-Mimecast-MFC-AGG-ID: So7fCxQpN4eMF9XmS5UGKA_1786966861 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (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-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id F27E8195609D; Mon, 17 Aug 2026 11:41:00 +0000 (UTC) Received: from [192.168.1.153] (headnet03.pony-001.prod.iad2.dc.redhat.com [10.2.32.114]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 19CD4422; Mon, 17 Aug 2026 11:40:55 +0000 (UTC) From: Albert Esteve Date: Mon, 17 Aug 2026 13:40:46 +0200 Subject: [PATCH 1/5] rust: of: add Node type Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260817-drm_panel_bindings-v1-1-1f974508a31c@redhat.com> References: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> In-Reply-To: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> To: Rob Herring , Saravana Kannan , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: devicetree@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Albert Esteve , mripard@kernel.org X-Developer-Signature: v=1; a=ed25519-sha256; t=1786966849; l=3194; i=aesteve@redhat.com; s=20260303; h=from:subject:message-id; bh=ZRV3ir9Mip4kV3HKkGcK9iC14+NTymLEZwyObo6R3pk=; b=tCLXlJC3jyytEXh/j3/QVe5pUEC9LZnGxNHzOen73iHx48cTODEaeJvYVebqEB8B1v13rQ1fx /bJqTcAh88+B13yWchudIT6SSYDRPWiAf3NReL5A0oidUA/hQmyJD2j X-Developer-Key: i=aesteve@redhat.com; a=ed25519; pk=YSFz6sOHd2L45+Fr8DIvHTi6lSIjhLZ5T+rkxspJt1s= X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 Add a Rust abstraction for `struct device_node`, the device tree node type. `Node` wraps `device_node` type pointer and implements `AlwaysRefCounted`, allowing owned references to device tree nodes to be held. To do so, add C helpers to make them always accesible from Rust bindings, independently from the configuration. This abstraction is needed for subsequent patches, in particular for creating drm_panel instances from a `const struct device_node` pointer argument. Signed-off-by: Albert Esteve --- rust/helpers/of.c | 10 ++++++++++ rust/kernel/of.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/rust/helpers/of.c b/rust/helpers/of.c index 8f62ca69e8ba5..dca306852975e 100644 --- a/rust/helpers/of.c +++ b/rust/helpers/of.c @@ -6,3 +6,13 @@ __rust_helper bool rust_helper_is_of_node(const struct fwn= ode_handle *fwnode) { return is_of_node(fwnode); } + +__rust_helper struct device_node *rust_helper_of_node_get(struct device_no= de *node) +{ + return of_node_get(node); +} + +__rust_helper void rust_helper_of_node_put(struct device_node *node) +{ + of_node_put(node); +} diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs index 58b20c367f993..e75ab81cfe1f7 100644 --- a/rust/kernel/of.rs +++ b/rust/kernel/of.rs @@ -6,7 +6,10 @@ bindings, device_id::{RawDeviceId, RawDeviceIdIndex}, prelude::*, + sync::aref::AlwaysRefCounted, + types::Opaque, }; +use core::ptr::NonNull; =20 /// IdTable type for OF drivers. pub type IdTable =3D &'static dyn kernel::device_id::IdTable; @@ -63,3 +66,43 @@ macro_rules! of_device_table { $crate::module_device_table!("of", $module_table_name, $table_name= ); }; } + +/// A device tree node (`struct device_node`). +/// +/// # Invariants +/// +/// The inner pointer is always a valid, non-null pointer to a `struct dev= ice_node` +/// with a positive reference count. +#[repr(transparent)] +pub struct Node(Opaque); + +impl Node { + /// Creates a reference from a raw pointer. + /// + /// # Safety + /// + /// `ptr` must be a valid, non-null `struct device_node` pointer that = remains + /// valid for the lifetime `'a`. + pub unsafe fn from_raw<'a>(ptr: *const bindings::device_node) -> &'a S= elf { + // SAFETY: Caller guarantees `ptr` is valid and lives for `'a`. + unsafe { &*ptr.cast() } + } + + /// Returns the raw pointer to the underlying `struct device_node`. + pub fn as_raw(&self) -> *const bindings::device_node { + self.0.get() as _ + } +} + +// SAFETY: By the type invariants, this type is always refcounted. +unsafe impl AlwaysRefCounted for Node { + fn inc_ref(&self) { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::of_node_get(self.as_raw().cast_mut()) }; + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The safety requirements guarantee that the refcount is = non-zero. + unsafe { bindings::of_node_put(obj.cast().as_ptr()) }; + } +} --=20 2.55.0 From nobody Mon Sep 28 21:05:36 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 98566406804 for ; Mon, 17 Aug 2026 11:41:24 +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=1786966886; cv=none; b=DIgNLjGaEdh28A5gYi2YovTM340tc8e7DtEZJdlVCM3Unt4vt4JV+09ADUAJBWdYMxNWFZXe6M7fWZuz8mwonxG2ZokwQlzw/GCE2o7e5EJZyQr6ID0NK9RL6sY8DSU6F9ZhZNXsanvMA9wodSmIE6HOtBoUEGZygpl0rYCbkeM= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786966886; c=relaxed/simple; bh=iF8KpoytzvpnqLwM+WPDFZesluui1b680ixY9h/BEKs=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=nlfcy65HIXvf+THv5SK5XHZk+666FSp1B9SHofcKulNEJ29p5HhLe/WTpbsxDolvhm25Xui9A7r5maetjMV8MExS2mdk9jBIC0nVCalq7sGC/+8fqmOkIIykO0zmwke3zARIG/KQ9IdeXNIGMn+aUfpSVbwh0L5edNxsTIdYZvk= 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=XDagyK/r; 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="XDagyK/r" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1786966883; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=yF5hSMmrhAchBrFi1aBwQrM8p8lTLNxn2Y/Hj6Zq5sA=; b=XDagyK/rTNzLMMovv17TtY2w9JRWEAX3NKqpMZ++lo5DRax61wzEUaRhVWWFcEnIvWyQwc ewrfS/WEI1nU1WRgx9OWUpHc3dfcDpAQfBlbK4Tk+SxPNbE0FYV82nLMFO7GTeLZgRqJsv Pbuvck3olA900YxODtAQYKzMkalQwbY= Received: from mx-prod-mc-06.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-549-AcEoYBedOZm0vnA0TqXCCA-1; Mon, 17 Aug 2026 07:41:09 -0400 X-MC-Unique: AcEoYBedOZm0vnA0TqXCCA-1 X-Mimecast-MFC-AGG-ID: AcEoYBedOZm0vnA0TqXCCA_1786966866 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (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-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 2A7DF1800471; Mon, 17 Aug 2026 11:41:06 +0000 (UTC) Received: from [192.168.1.153] (headnet03.pony-001.prod.iad2.dc.redhat.com [10.2.32.114]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 52B69422; Mon, 17 Aug 2026 11:41:01 +0000 (UTC) From: Albert Esteve Date: Mon, 17 Aug 2026 13:40:47 +0200 Subject: [PATCH 2/5] rust: drm: add connector abstraction Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260817-drm_panel_bindings-v1-2-1f974508a31c@redhat.com> References: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> In-Reply-To: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> To: Rob Herring , Saravana Kannan , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: devicetree@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Albert Esteve , mripard@kernel.org X-Developer-Signature: v=1; a=ed25519-sha256; t=1786966849; l=2548; i=aesteve@redhat.com; s=20260303; h=from:subject:message-id; bh=iF8KpoytzvpnqLwM+WPDFZesluui1b680ixY9h/BEKs=; b=3yFrkKa4o8rqbjEkeGSkXfrl1LovnqUzx3mw+hXpd+psOy8Bh6nBQuEewpstRcMUwhbafMc2i 0hvL9nzn65LCnTCZvI6rGEcY5VlFbAOeqhlZ+rP99E3KZ2maq6NhzXs X-Developer-Key: i=aesteve@redhat.com; a=ed25519; pk=YSFz6sOHd2L45+Fr8DIvHTi6lSIjhLZ5T+rkxspJt1s= X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 Add DRM Connector abstraction wrapping `struct drm_connector`. Used by the DRM panel abstractions added in the following patch. Since it is merely used as a raw pointer and managed internally in the panel's C functions, the Connector does not need to expose anything else for now. Signed-off-by: Albert Esteve --- rust/bindings/bindings_helper.h | 1 + rust/kernel/drm/connector.rs | 33 +++++++++++++++++++++++++++++++++ rust/kernel/drm/mod.rs | 1 + 3 files changed, 35 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 1124785e210b3..7f3030ac0feb6 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -30,6 +30,7 @@ =20 #include #include +#include #include #include #include diff --git a/rust/kernel/drm/connector.rs b/rust/kernel/drm/connector.rs new file mode 100644 index 0000000000000..b3b36644375dd --- /dev/null +++ b/rust/kernel/drm/connector.rs @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! DRM connector abstractions. +//! +//! C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_conn= ector.h) + +use crate::{bindings, types::Opaque}; + +/// A DRM connector (`struct drm_connector`). +/// +/// # Invariants +/// +/// The inner pointer is always a valid, non-null pointer to a `struct drm= _connector`. +#[repr(transparent)] +pub struct Connector(Opaque); + +impl Connector { + /// Creates a reference to a [`Connector`] from a raw pointer. + /// + /// # Safety + /// + /// `ptr` must be a valid, non-null pointer to a `struct drm_connector= ` that + /// remains valid for at least the lifetime `'a`. + pub unsafe fn from_raw<'a>(ptr: *mut bindings::drm_connector) -> &'a S= elf { + // SAFETY: Caller guarantees `ptr` is valid and lives for `'a`. + unsafe { &*ptr.cast() } + } + + /// Returns the raw pointer to the underlying `struct drm_connector`. + pub fn as_raw(&self) -> *mut bindings::drm_connector { + self.0.get() + } +} diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index a66e7166f66b5..672ea8728e1c3 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -2,6 +2,7 @@ =20 //! DRM subsystem abstractions. =20 +pub mod connector; pub mod device; pub mod driver; pub mod file; --=20 2.55.0 From nobody Mon Sep 28 21:05:36 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 04C52403E8A for ; Mon, 17 Aug 2026 11:41:18 +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=1786966881; cv=none; b=kxNJ/74oRrYUwSyA2e6CPoECxnTQ0NfP86BctkgxEEKQZw8XbHh5s6B5tF9X07tSGntz2c9d1MgqKWsjTF6bQsKAImG5OGKmSb17czREl/diUafk2FphLIPpq10le6WnhxRKxjXb2+MllkjkAjbd6lXVizS3BldVbijjIgqMz/k= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786966881; c=relaxed/simple; bh=gKhVm8XBGs7x/Liyol8f7STgqIpe5f31i5j5M/rx+4U=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=IFpTZDinfsDZ35XpbKOwlcrrekgXcqGkRBmpj46IJKnLkg6EPWQP5uolP46VgmtD32u4Hei85ADUzCp3Z38Yvq1mXIiMiJVgm++6a57/X5Hg0lcThZftR7qKRie5blIlNYa+mupu5HRc3nE+oYSJ31mCxlab3qsddC08OR4eUcU= 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=bpo+Si6t; 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="bpo+Si6t" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1786966878; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=k6Xz9SzRekDuFjQRTihEV7izlswBoX4HzYpM9NVUmkg=; b=bpo+Si6tIrIezuGcKGf07uyTDF2Cpv7MSh7LwaEZqxZ4nDcKashdi98ZeQTERSsyHkeAqH SzGXn29aSksn2I5mmQfNCcc7cY+0+5QGOzUPy/Mz5rNwxEaxRECEJ87RdiblSLUBQUC8eN bZ5mXqJHCNmaT8LntfgM3mz0QZ9nCHA= Received: from mx-prod-mc-01.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-159-0TWtXQM8OC-NJPliwDfYmQ-1; Mon, 17 Aug 2026 07:41:14 -0400 X-MC-Unique: 0TWtXQM8OC-NJPliwDfYmQ-1 X-Mimecast-MFC-AGG-ID: 0TWtXQM8OC-NJPliwDfYmQ_1786966872 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (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-01.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id E16241956069; Mon, 17 Aug 2026 11:41:11 +0000 (UTC) Received: from [192.168.1.153] (headnet03.pony-001.prod.iad2.dc.redhat.com [10.2.32.114]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 8EF44423; Mon, 17 Aug 2026 11:41:06 +0000 (UTC) From: Albert Esteve Date: Mon, 17 Aug 2026 13:40:48 +0200 Subject: [PATCH 3/5] rust: drm: add panel consumer abstractions Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260817-drm_panel_bindings-v1-3-1f974508a31c@redhat.com> References: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> In-Reply-To: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> To: Rob Herring , Saravana Kannan , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: devicetree@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Albert Esteve , mripard@kernel.org X-Developer-Signature: v=1; a=ed25519-sha256; t=1786966849; l=11877; i=aesteve@redhat.com; s=20260303; h=from:subject:message-id; bh=gKhVm8XBGs7x/Liyol8f7STgqIpe5f31i5j5M/rx+4U=; b=MdjJz7j1F5Y9JB+An3y2Kzpeky34AJRWOojwnNzQJYW5ZnS2df5gKLpCSQlvxV1CPWmmL69e8 YYO45pMMpa1BUP/ZPk2MjJtTXksUpPbiRhpBWv44MXy/SggWe8G/FhF X-Developer-Key: i=aesteve@redhat.com; a=ed25519; pk=YSFz6sOHd2L45+Fr8DIvHTi6lSIjhLZ5T+rkxspJt1s= X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 Add Rust abstraction for the DRM panel subsystem, covering the consumer side of the panel API. `Panel` wraps a reference-counted `struct drm_panel` and implements `AlwaysRefCounted`. It exposes consumer-side lifecycle operations and provides `from_of_node()` associated method for looking up a registered panel by device tree node. `PanelOrientation` wraps `enum drm_panel_orientation` and provides `from_of_node()` to read "rotation" property from the device tree. `Registration` manages the panel's presence in the global registry, decoupling its lifetime from reference count lifetime. Following patches will handle the producer side. Signed-off-by: Albert Esteve --- rust/bindings/bindings_helper.h | 1 + rust/helpers/drm.c | 15 +++ rust/kernel/drm/mod.rs | 1 + rust/kernel/drm/panel.rs | 227 ++++++++++++++++++++++++++++++++++++= ++++ 4 files changed, 244 insertions(+) diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helpe= r.h index 7f3030ac0feb6..0f23d323cbb24 100644 --- a/rust/bindings/bindings_helper.h +++ b/rust/bindings/bindings_helper.h @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include diff --git a/rust/helpers/drm.c b/rust/helpers/drm.c index 65f3f22b0e1d2..fc5b9af6e4d23 100644 --- a/rust/helpers/drm.c +++ b/rust/helpers/drm.c @@ -2,6 +2,7 @@ =20 #include #include +#include #include =20 #ifdef CONFIG_DRM @@ -75,4 +76,18 @@ rust_helper_drm_gem_shmem_object_mmap(struct drm_gem_obj= ect *obj, struct vm_area } =20 #endif /* CONFIG_DRM_GEM_SHMEM_HELPER */ + +__rust_helper struct drm_panel * +rust_helper_of_drm_find_panel(const struct device_node *np) +{ + return of_drm_find_panel(np); +} + +__rust_helper int +rust_helper_of_drm_get_panel_orientation(const struct device_node *np, + enum drm_panel_orientation *orientation) +{ + return of_drm_get_panel_orientation(np, orientation); +} + #endif /* CONFIG_DRM */ diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs index 672ea8728e1c3..d1de5d448a1a5 100644 --- a/rust/kernel/drm/mod.rs +++ b/rust/kernel/drm/mod.rs @@ -9,6 +9,7 @@ pub mod gem; pub mod gpuvm; pub mod ioctl; +pub mod panel; =20 pub use self::device::Device; pub use self::device::DeviceContext; diff --git a/rust/kernel/drm/panel.rs b/rust/kernel/drm/panel.rs new file mode 100644 index 0000000000000..fd21cc2236685 --- /dev/null +++ b/rust/kernel/drm/panel.rs @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0 + +//! DRM panel abstractions. +//! +//! C header: [`include/drm/drm_panel.h`](srctree/include/drm/drm_panel.h) + +use crate::drm::connector::Connector; +use crate::{ + bindings, error, of, + prelude::*, + sync::aref::{ARef, AlwaysRefCounted}, + types::Opaque, +}; +use core::ptr::NonNull; + +/// A DRM panel object. +/// +/// Wraps `struct drm_panel`. Instances are reference-counted via [`drm_pa= nel_get`] and +/// [`drm_panel_put`]; use [`ARef`] to hold an owned reference. +/// +/// The DRM panel methods allow drivers to register panel objects with a +/// central registry and provide functions to retrieve those panels in dis= play +/// drivers. +/// +/// # Invariants +/// +/// The inner pointer is always a valid, non-null pointer to a `struct drm= _panel` with a +/// positive reference count. +/// +/// [`drm_panel_get`]: srctree/include/drm/drm_panel.h +/// [`drm_panel_put`]: srctree/include/drm/drm_panel.h +#[repr(transparent)] +pub struct Panel(Opaque); + +impl Panel { + /// Creates a reference from a raw pointer. + /// + /// # Safety + /// + /// `ptr` must be a valid, non-null `struct drm_panel` pointer that re= mains + /// valid for the lifetime `'a`. + pub unsafe fn from_raw<'a>(ptr: *const bindings::drm_panel) -> &'a Sel= f { + // SAFETY: Caller guarantees `ptr` is valid and lives for `'a`. + unsafe { &*ptr.cast() } + } + + /// Returns the raw pointer to the underlying `struct drm_panel`. + pub fn as_raw(&self) -> *mut bindings::drm_panel { + self.0.get() + } + + /// Power on a panel. + /// + /// Calling this function will enable power and deassert any reset sig= nals to + /// the panel. After this has completed it is possible to communicate = with any + /// integrated circuitry via a command bus. This function cannot fail = (as it is + /// called from the pre_enable call chain). There will always be a cal= l to + /// [`Panel::disable`] afterwards. + pub fn prepare(&self) { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::drm_panel_prepare(self.as_raw()) } + } + + /// Power off a panel. + /// + /// Calling this function will completely power off a panel (assert th= e panel's + /// reset, turn off power supplies, ...). After this function has comp= leted, it + /// is usually no longer possible to communicate with the panel until = another + /// call to [`Panel::prepare`]. + pub fn unprepare(&self) { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::drm_panel_unprepare(self.as_raw()) } + } + + /// Enable a panel. + /// + /// Calling this function will cause the panel display drivers to be t= urned on + /// and the backlight to be enabled. Content will be visible on screen= after + /// this call completes. This function cannot fail (as it is called fr= om the + /// enable call chain). There will always be a call to [`Panel::disabl= e`] + /// afterwards. + pub fn enable(&self) { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::drm_panel_enable(self.as_raw()) } + } + + /// Disable a panel. + /// + /// This will typically turn off the panel's backlight or disable the = display + /// drivers. For smart panels it should still be possible to communica= te with + /// the integrated circuitry via any command bus after this call. + pub fn disable(&self) { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::drm_panel_disable(self.as_raw()) } + } + + /// Probe the available display modes of a panel. + /// + /// The modes probed from the panel are automatically added to the con= nector + /// that the panel is attached to. + /// + /// Return: The number of modes available from the panel on success, o= r 0 on + /// failure (no modes). + pub fn get_modes(&self, connector: &Connector) -> i32 { + // SAFETY: The type invariants guarantee the pointers are valid. + unsafe { bindings::drm_panel_get_modes(self.as_raw(), connector.as= _raw()) } as i32 + } + + /// Use backlight device node for backlight. + /// + /// Use this function to enable backlight handling if your panel + /// uses device tree and has a backlight phandle. + /// + /// When the panel is enabled backlight will be enabled after a + /// successful call to [`Panel::enable`]. + /// + /// When the panel is disabled backlight will be disabled before the + /// call to [`Panel::disable`]. + /// + /// A typical implementation for a panel driver supporting device tree + /// will call this function at probe time. Backlight will then be hand= led + /// transparently without requiring any intervention from the driver. + #[cfg(CONFIG_BACKLIGHT_CLASS_DEVICE)] + pub fn of_backlight(&self) -> Result<()> { + // SAFETY: The type invariant guarantees the pointer is valid. + error::to_result(unsafe { bindings::drm_panel_of_backlight(self.as= _raw()) })?; + Ok(()) + } + + /// Look up the panel associated with the given device tree node. + /// + /// Searches the set of registered panels for one that matches the giv= en device + /// tree node. If a matching panel is found, return a pointer to it. + pub fn from_of_node(node: &of::Node) -> Result> { + // SAFETY: `node.as_raw()` is a valid device_node pointer. + let panel =3D error::from_err_ptr(unsafe { bindings::of_drm_find_p= anel(node.as_raw()) })?; + + // SAFETY: `from_err_ptr` guarantees a non-null pointer on success. + // `of_drm_find_panel` returns a kref-incremented reference. + Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(panel).cast()) }) + } +} + +// SAFETY: By the type invariants, this type is always refcounted. +unsafe impl AlwaysRefCounted for Panel { + fn inc_ref(&self) { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::drm_panel_get(self.as_raw()) }; + } + + unsafe fn dec_ref(obj: NonNull) { + // SAFETY: The existence of `obj` guarantees the refcount is posit= ive. + unsafe { bindings::drm_panel_put(obj.cast().as_ptr()) }; + } +} + +/// This enum is used to track the (LCD) panel orientation. +/// +/// C header: [`include/drm/drm_connector.h`](srctree/include/drm/drm_conn= ector.h) +#[repr(i32)] +pub enum PanelOrientation { + /// The drm driver has not provided any panel orientation information. + Unknown =3D -1, + /// The top side of the panel matches the top side of the device's cas= ing. + Normal =3D 0, + /// The top side of the panel matches the bottom side of the device's = casing. + BottomUp =3D 1, + /// The left side of the panel matches the top side of the device's ca= sing. + LeftUp =3D 2, + /// The right side of the panel matches the top side of the device's c= asing. + RightUp =3D 3, +} + +impl TryFrom for PanelOrientation { + type Error =3D Error; + fn try_from(v: i32) -> Result { + match v { + -1 =3D> Ok(Self::Unknown), + 0 =3D> Ok(Self::Normal), + 1 =3D> Ok(Self::BottomUp), + 2 =3D> Ok(Self::LeftUp), + 3 =3D> Ok(Self::RightUp), + _ =3D> Err(EINVAL), + } + } +} + +impl PanelOrientation { + /// Look up the orientation of the panel through the "rotation" binding + /// from a device tree node + /// + /// Looks up the rotation of a panel in the device tree. The orientati= on of the + /// panel is expressed as a property name "rotation" in the device tre= e. The + /// rotation in the device tree is counter clockwise. + pub fn from_of_node(node: &of::Node) -> Result { + let mut orientation =3D 0i32; + // SAFETY: `node.as_raw()` is a valid device_node pointer. + error::to_result(unsafe { + bindings::of_drm_get_panel_orientation(node.as_raw(), &mut ori= entation) + })?; + Ok(PanelOrientation::try_from(orientation)?) + } +} + +/// A registration of a panel to the global panel registry. +pub struct Registration(ARef); + +impl Registration { + /// Registers a panel with the global panel registry. + pub fn register(panel: ARef) -> Self { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::drm_panel_add(panel.as_raw()) }; + Self(panel) + } + + /// Returns a reference to the panel. + pub fn panel(&self) -> &Panel { + &self.0 + } +} + +impl Drop for Registration { + fn drop(&mut self) { + // SAFETY: The type invariant guarantees the pointer is valid. + unsafe { bindings::drm_panel_remove(self.0.as_raw()) }; + } +} --=20 2.55.0 From nobody Mon Sep 28 21:05:36 2026 Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.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 96BD540DB58 for ; Mon, 17 Aug 2026 11:41:27 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786966890; cv=none; b=s2zacdUJ8Vaw86WajtcUovclltEwgQNvWGRgp0AK9LGX7Q2yPuu6vZOtV8xh2VtRbnodzzQ4hIJK3MHWAfIeOV7xfVbxj+uM5TM+weGLVEvXwZ5rnadkpbi4aYUBCFckqKpqb+kueKAkG0lwmF0F1qBXQ8bzzRaxcFdNXVWr6Cs= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786966890; c=relaxed/simple; bh=93LQvEkhKRf7JgS8cXWiOHE5WFGnK0FXkIOKmrKDAZ0=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=YVIqfPNZf2tN72gAFZLII5X1qCxhuYewByRdM3ZxRD6AMwKA/FlS5BG7gWmbphOvQiat2b8lnQZZoZRMhJVde8nxeSUGl+TfJUgsBPhaggWl+GjcFT3XbyQpzn60vEaxlIn2VE11dHk0HEbzxduENZtmfg+IsIdiL8E7bep5zd4= 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=jKNESjn8; arc=none smtp.client-ip=170.10.133.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="jKNESjn8" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1786966886; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=1RAgrzHZYe2l55ocIc4U6z1x+YgIHjnqqwdVi242aYk=; b=jKNESjn8yD22uEpNBpnxgrZnCxrD088Io4wgd+nNKVCAEnU/MESZyAvg/lEcZ+Zh38zD54 6HkDneA/X3eMp1S6ghY7kaftt5loVO/pFvqe+3enaiL1NwHhjoxSGwceRgzHIdTtjj7SxV dqALhSGpcGoxakzUbTY+969GQRdkMEo= Received: from mx-prod-mc-05.mail-002.prod.us-west-2.aws.redhat.com (ec2-54-186-198-63.us-west-2.compute.amazonaws.com [54.186.198.63]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-686-zMmBDt2KPT-FSVb_H4Oc8Q-1; Mon, 17 Aug 2026 07:41:21 -0400 X-MC-Unique: zMmBDt2KPT-FSVb_H4Oc8Q-1 X-Mimecast-MFC-AGG-ID: zMmBDt2KPT-FSVb_H4Oc8Q_1786966878 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (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-05.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id DE8A5195609D; Mon, 17 Aug 2026 11:41:17 +0000 (UTC) Received: from [192.168.1.153] (headnet03.pony-001.prod.iad2.dc.redhat.com [10.2.32.114]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 540EA426; Mon, 17 Aug 2026 11:41:12 +0000 (UTC) From: Albert Esteve Date: Mon, 17 Aug 2026 13:40:49 +0200 Subject: [PATCH 4/5] rust: drm: add panel producer abstractions Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260817-drm_panel_bindings-v1-4-1f974508a31c@redhat.com> References: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> In-Reply-To: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> To: Rob Herring , Saravana Kannan , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: devicetree@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Albert Esteve , mripard@kernel.org X-Developer-Signature: v=1; a=ed25519-sha256; t=1786966849; l=17932; i=aesteve@redhat.com; s=20260303; h=from:subject:message-id; bh=93LQvEkhKRf7JgS8cXWiOHE5WFGnK0FXkIOKmrKDAZ0=; b=WFfgJZ1mGm+21yJAM36wpwxn2zJE+VA9e53E0uDuIJwfGzEMmZqD5LA3fuxOr5udpJXZAYslA LS73LQsUr9HB5I93ozKM4ccO+7/K2M91rtPj8cYOXf9cnkGmyNwSPy8 X-Developer-Key: i=aesteve@redhat.com; a=ed25519; pk=YSFz6sOHd2L45+Fr8DIvHTi6lSIjhLZ5T+rkxspJt1s= X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 `PanelFuncs` is a `#[vtable]` trait that panel drivers implement to provide their callbacks. `PanelFuncsVTable` builds a `struct drm_panel_funcs` from `PanelFuncs` through `extern "C"` trampolines, using the `HAS_*` flags generated by `#[vtable]` to populate optional callbacks selectively. `PanelContainer` is a `#[repr(C)]` struct embedding `ManuallyDrop` at offset zero followed by the `drm_panel`. This layout lets `__devm_drm_panel_alloc` allocate both in a single `kzalloc` call with `panel->container` pointing to the base, so `__drm_panel_free` can call `kfree(container)` to free the entire block. A separate `devm_add_action_or_reset` runs `drop_in_place::` before the memory is reclaimed, ensuring T's destructor is called at the right time. `Panel::new` wraps `__devm_drm_panel_alloc` and ties the above together, returning an `ARef`. Registration with the global registry is kept separate via `Registration::register`, following the pattern established by `drm::Device::new`. `ConnectorType` mirrors the `DRM_MODE_CONNECTOR_*` defines from `include/uapi/drm/drm_mode.h`, required by `Panel::new` to specify the panel's connector type. Assisted-by: Claude:claude-sonnet-4-6 Signed-off-by: Albert Esteve --- rust/kernel/drm/panel.rs | 376 +++++++++++++++++++++++++++++++++++++++++++= +++- 1 file changed, 373 insertions(+), 3 deletions(-) diff --git a/rust/kernel/drm/panel.rs b/rust/kernel/drm/panel.rs index fd21cc2236685..8f87774e06ed7 100644 --- a/rust/kernel/drm/panel.rs +++ b/rust/kernel/drm/panel.rs @@ -6,11 +6,15 @@ =20 use crate::drm::connector::Connector; use crate::{ - bindings, error, of, + bindings, + device::Device, + error, of, prelude::*, sync::aref::{ARef, AlwaysRefCounted}, types::Opaque, }; +use core::marker::PhantomData; +use core::mem::{ManuallyDrop, MaybeUninit}; use core::ptr::NonNull; =20 /// A DRM panel object. @@ -103,7 +107,7 @@ pub fn disable(&self) { /// failure (no modes). pub fn get_modes(&self, connector: &Connector) -> i32 { // SAFETY: The type invariants guarantee the pointers are valid. - unsafe { bindings::drm_panel_get_modes(self.as_raw(), connector.as= _raw()) } as i32 + unsafe { bindings::drm_panel_get_modes(self.as_raw(), connector.as= _raw()) } } =20 /// Use backlight device node for backlight. @@ -139,6 +143,66 @@ pub fn from_of_node(node: &of::Node) -> Result> { // `of_drm_find_panel` returns a kref-incremented reference. Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(panel).cast()) }) } + + /// Allocates and initialises a device-managed panel. + /// + /// `data` is embedded in the same allocation as the `drm_panel` and i= ts + /// destructor is called automatically when `dev` is unbound. + /// + /// Use [`Registration::register`] to add the panel to the global regi= stry + /// once it is ready to be used by display drivers. + pub fn new( + dev: &Device, + data: T, + connector_type: ConnectorType, + ) -> Result> { + // SAFETY: `dev` is valid by its type invariants; `PanelFuncsVTabl= e::build()` + // returns a valid, static `drm_panel_funcs` pointer. + let container =3D error::from_err_ptr(unsafe { + bindings::__devm_drm_panel_alloc( + dev.as_raw(), + core::mem::size_of::>(), + core::mem::offset_of!(PanelContainer, panel), + PanelFuncsVTable::::build(), + connector_type as i32, + ) + })? as *mut PanelContainer; + + // SAFETY: `container` is a valid pointer to uninitialized memory. + unsafe { + core::ptr::write( + core::ptr::addr_of_mut!((*container).data), + ManuallyDrop::new(data), + ) + }; + + // SAFETY: + // - `dev.as_raw()` is a pointer to a valid and bound device. + // - `container.cast()` is a valid pointer to the initialized `Pan= elContainer`. + error::to_result(unsafe { + // `devm_add_action_or_reset` calls `drop_panel_data` on failu= re, so `data` + // is dropped even if this registration fails. + // Registering after `__devm_drm_panel_alloc` ensures devres L= IFO order: + // `drop_panel_data` runs before `kfree(container)`. + bindings::devm_add_action_or_reset( + dev.as_raw(), + Some(drop_panel_data::), + container.cast(), + ) + })?; + + // SAFETY: `__devm_drm_panel_alloc` was successful, hence `contain= er` is + // valid and the `drm_panel` at this offset is initialised. + let raw =3D unsafe { + (container as *mut u8) + .add(core::mem::offset_of!(PanelContainer, panel)) + .cast::() + }; + + // SAFETY: `__devm_drm_panel_alloc` was successful, hence `raw` is= valid + // and the refcount is non-zero. + Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(raw).cast()) }) + } } =20 // SAFETY: By the type invariants, this type is always refcounted. @@ -149,7 +213,7 @@ fn inc_ref(&self) { } =20 unsafe fn dec_ref(obj: NonNull) { - // SAFETY: The existence of `obj` guarantees the refcount is posit= ive. + // SAFETY: The safety requirements guarantee that the refcount is = non-zero. unsafe { bindings::drm_panel_put(obj.cast().as_ptr()) }; } } @@ -202,6 +266,87 @@ pub fn from_of_node(node: &of::Node) -> Result { } } =20 +/// The type of a DRM connector. +/// +/// Mirrors the `DRM_MODE_CONNECTOR_*` defines in +/// [`include/uapi/drm/drm_mode.h`](srctree/include/uapi/drm/drm_mode.h). +#[repr(u32)] +pub enum ConnectorType { + /// Unknown connector type (`DRM_MODE_CONNECTOR_Unknown`). + Unknown =3D 0, + /// VGA connector (`DRM_MODE_CONNECTOR_VGA`). + VGA =3D 1, + /// DVI-I connector (`DRM_MODE_CONNECTOR_DVII`). + DVII =3D 2, + /// DVI-D connector (`DRM_MODE_CONNECTOR_DVID`). + DVID =3D 3, + /// DVI-A connector (`DRM_MODE_CONNECTOR_DVIA`). + DVIA =3D 4, + /// Composite connector (`DRM_MODE_CONNECTOR_Composite`). + Composite =3D 5, + /// S-Video connector (`DRM_MODE_CONNECTOR_SVIDEO`). + SVIDEO =3D 6, + /// LVDS connector (`DRM_MODE_CONNECTOR_LVDS`). + LVDS =3D 7, + /// Component connector (`DRM_MODE_CONNECTOR_Component`). + Component =3D 8, + /// 9-pin DIN connector (`DRM_MODE_CONNECTOR_9PinDIN`). + NinePinDin =3D 9, + /// DisplayPort connector (`DRM_MODE_CONNECTOR_DisplayPort`). + DisplayPort =3D 10, + /// HDMI type A connector (`DRM_MODE_CONNECTOR_HDMIA`). + HDMIA =3D 11, + /// HDMI type B connector (`DRM_MODE_CONNECTOR_HDMIB`). + HDMIB =3D 12, + /// TV connector (`DRM_MODE_CONNECTOR_TV`). + TV =3D 13, + /// Embedded DisplayPort connector (`DRM_MODE_CONNECTOR_eDP`). + #[allow(non_camel_case_types)] + eDP =3D 14, + /// Virtual connector (`DRM_MODE_CONNECTOR_VIRTUAL`). + Virtual =3D 15, + /// MIPI DSI connector (`DRM_MODE_CONNECTOR_DSI`). + DSI =3D 16, + /// DPI connector (`DRM_MODE_CONNECTOR_DPI`). + DPI =3D 17, + /// Writeback connector (`DRM_MODE_CONNECTOR_WRITEBACK`). + Writeback =3D 18, + /// SPI connector (`DRM_MODE_CONNECTOR_SPI`). + SPI =3D 19, + /// USB connector (`DRM_MODE_CONNECTOR_USB`). + USB =3D 20, +} + +impl TryFrom for ConnectorType { + type Error =3D Error; + fn try_from(v: u32) -> Result { + match v { + 0 =3D> Ok(Self::Unknown), + 1 =3D> Ok(Self::VGA), + 2 =3D> Ok(Self::DVII), + 3 =3D> Ok(Self::DVID), + 4 =3D> Ok(Self::DVIA), + 5 =3D> Ok(Self::Composite), + 6 =3D> Ok(Self::SVIDEO), + 7 =3D> Ok(Self::LVDS), + 8 =3D> Ok(Self::Component), + 9 =3D> Ok(Self::NinePinDin), + 10 =3D> Ok(Self::DisplayPort), + 11 =3D> Ok(Self::HDMIA), + 12 =3D> Ok(Self::HDMIB), + 13 =3D> Ok(Self::TV), + 14 =3D> Ok(Self::eDP), + 15 =3D> Ok(Self::Virtual), + 16 =3D> Ok(Self::DSI), + 17 =3D> Ok(Self::DPI), + 18 =3D> Ok(Self::Writeback), + 19 =3D> Ok(Self::SPI), + 20 =3D> Ok(Self::USB), + _ =3D> Err(EINVAL), + } + } +} + /// A registration of a panel to the global panel registry. pub struct Registration(ARef); =20 @@ -225,3 +370,228 @@ fn drop(&mut self) { unsafe { bindings::drm_panel_remove(self.0.as_raw()) }; } } + +/// Operations implemented by a DRM panel driver. +/// +/// Implement this trait to provide a DRM panel driver and its callbacks. = Use +/// [`Panel::new`] to allocate the panel, passing the driver data as `T`. +/// +/// C header: [`include/drm/drm_panel.h`](srctree/include/drm/drm_panel.h) +#[vtable] +pub trait PanelFuncs { + /// Turn on panel and perform set up. + /// + /// This function is optional. + fn prepare(&self, _panel: &Panel) -> Result<()> { + Ok(()) + } + + /// Turn off panel. + /// + /// This function is optional. + fn unprepare(&self, _panel: &Panel) -> Result<()> { + Ok(()) + } + + /// Enable panel (turn on back light, etc.). + /// + /// This function is optional. + fn enable(&self, _panel: &Panel) -> Result<()> { + Ok(()) + } + + /// Disable panel (turn off back light, etc.). + /// + /// This function is optional. + fn disable(&self, _panel: &Panel) -> Result<()> { + Ok(()) + } + + /// Add modes to the connector that the panel is attached to + /// and returns the number of modes added. + /// + /// This function is mandatory. + fn get_modes(&self, _panel: &Panel, _connector: &Connector) -> i32 { + build_error!("get_modes is mandatory") + } + + /// Return the panel orientation set by device tree or EDID. + /// + /// This function is optional. + fn get_orientation(&self, _panel: &Panel) -> PanelOrientation { + PanelOrientation::Unknown + } +} + +// Outer allocation layout used by `Panel::new`. +// +// `__devm_drm_panel_alloc` allocates a block of `size_of::>()` +// bytes, places `drm_panel` at `offset_of!(PanelContainer, panel)`, and +// stores the block's base address in `panel->container`. +// +// Lifetime: +// 1. A devres action registered right after allocation calls `drop_in_p= lace` +// on the `data` field (T's destructor) when the device is unbound. +// 2. `__drm_panel_free` calls `kfree(panel->container)` when the kref h= its +// zero, freeing the entire block. +// +// `data` is `ManuallyDrop` so that Rust does not implicitly drop it; t= he +// devres action owns the destructor call. +#[repr(C)] +struct PanelContainer { + data: ManuallyDrop, + panel: MaybeUninit, +} + +// Devres action: run T's destructor before `kfree(container)`. +// +// # Safety +// +// `ptr` must be the base of a live `PanelContainer` whose `data` field= was +// initialised by `Panel::new` and has not yet been dropped. +unsafe extern "C" fn drop_panel_data(ptr: *mut core::ffi::c_void) { + // SAFETY: Caller guarantees `ptr` is the base of a live `PanelContain= er` + // with an initialised `data` field. `data` is at offset 0, so `ptr as= *mut T` + // is valid. + unsafe { core::ptr::drop_in_place(ptr as *mut T) }; +} + +/// A vtable for the DRM core to interact with a panel driver. +/// +/// A `bindings::drm_panel_funcs` vtable is constructed from pointers to t= he +/// `extern "C"` functions of this struct, exposed through +/// `PanelFuncsVTable::VTABLE`. +/// +/// For general documentation of these methods, see the kernel source +/// documentation related to `struct drm_panel_funcs` in +/// [`include/drm/drm_panel.h`]. +/// +/// [`include/drm/drm_panel.h`]: srctree/include/drm/drm_panel.h +pub(crate) struct PanelFuncsVTable(PhantomData); + +impl PanelFuncsVTable { + // Recover &T from panel->container. + // + // # Safety + // + // `panel` must be a valid pointer to a live `drm_panel` allocated by + // `Panel::new`, whose `container` field points to the base of a live + // `PanelContainer` with an initialised `data` field. + unsafe fn data_from_panel<'a>(panel: *mut bindings::drm_panel) -> &'a = T { + // SAFETY: Caller guarantees `panel` is valid and `panel->containe= r` points + // to the base of a live `PanelContainer` with an initialised `= data` + // field. `data` is at offset 0, so `container as *const T` is val= id. + unsafe { &*((*panel).container as *const T) } + } + + unsafe extern "C" fn prepare_callback(panel: *mut bindings::drm_panel)= -> i32 { + // SAFETY: The C DRM core only invokes callbacks on a live, initia= lised + // panel allocated by `Panel::new` (see `data_from_panel`). + let data =3D unsafe { Self::data_from_panel(panel) }; + // SAFETY: `panel` is a valid `drm_panel` pointer per the callback= contract. + let panel_ref =3D unsafe { Panel::from_raw(panel) }; + match T::prepare(data, panel_ref) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + unsafe extern "C" fn unprepare_callback(panel: *mut bindings::drm_pane= l) -> i32 { + // SAFETY: The C DRM core only invokes callbacks on a live, initia= lised + // panel allocated by `Panel::new` (see `data_from_panel`). + let data =3D unsafe { Self::data_from_panel(panel) }; + // SAFETY: `panel` is a valid `drm_panel` pointer per the callback= contract. + let panel_ref =3D unsafe { Panel::from_raw(panel) }; + match T::unprepare(data, panel_ref) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + unsafe extern "C" fn enable_callback(panel: *mut bindings::drm_panel) = -> i32 { + // SAFETY: The C DRM core only invokes callbacks on a live, initia= lised + // panel allocated by `Panel::new` (see `data_from_panel`). + let data =3D unsafe { Self::data_from_panel(panel) }; + // SAFETY: `panel` is a valid `drm_panel` pointer per the callback= contract. + let panel_ref =3D unsafe { Panel::from_raw(panel) }; + match T::enable(data, panel_ref) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + unsafe extern "C" fn disable_callback(panel: *mut bindings::drm_panel)= -> i32 { + // SAFETY: The C DRM core only invokes callbacks on a live, initia= lised + // panel allocated by `Panel::new` (see `data_from_panel`). + let data =3D unsafe { Self::data_from_panel(panel) }; + // SAFETY: `panel` is a valid `drm_panel` pointer per the callback= contract. + let panel_ref =3D unsafe { Panel::from_raw(panel) }; + match T::disable(data, panel_ref) { + Ok(()) =3D> 0, + Err(e) =3D> e.to_errno(), + } + } + + unsafe extern "C" fn get_modes_callback( + panel: *mut bindings::drm_panel, + connector: *mut bindings::drm_connector, + ) -> i32 { + // SAFETY: The C DRM core only invokes callbacks on a live, initia= lised + // panel allocated by `Panel::new` (see `data_from_panel`). + let data =3D unsafe { Self::data_from_panel(panel) }; + // SAFETY: `panel` is a valid `drm_panel` pointer per the callback= contract. + let panel_ref =3D unsafe { Panel::from_raw(panel) }; + // SAFETY: `connector` is a valid, non-null `drm_connector` pointer + // supplied by the DRM core for the duration of the callback. + let connector_ref =3D unsafe { Connector::from_raw(connector) }; + T::get_modes(data, panel_ref, connector_ref) + } + + unsafe extern "C" fn get_orientation_callback(panel: *mut bindings::dr= m_panel) -> i32 { + // SAFETY: The C DRM core only invokes callbacks on a live, initia= lised + // panel allocated by `Panel::new` (see `data_from_panel`). + let data =3D unsafe { Self::data_from_panel(panel) }; + // SAFETY: `panel` is a valid `drm_panel` pointer per the callback= contract. + let panel_ref =3D unsafe { Panel::from_raw(panel) }; + T::get_orientation(data, panel_ref) as i32 + } + + const VTABLE: bindings::drm_panel_funcs =3D bindings::drm_panel_funcs { + // Initialize optional callbacks based on the traits of `T`. + prepare: if T::HAS_PREPARE { + Some(Self::prepare_callback) + } else { + None + }, + unprepare: if T::HAS_UNPREPARE { + Some(Self::unprepare_callback) + } else { + None + }, + enable: if T::HAS_ENABLE { + Some(Self::enable_callback) + } else { + None + }, + disable: if T::HAS_DISABLE { + Some(Self::disable_callback) + } else { + None + }, + get_orientation: if T::HAS_GET_ORIENTATION { + Some(Self::get_orientation_callback) + } else { + None + }, + + // Initialize mandatory callbacks. + get_modes: Some(Self::get_modes_callback), + + get_timings: None, + debugfs_init: None, + }; + + pub(crate) const fn build() -> &'static bindings::drm_panel_funcs { + &Self::VTABLE + } +} --=20 2.55.0 From nobody Mon Sep 28 21:05:36 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 492AB40D574 for ; Mon, 17 Aug 2026 11:41:32 +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=1786966893; cv=none; b=P+Zx61Vde750Rbm3vHhfBnO01rMGsdNK99J+Dvnh5Vyo0ExVLLNQyAy3yswF6FWPpiNFkpIMEgEg5/xMwFqXT3dbsJRxMdgaPq+jo+fvNTIEmp58/hj3YXq+kELWudOV5oJT8HQyTUVf974KE2DM/0ouYTG+F7T7HILQjjmvTKc= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786966893; c=relaxed/simple; bh=oTsFEHigKZDagVoAOr5IwLFtvYBBWBCaIx6vNhGa6+8=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:References: In-Reply-To:To:Cc; b=mXBa5K4k9yB397QQcYyMkPE4V1M8FWUiyg9ggqCkykg3V8Smcuxv5+3p+eaGCmv9WFGzRPkw0p3xf0hrIw2oeDCGPO3xo5aICNbgZHdoZqs5q9GHncnWLORe0dE6M98DCx3qHoERNFLYPRsxxbxtIFz+rrUHvPbDzc6tLP1V17w= 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=EIcVYxNR; 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="EIcVYxNR" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1786966891; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=eye+Imc/4M3S1qY3eqjb5bqUcZ4zH/Id72nS2Qv+iaA=; b=EIcVYxNR9DeG4vOxV3B6j+ZohBHcG09qI1MPKQ42wsREJbc1YR2CnOh3ghqdegJ1x07AwT L34UijbANZ8Q/XC0vhHaicXHEunWgutepP2pIPApaw5PEiJXR/kiahirwbUcY80ftLpof9 gbT3t4AGd33/CAg4eZOWXQKVyBrs5jg= Received: from mx-prod-mc-06.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-145-NV6esz-kNvSZhbgo-XL6oQ-1; Mon, 17 Aug 2026 07:41:26 -0400 X-MC-Unique: NV6esz-kNvSZhbgo-XL6oQ-1 X-Mimecast-MFC-AGG-ID: NV6esz-kNvSZhbgo-XL6oQ_1786966883 Received: from mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com [10.30.177.95]) (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-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 6222018001DE; Mon, 17 Aug 2026 11:41:23 +0000 (UTC) Received: from [192.168.1.153] (headnet03.pony-001.prod.iad2.dc.redhat.com [10.2.32.114]) by mx-prod-int-10.mail-002.prod.us-west-2.aws.redhat.com (Postfix) with ESMTPS id 3F601422; Mon, 17 Aug 2026 11:41:18 +0000 (UTC) From: Albert Esteve Date: Mon, 17 Aug 2026 13:40:50 +0200 Subject: [PATCH 5/5] rust: drm: add KUnit tests for panel Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260817-drm_panel_bindings-v1-5-1f974508a31c@redhat.com> References: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> In-Reply-To: <20260817-drm_panel_bindings-v1-0-1f974508a31c@redhat.com> To: Rob Herring , Saravana Kannan , Miguel Ojeda , Boqun Feng , Gary Guo , =?utf-8?q?Bj=C3=B6rn_Roy_Baron?= , Benno Lossin , Andreas Hindborg , Alice Ryhl , Trevor Gross , Danilo Krummrich , Daniel Almeida , Tamir Duberstein , Alexandre Courbot , =?utf-8?q?Onur_=C3=96zkan?= , David Airlie , Simona Vetter Cc: devicetree@vger.kernel.org, rust-for-linux@vger.kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, Albert Esteve , mripard@kernel.org X-Developer-Signature: v=1; a=ed25519-sha256; t=1786966849; l=4048; i=aesteve@redhat.com; s=20260303; h=from:subject:message-id; bh=oTsFEHigKZDagVoAOr5IwLFtvYBBWBCaIx6vNhGa6+8=; b=6QwxHfNOkF/90Z13R5XUwzf52g9t5Ni068MYbGJL53bg9fbDgOYsTTjn3hXH5mBfps9ZKPqmb /2hmNzKs8CTBesR2zwdbQqUwz1eeXDavHMnJRjf4n8IR+assQYCtN9x X-Developer-Key: i=aesteve@redhat.com; a=ed25519; pk=YSFz6sOHd2L45+Fr8DIvHTi6lSIjhLZ5T+rkxspJt1s= X-Scanned-By: MIMEDefang 3.6 on 10.30.177.95 Add a test module to verify basic data type conversions and layout. The tests covers: - PanelOrientation and ConnectorType: TryFrom conversion for valid values and also out-of-range invalid inputs. - PanelContainer: layout invariants (offset, alignment, size) on which Panel::new relies on for memory access. Tests are gated on CONFIG_RUST_DRM_PANEL_KUNIT_TEST. Signed-off-by: Albert Esteve --- rust/kernel/Kconfig.test | 10 ++++++ rust/kernel/drm/panel.rs | 87 ++++++++++++++++++++++++++++++++++++++++++++= ++++ 2 files changed, 97 insertions(+) diff --git a/rust/kernel/Kconfig.test b/rust/kernel/Kconfig.test index e6a5c7a795f0f..397b916dca0d4 100644 --- a/rust/kernel/Kconfig.test +++ b/rust/kernel/Kconfig.test @@ -83,4 +83,14 @@ config RUST_BITFIELD_KUNIT_TEST =20 If unsure, say N. =20 +config RUST_DRM_PANEL_KUNIT_TEST + bool "KUnit tests for Rust panel API" if !KUNIT_ALL_TESTS + default KUNIT_ALL_TESTS + help + This option enables KUnit tests for the Rust panel API. + These are only for development and testing, not for regular + kernel use cases. + + If unsure, say N. + endif diff --git a/rust/kernel/drm/panel.rs b/rust/kernel/drm/panel.rs index 8f87774e06ed7..3da203c39edd6 100644 --- a/rust/kernel/drm/panel.rs +++ b/rust/kernel/drm/panel.rs @@ -595,3 +595,90 @@ pub(crate) const fn build() -> &'static bindings::drm_= panel_funcs { &Self::VTABLE } } + +#[cfg(CONFIG_RUST_DRM_PANEL_KUNIT_TEST)] +#[macros::kunit_tests(rust_kernel_drm_panel)] +mod tests { + use super::*; + use core::mem::{align_of, offset_of, size_of, ManuallyDrop, MaybeUnini= t}; + + struct TestData { + _ptr: *const u8, + _byte: u8, + } + + #[test] + fn panel_orientation() { + // Test valid values. + assert!(matches!( + PanelOrientation::try_from(-1), + Ok(PanelOrientation::Unknown) + )); + assert!(matches!( + PanelOrientation::try_from(0), + Ok(PanelOrientation::Normal) + )); + assert!(matches!( + PanelOrientation::try_from(1), + Ok(PanelOrientation::BottomUp) + )); + assert!(matches!( + PanelOrientation::try_from(2), + Ok(PanelOrientation::LeftUp) + )); + assert!(matches!( + PanelOrientation::try_from(3), + Ok(PanelOrientation::RightUp) + )); + + // Test invalid values. + assert!(PanelOrientation::try_from(4).is_err()); + assert!(PanelOrientation::try_from(-2).is_err()); + } + + #[test] + fn connector_type() { + // Test valid values. + assert!(matches!( + ConnectorType::try_from(0), + Ok(ConnectorType::Unknown) + )); + assert!(matches!( + ConnectorType::try_from(10), + Ok(ConnectorType::DisplayPort) + )); + assert!(matches!( + ConnectorType::try_from(14), + Ok(ConnectorType::eDP) + )); + assert!(matches!( + ConnectorType::try_from(16), + Ok(ConnectorType::DSI) + )); + assert!(matches!( + ConnectorType::try_from(20), + Ok(ConnectorType::USB) + )); + + // Test invalid values. + assert!(ConnectorType::try_from(21).is_err()); + } + + #[test] + fn panel_container_layout() { + assert_eq!(offset_of!(PanelContainer, data), 0); + + assert_eq!(size_of::>(), size_of::()); + + let panel_offset =3D offset_of!(PanelContainer, panel); + assert_eq!( + panel_offset % align_of::>(), + 0 + ); + + assert!( + size_of::>() + >=3D panel_offset + size_of::() + ); + } +} --=20 2.55.0